parent
7d150d5782
commit
7fbdd0ae93
|
@ -2,12 +2,15 @@
|
|||
|
||||
### **Breaking changes**
|
||||
|
||||
The cpufreq metrics now separate the `cpufreq` and `scaling` data based on what the driver provides. #1248
|
||||
|
||||
### Changes
|
||||
|
||||
* [BUGFIX]
|
||||
* [BUGFIX] Add fallback for missing /proc/1/mounts #1172
|
||||
* [CHANGE] Add TCPSynRetrans to netstat default filter #1143
|
||||
* [CHANGE] Add a limit to the number of in-flight requests #1166
|
||||
* [CHANGE] Add separate cpufreq and scaling metrics #1248
|
||||
* [ENHANCEMENT] Add Infiniband counters #1120
|
||||
* [FEATURE] Add a flag to disable exporter metrics #1148
|
||||
* [FEATURE] Add kstat-based Solaris metrics for boottime, cpu and zfs collectors #1197
|
||||
|
|
|
@ -32,6 +32,9 @@ type cpuCollector struct {
|
|||
cpuFreq *prometheus.Desc
|
||||
cpuFreqMin *prometheus.Desc
|
||||
cpuFreqMax *prometheus.Desc
|
||||
scalingFreq *prometheus.Desc
|
||||
scalingFreqMin *prometheus.Desc
|
||||
scalingFreqMax *prometheus.Desc
|
||||
cpuCoreThrottle *prometheus.Desc
|
||||
cpuPackageThrottle *prometheus.Desc
|
||||
}
|
||||
|
@ -64,6 +67,21 @@ func NewCPUCollector() (Collector, error) {
|
|||
"Maximum cpu thread frequency in hertz.",
|
||||
[]string{"cpu"}, nil,
|
||||
),
|
||||
scalingFreq: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_frequency_hertz"),
|
||||
"Current scaled cpu thread frequency in hertz.",
|
||||
[]string{"cpu"}, nil,
|
||||
),
|
||||
scalingFreqMin: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_frequency_min_hrts"),
|
||||
"Minimum scaled cpu thread frequency in hertz.",
|
||||
[]string{"cpu"}, nil,
|
||||
),
|
||||
scalingFreqMax: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "scaling_frequency_max_hrts"),
|
||||
"Maximum scaled cpu thread frequency in hertz.",
|
||||
[]string{"cpu"}, nil,
|
||||
),
|
||||
cpuCoreThrottle: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, cpuCollectorSubsystem, "core_throttles_total"),
|
||||
"Number of times this cpu core has been throttled.",
|
||||
|
@ -106,24 +124,54 @@ func (c *cpuCollector) updateCPUfreq(ch chan<- prometheus.Metric) error {
|
|||
// sysfs cpufreq values are kHz, thus multiply by 1000 to export base units (hz).
|
||||
// See https://www.kernel.org/doc/Documentation/cpu-freq/user-guide.txt
|
||||
for _, stats := range cpuFreqs {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuFreq,
|
||||
prometheus.GaugeValue,
|
||||
float64(stats.CurrentFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuFreqMin,
|
||||
prometheus.GaugeValue,
|
||||
float64(stats.MinimumFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuFreqMax,
|
||||
prometheus.GaugeValue,
|
||||
float64(stats.MaximumFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
if stats.CpuinfoCurrentFrequency != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuFreq,
|
||||
prometheus.GaugeValue,
|
||||
float64(*stats.CpuinfoCurrentFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
}
|
||||
if stats.CpuinfoMinimumFrequency != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuFreqMin,
|
||||
prometheus.GaugeValue,
|
||||
float64(*stats.CpuinfoMinimumFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
}
|
||||
if stats.CpuinfoMaximumFrequency != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.cpuFreqMax,
|
||||
prometheus.GaugeValue,
|
||||
float64(*stats.CpuinfoMaximumFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
}
|
||||
if stats.ScalingCurrentFrequency != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.scalingFreq,
|
||||
prometheus.GaugeValue,
|
||||
float64(*stats.ScalingCurrentFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
}
|
||||
if stats.ScalingMinimumFrequency != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.scalingFreqMin,
|
||||
prometheus.GaugeValue,
|
||||
float64(*stats.ScalingMinimumFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
}
|
||||
if stats.ScalingMaximumFrequency != nil {
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.scalingFreqMax,
|
||||
prometheus.GaugeValue,
|
||||
float64(*stats.ScalingMaximumFrequency)*1000.0,
|
||||
stats.Name,
|
||||
)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -184,24 +184,6 @@ node_cpu_core_throttles_total{core="0",package="0"} 5
|
|||
node_cpu_core_throttles_total{core="0",package="1"} 0
|
||||
node_cpu_core_throttles_total{core="1",package="0"} 0
|
||||
node_cpu_core_throttles_total{core="1",package="1"} 9
|
||||
# HELP node_cpu_frequency_hertz Current cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_frequency_hertz gauge
|
||||
node_cpu_frequency_hertz{cpu="0"} 1.699981e+09
|
||||
node_cpu_frequency_hertz{cpu="1"} 1.699981e+09
|
||||
node_cpu_frequency_hertz{cpu="2"} 8e+06
|
||||
node_cpu_frequency_hertz{cpu="3"} 8e+06
|
||||
# HELP node_cpu_frequency_max_hertz Maximum cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_frequency_max_hertz gauge
|
||||
node_cpu_frequency_max_hertz{cpu="0"} 3.7e+09
|
||||
node_cpu_frequency_max_hertz{cpu="1"} 3.7e+09
|
||||
node_cpu_frequency_max_hertz{cpu="2"} 4.2e+09
|
||||
node_cpu_frequency_max_hertz{cpu="3"} 4.2e+09
|
||||
# HELP node_cpu_frequency_min_hertz Minimum cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_frequency_min_hertz gauge
|
||||
node_cpu_frequency_min_hertz{cpu="0"} 8e+08
|
||||
node_cpu_frequency_min_hertz{cpu="1"} 8e+08
|
||||
node_cpu_frequency_min_hertz{cpu="2"} 1e+06
|
||||
node_cpu_frequency_min_hertz{cpu="3"} 1e+06
|
||||
# HELP node_cpu_guest_seconds_total Seconds the cpus spent in guests (VMs) for each mode.
|
||||
# TYPE node_cpu_guest_seconds_total counter
|
||||
node_cpu_guest_seconds_total{cpu="0",mode="nice"} 0.01
|
||||
|
@ -224,6 +206,24 @@ node_cpu_guest_seconds_total{cpu="7",mode="user"} 0.09
|
|||
# TYPE node_cpu_package_throttles_total counter
|
||||
node_cpu_package_throttles_total{package="0"} 30
|
||||
node_cpu_package_throttles_total{package="1"} 6
|
||||
# HELP node_cpu_scaling_frequency_hertz Current scaled cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_scaling_frequency_hertz gauge
|
||||
node_cpu_scaling_frequency_hertz{cpu="0"} 1.699981e+09
|
||||
node_cpu_scaling_frequency_hertz{cpu="1"} 1.699981e+09
|
||||
node_cpu_scaling_frequency_hertz{cpu="2"} 8e+06
|
||||
node_cpu_scaling_frequency_hertz{cpu="3"} 8e+06
|
||||
# HELP node_cpu_scaling_frequency_max_hrts Maximum scaled cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_scaling_frequency_max_hrts gauge
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="0"} 3.7e+09
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="1"} 3.7e+09
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="2"} 4.2e+09
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="3"} 4.2e+09
|
||||
# HELP node_cpu_scaling_frequency_min_hrts Minimum scaled cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_scaling_frequency_min_hrts gauge
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="0"} 8e+08
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="1"} 8e+08
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="2"} 1e+06
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="3"} 1e+06
|
||||
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
|
||||
# TYPE node_cpu_seconds_total counter
|
||||
node_cpu_seconds_total{cpu="0",mode="idle"} 10870.69
|
||||
|
|
|
@ -184,24 +184,6 @@ node_cpu_core_throttles_total{core="0",package="0"} 5
|
|||
node_cpu_core_throttles_total{core="0",package="1"} 0
|
||||
node_cpu_core_throttles_total{core="1",package="0"} 0
|
||||
node_cpu_core_throttles_total{core="1",package="1"} 9
|
||||
# HELP node_cpu_frequency_hertz Current cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_frequency_hertz gauge
|
||||
node_cpu_frequency_hertz{cpu="0"} 1.699981e+09
|
||||
node_cpu_frequency_hertz{cpu="1"} 1.699981e+09
|
||||
node_cpu_frequency_hertz{cpu="2"} 8e+06
|
||||
node_cpu_frequency_hertz{cpu="3"} 8e+06
|
||||
# HELP node_cpu_frequency_max_hertz Maximum cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_frequency_max_hertz gauge
|
||||
node_cpu_frequency_max_hertz{cpu="0"} 3.7e+09
|
||||
node_cpu_frequency_max_hertz{cpu="1"} 3.7e+09
|
||||
node_cpu_frequency_max_hertz{cpu="2"} 4.2e+09
|
||||
node_cpu_frequency_max_hertz{cpu="3"} 4.2e+09
|
||||
# HELP node_cpu_frequency_min_hertz Minimum cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_frequency_min_hertz gauge
|
||||
node_cpu_frequency_min_hertz{cpu="0"} 8e+08
|
||||
node_cpu_frequency_min_hertz{cpu="1"} 8e+08
|
||||
node_cpu_frequency_min_hertz{cpu="2"} 1e+06
|
||||
node_cpu_frequency_min_hertz{cpu="3"} 1e+06
|
||||
# HELP node_cpu_guest_seconds_total Seconds the cpus spent in guests (VMs) for each mode.
|
||||
# TYPE node_cpu_guest_seconds_total counter
|
||||
node_cpu_guest_seconds_total{cpu="0",mode="nice"} 0.01
|
||||
|
@ -224,6 +206,24 @@ node_cpu_guest_seconds_total{cpu="7",mode="user"} 0.09
|
|||
# TYPE node_cpu_package_throttles_total counter
|
||||
node_cpu_package_throttles_total{package="0"} 30
|
||||
node_cpu_package_throttles_total{package="1"} 6
|
||||
# HELP node_cpu_scaling_frequency_hertz Current scaled cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_scaling_frequency_hertz gauge
|
||||
node_cpu_scaling_frequency_hertz{cpu="0"} 1.699981e+09
|
||||
node_cpu_scaling_frequency_hertz{cpu="1"} 1.699981e+09
|
||||
node_cpu_scaling_frequency_hertz{cpu="2"} 8e+06
|
||||
node_cpu_scaling_frequency_hertz{cpu="3"} 8e+06
|
||||
# HELP node_cpu_scaling_frequency_max_hrts Maximum scaled cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_scaling_frequency_max_hrts gauge
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="0"} 3.7e+09
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="1"} 3.7e+09
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="2"} 4.2e+09
|
||||
node_cpu_scaling_frequency_max_hrts{cpu="3"} 4.2e+09
|
||||
# HELP node_cpu_scaling_frequency_min_hrts Minimum scaled cpu thread frequency in hertz.
|
||||
# TYPE node_cpu_scaling_frequency_min_hrts gauge
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="0"} 8e+08
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="1"} 8e+08
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="2"} 1e+06
|
||||
node_cpu_scaling_frequency_min_hrts{cpu="3"} 1e+06
|
||||
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
|
||||
# TYPE node_cpu_seconds_total counter
|
||||
node_cpu_seconds_total{cpu="0",mode="idle"} 10870.69
|
||||
|
|
2
go.mod
2
go.mod
|
@ -21,7 +21,7 @@ require (
|
|||
github.com/prometheus/client_golang v0.9.1
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910
|
||||
github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d
|
||||
github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5
|
||||
github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745
|
||||
github.com/sirupsen/logrus v1.1.1 // indirect
|
||||
github.com/soundcloud/go-runit v0.0.0-20150630195641-06ad41a06c4a
|
||||
|
|
4
go.sum
4
go.sum
|
@ -43,8 +43,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f
|
|||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37 h1:Y7YdJ9Xb3MoQOzAWXnDunAJYpvhVwZdTirNfGUgPKaA=
|
||||
github.com/prometheus/common v0.0.0-20181015124227-bcb74de08d37/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5 h1:Etei0Wx6pooT/DeOKcGTr1M/01ggz95Ajq8BBwCOKBU=
|
||||
github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745 h1:IuH7WumZNax0D+rEqmy2TyhKCzrtMGqbZO0b8rO00JA=
|
||||
github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745/go.mod h1:G81aIFAMS9ECrwBYR9YxhlPjWgrItd+Kje78O6+uqm8=
|
||||
github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg=
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
* Tobias Schmidt <tobidt@gmail.com>
|
||||
* Tobias Schmidt <tobidt@gmail.com> @grobie
|
||||
* Johannes 'fish' Ziemke <github@freigeist.org> @discordianfish
|
||||
|
|
|
@ -11,49 +11,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Ensure GOBIN is not set during build so that promu is installed to the correct path
|
||||
unexport GOBIN
|
||||
|
||||
GO ?= go
|
||||
GOFMT ?= $(GO)fmt
|
||||
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
|
||||
STATICCHECK := $(FIRST_GOPATH)/bin/staticcheck
|
||||
pkgs = $(shell $(GO) list ./... | grep -v /vendor/)
|
||||
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
|
||||
ifdef DEBUG
|
||||
bindata_flags = -debug
|
||||
endif
|
||||
|
||||
STATICCHECK_IGNORE =
|
||||
|
||||
all: format staticcheck build test
|
||||
|
||||
style:
|
||||
@echo ">> checking code style"
|
||||
@! $(GOFMT) -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^'
|
||||
|
||||
check_license:
|
||||
@echo ">> checking license header"
|
||||
@./scripts/check_license.sh
|
||||
|
||||
test: fixtures/.unpacked sysfs/fixtures/.unpacked
|
||||
@echo ">> running all tests"
|
||||
@$(GO) test -race $(shell $(GO) list ./... | grep -v /vendor/ | grep -v examples)
|
||||
|
||||
format:
|
||||
@echo ">> formatting code"
|
||||
@$(GO) fmt $(pkgs)
|
||||
|
||||
vet:
|
||||
@echo ">> vetting code"
|
||||
@$(GO) vet $(pkgs)
|
||||
|
||||
staticcheck: $(STATICCHECK)
|
||||
@echo ">> running staticcheck"
|
||||
@$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs)
|
||||
include Makefile.common
|
||||
|
||||
%/.unpacked: %.ttar
|
||||
./ttar -C $(dir $*) -x -f $*.ttar
|
||||
|
@ -65,13 +23,8 @@ update_fixtures: fixtures.ttar sysfs/fixtures.ttar
|
|||
rm -v $(dir $*)fixtures/.unpacked
|
||||
./ttar -C $(dir $*) -c -f $*fixtures.ttar fixtures/
|
||||
|
||||
$(FIRST_GOPATH)/bin/staticcheck:
|
||||
@GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck
|
||||
.PHONY: build
|
||||
build:
|
||||
|
||||
.PHONY: all style check_license format test vet staticcheck
|
||||
|
||||
# Declaring the binaries at their default locations as PHONY targets is a hack
|
||||
# to ensure the latest version is downloaded on every make execution.
|
||||
# If this is not desired, copy/symlink these binaries to a different path and
|
||||
# set the respective environment variables.
|
||||
.PHONY: $(GOPATH)/bin/staticcheck
|
||||
.PHONY: test
|
||||
test: fixtures/.unpacked sysfs/fixtures/.unpacked common-test
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
# Copyright 2018 The Prometheus Authors
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# A common Makefile that includes rules to be reused in different prometheus projects.
|
||||
# !!! Open PRs only against the prometheus/prometheus/Makefile.common repository!
|
||||
|
||||
# Example usage :
|
||||
# Create the main Makefile in the root project directory.
|
||||
# include Makefile.common
|
||||
# customTarget:
|
||||
# @echo ">> Running customTarget"
|
||||
#
|
||||
|
||||
# Ensure GOBIN is not set during build so that promu is installed to the correct path
|
||||
unexport GOBIN
|
||||
|
||||
GO ?= go
|
||||
GOFMT ?= $(GO)fmt
|
||||
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
|
||||
GOOPTS ?=
|
||||
|
||||
GO_VERSION ?= $(shell $(GO) version)
|
||||
GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION))
|
||||
PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.')
|
||||
|
||||
unexport GOVENDOR
|
||||
ifeq (, $(PRE_GO_111))
|
||||
ifneq (,$(wildcard go.mod))
|
||||
# Enforce Go modules support just in case the directory is inside GOPATH (and for Travis CI).
|
||||
GO111MODULE := on
|
||||
|
||||
ifneq (,$(wildcard vendor))
|
||||
# Always use the local vendor/ directory to satisfy the dependencies.
|
||||
GOOPTS := $(GOOPTS) -mod=vendor
|
||||
endif
|
||||
endif
|
||||
else
|
||||
ifneq (,$(wildcard go.mod))
|
||||
ifneq (,$(wildcard vendor))
|
||||
$(warning This repository requires Go >= 1.11 because of Go modules)
|
||||
$(warning Some recipes may not work as expected as the current Go runtime is '$(GO_VERSION_NUMBER)')
|
||||
endif
|
||||
else
|
||||
# This repository isn't using Go modules (yet).
|
||||
GOVENDOR := $(FIRST_GOPATH)/bin/govendor
|
||||
endif
|
||||
|
||||
unexport GO111MODULE
|
||||
endif
|
||||
PROMU := $(FIRST_GOPATH)/bin/promu
|
||||
STATICCHECK := $(FIRST_GOPATH)/bin/staticcheck
|
||||
pkgs = ./...
|
||||
|
||||
GO_VERSION ?= $(shell $(GO) version)
|
||||
GO_BUILD_PLATFORM ?= $(subst /,-,$(lastword $(GO_VERSION)))
|
||||
|
||||
PROMU_VERSION ?= 0.2.0
|
||||
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
|
||||
|
||||
PREFIX ?= $(shell pwd)
|
||||
BIN_DIR ?= $(shell pwd)
|
||||
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
|
||||
DOCKER_REPO ?= prom
|
||||
|
||||
.PHONY: all
|
||||
all: precheck style staticcheck unused build test
|
||||
|
||||
# This rule is used to forward a target like "build" to "common-build". This
|
||||
# allows a new "build" target to be defined in a Makefile which includes this
|
||||
# one and override "common-build" without override warnings.
|
||||
%: common-% ;
|
||||
|
||||
.PHONY: common-style
|
||||
common-style:
|
||||
@echo ">> checking code style"
|
||||
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
|
||||
if [ -n "$${fmtRes}" ]; then \
|
||||
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
|
||||
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-check_license
|
||||
common-check_license:
|
||||
@echo ">> checking license header"
|
||||
@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
|
||||
awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
|
||||
done); \
|
||||
if [ -n "$${licRes}" ]; then \
|
||||
echo "license header checking failed:"; echo "$${licRes}"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
.PHONY: common-test-short
|
||||
common-test-short:
|
||||
@echo ">> running short tests"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-test
|
||||
common-test:
|
||||
@echo ">> running all tests"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) test -race $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-format
|
||||
common-format:
|
||||
@echo ">> formatting code"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) fmt $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-vet
|
||||
common-vet:
|
||||
@echo ">> vetting code"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)
|
||||
|
||||
.PHONY: common-staticcheck
|
||||
common-staticcheck: $(STATICCHECK)
|
||||
@echo ">> running staticcheck"
|
||||
ifdef GO111MODULE
|
||||
GO111MODULE=$(GO111MODULE) $(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" -checks "SA*" $(pkgs)
|
||||
else
|
||||
$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs)
|
||||
endif
|
||||
|
||||
.PHONY: common-unused
|
||||
common-unused: $(GOVENDOR)
|
||||
ifdef GOVENDOR
|
||||
@echo ">> running check for unused packages"
|
||||
@$(GOVENDOR) list +unused | grep . && exit 1 || echo 'No unused packages'
|
||||
else
|
||||
ifdef GO111MODULE
|
||||
@echo ">> running check for unused/missing packages in go.mod"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
|
||||
@git diff --exit-code -- go.sum go.mod
|
||||
ifneq (,$(wildcard vendor))
|
||||
@echo ">> running check for unused packages in vendor/"
|
||||
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
|
||||
@git diff --exit-code -- go.sum go.mod vendor/
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: common-build
|
||||
common-build: promu
|
||||
@echo ">> building binaries"
|
||||
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX)
|
||||
|
||||
.PHONY: common-tarball
|
||||
common-tarball: promu
|
||||
@echo ">> building release tarball"
|
||||
$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR)
|
||||
|
||||
.PHONY: common-docker
|
||||
common-docker:
|
||||
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" .
|
||||
|
||||
.PHONY: common-docker-publish
|
||||
common-docker-publish:
|
||||
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)"
|
||||
|
||||
.PHONY: common-docker-tag-latest
|
||||
common-docker-tag-latest:
|
||||
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):latest"
|
||||
|
||||
.PHONY: promu
|
||||
promu: $(PROMU)
|
||||
|
||||
$(PROMU):
|
||||
curl -s -L $(PROMU_URL) | tar -xvz -C /tmp
|
||||
mkdir -v -p $(FIRST_GOPATH)/bin
|
||||
cp -v /tmp/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(PROMU)
|
||||
|
||||
.PHONY: proto
|
||||
proto:
|
||||
@echo ">> generating code from proto files"
|
||||
@./scripts/genproto.sh
|
||||
|
||||
.PHONY: $(STATICCHECK)
|
||||
$(STATICCHECK):
|
||||
ifdef GO111MODULE
|
||||
# Get staticcheck from a temporary directory to avoid modifying the local go.{mod,sum}.
|
||||
# See https://github.com/golang/go/issues/27643.
|
||||
# For now, we are using the next branch of staticcheck because master isn't compatible yet with Go modules.
|
||||
tmpModule=$$(mktemp -d 2>&1) && \
|
||||
mkdir -p $${tmpModule}/staticcheck && \
|
||||
cd "$${tmpModule}"/staticcheck && \
|
||||
GO111MODULE=on $(GO) mod init example.com/staticcheck && \
|
||||
GO111MODULE=on GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck@next && \
|
||||
rm -rf $${tmpModule};
|
||||
else
|
||||
GOOS= GOARCH= GO111MODULE=off $(GO) get -u honnef.co/go/tools/cmd/staticcheck
|
||||
endif
|
||||
|
||||
ifdef GOVENDOR
|
||||
.PHONY: $(GOVENDOR)
|
||||
$(GOVENDOR):
|
||||
GOOS= GOARCH= $(GO) get -u github.com/kardianos/govendor
|
||||
endif
|
||||
|
||||
.PHONY: precheck
|
||||
precheck::
|
||||
|
||||
define PRECHECK_COMMAND_template =
|
||||
precheck:: $(1)_precheck
|
||||
|
||||
|
||||
PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1)))
|
||||
.PHONY: $(1)_precheck
|
||||
$(1)_precheck:
|
||||
@if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \
|
||||
echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endef
|
|
@ -0,0 +1 @@
|
|||
module github.com/prometheus/procfs
|
|
@ -57,3 +57,17 @@ func ReadUintFromFile(path string) (uint64, error) {
|
|||
}
|
||||
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||
}
|
||||
|
||||
// ParseBool parses a string into a boolean pointer.
|
||||
func ParseBool(b string) *bool {
|
||||
var truth bool
|
||||
switch b {
|
||||
case "enabled":
|
||||
truth = true
|
||||
case "disabled":
|
||||
truth = false
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return &truth
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ type MountStats interface {
|
|||
type MountStatsNFS struct {
|
||||
// The version of statistics provided.
|
||||
StatVersion string
|
||||
// The optional mountaddr of the NFS mount.
|
||||
MountAddress string
|
||||
// The age of the NFS mount.
|
||||
Age time.Duration
|
||||
// Statistics related to byte counters for various operations.
|
||||
|
@ -317,6 +319,7 @@ func parseMount(ss []string) (*Mount, error) {
|
|||
func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
|
||||
// Field indicators for parsing specific types of data
|
||||
const (
|
||||
fieldOpts = "opts:"
|
||||
fieldAge = "age:"
|
||||
fieldBytes = "bytes:"
|
||||
fieldEvents = "events:"
|
||||
|
@ -338,6 +341,13 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
|
|||
}
|
||||
|
||||
switch ss[0] {
|
||||
case fieldOpts:
|
||||
for _, opt := range strings.Split(ss[1], ",") {
|
||||
split := strings.Split(opt, "=")
|
||||
if len(split) == 2 && split[0] == "mountaddr" {
|
||||
stats.MountAddress = split[1]
|
||||
}
|
||||
}
|
||||
case fieldAge:
|
||||
// Age integer is in seconds
|
||||
d, err := time.ParseDuration(ss[1] + "s")
|
||||
|
|
|
@ -95,7 +95,7 @@ type ProcStat struct {
|
|||
// in clock ticks.
|
||||
Starttime uint64
|
||||
// Virtual memory size in bytes.
|
||||
VSize int
|
||||
VSize uint
|
||||
// Resident set size in pages.
|
||||
RSS int
|
||||
|
||||
|
@ -164,7 +164,7 @@ func (p Proc) NewStat() (ProcStat, error) {
|
|||
}
|
||||
|
||||
// VirtualMemory returns the virtual memory size in bytes.
|
||||
func (s ProcStat) VirtualMemory() int {
|
||||
func (s ProcStat) VirtualMemory() uint {
|
||||
return s.VSize
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/util"
|
||||
)
|
||||
|
||||
// ClassThermalZoneStats contains info from files in /sys/class/thermal/thermal_zone<zone>
|
||||
// for a single <zone>.
|
||||
// https://www.kernel.org/doc/Documentation/thermal/sysfs-api.txt
|
||||
type ClassThermalZoneStats struct {
|
||||
Name string // The name of the zone from the directory structure.
|
||||
Type string // The type of thermal zone.
|
||||
Temp uint64 // Temperature in millidegree Celsius.
|
||||
Policy string // One of the various thermal governors used for a particular zone.
|
||||
Mode *bool // Optional: One of the predefined values in [enabled, disabled].
|
||||
Passive *uint64 // Optional: millidegrees Celsius. (0 for disabled, > 1000 for enabled+value)
|
||||
}
|
||||
|
||||
// NewClassThermalZoneStats returns Thermal Zone metrics for all zones.
|
||||
func (fs FS) NewClassThermalZoneStats() ([]ClassThermalZoneStats, error) {
|
||||
zones, err := filepath.Glob(fs.Path("class/thermal/thermal_zone[0-9]*"))
|
||||
if err != nil {
|
||||
return []ClassThermalZoneStats{}, err
|
||||
}
|
||||
|
||||
var zoneStats = ClassThermalZoneStats{}
|
||||
stats := make([]ClassThermalZoneStats, len(zones))
|
||||
for i, zone := range zones {
|
||||
zoneName := strings.TrimPrefix(filepath.Base(zone), "thermal_zone")
|
||||
|
||||
zoneStats, err = parseClassThermalZone(zone)
|
||||
if err != nil {
|
||||
return []ClassThermalZoneStats{}, err
|
||||
}
|
||||
zoneStats.Name = zoneName
|
||||
stats[i] = zoneStats
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func parseClassThermalZone(zone string) (ClassThermalZoneStats, error) {
|
||||
// Required attributes.
|
||||
zoneType, err := util.SysReadFile(filepath.Join(zone, "type"))
|
||||
if err != nil {
|
||||
return ClassThermalZoneStats{}, err
|
||||
}
|
||||
zonePolicy, err := util.SysReadFile(filepath.Join(zone, "policy"))
|
||||
if err != nil {
|
||||
return ClassThermalZoneStats{}, err
|
||||
}
|
||||
zoneTemp, err := util.ReadUintFromFile(filepath.Join(zone, "temp"))
|
||||
if err != nil {
|
||||
return ClassThermalZoneStats{}, err
|
||||
}
|
||||
|
||||
// Optional attributes.
|
||||
mode, err := util.SysReadFile(filepath.Join(zone, "mode"))
|
||||
if err != nil && !os.IsNotExist(err) && !os.IsPermission(err) {
|
||||
return ClassThermalZoneStats{}, err
|
||||
}
|
||||
zoneMode := util.ParseBool(mode)
|
||||
|
||||
var zonePassive *uint64
|
||||
passive, err := util.ReadUintFromFile(filepath.Join(zone, "passive"))
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
zonePassive = nil
|
||||
} else if err != nil {
|
||||
return ClassThermalZoneStats{}, err
|
||||
} else {
|
||||
zonePassive = &passive
|
||||
}
|
||||
|
||||
return ClassThermalZoneStats{
|
||||
Type: zoneType,
|
||||
Policy: zonePolicy,
|
||||
Temp: zoneTemp,
|
||||
Mode: zoneMode,
|
||||
Passive: zonePassive,
|
||||
}, nil
|
||||
}
|
|
@ -137,6 +137,55 @@ Lines: 1
|
|||
1
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/class/thermal
|
||||
Mode: 775
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/class/thermal/thermal_zone0
|
||||
Mode: 775
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone0/policy
|
||||
Lines: 1
|
||||
step_wise
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone0/temp
|
||||
Lines: 1
|
||||
49925
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone0/type
|
||||
Lines: 1
|
||||
bcm2835_thermal
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/class/thermal/thermal_zone1
|
||||
Mode: 755
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone1/mode
|
||||
Lines: 1
|
||||
enabled
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone1/passive
|
||||
Lines: 1
|
||||
0
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone1/policy
|
||||
Lines: 1
|
||||
step_wise
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone1/temp
|
||||
Lines: 1
|
||||
44000
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/class/thermal/thermal_zone1/type
|
||||
Lines: 1
|
||||
acpitz
|
||||
Mode: 664
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/devices
|
||||
Mode: 755
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
package sysfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -26,16 +25,19 @@ import (
|
|||
|
||||
// SystemCPUCpufreqStats contains stats from devices/system/cpu/cpu[0-9]*/cpufreq/...
|
||||
type SystemCPUCpufreqStats struct {
|
||||
Name string
|
||||
CurrentFrequency uint64
|
||||
MinimumFrequency uint64
|
||||
MaximumFrequency uint64
|
||||
TransitionLatency uint64
|
||||
AvailableGovernors string
|
||||
Driver string
|
||||
Govenor string
|
||||
RelatedCpus string
|
||||
SetSpeed string
|
||||
Name string
|
||||
CpuinfoCurrentFrequency *uint64
|
||||
CpuinfoMinimumFrequency *uint64
|
||||
CpuinfoMaximumFrequency *uint64
|
||||
CpuinfoTransitionLatency *uint64
|
||||
ScalingCurrentFrequency *uint64
|
||||
ScalingMinimumFrequency *uint64
|
||||
ScalingMaximumFrequency *uint64
|
||||
AvailableGovernors string
|
||||
Driver string
|
||||
Govenor string
|
||||
RelatedCpus string
|
||||
SetSpeed string
|
||||
}
|
||||
|
||||
// TODO: Add topology support.
|
||||
|
@ -74,14 +76,7 @@ func (fs FS) NewSystemCpufreq() ([]SystemCPUCpufreqStats, error) {
|
|||
return []SystemCPUCpufreqStats{}, err
|
||||
}
|
||||
|
||||
if _, err = os.Stat(filepath.Join(cpuCpufreqPath, "scaling_cur_freq")); err == nil {
|
||||
cpufreq, err = parseCpufreqCpuinfo("scaling", cpuCpufreqPath)
|
||||
} else if _, err = os.Stat(filepath.Join(cpuCpufreqPath, "cpuinfo_cur_freq")); err == nil {
|
||||
// Older kernels have metrics named `cpuinfo_...`.
|
||||
cpufreq, err = parseCpufreqCpuinfo("cpuinfo", cpuCpufreqPath)
|
||||
} else {
|
||||
return []SystemCPUCpufreqStats{}, fmt.Errorf("CPU %v is missing cpufreq", cpu)
|
||||
}
|
||||
cpufreq, err = parseCpufreqCpuinfo(cpuCpufreqPath)
|
||||
if err != nil {
|
||||
return []SystemCPUCpufreqStats{}, err
|
||||
}
|
||||
|
@ -92,22 +87,28 @@ func (fs FS) NewSystemCpufreq() ([]SystemCPUCpufreqStats, error) {
|
|||
return systemCpufreq, nil
|
||||
}
|
||||
|
||||
func parseCpufreqCpuinfo(prefix string, cpuPath string) (*SystemCPUCpufreqStats, error) {
|
||||
func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) {
|
||||
uintFiles := []string{
|
||||
prefix + "_cur_freq",
|
||||
prefix + "_max_freq",
|
||||
prefix + "_min_freq",
|
||||
"cpuinfo_cur_freq",
|
||||
"cpuinfo_max_freq",
|
||||
"cpuinfo_min_freq",
|
||||
"cpuinfo_transition_latency",
|
||||
"scaling_cur_freq",
|
||||
"scaling_max_freq",
|
||||
"scaling_min_freq",
|
||||
}
|
||||
uintOut := make([]uint64, len(uintFiles))
|
||||
uintOut := make([]*uint64, len(uintFiles))
|
||||
|
||||
for i, f := range uintFiles {
|
||||
v, err := util.ReadUintFromFile(filepath.Join(cpuPath, f))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
continue
|
||||
}
|
||||
return &SystemCPUCpufreqStats{}, err
|
||||
}
|
||||
|
||||
uintOut[i] = v
|
||||
uintOut[i] = &v
|
||||
}
|
||||
|
||||
stringFiles := []string{
|
||||
|
@ -128,14 +129,17 @@ func parseCpufreqCpuinfo(prefix string, cpuPath string) (*SystemCPUCpufreqStats,
|
|||
}
|
||||
|
||||
return &SystemCPUCpufreqStats{
|
||||
CurrentFrequency: uintOut[0],
|
||||
MaximumFrequency: uintOut[1],
|
||||
MinimumFrequency: uintOut[2],
|
||||
TransitionLatency: uintOut[3],
|
||||
AvailableGovernors: stringOut[0],
|
||||
Driver: stringOut[1],
|
||||
Govenor: stringOut[2],
|
||||
RelatedCpus: stringOut[3],
|
||||
SetSpeed: stringOut[4],
|
||||
CpuinfoCurrentFrequency: uintOut[0],
|
||||
CpuinfoMaximumFrequency: uintOut[1],
|
||||
CpuinfoMinimumFrequency: uintOut[2],
|
||||
CpuinfoTransitionLatency: uintOut[3],
|
||||
ScalingCurrentFrequency: uintOut[4],
|
||||
ScalingMaximumFrequency: uintOut[5],
|
||||
ScalingMinimumFrequency: uintOut[6],
|
||||
AvailableGovernors: stringOut[0],
|
||||
Driver: stringOut[1],
|
||||
Govenor: stringOut[2],
|
||||
RelatedCpus: stringOut[3],
|
||||
SetSpeed: stringOut[4],
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -43,15 +43,15 @@ func ParseStats(r io.Reader) (*Stats, error) {
|
|||
fieldXpc = "xpc"
|
||||
|
||||
// Unimplemented at this time due to lack of documentation.
|
||||
fieldPushAil = "push_ail"
|
||||
fieldXstrat = "xstrat"
|
||||
fieldAbtb2 = "abtb2"
|
||||
fieldAbtc2 = "abtc2"
|
||||
fieldBmbt2 = "bmbt2"
|
||||
fieldIbt2 = "ibt2"
|
||||
fieldFibt2 = "fibt2"
|
||||
fieldQm = "qm"
|
||||
fieldDebug = "debug"
|
||||
// fieldPushAil = "push_ail"
|
||||
// fieldXstrat = "xstrat"
|
||||
// fieldAbtb2 = "abtb2"
|
||||
// fieldAbtc2 = "abtc2"
|
||||
// fieldBmbt2 = "bmbt2"
|
||||
// fieldIbt2 = "ibt2"
|
||||
// fieldFibt2 = "fibt2"
|
||||
// fieldQm = "qm"
|
||||
// fieldDebug = "debug"
|
||||
)
|
||||
|
||||
var xfss Stats
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
*~
|
|
@ -0,0 +1,5 @@
|
|||
all:
|
||||
@echo "Use 'make doctext' on a Solaris/Illumos/etc machine"
|
||||
|
||||
doctext:
|
||||
godoc github.com/siebenmann/go-kstat >kstat-godoc.txt
|
|
@ -0,0 +1,37 @@
|
|||
Go-kstat provides a Go API for the kstat kernel statistics system
|
||||
on Solaris, Illumos, OmniOS, and other Solaris derived systems. For
|
||||
general information on kstats, see the kstat(1) and kstat(3kstat)
|
||||
manpages. For more documentation on the details of the package, see
|
||||
doc.go, kstat_solaris.go, types_solaris_amd64.go, and raw_solaris.go.
|
||||
|
||||
This package is quite young, so the API may well change as I and
|
||||
other people gain experience with using it.
|
||||
|
||||
The API supports access to 'named' kstat statistics, IO statistics,
|
||||
and the most common and useful sorts of 'raw' kstat statistics
|
||||
(unix:0:sysinfo, unix:0:vminfo, unix:0:var, and mnt:*:mntinfo).
|
||||
Other raw kstat statistics are not explicitly supported, but the
|
||||
API provides some escape hatches for access to custom raw
|
||||
statistics.
|
||||
|
||||
This is a cgo-based package so it can't be cross compiled like a regular
|
||||
Go package. It may also have bugs with memory management, since it
|
||||
interacts with the Solaris kstat library and holds references to memory
|
||||
that's been dynamically allocated in C.
|
||||
|
||||
See kstat-godoc.txt for a text dump of the full godoc for the package.
|
||||
Unfortunately Go tool limitations appear to make it impossible to see
|
||||
full package documentat on anything except a Solaris machine (including
|
||||
https://godoc.org/, sadly).
|
||||
|
||||
Bug reports and other contributions are highly welcome.
|
||||
|
||||
Author:
|
||||
|
||||
Chris Siebenmann
|
||||
https://github.com/siebenmann/go-kstat
|
||||
https://utcc.utoronto.ca/~cks/space/blog/
|
||||
|
||||
(and elsewhere)
|
||||
|
||||
Copyright: standard Go copyright
|
|
@ -0,0 +1,106 @@
|
|||
//
|
||||
// Package kstat provides a Go interface to the Solaris/OmniOS
|
||||
// kstat(s) system for user-level access to a lot of kernel
|
||||
// statistics. For more documentation on kstats, see kstat(1) and
|
||||
// kstat(3kstat).
|
||||
//
|
||||
// The package can retrieve what are called 'named' kstat statistics,
|
||||
// IO statistics, and the most common additional types of 'raw'
|
||||
// statistics, which covers almost all kstats you will normally find
|
||||
// in the kernel. You can see the names and types of other kstats, but
|
||||
// not currently retrieve data for them. Named statistics are the most
|
||||
// common type for general information; IO statistics are exported by
|
||||
// disks and some other things. Supported additional raw kstats are
|
||||
// unix:0:sysinfo, unix:0:vminfo, unix:0:var, and mnt:*:mntinfo.
|
||||
//
|
||||
// General usage for named statistics: call Open() to obtain a Token,
|
||||
// then call GetNamed() on it to obtain Named(s) for specific
|
||||
// statistics. Note that this always gives you the very latest value
|
||||
// for the statistic. If you want a number of statistics from the same
|
||||
// module:inst:name triplet (eg several network counters from the same
|
||||
// network interface) and you want them to all have been gathered at
|
||||
// the same time, you need to call .Lookup() to obtain a KStat and
|
||||
// then repeatedly call its .GetNamed() (this is also slightly more
|
||||
// efficient).
|
||||
//
|
||||
// The short version: a kstat is a collection of some related
|
||||
// statistics, eg various network counters for a particular network
|
||||
// interface. A Token is a handle for a collection of kstats. You go
|
||||
// collection (Token) -> kstat (KStat) -> specific statistic (Named)
|
||||
// in order to retrieve the value of a specific statistic.
|
||||
//
|
||||
// (IO stats are retrieved all at once with GetIO(), because they come
|
||||
// to us from the kernel as one single struct so that's what you get.)
|
||||
//
|
||||
// This is a cgo-based package. Cross compilation is up to you.
|
||||
// Goroutine safety is in no way guaranteed because the underlying
|
||||
// C kstat library is probably not thread or goroutine safe (and
|
||||
// there are some all-Go concurrency races involving .Close()).
|
||||
//
|
||||
// This package may leak memory, especially since the Solaris kstat
|
||||
// manpage is not clear on the requirements here. However I believe
|
||||
// it's reasonably memory safe. It's possible to totally corrupt
|
||||
// memory with use-after-free errors if you do operations on kstats
|
||||
// after calling Token.Close(), although we try to avoid that.
|
||||
//
|
||||
// NOTE: this package is quite young. The API may well change as
|
||||
// I (and other people) gain more experience with it.
|
||||
//
|
||||
// PERFORMANCE
|
||||
//
|
||||
// In general this is not going to be as lean and mean as calling
|
||||
// C directly, partly because of intrinsic CGo overheads and partly
|
||||
// because we do more memory allocation and deallocation than a C
|
||||
// program would (partly because we prioritize not leaking memory).
|
||||
//
|
||||
// SUPPORTED AND UNSUPPORTED KSTAT TYPES
|
||||
//
|
||||
// We support named kstats and IO kstats (KSTAT_TYPE_NAMED and
|
||||
// KSTAT_TYPE_IO / kstat_io_t respectively). kstat(1) also knows about
|
||||
// a number of magic specific 'raw' stats (which are generally custom
|
||||
// C structs); of these we support unix:0:sysinfo, unix:0:vminfo,
|
||||
// unix:0:var, and mnt:*:mntinfo for NFS filesystem mounts.
|
||||
//
|
||||
// In theory kstat supports general timer and interrupt stats. In
|
||||
// practice there is no use of KSTAT_TYPE_TIMER in the current Illumos
|
||||
// kernel source and very little use of KSTAT_TYPE_INTR (mostly by
|
||||
// very old hardware drivers, although the vioif driver uses it too).
|
||||
// Since I can't test KSTAT_TYPE_INTR stats, we don't currently
|
||||
// support it.
|
||||
//
|
||||
// There are also a few additional KSTAT_TYPE_RAW raw stats that we
|
||||
// don't support, mostly because they seem to be effectively obsolete.
|
||||
// These specific raw stats can be found listed in the Illumos source
|
||||
// code in cmd/stat/kstat/kstat.h in the ks_raw_lookup array. See
|
||||
// cmd/stat/kstat/kstat.c for how they're interpreted. If you need
|
||||
// access to one of these kstats, the KStat.CopyTo() and KStat.Raw()
|
||||
// methods give you an escape hatch to roll your own. You'll probably
|
||||
// need to use cgo to generate an appropriate Go struct that matches
|
||||
// the C struct you need. My notes on this process may be helpful:
|
||||
//
|
||||
// https://utcc.utoronto.ca/~cks/space/blog/programming/GoCGoCompatibleStructs
|
||||
//
|
||||
// Author: Chris Siebenmann
|
||||
// https://github.com/siebenmann/go-kstat
|
||||
//
|
||||
// Copyright: standard Go copyright.
|
||||
//
|
||||
// (If you're reading this documentation on a non-Solaris platform,
|
||||
// you're probably not seeing the detailed API documentation for
|
||||
// constants, types, and so on because of tooling limitations in godoc
|
||||
// et al.)
|
||||
//
|
||||
package kstat
|
||||
|
||||
//
|
||||
// This exists in large part to give non-Solaris systems something
|
||||
// that makes the package name visible. Otherwise eg goimports thinks
|
||||
// that this is an empty package and deletes 'import ...' statements
|
||||
// for it if you process a Go file that imports the package on a
|
||||
// non-Solaris platform.
|
||||
//
|
||||
// Since this file exists, I've put the package-level documentation
|
||||
// here to increase the odds that tools running on non-Solaris systems
|
||||
// will be able to show you at least some documentation.
|
||||
//
|
||||
// This is a hack, and annoying.
|
|
@ -0,0 +1,455 @@
|
|||
PACKAGE DOCUMENTATION
|
||||
|
||||
package kstat
|
||||
import "github.com/siebenmann/go-kstat"
|
||||
|
||||
Package kstat provides a Go interface to the Solaris/OmniOS kstat(s)
|
||||
system for user-level access to a lot of kernel statistics. For more
|
||||
documentation on kstats, see kstat(1) and kstat(3kstat).
|
||||
|
||||
The package can retrieve what are called 'named' kstat statistics, IO
|
||||
statistics, and the most common additional types of 'raw' statistics,
|
||||
which covers almost all kstats you will normally find in the kernel. You
|
||||
can see the names and types of other kstats, but not currently retrieve
|
||||
data for them. Named statistics are the most common type for general
|
||||
information; IO statistics are exported by disks and some other things.
|
||||
Supported additional raw kstats are unix:0:sysinfo, unix:0:vminfo,
|
||||
unix:0:var, and mnt:*:mntinfo.
|
||||
|
||||
General usage for named statistics: call Open() to obtain a Token, then
|
||||
call GetNamed() on it to obtain Named(s) for specific statistics. Note
|
||||
that this always gives you the very latest value for the statistic. If
|
||||
you want a number of statistics from the same module:inst:name triplet
|
||||
(eg several network counters from the same network interface) and you
|
||||
want them to all have been gathered at the same time, you need to call
|
||||
.Lookup() to obtain a KStat and then repeatedly call its .GetNamed()
|
||||
(this is also slightly more efficient).
|
||||
|
||||
The short version: a kstat is a collection of some related statistics,
|
||||
eg various network counters for a particular network interface. A Token
|
||||
is a handle for a collection of kstats. You go collection (Token) ->
|
||||
kstat (KStat) -> specific statistic (Named) in order to retrieve the
|
||||
value of a specific statistic.
|
||||
|
||||
(IO stats are retrieved all at once with GetIO(), because they come to
|
||||
us from the kernel as one single struct so that's what you get.)
|
||||
|
||||
This is a cgo-based package. Cross compilation is up to you. Goroutine
|
||||
safety is in no way guaranteed because the underlying C kstat library is
|
||||
probably not thread or goroutine safe (and there are some all-Go
|
||||
concurrency races involving .Close()).
|
||||
|
||||
This package may leak memory, especially since the Solaris kstat manpage
|
||||
is not clear on the requirements here. However I believe it's reasonably
|
||||
memory safe. It's possible to totally corrupt memory with use-after-free
|
||||
errors if you do operations on kstats after calling Token.Close(),
|
||||
although we try to avoid that.
|
||||
|
||||
NOTE: this package is quite young. The API may well change as I (and
|
||||
other people) gain more experience with it.
|
||||
|
||||
|
||||
PERFORMANCE
|
||||
|
||||
In general this is not going to be as lean and mean as calling C
|
||||
directly, partly because of intrinsic CGo overheads and partly because
|
||||
we do more memory allocation and deallocation than a C program would
|
||||
(partly because we prioritize not leaking memory).
|
||||
|
||||
|
||||
SUPPORTED AND UNSUPPORTED KSTAT TYPES
|
||||
|
||||
We support named kstats and IO kstats (KSTAT_TYPE_NAMED and
|
||||
KSTAT_TYPE_IO / kstat_io_t respectively). kstat(1) also knows about a
|
||||
number of magic specific 'raw' stats (which are generally custom C
|
||||
structs); of these we support unix:0:sysinfo, unix:0:vminfo, unix:0:var,
|
||||
and mnt:*:mntinfo for NFS filesystem mounts.
|
||||
|
||||
In theory kstat supports general timer and interrupt stats. In practice
|
||||
there is no use of KSTAT_TYPE_TIMER in the current Illumos kernel source
|
||||
and very little use of KSTAT_TYPE_INTR (mostly by very old hardware
|
||||
drivers, although the vioif driver uses it too). Since I can't test
|
||||
KSTAT_TYPE_INTR stats, we don't currently support it.
|
||||
|
||||
There are also a few additional KSTAT_TYPE_RAW raw stats that we don't
|
||||
support, mostly because they seem to be effectively obsolete. These
|
||||
specific raw stats can be found listed in the Illumos source code in
|
||||
cmd/stat/kstat/kstat.h in the ks_raw_lookup array. See
|
||||
cmd/stat/kstat/kstat.c for how they're interpreted. If you need access
|
||||
to one of these kstats, the KStat.CopyTo() and KStat.Raw() methods give
|
||||
you an escape hatch to roll your own. You'll probably need to use cgo to
|
||||
generate an appropriate Go struct that matches the C struct you need. My
|
||||
notes on this process may be helpful:
|
||||
|
||||
https://utcc.utoronto.ca/~cks/space/blog/programming/GoCGoCompatibleStructs
|
||||
|
||||
Author: Chris Siebenmann https://github.com/siebenmann/go-kstat
|
||||
|
||||
Copyright: standard Go copyright.
|
||||
|
||||
(If you're reading this documentation on a non-Solaris platform, you're
|
||||
probably not seeing the detailed API documentation for constants, types,
|
||||
and so on because of tooling limitations in godoc et al.)
|
||||
|
||||
FUNCTIONS
|
||||
|
||||
func CFieldString(src []int8) string
|
||||
CFieldString converts a (null-terminated) C string embedded in an []int8
|
||||
slice to a (Go) string. The []int8 slice is likely to come from an
|
||||
[N]int8 fixed-size field in a statistics struct. If there is no null in
|
||||
the slice, the entire slice is returned.
|
||||
|
||||
(The no-null behavior is common in C APIs; a string is often allowed to
|
||||
exactly fill the field with no room for a trailing null.)
|
||||
|
||||
TYPES
|
||||
|
||||
type IO struct {
|
||||
Nread uint64
|
||||
Nwritten uint64
|
||||
Reads uint32
|
||||
Writes uint32
|
||||
Wtime int64
|
||||
Wlentime int64
|
||||
Wlastupdate int64
|
||||
Rtime int64
|
||||
Rlentime int64
|
||||
Rlastupdate int64
|
||||
Wcnt uint32
|
||||
Rcnt uint32
|
||||
}
|
||||
IO represents the entire collection of KStat (disk) IO statistics
|
||||
exposed by an IoStat type KStat.
|
||||
|
||||
Because IO is an exact copy of the C kstat_io_t structure from the
|
||||
kernel, it does not have a Snaptime or KStat field. You must save that
|
||||
information separately if you need it, perhaps by embedded the IO struct
|
||||
as an anonymous struct in an additional struct of your own.
|
||||
|
||||
type KSType int
|
||||
KSType is the type of the data in a KStat.
|
||||
|
||||
const (
|
||||
RawStat KSType = C.KSTAT_TYPE_RAW
|
||||
NamedStat KSType = C.KSTAT_TYPE_NAMED
|
||||
IntrStat KSType = C.KSTAT_TYPE_INTR
|
||||
IoStat KSType = C.KSTAT_TYPE_IO
|
||||
TimerStat KSType = C.KSTAT_TYPE_TIMER
|
||||
)
|
||||
The different types of data that a KStat may contain, ie these are the
|
||||
value of a KStat.Type. We currently only support getting Named and IO
|
||||
statistics.
|
||||
|
||||
func (tp KSType) String() string
|
||||
|
||||
type KStat struct {
|
||||
Module string
|
||||
Instance int
|
||||
Name string
|
||||
|
||||
// Class is eg 'net' or 'disk'. In kstat(1) it shows up as a
|
||||
// ':class' statistic.
|
||||
Class string
|
||||
// Type is the type of kstat.
|
||||
Type KSType
|
||||
|
||||
// Creation time of a kstat in nanoseconds since sometime.
|
||||
// See gethrtime(3) and kstat(3kstat).
|
||||
Crtime int64
|
||||
// Snaptime is what kstat(1) reports as 'snaptime', the time
|
||||
// that this data was obtained. As with Crtime, it is in
|
||||
// nanoseconds since some arbitrary point in time.
|
||||
// Snaptime may not be valid until .Refresh() or .GetNamed()
|
||||
// has been called.
|
||||
Snaptime int64
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
KStat is the access handle for the collection of statistics for a
|
||||
particular module:instance:name kstat.
|
||||
|
||||
func (k *KStat) AllNamed() ([]*Named, error)
|
||||
AllNamed returns an array of all named statistics for a particular
|
||||
named-type KStat. Entries are returned in no particular order.
|
||||
|
||||
func (k *KStat) CopyTo(ptr interface{}) error
|
||||
CopyTo copies a RawStat KStat into a struct that you supply a pointer
|
||||
to. The size of the struct must exactly match the size of the RawStat's
|
||||
data.
|
||||
|
||||
CopyStat imposes conditions on the struct that you are copying to: it
|
||||
must be composed entirely of primitive integer types with defined sizes
|
||||
(intN and uintN), or arrays and structs that ultimately only contain
|
||||
them. All fields should be exported.
|
||||
|
||||
If you give CopyStat a bad argument, it generally panics.
|
||||
|
||||
This API is provisional and may be changed or deleted.
|
||||
|
||||
func (k *KStat) GetIO() (*IO, error)
|
||||
GetIO retrieves the IO statistics data from an IoStat type KStat. It
|
||||
always refreshes the KStat to provide current data.
|
||||
|
||||
It corresponds to kstat_read() followed by getting a copy of ks_data
|
||||
(which is a kstat_io_t).
|
||||
|
||||
func (k *KStat) GetMntinfo() (*Mntinfo, error)
|
||||
GetMntinfo retrieves a Mntinfo struct from a nfs:*:mntinfo KStat. It
|
||||
does not force a refresh of the KStat.
|
||||
|
||||
func (k *KStat) GetNamed(name string) (*Named, error)
|
||||
GetNamed obtains a particular named statistic from a KStat. It does not
|
||||
refresh the KStat's statistics data, so multiple calls to GetNamed on a
|
||||
single KStat will get a coherent set of statistic values from it.
|
||||
|
||||
It corresponds to kstat_data_lookup().
|
||||
|
||||
func (k *KStat) Raw() (*Raw, error)
|
||||
Raw returns the raw byte data of a KStat. It may be called on any KStat.
|
||||
It does not refresh the KStat's data.
|
||||
|
||||
func (k *KStat) Refresh() error
|
||||
Refresh the statistics data for a KStat.
|
||||
|
||||
Note that this does not update any existing Named objects for statistics
|
||||
from this KStat. You must re-do .GetNamed() to get new ones in order to
|
||||
see any updates.
|
||||
|
||||
Under the hood this does a kstat_read(). You don't need to call it
|
||||
explicitly before obtaining statistics from a KStat.
|
||||
|
||||
func (k *KStat) String() string
|
||||
|
||||
func (k *KStat) Valid() bool
|
||||
Valid returns true if a KStat is still valid after a Token.Update() call
|
||||
has returned true. If a KStat becomes invalid after an update, its
|
||||
fields remain available but you can no longer call methods on it. You
|
||||
may be able to look it up again with token.Lookup(k.Module, k.Instance,
|
||||
k.Name), although it's possible that the module:instance:name now refers
|
||||
to something else. Even if it is still the same thing, there is no
|
||||
continuity in the actual statistics once Valid becomes false; you must
|
||||
restart tracking from scratch.
|
||||
|
||||
(For example, if one disk is removed from the system and another is
|
||||
added, the new disk may use the same module:instance:name as some of the
|
||||
old disk's KStats. Your .Lookup() may succeed, but what you get back is
|
||||
not in any way a continuation of the old disk's information.)
|
||||
|
||||
Valid also returns false after the KStat's token has been closed.
|
||||
|
||||
type Mntinfo struct {
|
||||
RProto [128]int8
|
||||
Vers uint32
|
||||
Flags uint32
|
||||
Secmod uint32
|
||||
Curread uint32
|
||||
Curwrite uint32
|
||||
Timeo int32
|
||||
Retrans int32
|
||||
Acregmin uint32
|
||||
Acregmax uint32
|
||||
Acdirmin uint32
|
||||
Acdirmax uint32
|
||||
Timers [4]struct {
|
||||
Srtt uint32
|
||||
Deviate uint32
|
||||
Rtxcur uint32
|
||||
}
|
||||
Noresponse uint32
|
||||
Failover uint32
|
||||
Remap uint32
|
||||
RCurserver [257]int8
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
Mntinfo is the kernel data from nfs:*:mntinfo, which is a 'struct
|
||||
mntinfo_kstat'. Use .Proto() and .Curserver() to get the RProto and
|
||||
RCurserver fields as strings instead of their awkward raw form.
|
||||
|
||||
func (m Mntinfo) Curserver() string
|
||||
Curserver returns a Mntinfo RCurserver as a string.
|
||||
|
||||
func (m Mntinfo) Proto() string
|
||||
Proto returns a Mntinfo RProto as a string.
|
||||
|
||||
type Named struct {
|
||||
Name string
|
||||
Type NamedType
|
||||
|
||||
// Only one of the following values is valid; the others are zero
|
||||
// values.
|
||||
//
|
||||
// StringVal holds the value for both CharData and String Type(s).
|
||||
StringVal string
|
||||
IntVal int64
|
||||
UintVal uint64
|
||||
|
||||
// The Snaptime this Named was obtained. Note that while you
|
||||
// use the parent KStat's Crtime, you cannot use its Snaptime.
|
||||
// The KStat may have been refreshed since this Named was
|
||||
// created, which updates the Snaptime.
|
||||
Snaptime int64
|
||||
|
||||
// Pointer to the parent KStat, for access to the full name
|
||||
// and the crtime associated with this Named.
|
||||
KStat *KStat
|
||||
}
|
||||
Named represents a particular kstat named statistic, ie the full
|
||||
|
||||
module:instance:name:statistic
|
||||
|
||||
and its current value.
|
||||
|
||||
Name and Type are always valid, but only one of StringVal, IntVal, or
|
||||
UintVal is valid for any particular statistic; which one is valid is
|
||||
determined by its Type. Generally you'll already know what type a given
|
||||
named kstat statistic is; I don't believe Solaris changes their type
|
||||
once they're defined.
|
||||
|
||||
func (ks *Named) String() string
|
||||
|
||||
type NamedType int
|
||||
NamedType represents the various types of named kstat statistics.
|
||||
|
||||
const (
|
||||
CharData NamedType = C.KSTAT_DATA_CHAR
|
||||
Int32 NamedType = C.KSTAT_DATA_INT32
|
||||
Uint32 NamedType = C.KSTAT_DATA_UINT32
|
||||
Int64 NamedType = C.KSTAT_DATA_INT64
|
||||
Uint64 NamedType = C.KSTAT_DATA_UINT64
|
||||
String NamedType = C.KSTAT_DATA_STRING
|
||||
)
|
||||
The different types of data that a named kstat statistic can be (ie,
|
||||
these are the potential values of Named.Type).
|
||||
|
||||
func (tp NamedType) String() string
|
||||
|
||||
type Raw struct {
|
||||
Data []byte
|
||||
Ndata uint64
|
||||
Snaptime int64
|
||||
KStat *KStat
|
||||
}
|
||||
Raw is the raw data of a KStat. The actual bytes are in Data; Ndata is
|
||||
kstat_t.ks_ndata, and is not normally useful.
|
||||
|
||||
Note that with RawStat KStats, it turns out that Ndata == len(Data).
|
||||
This is contrary to its meaning for other types of kstats.
|
||||
|
||||
type Sysinfo struct {
|
||||
Updates uint32
|
||||
Runque uint32
|
||||
Runocc uint32
|
||||
Swpque uint32
|
||||
Swpocc uint32
|
||||
Waiting uint32
|
||||
}
|
||||
Sysinfo is the data from unix:0:sysinfo, which is a sysinfo_t.
|
||||
|
||||
type Token struct {
|
||||
// contains filtered or unexported fields
|
||||
}
|
||||
Token is an access token for obtaining kstats.
|
||||
|
||||
func Open() (*Token, error)
|
||||
Open returns a kstat Token that is used to obtain kstats. It corresponds
|
||||
to kstat_open(). You should call .Close() when you're done and then not
|
||||
use any KStats or Nameds obtained through this token.
|
||||
|
||||
(Failing to call .Close() will cause memory leaks.)
|
||||
|
||||
func (t *Token) All() []*KStat
|
||||
All returns an array of all available KStats.
|
||||
|
||||
(It has no error return because due to how kstats are implemented, it
|
||||
cannot fail.)
|
||||
|
||||
func (t *Token) Close() error
|
||||
Close a kstat access token. A closed token cannot be used for anything
|
||||
and cannot be reopened.
|
||||
|
||||
After a Token has been closed it remains safe to look at fields on KStat
|
||||
and Named objects obtained through the Token, but it is not safe to call
|
||||
methods on them other than String(); doing so may cause memory
|
||||
corruption, although we try to avoid that.
|
||||
|
||||
This corresponds to kstat_close().
|
||||
|
||||
func (t *Token) GetNamed(module string, instance int, name, stat string) (*Named, error)
|
||||
GetNamed obtains the Named representing a particular (named) kstat
|
||||
module:instance:name:statistic statistic. It always returns current data
|
||||
for the kstat statistic, even if it's called repeatedly for the same
|
||||
statistic.
|
||||
|
||||
It is equivalent to .Lookup() then KStat.GetNamed().
|
||||
|
||||
func (t *Token) Lookup(module string, instance int, name string) (*KStat, error)
|
||||
Lookup looks up a particular kstat. module and name may be "" and
|
||||
instance may be -1 to mean 'the first one that kstats can find'. It also
|
||||
refreshes (or retrieves) the kstat's data and thus sets Snaptime.
|
||||
|
||||
Lookup() corresponds to kstat_lookup() *plus kstat_read()*.
|
||||
|
||||
func (tok *Token) Sysinfo() (*KStat, *Sysinfo, error)
|
||||
Sysinfo returns the KStat and the statistics from unix:0:sysinfo. It
|
||||
always returns a current, refreshed copy.
|
||||
|
||||
func (t *Token) Update() (bool, error)
|
||||
Update synchronizes the Token to the current state of available kernel
|
||||
kstats, returning true if the kernel's list of available kstats changed
|
||||
and false otherwise. If there have been no changes in the kernel's kstat
|
||||
list, all KStats remain valid. If there was a kstat update, some or all
|
||||
of the KStats obtained through the Token may now be invalid. Some of the
|
||||
now-invalid KStats may still exist and be the same thing, but if so they
|
||||
will have to be looked up again.
|
||||
|
||||
(This happens if, for example, a device disappears and then reappears.
|
||||
At the kernel level, the device's kstat is deleted when it disappears
|
||||
and then is recreated when it reappears; the kernel considers the
|
||||
recreated version to be a different kstat, although it has the same
|
||||
module:instance:name. Note that the same module:instance:name still
|
||||
existing does not guarantee that the kstat is for the same thing; one
|
||||
disk might have removed and then an entirely different new disk added.)
|
||||
|
||||
Update corresponds to kstat_chain_update().
|
||||
|
||||
func (tok *Token) Var() (*KStat, *Var, error)
|
||||
Var returns the KStat and the statistics from unix:0:var. It always
|
||||
returns a current, refreshed copy.
|
||||
|
||||
func (tok *Token) Vminfo() (*KStat, *Vminfo, error)
|
||||
Vminfo returns the KStat and the statistics from unix:0:vminfo. It
|
||||
always returns a current, refreshed copy.
|
||||
|
||||
type Var struct {
|
||||
Buf int32
|
||||
Call int32
|
||||
Proc int32
|
||||
Maxupttl int32
|
||||
Nglobpris int32
|
||||
Maxsyspri int32
|
||||
Clist int32
|
||||
Maxup int32
|
||||
Hbuf int32
|
||||
Hmask int32
|
||||
Pbuf int32
|
||||
Sptmap int32
|
||||
Maxpmem int32
|
||||
Autoup int32
|
||||
Bufhwm int32
|
||||
}
|
||||
Var is the data from unix:0:var, which is a 'struct var'.
|
||||
|
||||
type Vminfo struct {
|
||||
Freemem uint64
|
||||
Resv uint64
|
||||
Alloc uint64
|
||||
Avail uint64
|
||||
Free uint64
|
||||
Updates uint64
|
||||
}
|
||||
Vminfo is the data from unix:0:vminfo, which is a vminfo_t.
|
||||
|
||||
SUBDIRECTORIES
|
||||
|
||||
cmd
|
||||
gen
|
||||
|
|
@ -0,0 +1,658 @@
|
|||
//
|
||||
// The kstat package provides a Go interface to the Solaris/OmniOS
|
||||
// kstat(s) system for user-level access to a lot of kernel
|
||||
// statistics. For more documentation on kstats, see kstat(1) and
|
||||
// kstat(3kstat).
|
||||
//
|
||||
// In an ideal world the package documentation would go here. This is
|
||||
// not an ideal world, because any number of tools like godoc choke on
|
||||
// Go files that are not for their architecture (although I'll admit
|
||||
// it's a hard problem). So see doc.go for the actual package level
|
||||
// documentation.
|
||||
//
|
||||
// However, I refuse to push function level API documentation off to another
|
||||
// file, at least at the moment. It would be a horrible mess.
|
||||
//
|
||||
|
||||
package kstat
|
||||
|
||||
// #cgo LDFLAGS: -lkstat
|
||||
//
|
||||
// #include <sys/types.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <strings.h>
|
||||
// #include <kstat.h>
|
||||
//
|
||||
// /* We have to reach through unions, which cgo doesn't support.
|
||||
// So we have our own cheesy little routines for it. These assume
|
||||
// they are always being called on validly-typed named kstats.
|
||||
// */
|
||||
//
|
||||
// char *get_named_char(kstat_named_t *knp) {
|
||||
// return knp->value.str.addr.ptr;
|
||||
// }
|
||||
//
|
||||
// uint64_t get_named_uint(kstat_named_t *knp) {
|
||||
// if (knp->data_type == KSTAT_DATA_UINT32)
|
||||
// return knp->value.ui32;
|
||||
// else
|
||||
// return knp->value.ui64;
|
||||
// }
|
||||
//
|
||||
// int64_t get_named_int(kstat_named_t *knp) {
|
||||
// if (knp->data_type == KSTAT_DATA_INT32)
|
||||
// return knp->value.i32;
|
||||
// else
|
||||
// return knp->value.i64;
|
||||
// }
|
||||
//
|
||||
// /* Let's not try to do C pointer arithmetic in Go and get it wrong */
|
||||
// kstat_named_t *get_nth_named(kstat_t *ks, uint_t n) {
|
||||
// kstat_named_t *knp;
|
||||
// if (!ks || !ks->ks_data || ks->ks_type != KSTAT_TYPE_NAMED || n >= ks->ks_ndata)
|
||||
// return NULL;
|
||||
// knp = KSTAT_NAMED_PTR(ks);
|
||||
// return knp + n;
|
||||
// }
|
||||
//
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Token is an access token for obtaining kstats.
|
||||
type Token struct {
|
||||
kc *C.struct_kstat_ctl
|
||||
|
||||
// ksm maps kstat_t pointers to our Go-level KStats for them.
|
||||
// kstat_t's stay constant over the lifetime of a token, so
|
||||
// we want to keep unique KStats. This holds some Go-level
|
||||
// memory down, but I wave my hands.
|
||||
ksm map[*C.struct_kstat]*KStat
|
||||
}
|
||||
|
||||
// Open returns a kstat Token that is used to obtain kstats. It corresponds
|
||||
// to kstat_open(). You should call .Close() when you're done and then not
|
||||
// use any KStats or Nameds obtained through this token.
|
||||
//
|
||||
// (Failing to call .Close() will cause memory leaks.)
|
||||
func Open() (*Token, error) {
|
||||
r, err := C.kstat_open()
|
||||
if r == nil {
|
||||
return nil, err
|
||||
}
|
||||
t := Token{}
|
||||
t.kc = r
|
||||
t.ksm = make(map[*C.struct_kstat]*KStat)
|
||||
// A 'func (t *Token) Close()' is equivalent to
|
||||
// 'func Close(t *Token)'. The latter is what SetFinalizer()
|
||||
// needs.
|
||||
runtime.SetFinalizer(&t, (*Token).Close)
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// Close a kstat access token. A closed token cannot be used for
|
||||
// anything and cannot be reopened.
|
||||
//
|
||||
// After a Token has been closed it remains safe to look at fields
|
||||
// on KStat and Named objects obtained through the Token, but it is
|
||||
// not safe to call methods on them other than String(); doing so
|
||||
// may cause memory corruption, although we try to avoid that.
|
||||
//
|
||||
// This corresponds to kstat_close().
|
||||
func (t *Token) Close() error {
|
||||
if t == nil || t.kc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Go through our KStats and null out fields that are no longer
|
||||
// valid. We opt to do this before we actually destroy the memory
|
||||
// KStat.ksp is pointing to by calling kstat_close().
|
||||
for _, v := range t.ksm {
|
||||
v.ksp = nil
|
||||
v.tok = nil
|
||||
}
|
||||
|
||||
res, err := C.kstat_close(t.kc)
|
||||
t.kc = nil
|
||||
|
||||
// clear the map to drop all references to KStats.
|
||||
t.ksm = make(map[*C.struct_kstat]*KStat)
|
||||
|
||||
// cancel finalizer
|
||||
runtime.SetFinalizer(&t, nil)
|
||||
|
||||
if res != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update synchronizes the Token to the current state of available
|
||||
// kernel kstats, returning true if the kernel's list of available
|
||||
// kstats changed and false otherwise. If there have been no changes
|
||||
// in the kernel's kstat list, all KStats remain valid. If there was a
|
||||
// kstat update, some or all of the KStats obtained through the Token
|
||||
// may now be invalid. Some of the now-invalid KStats may still exist
|
||||
// and be the same thing, but if so they will have to be looked up
|
||||
// again.
|
||||
//
|
||||
// (This happens if, for example, a device disappears and then
|
||||
// reappears. At the kernel level, the device's kstat is deleted when
|
||||
// it disappears and then is recreated when it reappears; the kernel
|
||||
// considers the recreated version to be a different kstat, although
|
||||
// it has the same module:instance:name. Note that the same
|
||||
// module:instance:name still existing does not guarantee that the
|
||||
// kstat is for the same thing; one disk might have removed and then
|
||||
// an entirely different new disk added.)
|
||||
//
|
||||
// Update corresponds to kstat_chain_update().
|
||||
func (t *Token) Update() (bool, error) {
|
||||
if t == nil || t.kc == nil {
|
||||
return true, errors.New("token is closed")
|
||||
}
|
||||
oid := t.kc.kc_chain_id
|
||||
// NOTE that we can't assume err == nil on success and just
|
||||
// check for err != nil. The error return is set from errno,
|
||||
// and kstat_chain_update() does not guarantee that errno is
|
||||
// 0 if it succeeds.
|
||||
nid, err := C.kstat_chain_update(t.kc)
|
||||
switch {
|
||||
case nid < 0:
|
||||
// We generously assume that if there has been an
|
||||
// error, the chain is intact. Otherwise we should
|
||||
// invalidate all KStats in t.ksm, as in .Close().
|
||||
// assumption: err != nil if n < 0.
|
||||
return false, err
|
||||
case nid == 0:
|
||||
// No change is good news.
|
||||
return false, nil
|
||||
case nid == oid:
|
||||
// Should never be the case, but...
|
||||
return false, fmt.Errorf("new KCID is old KCID: %d", nid)
|
||||
}
|
||||
|
||||
// The simple approach to KStats after a chain update would be
|
||||
// to invalidate all existing KStats. However, we can do
|
||||
// better. kstat_chain_update() implicitly guarantees that it
|
||||
// will not reuse memory addresses of kstat_t structures for
|
||||
// different ones within a single call, so we can walk the
|
||||
// chain and look for addresses that we already know; the
|
||||
// KStats for those addresses are still valid.
|
||||
|
||||
// Copy all valid chain entries that we have in the token ksm
|
||||
// map to a new map and delete them from the old (current) map.
|
||||
nksm := make(map[*C.struct_kstat]*KStat)
|
||||
for r := t.kc.kc_chain; r != nil; r = r.ks_next {
|
||||
if v, ok := t.ksm[r]; ok {
|
||||
nksm[r] = v
|
||||
delete(t.ksm, r)
|
||||
}
|
||||
}
|
||||
// Anything left in t.ksm is an old chain entry that was
|
||||
// removed by kstat_chain_update(). Explicitly zap their
|
||||
// KStat's references to make them invalid.
|
||||
for _, v := range t.ksm {
|
||||
v.ksp = nil
|
||||
v.tok = nil
|
||||
}
|
||||
// Make our new ksm map the current ksm map.
|
||||
t.ksm = nksm
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// All returns an array of all available KStats.
|
||||
//
|
||||
// (It has no error return because due to how kstats are implemented,
|
||||
// it cannot fail.)
|
||||
func (t *Token) All() []*KStat {
|
||||
n := []*KStat{}
|
||||
if t == nil || t.kc == nil {
|
||||
return n
|
||||
}
|
||||
|
||||
for r := t.kc.kc_chain; r != nil; r = r.ks_next {
|
||||
n = append(n, newKStat(t, r))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
//
|
||||
// allocate a C string for a non-blank string; otherwise return nil
|
||||
func maybeCString(src string) *C.char {
|
||||
if src == "" {
|
||||
return nil
|
||||
}
|
||||
return C.CString(src)
|
||||
}
|
||||
|
||||
// free a non-nil C string
|
||||
func maybeFree(cs *C.char) {
|
||||
if cs != nil {
|
||||
C.free(unsafe.Pointer(cs))
|
||||
}
|
||||
}
|
||||
|
||||
// strndup behaves like the C function; given a *C.char and a len, it
|
||||
// returns a string that is up to len characters long at most.
|
||||
// Shorn of casts, it is:
|
||||
// C.GoStringN(p, C.strnlen(p, len))
|
||||
//
|
||||
// strndup() is necessary to copy fields of the type 'char
|
||||
// name[SIZE];' where a string of exactly SIZE length will not be
|
||||
// null-terminated. GoStringN() will always copy trailing null bytes
|
||||
// and other garbage; GoString()'s internal strlen() may run off the
|
||||
// end of the 'name' field and either fault or copy too much.
|
||||
func strndup(cs *C.char, len C.size_t) string {
|
||||
// credit: Ian Lance Taylor in
|
||||
// https://github.com/golang/go/issues/12428
|
||||
return C.GoStringN(cs, C.int(C.strnlen(cs, len)))
|
||||
}
|
||||
|
||||
// Lookup looks up a particular kstat. module and name may be "" and
|
||||
// instance may be -1 to mean 'the first one that kstats can find'.
|
||||
// It also refreshes (or retrieves) the kstat's data and thus sets
|
||||
// Snaptime.
|
||||
//
|
||||
// Lookup() corresponds to kstat_lookup() *plus kstat_read()*.
|
||||
func (t *Token) Lookup(module string, instance int, name string) (*KStat, error) {
|
||||
if t == nil || t.kc == nil {
|
||||
return nil, errors.New("Token not valid or closed")
|
||||
}
|
||||
|
||||
ms := maybeCString(module)
|
||||
ns := maybeCString(name)
|
||||
r, err := C.kstat_lookup(t.kc, ms, C.int(instance), ns)
|
||||
maybeFree(ms)
|
||||
maybeFree(ns)
|
||||
|
||||
if r == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := newKStat(t, r)
|
||||
|
||||
// People rarely look up kstats to not use them, so we immediately
|
||||
// attempt to kstat_read() the data. If this fails, we don't return
|
||||
// the kstat. However, we don't scrub it from the kstat_t mapping
|
||||
// that the Token maintains; we have no reason to believe that it
|
||||
// needs to be remade. Our return of nil is a convenience to avoid
|
||||
// problems in callers.
|
||||
// TODO: this may be a mistake in the API.
|
||||
//
|
||||
// NOTE: this means that calling Lookup() on an existing KStat
|
||||
// (either directly or via tok.GetNamed()) has the effect of
|
||||
// updating its statistics data to the current time. Right now
|
||||
// we consider this a feature.
|
||||
err = k.Refresh()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// GetNamed obtains the Named representing a particular (named) kstat
|
||||
// module:instance:name:statistic statistic. It always returns current
|
||||
// data for the kstat statistic, even if it's called repeatedly for the
|
||||
// same statistic.
|
||||
//
|
||||
// It is equivalent to .Lookup() then KStat.GetNamed().
|
||||
func (t *Token) GetNamed(module string, instance int, name, stat string) (*Named, error) {
|
||||
stats, err := t.Lookup(module, instance, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return stats.GetNamed(stat)
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
// KSType is the type of the data in a KStat.
|
||||
type KSType int
|
||||
|
||||
// The different types of data that a KStat may contain, ie these
|
||||
// are the value of a KStat.Type. We currently only support getting
|
||||
// Named and IO statistics.
|
||||
const (
|
||||
RawStat KSType = C.KSTAT_TYPE_RAW
|
||||
NamedStat KSType = C.KSTAT_TYPE_NAMED
|
||||
IntrStat KSType = C.KSTAT_TYPE_INTR
|
||||
IoStat KSType = C.KSTAT_TYPE_IO
|
||||
TimerStat KSType = C.KSTAT_TYPE_TIMER
|
||||
)
|
||||
|
||||
func (tp KSType) String() string {
|
||||
switch tp {
|
||||
case RawStat:
|
||||
return "raw"
|
||||
case NamedStat:
|
||||
return "named"
|
||||
case IntrStat:
|
||||
return "interrupt"
|
||||
case IoStat:
|
||||
return "io"
|
||||
case TimerStat:
|
||||
return "timer"
|
||||
default:
|
||||
return fmt.Sprintf("kstat_type:%d", tp)
|
||||
}
|
||||
}
|
||||
|
||||
// KStat is the access handle for the collection of statistics for a
|
||||
// particular module:instance:name kstat.
|
||||
//
|
||||
type KStat struct {
|
||||
Module string
|
||||
Instance int
|
||||
Name string
|
||||
|
||||
// Class is eg 'net' or 'disk'. In kstat(1) it shows up as a
|
||||
// ':class' statistic.
|
||||
Class string
|
||||
// Type is the type of kstat.
|
||||
Type KSType
|
||||
|
||||
// Creation time of a kstat in nanoseconds since sometime.
|
||||
// See gethrtime(3) and kstat(3kstat).
|
||||
Crtime int64
|
||||
// Snaptime is what kstat(1) reports as 'snaptime', the time
|
||||
// that this data was obtained. As with Crtime, it is in
|
||||
// nanoseconds since some arbitrary point in time.
|
||||
// Snaptime may not be valid until .Refresh() or .GetNamed()
|
||||
// has been called.
|
||||
Snaptime int64
|
||||
|
||||
ksp *C.struct_kstat
|
||||
// We need access to the token to refresh the data
|
||||
tok *Token
|
||||
}
|
||||
|
||||
// newKStat is our internal KStat constructor.
|
||||
//
|
||||
// This also has the responsibility of maintaining (and using) the
|
||||
// kstat_t to KStat mapping cache, so that we don't recreate new
|
||||
// KStats for the same kstat_t all the time.
|
||||
func newKStat(tok *Token, ks *C.struct_kstat) *KStat {
|
||||
if kst, ok := tok.ksm[ks]; ok {
|
||||
return kst
|
||||
}
|
||||
|
||||
kst := KStat{}
|
||||
kst.ksp = ks
|
||||
kst.tok = tok
|
||||
|
||||
kst.Instance = int(ks.ks_instance)
|
||||
kst.Module = strndup((*C.char)(unsafe.Pointer(&ks.ks_module)), C.KSTAT_STRLEN)
|
||||
kst.Name = strndup((*C.char)(unsafe.Pointer(&ks.ks_name)), C.KSTAT_STRLEN)
|
||||
kst.Class = strndup((*C.char)(unsafe.Pointer(&ks.ks_class)), C.KSTAT_STRLEN)
|
||||
kst.Type = KSType(ks.ks_type)
|
||||
kst.Crtime = int64(ks.ks_crtime)
|
||||
|
||||
// Inside the kernel, the ks_snaptime of a kstat is of course
|
||||
// a global thing. This 'global' snaptime is copied to user
|
||||
// level as part of the kstat header(s) on kstat_open(), which
|
||||
// means that kstats that have never been kstat_read() by us
|
||||
// are almost certain to have a non-zero ks_snaptime (because
|
||||
// someone, somewhere, will have read them since the system
|
||||
// booted, eg 'kstat -p | grep ...' reads all kstats).
|
||||
// Because this ks_snaptime is not useful, we don't copy it
|
||||
// to Snaptime; instead we leave Snaptime unset (zero) as
|
||||
// an explicit signal that this KStat has never had its data
|
||||
// read.
|
||||
//
|
||||
//kst.Snaptime = int64(ks.ks_snaptime)
|
||||
|
||||
tok.ksm[ks] = &kst
|
||||
return &kst
|
||||
}
|
||||
|
||||
// invalid is a desperate attempt to keep usage errors from causing
|
||||
// memory corruption. Don't count on it.
|
||||
func (k *KStat) invalid() bool {
|
||||
return k == nil || k.ksp == nil || k.tok == nil || k.tok.kc == nil
|
||||
}
|
||||
|
||||
// setup does validity checks and setup, such as loading data via Refresh().
|
||||
// It applies only to named kstats.
|
||||
//
|
||||
// TODO: setup() vs prep() is a code smell.
|
||||
func (k *KStat) setup() error {
|
||||
if k.invalid() {
|
||||
return errors.New("invalid KStat or closed token")
|
||||
}
|
||||
|
||||
if k.ksp.ks_type != C.KSTAT_TYPE_NAMED {
|
||||
return fmt.Errorf("kstat %s (type %d) is not a named kstat", k, k.ksp.ks_type)
|
||||
}
|
||||
|
||||
// Do the initial load of the data if necessary.
|
||||
if k.ksp.ks_data == nil {
|
||||
if err := k.Refresh(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *KStat) String() string {
|
||||
return fmt.Sprintf("%s:%d:%s (%s)", k.Module, k.Instance, k.Name, k.Class)
|
||||
}
|
||||
|
||||
// Valid returns true if a KStat is still valid after a Token.Update()
|
||||
// call has returned true. If a KStat becomes invalid after an update,
|
||||
// its fields remain available but you can no longer call methods on
|
||||
// it. You may be able to look it up again with token.Lookup(k.Module,
|
||||
// k.Instance, k.Name), although it's possible that the
|
||||
// module:instance:name now refers to something else. Even if it is
|
||||
// still the same thing, there is no continuity in the actual
|
||||
// statistics once Valid becomes false; you must restart tracking from
|
||||
// scratch.
|
||||
//
|
||||
// (For example, if one disk is removed from the system and another is
|
||||
// added, the new disk may use the same module:instance:name as some
|
||||
// of the old disk's KStats. Your .Lookup() may succeed, but what you
|
||||
// get back is not in any way a continuation of the old disk's
|
||||
// information.)
|
||||
//
|
||||
// Valid also returns false after the KStat's token has been closed.
|
||||
func (k *KStat) Valid() bool {
|
||||
return !k.invalid()
|
||||
}
|
||||
|
||||
// Refresh the statistics data for a KStat.
|
||||
//
|
||||
// Note that this does not update any existing Named objects for
|
||||
// statistics from this KStat. You must re-do .GetNamed() to get
|
||||
// new ones in order to see any updates.
|
||||
//
|
||||
// Under the hood this does a kstat_read(). You don't need to call it
|
||||
// explicitly before obtaining statistics from a KStat.
|
||||
func (k *KStat) Refresh() error {
|
||||
if k.invalid() {
|
||||
return errors.New("invalid KStat or closed token")
|
||||
}
|
||||
|
||||
res, err := C.kstat_read(k.tok.kc, k.ksp, nil)
|
||||
if res == -1 {
|
||||
return err
|
||||
}
|
||||
k.Snaptime = int64(k.ksp.ks_snaptime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIO retrieves the IO statistics data from an IoStat type
|
||||
// KStat. It always refreshes the KStat to provide current data.
|
||||
//
|
||||
// It corresponds to kstat_read() followed by getting a copy of
|
||||
// ks_data (which is a kstat_io_t).
|
||||
func (k *KStat) GetIO() (*IO, error) {
|
||||
if err := k.Refresh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k.ksp.ks_type != C.KSTAT_TYPE_IO {
|
||||
return nil, fmt.Errorf("kstat %s (type %d) is not an IO kstat", k, k.ksp.ks_type)
|
||||
}
|
||||
|
||||
// We make our own copy of ks_data (as an IO) so that we don't
|
||||
// point into C-owned memory. 'go tool cgo -godef' apparently
|
||||
// guarantees that the IO struct/type it creates has exactly
|
||||
// the same in-memory layout as the C struct, so we can safely
|
||||
// do this copy and expect to get good results.
|
||||
io := IO{}
|
||||
io = *((*IO)(k.ksp.ks_data))
|
||||
return &io, nil
|
||||
}
|
||||
|
||||
// GetNamed obtains a particular named statistic from a KStat. It does
|
||||
// not refresh the KStat's statistics data, so multiple calls to
|
||||
// GetNamed on a single KStat will get a coherent set of statistic
|
||||
// values from it.
|
||||
//
|
||||
// It corresponds to kstat_data_lookup().
|
||||
func (k *KStat) GetNamed(name string) (*Named, error) {
|
||||
if err := k.setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns := C.CString(name)
|
||||
r, err := C.kstat_data_lookup(k.ksp, ns)
|
||||
C.free(unsafe.Pointer(ns))
|
||||
if r == nil || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newNamed(k, (*C.struct_kstat_named)(r)), err
|
||||
}
|
||||
|
||||
// AllNamed returns an array of all named statistics for a particular
|
||||
// named-type KStat. Entries are returned in no particular order.
|
||||
func (k *KStat) AllNamed() ([]*Named, error) {
|
||||
if err := k.setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lst := make([]*Named, k.ksp.ks_ndata)
|
||||
for i := C.uint_t(0); i < k.ksp.ks_ndata; i++ {
|
||||
ks := C.get_nth_named(k.ksp, i)
|
||||
if ks == nil {
|
||||
panic("get_nth_named returned surprise nil")
|
||||
}
|
||||
lst[i] = newNamed(k, ks)
|
||||
}
|
||||
return lst, nil
|
||||
}
|
||||
|
||||
// Named represents a particular kstat named statistic, ie the full
|
||||
// module:instance:name:statistic
|
||||
// and its current value.
|
||||
//
|
||||
// Name and Type are always valid, but only one of StringVal, IntVal,
|
||||
// or UintVal is valid for any particular statistic; which one is
|
||||
// valid is determined by its Type. Generally you'll already know what
|
||||
// type a given named kstat statistic is; I don't believe Solaris
|
||||
// changes their type once they're defined.
|
||||
type Named struct {
|
||||
Name string
|
||||
Type NamedType
|
||||
|
||||
// Only one of the following values is valid; the others are zero
|
||||
// values.
|
||||
//
|
||||
// StringVal holds the value for both CharData and String Type(s).
|
||||
StringVal string
|
||||
IntVal int64
|
||||
UintVal uint64
|
||||
|
||||
// The Snaptime this Named was obtained. Note that while you
|
||||
// use the parent KStat's Crtime, you cannot use its Snaptime.
|
||||
// The KStat may have been refreshed since this Named was
|
||||
// created, which updates the Snaptime.
|
||||
Snaptime int64
|
||||
|
||||
// Pointer to the parent KStat, for access to the full name
|
||||
// and the crtime associated with this Named.
|
||||
KStat *KStat
|
||||
}
|
||||
|
||||
func (ks *Named) String() string {
|
||||
return fmt.Sprintf("%s:%d:%s:%s", ks.KStat.Module, ks.KStat.Instance, ks.KStat.Name, ks.Name)
|
||||
}
|
||||
|
||||
// NamedType represents the various types of named kstat statistics.
|
||||
type NamedType int
|
||||
|
||||
// The different types of data that a named kstat statistic can be
|
||||
// (ie, these are the potential values of Named.Type).
|
||||
const (
|
||||
CharData NamedType = C.KSTAT_DATA_CHAR
|
||||
Int32 NamedType = C.KSTAT_DATA_INT32
|
||||
Uint32 NamedType = C.KSTAT_DATA_UINT32
|
||||
Int64 NamedType = C.KSTAT_DATA_INT64
|
||||
Uint64 NamedType = C.KSTAT_DATA_UINT64
|
||||
String NamedType = C.KSTAT_DATA_STRING
|
||||
|
||||
// CharData is found in StringVal. At the moment we assume that
|
||||
// it is a real string, because this matches how it seems to be
|
||||
// used for short strings in the Solaris kernel. Someday we may
|
||||
// find something that uses it as just a data dump for 16 bytes.
|
||||
|
||||
// Solaris sys/kstat.h also has _FLOAT (5) and _DOUBLE (6) types,
|
||||
// but labels them as obsolete.
|
||||
)
|
||||
|
||||
func (tp NamedType) String() string {
|
||||
switch tp {
|
||||
case CharData:
|
||||
return "char"
|
||||
case Int32:
|
||||
return "int32"
|
||||
case Uint32:
|
||||
return "uint32"
|
||||
case Int64:
|
||||
return "int64"
|
||||
case Uint64:
|
||||
return "uint64"
|
||||
case String:
|
||||
return "string"
|
||||
default:
|
||||
return fmt.Sprintf("named_type-%d", tp)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new Stat from the kstat_named_t
|
||||
// We set the appropriate *Value field.
|
||||
func newNamed(k *KStat, knp *C.struct_kstat_named) *Named {
|
||||
st := Named{}
|
||||
st.KStat = k
|
||||
st.Name = strndup((*C.char)(unsafe.Pointer(&knp.name)), C.KSTAT_STRLEN)
|
||||
st.Type = NamedType(knp.data_type)
|
||||
st.Snaptime = k.Snaptime
|
||||
|
||||
switch st.Type {
|
||||
case String:
|
||||
// The comments in sys/kstat.h explicitly guarantee
|
||||
// that these strings are null-terminated, although
|
||||
// knp.value.str.len also holds the length.
|
||||
st.StringVal = C.GoString(C.get_named_char(knp))
|
||||
case CharData:
|
||||
// Solaris/etc appears to use CharData for short strings
|
||||
// so that they can be embedded directly into
|
||||
// knp.value.c[16] instead of requiring an out of line
|
||||
// allocation. In theory we may find someone who is
|
||||
// using it as 128-bit ints or the like.
|
||||
// However I scanned the Illumos kernel source and
|
||||
// everyone using it appears to really be using it for
|
||||
// strings.
|
||||
st.StringVal = strndup((*C.char)(unsafe.Pointer(&knp.value)), 16)
|
||||
case Int32, Int64:
|
||||
st.IntVal = int64(C.get_named_int(knp))
|
||||
case Uint32, Uint64:
|
||||
st.UintVal = uint64(C.get_named_uint(knp))
|
||||
default:
|
||||
// TODO: should do better.
|
||||
panic(fmt.Sprintf("unknown stat type: %d", st.Type))
|
||||
}
|
||||
return &st
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
//
|
||||
// Really raw access to KStat data
|
||||
|
||||
package kstat
|
||||
|
||||
// #cgo LDFLAGS: -lkstat
|
||||
//
|
||||
// #include <sys/types.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <strings.h>
|
||||
// #include <kstat.h>
|
||||
// #include <nfs/nfs_clnt.h>
|
||||
//
|
||||
import "C"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Raw is the raw data of a KStat. The actual bytes are in Data;
|
||||
// Ndata is kstat_t.ks_ndata, and is not normally useful.
|
||||
//
|
||||
// Note that with RawStat KStats, it turns out that Ndata == len(Data).
|
||||
// This is contrary to its meaning for other types of kstats.
|
||||
type Raw struct {
|
||||
Data []byte
|
||||
Ndata uint64
|
||||
Snaptime int64
|
||||
KStat *KStat
|
||||
}
|
||||
|
||||
// TODO: better functionality split here
|
||||
func (k *KStat) prep() error {
|
||||
if k.invalid() {
|
||||
return errors.New("invalid KStat or closed token")
|
||||
}
|
||||
|
||||
// Do the initial load of the data if necessary.
|
||||
if k.ksp.ks_data == nil {
|
||||
if err := k.Refresh(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Raw returns the raw byte data of a KStat. It may be called on any
|
||||
// KStat. It does not refresh the KStat's data.
|
||||
func (k *KStat) Raw() (*Raw, error) {
|
||||
if err := k.prep(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := Raw{}
|
||||
r.KStat = k
|
||||
r.Snaptime = k.Snaptime
|
||||
r.Ndata = uint64(k.ksp.ks_ndata)
|
||||
// The forced C.int() conversion is dangerous, because C.int
|
||||
// is not necessarily large enough to contain a
|
||||
// size_t. However this is the interface that Go gives us, so
|
||||
// we live with it.
|
||||
r.Data = C.GoBytes(unsafe.Pointer(k.ksp.ks_data), C.int(k.ksp.ks_data_size))
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func (tok *Token) prepunix(name string, size uintptr) (*KStat, error) {
|
||||
k, err := tok.Lookup("unix", 0, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: handle better?
|
||||
if k.ksp.ks_type != C.KSTAT_TYPE_RAW {
|
||||
return nil, fmt.Errorf("%s is wrong type %s", k, k.Type)
|
||||
}
|
||||
if uintptr(k.ksp.ks_data_size) != size {
|
||||
return nil, fmt.Errorf("%s is wrong size %d (should be %d)", k, k.ksp.ks_data_size, size)
|
||||
}
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// Sysinfo returns the KStat and the statistics from unix:0:sysinfo.
|
||||
// It always returns a current, refreshed copy.
|
||||
func (tok *Token) Sysinfo() (*KStat, *Sysinfo, error) {
|
||||
var si Sysinfo
|
||||
k, err := tok.prepunix("sysinfo", unsafe.Sizeof(si))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
si = *((*Sysinfo)(k.ksp.ks_data))
|
||||
return k, &si, nil
|
||||
}
|
||||
|
||||
// Vminfo returns the KStat and the statistics from unix:0:vminfo.
|
||||
// It always returns a current, refreshed copy.
|
||||
func (tok *Token) Vminfo() (*KStat, *Vminfo, error) {
|
||||
var vi Vminfo
|
||||
k, err := tok.prepunix("vminfo", unsafe.Sizeof(vi))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vi = *((*Vminfo)(k.ksp.ks_data))
|
||||
return k, &vi, nil
|
||||
}
|
||||
|
||||
// Var returns the KStat and the statistics from unix:0:var.
|
||||
// It always returns a current, refreshed copy.
|
||||
func (tok *Token) Var() (*KStat, *Var, error) {
|
||||
var vi Var
|
||||
k, err := tok.prepunix("var", unsafe.Sizeof(vi))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
vi = *((*Var)(k.ksp.ks_data))
|
||||
return k, &vi, nil
|
||||
}
|
||||
|
||||
// GetMntinfo retrieves a Mntinfo struct from a nfs:*:mntinfo KStat.
|
||||
// It does not force a refresh of the KStat.
|
||||
func (k *KStat) GetMntinfo() (*Mntinfo, error) {
|
||||
var mi Mntinfo
|
||||
if err := k.prep(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if k.Type != RawStat || k.Module != "nfs" || k.Name != "mntinfo" {
|
||||
return nil, errors.New("KStat is not a Mntinfo kstat")
|
||||
}
|
||||
if uintptr(k.ksp.ks_data_size) != unsafe.Sizeof(mi) {
|
||||
return nil, fmt.Errorf("KStat is wrong size %d (should be %d)", k.ksp.ks_data_size, unsafe.Sizeof(mi))
|
||||
}
|
||||
mi = *((*Mntinfo)(k.ksp.ks_data))
|
||||
return &mi, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Support for copying semi-arbitrary structures out of raw
|
||||
// KStats.
|
||||
//
|
||||
|
||||
// safeThing returns true if a given type is either a simple defined
|
||||
// size primitive integer type or an array and/or struct composed
|
||||
// entirely of safe things. A safe thing is entirely self contained
|
||||
// and may be initialized from random memory without breaking Go's
|
||||
// memory safety (although the values it contains may be garbage).
|
||||
//
|
||||
func safeThing(t reflect.Type) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
case reflect.Array:
|
||||
// an array is safe if it's an array of something safe
|
||||
return safeThing(t.Elem())
|
||||
case reflect.Struct:
|
||||
// a struct is safe if all its components are safe
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if !safeThing(t.Field(i).Type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
// other things are not safe.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add floats to the supported list? It's unlikely to be needed
|
||||
// but it should just work.
|
||||
|
||||
// CopyTo copies a RawStat KStat into a struct that you supply a
|
||||
// pointer to. The size of the struct must exactly match the size of
|
||||
// the RawStat's data.
|
||||
//
|
||||
// CopyStat imposes conditions on the struct that you are copying to:
|
||||
// it must be composed entirely of primitive integer types with defined
|
||||
// sizes (intN and uintN), or arrays and structs that ultimately only
|
||||
// contain them. All fields should be exported.
|
||||
//
|
||||
// If you give CopyStat a bad argument, it generally panics.
|
||||
//
|
||||
// This API is provisional and may be changed or deleted.
|
||||
func (k *KStat) CopyTo(ptr interface{}) error {
|
||||
if err := k.prep(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if k.Type != RawStat {
|
||||
return errors.New("KStat is not a RawStat")
|
||||
}
|
||||
|
||||
// Validity checks: not nil value, not nil pointer value,
|
||||
// is a pointer to struct.
|
||||
if ptr == nil {
|
||||
panic("CopyTo given nil pointer")
|
||||
}
|
||||
vp := reflect.ValueOf(ptr)
|
||||
if vp.Kind() != reflect.Ptr {
|
||||
panic("CopyTo not given a pointer")
|
||||
}
|
||||
if vp.IsNil() {
|
||||
panic("CopyTo given nil pointer")
|
||||
}
|
||||
dst := vp.Elem()
|
||||
if dst.Kind() != reflect.Struct {
|
||||
panic("CopyTo: not pointer to struct")
|
||||
}
|
||||
// Is the struct safe to copy into, which means primitive types
|
||||
// and structs/arrays of primitive types?
|
||||
if !safeThing(dst.Type()) {
|
||||
panic("CopyTo: not a safe structure, contains unsupported fields")
|
||||
}
|
||||
if !dst.CanSet() {
|
||||
panic("CopyTo: struct cannot be set for some reason")
|
||||
}
|
||||
|
||||
// Verify that the size of the target struct matches the size
|
||||
// of the raw KStat.
|
||||
if uintptr(k.ksp.ks_data_size) != dst.Type().Size() {
|
||||
return errors.New("struct size does not match KStat size")
|
||||
}
|
||||
|
||||
// The following is exactly the magic that we performed for
|
||||
// specific types earlier. We take k.ksp.ks_data and turn
|
||||
// it into a typed pointer to the target object's type:
|
||||
//
|
||||
// src := ((*<type>)(k.kps.ks_data))
|
||||
src := reflect.NewAt(dst.Type(), unsafe.Pointer(k.ksp.ks_data))
|
||||
|
||||
// We now dereference that into the destination to copy the
|
||||
// data:
|
||||
//
|
||||
// dst = *src
|
||||
dst.Set(reflect.Indirect(src))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// Initially created by
|
||||
// cgo -godefs ctypes_solaris.go
|
||||
//
|
||||
// Now contains edits for documentation. This is considered okay by me
|
||||
// because these structs are not exactly likely to change any time
|
||||
// soon; that would break API compatibility.
|
||||
//
|
||||
// This is specific to amd64. It's unlikely that Go will support
|
||||
// 32-bit Solaris ('386'), but.
|
||||
|
||||
package kstat
|
||||
|
||||
// IO represents the entire collection of KStat (disk) IO statistics
|
||||
// exposed by an IoStat type KStat.
|
||||
//
|
||||
// Because IO is an exact copy of the C kstat_io_t structure from the
|
||||
// kernel, it does not have a Snaptime or KStat field. You must save
|
||||
// that information separately if you need it, perhaps by embedded the
|
||||
// IO struct as an anonymous struct in an additional struct of your
|
||||
// own.
|
||||
type IO struct {
|
||||
Nread uint64
|
||||
Nwritten uint64
|
||||
Reads uint32
|
||||
Writes uint32
|
||||
Wtime int64
|
||||
Wlentime int64
|
||||
Wlastupdate int64
|
||||
Rtime int64
|
||||
Rlentime int64
|
||||
Rlastupdate int64
|
||||
Wcnt uint32
|
||||
Rcnt uint32
|
||||
}
|
||||
|
||||
// Sysinfo is the data from unix:0:sysinfo, which is a sysinfo_t.
|
||||
type Sysinfo struct {
|
||||
Updates uint32
|
||||
Runque uint32
|
||||
Runocc uint32
|
||||
Swpque uint32
|
||||
Swpocc uint32
|
||||
Waiting uint32
|
||||
}
|
||||
|
||||
// Vminfo is the data from unix:0:vminfo, which is a vminfo_t.
|
||||
type Vminfo struct {
|
||||
Freemem uint64
|
||||
Resv uint64
|
||||
Alloc uint64
|
||||
Avail uint64
|
||||
Free uint64
|
||||
Updates uint64
|
||||
}
|
||||
|
||||
// Var is the data from unix:0:var, which is a 'struct var'.
|
||||
type Var struct {
|
||||
Buf int32
|
||||
Call int32
|
||||
Proc int32
|
||||
Maxupttl int32
|
||||
Nglobpris int32
|
||||
Maxsyspri int32
|
||||
Clist int32
|
||||
Maxup int32
|
||||
Hbuf int32
|
||||
Hmask int32
|
||||
Pbuf int32
|
||||
Sptmap int32
|
||||
Maxpmem int32
|
||||
Autoup int32
|
||||
Bufhwm int32
|
||||
}
|
||||
|
||||
// Mntinfo is the kernel data from nfs:*:mntinfo, which is a 'struct
|
||||
// mntinfo_kstat'. Use .Proto() and .Curserver() to get the RProto
|
||||
// and RCurserver fields as strings instead of their awkward raw form.
|
||||
type Mntinfo struct {
|
||||
RProto [128]int8
|
||||
Vers uint32
|
||||
Flags uint32
|
||||
Secmod uint32
|
||||
Curread uint32
|
||||
Curwrite uint32
|
||||
Timeo int32
|
||||
Retrans int32
|
||||
Acregmin uint32
|
||||
Acregmax uint32
|
||||
Acdirmin uint32
|
||||
Acdirmax uint32
|
||||
Timers [4]struct {
|
||||
Srtt uint32
|
||||
Deviate uint32
|
||||
Rtxcur uint32
|
||||
}
|
||||
Noresponse uint32
|
||||
Failover uint32
|
||||
Remap uint32
|
||||
RCurserver [257]int8
|
||||
pad0 [3]byte
|
||||
}
|
||||
|
||||
// CFieldString converts a (null-terminated) C string embedded in an
|
||||
// []int8 slice to a (Go) string. The []int8 slice is likely to come
|
||||
// from an [N]int8 fixed-size field in a statistics struct. If there
|
||||
// is no null in the slice, the entire slice is returned.
|
||||
//
|
||||
// (The no-null behavior is common in C APIs; a string is often allowed
|
||||
// to exactly fill the field with no room for a trailing null.)
|
||||
func CFieldString(src []int8) string {
|
||||
slen := len(src)
|
||||
buf := make([]byte, slen)
|
||||
for i := 0; i < len(src); i++ {
|
||||
buf[i] = byte(src[i])
|
||||
if src[i] == 0 {
|
||||
slen = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return string(buf[:slen])
|
||||
}
|
||||
|
||||
// Proto returns a Mntinfo RProto as a string.
|
||||
func (m Mntinfo) Proto() string {
|
||||
return CFieldString(m.RProto[:])
|
||||
}
|
||||
|
||||
// Curserver returns a Mntinfo RCurserver as a string.
|
||||
func (m Mntinfo) Curserver() string {
|
||||
return CFieldString(m.RCurserver[:])
|
||||
}
|
||||
|
||||
// The Mntinfo type is not an exact conversion as produced by cgo;
|
||||
// because the original struct mntinfo_kstat contains an embedded
|
||||
// anonymously typed struct, it runs into
|
||||
// https://github.com/golang/go/issues/5253. This version is manually
|
||||
// produced from a cgo starting point and then verified to be the same
|
||||
// size.
|
||||
// It also has Proto and Curserver renamed so we can add methods to
|
||||
// get them as Go strings.
|
|
@ -43,7 +43,7 @@ github.com/prometheus/common/version
|
|||
github.com/prometheus/common/expfmt
|
||||
github.com/prometheus/common/model
|
||||
github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
|
||||
# github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d
|
||||
# github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5
|
||||
github.com/prometheus/procfs
|
||||
github.com/prometheus/procfs/bcache
|
||||
github.com/prometheus/procfs/nfs
|
||||
|
|
Loading…
Reference in New Issue