Unverified Commit aafc1836 by Kubernetes Prow Robot Committed by GitHub

Merge pull request #189 from yonatankahana/component-helpers

build: import GetPersistentVolumeClass from component-helpers
parents 23794089 9015173f
...@@ -35,7 +35,7 @@ import ( ...@@ -35,7 +35,7 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/kubernetes/pkg/apis/core/v1/helper" storagehelpers "k8s.io/component-helpers/storage/volume"
"sigs.k8s.io/sig-storage-lib-external-provisioner/v6/controller" "sigs.k8s.io/sig-storage-lib-external-provisioner/v6/controller"
) )
...@@ -193,7 +193,7 @@ func (p *nfsProvisioner) getClassForVolume(ctx context.Context, pv *v1.Persisten ...@@ -193,7 +193,7 @@ func (p *nfsProvisioner) getClassForVolume(ctx context.Context, pv *v1.Persisten
if p.client == nil { if p.client == nil {
return nil, fmt.Errorf("cannot get kube client") return nil, fmt.Errorf("cannot get kube client")
} }
className := helper.GetPersistentVolumeClass(pv) className := storagehelpers.GetPersistentVolumeClass(pv)
if className == "" { if className == "" {
return nil, fmt.Errorf("volume has no storage class") return nil, fmt.Errorf("volume has no storage class")
} }
......
...@@ -7,7 +7,7 @@ require ( ...@@ -7,7 +7,7 @@ require (
k8s.io/api v0.23.4 k8s.io/api v0.23.4
k8s.io/apimachinery v0.23.4 k8s.io/apimachinery v0.23.4
k8s.io/client-go v0.23.4 k8s.io/client-go v0.23.4
k8s.io/kubernetes v1.23.4 k8s.io/component-helpers v0.23.4
sigs.k8s.io/sig-storage-lib-external-provisioner/v6 v6.0.0 sigs.k8s.io/sig-storage-lib-external-provisioner/v6 v6.0.0
) )
......
// Copyright 2020 The Prometheus Authors // Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
...@@ -11,9 +11,19 @@ ...@@ -11,9 +11,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build linux // +build go1.12
// +build arm arm64
package procfs package prometheus
var parseCPUInfo = parseCPUInfoARM import "runtime/debug"
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go 1.12+.
func readBuildInfo() (path, version, sum string) {
path, version, sum = "unknown", "unknown", "unknown"
if bi, ok := debug.ReadBuildInfo(); ok {
path = bi.Main.Path
version = bi.Main.Version
sum = bi.Main.Sum
}
return
}
// Copyright 2020 The Prometheus Authors // Copyright 2019 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
// You may obtain a copy of the License at // You may obtain a copy of the License at
...@@ -11,8 +11,12 @@ ...@@ -11,8 +11,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build linux // +build !go1.12
package procfs package prometheus
var parseCPUInfo = parseCPUInfoS390X // readBuildInfo is a wrapper around debug.ReadBuildInfo for Go versions before
// 1.12. Remove this whole file once the minimum supported Go version is 1.12.
func readBuildInfo() (path, version, sum string) {
return "unknown", "unknown", "unknown"
}
...@@ -163,7 +163,7 @@ func (c *counter) updateExemplar(v float64, l Labels) { ...@@ -163,7 +163,7 @@ func (c *counter) updateExemplar(v float64, l Labels) {
// (e.g. number of HTTP requests, partitioned by response code and // (e.g. number of HTTP requests, partitioned by response code and
// method). Create instances with NewCounterVec. // method). Create instances with NewCounterVec.
type CounterVec struct { type CounterVec struct {
*MetricVec *metricVec
} }
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and // NewCounterVec creates a new CounterVec based on the provided CounterOpts and
...@@ -176,11 +176,11 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { ...@@ -176,11 +176,11 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &CounterVec{ return &CounterVec{
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { metricVec: newMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) { if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs)) panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
} }
result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now} result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs), now: time.Now}
result.init(result) // Init self-collection. result.init(result) // Init self-collection.
return result return result
}), }),
...@@ -188,7 +188,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { ...@@ -188,7 +188,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
} }
// GetMetricWithLabelValues returns the Counter for the given slice of label // GetMetricWithLabelValues returns the Counter for the given slice of label
// values (same order as the variable labels in Desc). If that combination of // values (same order as the VariableLabels in Desc). If that combination of
// label values is accessed for the first time, a new Counter is created. // label values is accessed for the first time, a new Counter is created.
// //
// It is possible to call this method without using the returned Counter to only // It is possible to call this method without using the returned Counter to only
...@@ -202,7 +202,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { ...@@ -202,7 +202,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// Counter with the same label values is created later. // Counter with the same label values is created later.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels). // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
...@@ -211,7 +211,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec { ...@@ -211,7 +211,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) { func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Counter), err return metric.(Counter), err
} }
...@@ -219,19 +219,19 @@ func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) { ...@@ -219,19 +219,19 @@ func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
} }
// GetMetricWith returns the Counter for the given Labels map (the label names // GetMetricWith returns the Counter for the given Labels map (the label names
// must match those of the variable labels in Desc). If that label map is // must match those of the VariableLabels in Desc). If that label map is
// accessed for the first time, a new Counter is created. Implications of // accessed for the first time, a new Counter is created. Implications of
// creating a Counter without using it and keeping the Counter for later use are // creating a Counter without using it and keeping the Counter for later use are
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels). // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) { func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
metric, err := v.MetricVec.GetMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Counter), err return metric.(Counter), err
} }
...@@ -275,7 +275,7 @@ func (v *CounterVec) With(labels Labels) Counter { ...@@ -275,7 +275,7 @@ func (v *CounterVec) With(labels Labels) Counter {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) { func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
vec, err := v.MetricVec.CurryWith(labels) vec, err := v.curryWith(labels)
if vec != nil { if vec != nil {
return &CounterVec{vec}, err return &CounterVec{vec}, err
} }
...@@ -309,8 +309,6 @@ type CounterFunc interface { ...@@ -309,8 +309,6 @@ type CounterFunc interface {
// provided function must be concurrency-safe. The function should also honor // provided function must be concurrency-safe. The function should also honor
// the contract for a Counter (values only go up, not down), but compliance will // the contract for a Counter (values only go up, not down), but compliance will
// not be checked. // not be checked.
//
// Check out the ExampleGaugeFunc examples for the similar GaugeFunc.
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc { func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
return newValueFunc(NewDesc( return newValueFunc(NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name), BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
......
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"strings" "strings"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
...@@ -51,7 +50,7 @@ type Desc struct { ...@@ -51,7 +50,7 @@ type Desc struct {
// constLabelPairs contains precalculated DTO label pairs based on // constLabelPairs contains precalculated DTO label pairs based on
// the constant labels. // the constant labels.
constLabelPairs []*dto.LabelPair constLabelPairs []*dto.LabelPair
// variableLabels contains names of labels for which the metric // VariableLabels contains names of labels for which the metric
// maintains variable values. // maintains variable values.
variableLabels []string variableLabels []string
// id is a hash of the values of the ConstLabels and fqName. This // id is a hash of the values of the ConstLabels and fqName. This
......
...@@ -22,10 +22,43 @@ type expvarCollector struct { ...@@ -22,10 +22,43 @@ type expvarCollector struct {
exports map[string]*Desc exports map[string]*Desc
} }
// NewExpvarCollector is the obsolete version of collectors.NewExpvarCollector. // NewExpvarCollector returns a newly allocated expvar Collector that still has
// See there for documentation. // to be registered with a Prometheus registry.
// //
// Deprecated: Use collectors.NewExpvarCollector instead. // An expvar Collector collects metrics from the expvar interface. It provides a
// quick way to expose numeric values that are already exported via expvar as
// Prometheus metrics. Note that the data models of expvar and Prometheus are
// fundamentally different, and that the expvar Collector is inherently slower
// than native Prometheus metrics. Thus, the expvar Collector is probably great
// for experiments and prototying, but you should seriously consider a more
// direct implementation of Prometheus metrics for monitoring production
// systems.
//
// The exports map has the following meaning:
//
// The keys in the map correspond to expvar keys, i.e. for every expvar key you
// want to export as Prometheus metric, you need an entry in the exports
// map. The descriptor mapped to each key describes how to export the expvar
// value. It defines the name and the help string of the Prometheus metric
// proxying the expvar value. The type will always be Untyped.
//
// For descriptors without variable labels, the expvar value must be a number or
// a bool. The number is then directly exported as the Prometheus sample
// value. (For a bool, 'false' translates to 0 and 'true' to 1). Expvar values
// that are not numbers or bools are silently ignored.
//
// If the descriptor has one variable label, the expvar value must be an expvar
// map. The keys in the expvar map become the various values of the one
// Prometheus label. The values in the expvar map must be numbers or bools again
// as above.
//
// For descriptors with more than one variable label, the expvar must be a
// nested expvar map, i.e. where the values of the topmost map are maps again
// etc. until a depth is reached that corresponds to the number of labels. The
// leaves of that structure must be numbers or bools as above to serve as the
// sample values.
//
// Anything that does not fit into the scheme above is silently ignored.
func NewExpvarCollector(exports map[string]*Desc) Collector { func NewExpvarCollector(exports map[string]*Desc) Collector {
return &expvarCollector{ return &expvarCollector{
exports: exports, exports: exports,
......
...@@ -132,7 +132,7 @@ func (g *gauge) Write(out *dto.Metric) error { ...@@ -132,7 +132,7 @@ func (g *gauge) Write(out *dto.Metric) error {
// (e.g. number of operations queued, partitioned by user and operation // (e.g. number of operations queued, partitioned by user and operation
// type). Create instances with NewGaugeVec. // type). Create instances with NewGaugeVec.
type GaugeVec struct { type GaugeVec struct {
*MetricVec *metricVec
} }
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and // NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
...@@ -145,11 +145,11 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { ...@@ -145,11 +145,11 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &GaugeVec{ return &GaugeVec{
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { metricVec: newMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) { if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs)) panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
} }
result := &gauge{desc: desc, labelPairs: MakeLabelPairs(desc, lvs)} result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection. result.init(result) // Init self-collection.
return result return result
}), }),
...@@ -157,7 +157,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { ...@@ -157,7 +157,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
} }
// GetMetricWithLabelValues returns the Gauge for the given slice of label // GetMetricWithLabelValues returns the Gauge for the given slice of label
// values (same order as the variable labels in Desc). If that combination of // values (same order as the VariableLabels in Desc). If that combination of
// label values is accessed for the first time, a new Gauge is created. // label values is accessed for the first time, a new Gauge is created.
// //
// It is possible to call this method without using the returned Gauge to only // It is possible to call this method without using the returned Gauge to only
...@@ -172,7 +172,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { ...@@ -172,7 +172,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels). // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
...@@ -180,7 +180,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec { ...@@ -180,7 +180,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) { func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Gauge), err return metric.(Gauge), err
} }
...@@ -188,19 +188,19 @@ func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) { ...@@ -188,19 +188,19 @@ func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
} }
// GetMetricWith returns the Gauge for the given Labels map (the label names // GetMetricWith returns the Gauge for the given Labels map (the label names
// must match those of the variable labels in Desc). If that label map is // must match those of the VariableLabels in Desc). If that label map is
// accessed for the first time, a new Gauge is created. Implications of // accessed for the first time, a new Gauge is created. Implications of
// creating a Gauge without using it and keeping the Gauge for later use are // creating a Gauge without using it and keeping the Gauge for later use are
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels). // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) { func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
metric, err := v.MetricVec.GetMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Gauge), err return metric.(Gauge), err
} }
...@@ -244,7 +244,7 @@ func (v *GaugeVec) With(labels Labels) Gauge { ...@@ -244,7 +244,7 @@ func (v *GaugeVec) With(labels Labels) Gauge {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) { func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
vec, err := v.MetricVec.CurryWith(labels) vec, err := v.curryWith(labels)
if vec != nil { if vec != nil {
return &GaugeVec{vec}, err return &GaugeVec{vec}, err
} }
......
...@@ -36,10 +36,31 @@ type goCollector struct { ...@@ -36,10 +36,31 @@ type goCollector struct {
msMaxAge time.Duration // Maximum allowed age of old memstats. msMaxAge time.Duration // Maximum allowed age of old memstats.
} }
// NewGoCollector is the obsolete version of collectors.NewGoCollector. // NewGoCollector returns a collector that exports metrics about the current Go
// See there for documentation. // process. This includes memory stats. To collect those, runtime.ReadMemStats
// is called. This requires to “stop the world”, which usually only happens for
// garbage collection (GC). Take the following implications into account when
// deciding whether to use the Go collector:
// //
// Deprecated: Use collectors.NewGoCollector instead. // 1. The performance impact of stopping the world is the more relevant the more
// frequently metrics are collected. However, with Go1.9 or later the
// stop-the-world time per metrics collection is very short (~25µs) so that the
// performance impact will only matter in rare cases. However, with older Go
// versions, the stop-the-world duration depends on the heap size and can be
// quite significant (~1.7 ms/GiB as per
// https://go-review.googlesource.com/c/go/+/34937).
//
// 2. During an ongoing GC, nothing else can stop the world. Therefore, if the
// metrics collection happens to coincide with GC, it will only complete after
// GC has finished. Usually, GC is fast enough to not cause problems. However,
// with a very large heap, GC might take multiple seconds, which is enough to
// cause scrape timeouts in common setups. To avoid this problem, the Go
// collector will use the memstats from a previous collection if
// runtime.ReadMemStats takes more than 1s. However, if there are no previously
// collected memstats, or their collection is more than 5m ago, the collection
// will block until runtime.ReadMemStats succeeds. (The problem might be solved
// in Go1.13, see https://github.com/golang/go/issues/19812 for the related Go
// issue.)
func NewGoCollector() Collector { func NewGoCollector() Collector {
return &goCollector{ return &goCollector{
goroutinesDesc: NewDesc( goroutinesDesc: NewDesc(
...@@ -344,17 +365,25 @@ type memStatsMetrics []struct { ...@@ -344,17 +365,25 @@ type memStatsMetrics []struct {
valType ValueType valType ValueType
} }
// NewBuildInfoCollector is the obsolete version of collectors.NewBuildInfoCollector. // NewBuildInfoCollector returns a collector collecting a single metric
// See there for documentation. // "go_build_info" with the constant value 1 and three labels "path", "version",
// and "checksum". Their label values contain the main module path, version, and
// checksum, respectively. The labels will only have meaningful values if the
// binary is built with Go module support and from source code retrieved from
// the source repository (rather than the local file system). This is usually
// accomplished by building from outside of GOPATH, specifying the full address
// of the main package, e.g. "GO111MODULE=on go run
// github.com/prometheus/client_golang/examples/random". If built without Go
// module support, all label values will be "unknown". If built with Go module
// support but using the source code from the local file system, the "path" will
// be set appropriately, but "checksum" will be empty and "version" will be
// "(devel)".
// //
// Deprecated: Use collectors.NewBuildInfoCollector instead. // This collector uses only the build information for the main module. See
// https://github.com/povilasv/prommod for an example of a collector for the
// module dependencies.
func NewBuildInfoCollector() Collector { func NewBuildInfoCollector() Collector {
path, version, sum := "unknown", "unknown", "unknown" path, version, sum := readBuildInfo()
if bi, ok := debug.ReadBuildInfo(); ok {
path = bi.Main.Path
version = bi.Main.Version
sum = bi.Main.Sum
}
c := &selfCollector{MustNewConstMetric( c := &selfCollector{MustNewConstMetric(
NewDesc( NewDesc(
"go_build_info", "go_build_info",
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
...@@ -47,12 +46,7 @@ type Histogram interface { ...@@ -47,12 +46,7 @@ type Histogram interface {
Metric Metric
Collector Collector
// Observe adds a single observation to the histogram. Observations are // Observe adds a single observation to the histogram.
// usually positive or zero. Negative observations are accepted but
// prevent current versions of Prometheus from properly detecting
// counter resets in the sum of observations. See
// https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
// for details.
Observe(float64) Observe(float64)
} }
...@@ -197,7 +191,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr ...@@ -197,7 +191,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
h := &histogram{ h := &histogram{
desc: desc, desc: desc,
upperBounds: opts.Buckets, upperBounds: opts.Buckets,
labelPairs: MakeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
counts: [2]*histogramCounts{{}, {}}, counts: [2]*histogramCounts{{}, {}},
now: time.Now, now: time.Now,
} }
...@@ -414,7 +408,7 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) { ...@@ -414,7 +408,7 @@ func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
// (e.g. HTTP request latencies, partitioned by status code and method). Create // (e.g. HTTP request latencies, partitioned by status code and method). Create
// instances with NewHistogramVec. // instances with NewHistogramVec.
type HistogramVec struct { type HistogramVec struct {
*MetricVec *metricVec
} }
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
...@@ -427,14 +421,14 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec { ...@@ -427,14 +421,14 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &HistogramVec{ return &HistogramVec{
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { metricVec: newMetricVec(desc, func(lvs ...string) Metric {
return newHistogram(desc, opts, lvs...) return newHistogram(desc, opts, lvs...)
}), }),
} }
} }
// GetMetricWithLabelValues returns the Histogram for the given slice of label // GetMetricWithLabelValues returns the Histogram for the given slice of label
// values (same order as the variable labels in Desc). If that combination of // values (same order as the VariableLabels in Desc). If that combination of
// label values is accessed for the first time, a new Histogram is created. // label values is accessed for the first time, a new Histogram is created.
// //
// It is possible to call this method without using the returned Histogram to only // It is possible to call this method without using the returned Histogram to only
...@@ -449,7 +443,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec { ...@@ -449,7 +443,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels). // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
...@@ -458,7 +452,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec { ...@@ -458,7 +452,7 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
...@@ -466,19 +460,19 @@ func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) ...@@ -466,19 +460,19 @@ func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error)
} }
// GetMetricWith returns the Histogram for the given Labels map (the label names // GetMetricWith returns the Histogram for the given Labels map (the label names
// must match those of the variable labels in Desc). If that label map is // must match those of the VariableLabels in Desc). If that label map is
// accessed for the first time, a new Histogram is created. Implications of // accessed for the first time, a new Histogram is created. Implications of
// creating a Histogram without using it and keeping the Histogram for later use // creating a Histogram without using it and keeping the Histogram for later use
// are the same as for GetMetricWithLabelValues. // are the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels). // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) { func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
metric, err := v.MetricVec.GetMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
...@@ -522,7 +516,7 @@ func (v *HistogramVec) With(labels Labels) Observer { ...@@ -522,7 +516,7 @@ func (v *HistogramVec) With(labels Labels) Observer {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) { func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
vec, err := v.MetricVec.CurryWith(labels) vec, err := v.curryWith(labels)
if vec != nil { if vec != nil {
return &HistogramVec{vec}, err return &HistogramVec{vec}, err
} }
...@@ -607,12 +601,12 @@ func NewConstHistogram( ...@@ -607,12 +601,12 @@ func NewConstHistogram(
count: count, count: count,
sum: sum, sum: sum,
buckets: buckets, buckets: buckets,
labelPairs: MakeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
}, nil }, nil
} }
// MustNewConstHistogram is a version of NewConstHistogram that panics where // MustNewConstHistogram is a version of NewConstHistogram that panics where
// NewConstHistogram would have returned an error. // NewConstMetric would have returned an error.
func MustNewConstHistogram( func MustNewConstHistogram(
desc *Desc, desc *Desc,
count uint64, count uint64,
......
...@@ -17,7 +17,6 @@ import ( ...@@ -17,7 +17,6 @@ import (
"strings" "strings"
"time" "time"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
...@@ -58,7 +57,7 @@ type Metric interface { ...@@ -58,7 +57,7 @@ type Metric interface {
} }
// Opts bundles the options for creating most Metric types. Each metric // Opts bundles the options for creating most Metric types. Each metric
// implementation XXX has its own XXXOpts type, but in most cases, it is just // implementation XXX has its own XXXOpts type, but in most cases, it is just be
// an alias of this type (which might change when the requirement arises.) // an alias of this type (which might change when the requirement arises.)
// //
// It is mandatory to set Name to a non-empty string. All other fields are // It is mandatory to set Name to a non-empty string. All other fields are
...@@ -89,7 +88,7 @@ type Opts struct { ...@@ -89,7 +88,7 @@ type Opts struct {
// better covered by target labels set by the scraping Prometheus // better covered by target labels set by the scraping Prometheus
// server, or by one specific metric (e.g. a build_info or a // server, or by one specific metric (e.g. a build_info or a
// machine_role metric). See also // machine_role metric). See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
ConstLabels Labels ConstLabels Labels
} }
......
...@@ -15,11 +15,7 @@ package prometheus ...@@ -15,11 +15,7 @@ package prometheus
import ( import (
"errors" "errors"
"fmt"
"io/ioutil"
"os" "os"
"strconv"
"strings"
) )
type processCollector struct { type processCollector struct {
...@@ -54,10 +50,16 @@ type ProcessCollectorOpts struct { ...@@ -54,10 +50,16 @@ type ProcessCollectorOpts struct {
ReportErrors bool ReportErrors bool
} }
// NewProcessCollector is the obsolete version of collectors.NewProcessCollector. // NewProcessCollector returns a collector which exports the current state of
// See there for documentation. // process metrics including CPU, memory and file descriptor usage as well as
// the process start time. The detailed behavior is defined by the provided
// ProcessCollectorOpts. The zero value of ProcessCollectorOpts creates a
// collector for the current process with an empty namespace string and no error
// reporting.
// //
// Deprecated: Use collectors.NewProcessCollector instead. // The collector only works on operating systems with a Linux-style proc
// filesystem and on Microsoft Windows. On other operating systems, it will not
// collect any metrics.
func NewProcessCollector(opts ProcessCollectorOpts) Collector { func NewProcessCollector(opts ProcessCollectorOpts) Collector {
ns := "" ns := ""
if len(opts.Namespace) > 0 { if len(opts.Namespace) > 0 {
...@@ -147,20 +149,3 @@ func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) ...@@ -147,20 +149,3 @@ func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error)
} }
ch <- NewInvalidMetric(desc, err) ch <- NewInvalidMetric(desc, err)
} }
// NewPidFileFn returns a function that retrieves a pid from the specified file.
// It is meant to be used for the PidFn field in ProcessCollectorOpts.
func NewPidFileFn(pidFilePath string) func() (int, error) {
return func() (int, error) {
content, err := ioutil.ReadFile(pidFilePath)
if err != nil {
return 0, fmt.Errorf("can't read pid file %q: %+v", pidFilePath, err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(content)))
if err != nil {
return 0, fmt.Errorf("can't parse pid file %q: %+v", pidFilePath, err)
}
return pid, nil
}
}
...@@ -33,22 +33,18 @@ var ( ...@@ -33,22 +33,18 @@ var (
) )
type processMemoryCounters struct { type processMemoryCounters struct {
// System interface description // https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters_ex
// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-process_memory_counters_ex
// Refer to the Golang internal implementation
// https://golang.org/src/internal/syscall/windows/psapi_windows.go
_ uint32 _ uint32
PageFaultCount uint32 PageFaultCount uint32
PeakWorkingSetSize uintptr PeakWorkingSetSize uint64
WorkingSetSize uintptr WorkingSetSize uint64
QuotaPeakPagedPoolUsage uintptr QuotaPeakPagedPoolUsage uint64
QuotaPagedPoolUsage uintptr QuotaPagedPoolUsage uint64
QuotaPeakNonPagedPoolUsage uintptr QuotaPeakNonPagedPoolUsage uint64
QuotaNonPagedPoolUsage uintptr QuotaNonPagedPoolUsage uint64
PagefileUsage uintptr PagefileUsage uint64
PeakPagefileUsage uintptr PeakPagefileUsage uint64
PrivateUsage uintptr PrivateUsage uint64
} }
func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) { func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) {
......
...@@ -83,7 +83,8 @@ type readerFromDelegator struct{ *responseWriterDelegator } ...@@ -83,7 +83,8 @@ type readerFromDelegator struct{ *responseWriterDelegator }
type pusherDelegator struct{ *responseWriterDelegator } type pusherDelegator struct{ *responseWriterDelegator }
func (d closeNotifierDelegator) CloseNotify() <-chan bool { func (d closeNotifierDelegator) CloseNotify() <-chan bool {
//nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users. //lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
//remove support from client_golang yet.
return d.ResponseWriter.(http.CloseNotifier).CloseNotify() return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
} }
func (d flusherDelegator) Flush() { func (d flusherDelegator) Flush() {
...@@ -347,7 +348,8 @@ func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) deleg ...@@ -347,7 +348,8 @@ func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) deleg
} }
id := 0 id := 0
//nolint:staticcheck // Ignore SA1019. http.CloseNotifier is deprecated but we keep it here to not break existing users. //lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
//remove support from client_golang yet.
if _, ok := w.(http.CloseNotifier); ok { if _, ok := w.(http.CloseNotifier); ok {
id += closeNotifier id += closeNotifier
} }
......
...@@ -99,7 +99,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler { ...@@ -99,7 +99,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight) inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
} }
if opts.Registry != nil { if opts.Registry != nil {
// Initialize all possibilities that can occur below. // Initialize all possibilites that can occur below.
errCnt.WithLabelValues("gathering") errCnt.WithLabelValues("gathering")
errCnt.WithLabelValues("encoding") errCnt.WithLabelValues("encoding")
if err := opts.Registry.Register(errCnt); err != nil { if err := opts.Registry.Register(errCnt); err != nil {
...@@ -303,12 +303,8 @@ type Logger interface { ...@@ -303,12 +303,8 @@ type Logger interface {
// HandlerOpts specifies options how to serve metrics via an http.Handler. The // HandlerOpts specifies options how to serve metrics via an http.Handler. The
// zero value of HandlerOpts is a reasonable default. // zero value of HandlerOpts is a reasonable default.
type HandlerOpts struct { type HandlerOpts struct {
// ErrorLog specifies an optional Logger for errors collecting and // ErrorLog specifies an optional logger for errors collecting and
// serving metrics. If nil, errors are not logged at all. Note that the // serving metrics. If nil, errors are not logged at all.
// type of a reported error is often prometheus.MultiError, which
// formats into a multi-line error string. If you want to avoid the
// latter, create a Logger implementation that detects a
// prometheus.MultiError and formats the contained errors into one line.
ErrorLog Logger ErrorLog Logger
// ErrorHandling defines how errors are handled. Note that errors are // ErrorHandling defines how errors are handled. Note that errors are
// logged regardless of the configured ErrorHandling provided ErrorLog // logged regardless of the configured ErrorHandling provided ErrorLog
......
...@@ -43,14 +43,14 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl ...@@ -43,14 +43,14 @@ func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handl
// InstrumentHandlerDuration is a middleware that wraps the provided // InstrumentHandlerDuration is a middleware that wraps the provided
// http.Handler to observe the request duration with the provided ObserverVec. // http.Handler to observe the request duration with the provided ObserverVec.
// The ObserverVec must have valid metric and label names and must have zero, // The ObserverVec must have zero, one, or two non-const non-curried labels. For
// one, or two non-const non-curried labels. For those, the only allowed label // those, the only allowed label names are "code" and "method". The function
// names are "code" and "method". The function panics otherwise. The Observe // panics otherwise. The Observe method of the Observer in the ObserverVec is
// method of the Observer in the ObserverVec is called with the request duration // called with the request duration in seconds. Partitioning happens by HTTP
// in seconds. Partitioning happens by HTTP status code and/or HTTP method if // status code and/or HTTP method if the respective instance label names are
// the respective instance label names are present in the ObserverVec. For // present in the ObserverVec. For unpartitioned observations, use an
// unpartitioned observations, use an ObserverVec with zero labels. Note that // ObserverVec with zero labels. Note that partitioning of Histograms is
// partitioning of Histograms is expensive and should be used judiciously. // expensive and should be used judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
...@@ -80,12 +80,11 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht ...@@ -80,12 +80,11 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) ht
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler // InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
// to observe the request result with the provided CounterVec. The CounterVec // to observe the request result with the provided CounterVec. The CounterVec
// must have valid metric and label names and must have zero, one, or two // must have zero, one, or two non-const non-curried labels. For those, the only
// non-const non-curried labels. For those, the only allowed label names are // allowed label names are "code" and "method". The function panics
// "code" and "method". The function panics otherwise. Partitioning of the // otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
// CounterVec happens by HTTP status code and/or HTTP method if the respective // HTTP method if the respective instance label names are present in the
// instance label names are present in the CounterVec. For unpartitioned // CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
// counting, use a CounterVec with zero labels.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
...@@ -111,15 +110,14 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) ...@@ -111,15 +110,14 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler)
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided // InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
// http.Handler to observe with the provided ObserverVec the request duration // http.Handler to observe with the provided ObserverVec the request duration
// until the response headers are written. The ObserverVec must have valid // until the response headers are written. The ObserverVec must have zero, one,
// metric and label names and must have zero, one, or two non-const non-curried // or two non-const non-curried labels. For those, the only allowed label names
// labels. For those, the only allowed label names are "code" and "method". The // are "code" and "method". The function panics otherwise. The Observe method of
// function panics otherwise. The Observe method of the Observer in the // the Observer in the ObserverVec is called with the request duration in
// ObserverVec is called with the request duration in seconds. Partitioning // seconds. Partitioning happens by HTTP status code and/or HTTP method if the
// happens by HTTP status code and/or HTTP method if the respective instance // respective instance label names are present in the ObserverVec. For
// label names are present in the ObserverVec. For unpartitioned observations, // unpartitioned observations, use an ObserverVec with zero labels. Note that
// use an ObserverVec with zero labels. Note that partitioning of Histograms is // partitioning of Histograms is expensive and should be used judiciously.
// expensive and should be used judiciously.
// //
// If the wrapped Handler panics before calling WriteHeader, no value is // If the wrapped Handler panics before calling WriteHeader, no value is
// reported. // reported.
...@@ -142,14 +140,14 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha ...@@ -142,14 +140,14 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
// InstrumentHandlerRequestSize is a middleware that wraps the provided // InstrumentHandlerRequestSize is a middleware that wraps the provided
// http.Handler to observe the request size with the provided ObserverVec. The // http.Handler to observe the request size with the provided ObserverVec. The
// ObserverVec must have valid metric and label names and must have zero, one, // ObserverVec must have zero, one, or two non-const non-curried labels. For
// or two non-const non-curried labels. For those, the only allowed label names // those, the only allowed label names are "code" and "method". The function
// are "code" and "method". The function panics otherwise. The Observe method of // panics otherwise. The Observe method of the Observer in the ObserverVec is
// the Observer in the ObserverVec is called with the request size in // called with the request size in bytes. Partitioning happens by HTTP status
// bytes. Partitioning happens by HTTP status code and/or HTTP method if the // code and/or HTTP method if the respective instance label names are present in
// respective instance label names are present in the ObserverVec. For // the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
// unpartitioned observations, use an ObserverVec with zero labels. Note that // labels. Note that partitioning of Histograms is expensive and should be used
// partitioning of Histograms is expensive and should be used judiciously. // judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
...@@ -177,14 +175,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) ...@@ -177,14 +175,14 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler)
// InstrumentHandlerResponseSize is a middleware that wraps the provided // InstrumentHandlerResponseSize is a middleware that wraps the provided
// http.Handler to observe the response size with the provided ObserverVec. The // http.Handler to observe the response size with the provided ObserverVec. The
// ObserverVec must have valid metric and label names and must have zero, one, // ObserverVec must have zero, one, or two non-const non-curried labels. For
// or two non-const non-curried labels. For those, the only allowed label names // those, the only allowed label names are "code" and "method". The function
// are "code" and "method". The function panics otherwise. The Observe method of // panics otherwise. The Observe method of the Observer in the ObserverVec is
// the Observer in the ObserverVec is called with the response size in // called with the response size in bytes. Partitioning happens by HTTP status
// bytes. Partitioning happens by HTTP status code and/or HTTP method if the // code and/or HTTP method if the respective instance label names are present in
// respective instance label names are present in the ObserverVec. For // the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
// unpartitioned observations, use an ObserverVec with zero labels. Note that // labels. Note that partitioning of Histograms is expensive and should be used
// partitioning of Histograms is expensive and should be used judiciously. // judiciously.
// //
// If the wrapped Handler does not set a status code, a status code of 200 is assumed. // If the wrapped Handler does not set a status code, a status code of 200 is assumed.
// //
...@@ -200,11 +198,6 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler ...@@ -200,11 +198,6 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
}) })
} }
// checkLabels returns whether the provided Collector has a non-const,
// non-curried label named "code" and/or "method". It panics if the provided
// Collector does not have a Desc or has more than one Desc or its Desc is
// invalid. It also panics if the Collector has any non-const, non-curried
// labels that are not named "code" or "method".
func checkLabels(c prometheus.Collector) (code bool, method bool) { func checkLabels(c prometheus.Collector) (code bool, method bool) {
// TODO(beorn7): Remove this hacky way to check for instance labels // TODO(beorn7): Remove this hacky way to check for instance labels
// once Descriptors can have their dimensionality queried. // once Descriptors can have their dimensionality queried.
...@@ -232,10 +225,6 @@ func checkLabels(c prometheus.Collector) (code bool, method bool) { ...@@ -232,10 +225,6 @@ func checkLabels(c prometheus.Collector) (code bool, method bool) {
close(descc) close(descc)
// Make sure the Collector has a valid Desc by registering it with a
// temporary registry.
prometheus.NewRegistry().MustRegister(c)
// Create a ConstMetric with the Desc. Since we don't know how many // Create a ConstMetric with the Desc. Since we don't know how many
// variable labels there are, try for as long as it needs. // variable labels there are, try for as long as it needs.
for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) { for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
......
...@@ -26,7 +26,6 @@ import ( ...@@ -26,7 +26,6 @@ import (
"unicode/utf8" "unicode/utf8"
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt" "github.com/prometheus/common/expfmt"
...@@ -215,8 +214,6 @@ func (err AlreadyRegisteredError) Error() string { ...@@ -215,8 +214,6 @@ func (err AlreadyRegisteredError) Error() string {
// by a Gatherer to report multiple errors during MetricFamily gathering. // by a Gatherer to report multiple errors during MetricFamily gathering.
type MultiError []error type MultiError []error
// Error formats the contained errors as a bullet point list, preceded by the
// total number of errors. Note that this results in a multi-line string.
func (errs MultiError) Error() string { func (errs MultiError) Error() string {
if len(errs) == 0 { if len(errs) == 0 {
return "" return ""
......
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/beorn7/perks/quantile" "github.com/beorn7/perks/quantile"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
...@@ -55,12 +54,7 @@ type Summary interface { ...@@ -55,12 +54,7 @@ type Summary interface {
Metric Metric
Collector Collector
// Observe adds a single observation to the summary. Observations are // Observe adds a single observation to the summary.
// usually positive or zero. Negative observations are accepted but
// prevent current versions of Prometheus from properly detecting
// counter resets in the sum of observations. See
// https://prometheus.io/docs/practices/histograms/#count-and-sum-of-observations
// for details.
Observe(float64) Observe(float64)
} }
...@@ -115,7 +109,7 @@ type SummaryOpts struct { ...@@ -115,7 +109,7 @@ type SummaryOpts struct {
// better covered by target labels set by the scraping Prometheus // better covered by target labels set by the scraping Prometheus
// server, or by one specific metric (e.g. a build_info or a // server, or by one specific metric (e.g. a build_info or a
// machine_role metric). See also // machine_role metric). See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
ConstLabels Labels ConstLabels Labels
// Objectives defines the quantile rank estimates with their respective // Objectives defines the quantile rank estimates with their respective
...@@ -126,9 +120,7 @@ type SummaryOpts struct { ...@@ -126,9 +120,7 @@ type SummaryOpts struct {
Objectives map[float64]float64 Objectives map[float64]float64
// MaxAge defines the duration for which an observation stays relevant // MaxAge defines the duration for which an observation stays relevant
// for the summary. Only applies to pre-calculated quantiles, does not // for the summary. Must be positive. The default value is DefMaxAge.
// apply to _sum and _count. Must be positive. The default value is
// DefMaxAge.
MaxAge time.Duration MaxAge time.Duration
// AgeBuckets is the number of buckets used to exclude observations that // AgeBuckets is the number of buckets used to exclude observations that
...@@ -215,7 +207,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { ...@@ -215,7 +207,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
// Use the lock-free implementation of a Summary without objectives. // Use the lock-free implementation of a Summary without objectives.
s := &noObjectivesSummary{ s := &noObjectivesSummary{
desc: desc, desc: desc,
labelPairs: MakeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
counts: [2]*summaryCounts{{}, {}}, counts: [2]*summaryCounts{{}, {}},
} }
s.init(s) // Init self-collection. s.init(s) // Init self-collection.
...@@ -228,7 +220,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { ...@@ -228,7 +220,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
objectives: opts.Objectives, objectives: opts.Objectives,
sortedObjectives: make([]float64, 0, len(opts.Objectives)), sortedObjectives: make([]float64, 0, len(opts.Objectives)),
labelPairs: MakeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
hotBuf: make([]float64, 0, opts.BufCap), hotBuf: make([]float64, 0, opts.BufCap),
coldBuf: make([]float64, 0, opts.BufCap), coldBuf: make([]float64, 0, opts.BufCap),
...@@ -520,7 +512,7 @@ func (s quantSort) Less(i, j int) bool { ...@@ -520,7 +512,7 @@ func (s quantSort) Less(i, j int) bool {
// (e.g. HTTP request latencies, partitioned by status code and method). Create // (e.g. HTTP request latencies, partitioned by status code and method). Create
// instances with NewSummaryVec. // instances with NewSummaryVec.
type SummaryVec struct { type SummaryVec struct {
*MetricVec *metricVec
} }
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
...@@ -542,14 +534,14 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec { ...@@ -542,14 +534,14 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
opts.ConstLabels, opts.ConstLabels,
) )
return &SummaryVec{ return &SummaryVec{
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric { metricVec: newMetricVec(desc, func(lvs ...string) Metric {
return newSummary(desc, opts, lvs...) return newSummary(desc, opts, lvs...)
}), }),
} }
} }
// GetMetricWithLabelValues returns the Summary for the given slice of label // GetMetricWithLabelValues returns the Summary for the given slice of label
// values (same order as the variable labels in Desc). If that combination of // values (same order as the VariableLabels in Desc). If that combination of
// label values is accessed for the first time, a new Summary is created. // label values is accessed for the first time, a new Summary is created.
// //
// It is possible to call this method without using the returned Summary to only // It is possible to call this method without using the returned Summary to only
...@@ -564,7 +556,7 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec { ...@@ -564,7 +556,7 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
// example. // example.
// //
// An error is returned if the number of label values is not the same as the // An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels). // number of VariableLabels in Desc (minus any curried labels).
// //
// Note that for more than one label value, this method is prone to mistakes // Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as // caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
...@@ -573,7 +565,7 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec { ...@@ -573,7 +565,7 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example. // See also the GaugeVec example.
func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...) metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
...@@ -581,19 +573,19 @@ func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) { ...@@ -581,19 +573,19 @@ func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
} }
// GetMetricWith returns the Summary for the given Labels map (the label names // GetMetricWith returns the Summary for the given Labels map (the label names
// must match those of the variable labels in Desc). If that label map is // must match those of the VariableLabels in Desc). If that label map is
// accessed for the first time, a new Summary is created. Implications of // accessed for the first time, a new Summary is created. Implications of
// creating a Summary without using it and keeping the Summary for later use are // creating a Summary without using it and keeping the Summary for later use are
// the same as for GetMetricWithLabelValues. // the same as for GetMetricWithLabelValues.
// //
// An error is returned if the number and names of the Labels are inconsistent // An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels). // with those of the VariableLabels in Desc (minus any curried labels).
// //
// This method is used for the same purpose as // This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two // GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods. // methods.
func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) { func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
metric, err := v.MetricVec.GetMetricWith(labels) metric, err := v.metricVec.getMetricWith(labels)
if metric != nil { if metric != nil {
return metric.(Observer), err return metric.(Observer), err
} }
...@@ -637,7 +629,7 @@ func (v *SummaryVec) With(labels Labels) Observer { ...@@ -637,7 +629,7 @@ func (v *SummaryVec) With(labels Labels) Observer {
// registered with a given registry (usually the uncurried version). The Reset // registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector. // method deletes all metrics, even if called on a curried vector.
func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) { func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
vec, err := v.MetricVec.CurryWith(labels) vec, err := v.curryWith(labels)
if vec != nil { if vec != nil {
return &SummaryVec{vec}, err return &SummaryVec{vec}, err
} }
...@@ -723,7 +715,7 @@ func NewConstSummary( ...@@ -723,7 +715,7 @@ func NewConstSummary(
count: count, count: count,
sum: sum, sum: sum,
quantiles: quantiles, quantiles: quantiles,
labelPairs: MakeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
}, nil }, nil
} }
......
...@@ -19,7 +19,6 @@ import ( ...@@ -19,7 +19,6 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes"
...@@ -63,7 +62,7 @@ func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *val ...@@ -63,7 +62,7 @@ func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *val
desc: desc, desc: desc,
valType: valueType, valType: valueType,
function: function, function: function,
labelPairs: MakeLabelPairs(desc, nil), labelPairs: makeLabelPairs(desc, nil),
} }
result.init(result) result.init(result)
return result return result
...@@ -95,7 +94,7 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...@@ -95,7 +94,7 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues
desc: desc, desc: desc,
valType: valueType, valType: valueType,
val: value, val: value,
labelPairs: MakeLabelPairs(desc, labelValues), labelPairs: makeLabelPairs(desc, labelValues),
}, nil }, nil
} }
...@@ -145,14 +144,7 @@ func populateMetric( ...@@ -145,14 +144,7 @@ func populateMetric(
return nil return nil
} }
// MakeLabelPairs is a helper function to create protobuf LabelPairs from the func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
// variable and constant labels in the provided Desc. The values for the
// variable labels are defined by the labelValues slice, which must be in the
// same order as the corresponding variable labels in the Desc.
//
// This function is only needed for custom Metric implementations. See MetricVec
// example.
func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
totalLen := len(desc.variableLabels) + len(desc.constLabelPairs) totalLen := len(desc.variableLabels) + len(desc.constLabelPairs)
if totalLen == 0 { if totalLen == 0 {
// Super fast path. // Super fast path.
......
...@@ -20,20 +20,12 @@ import ( ...@@ -20,20 +20,12 @@ import (
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
) )
// MetricVec is a Collector to bundle metrics of the same name that differ in // metricVec is a Collector to bundle metrics of the same name that differ in
// their label values. MetricVec is not used directly but as a building block // their label values. metricVec is not used directly (and therefore
// for implementations of vectors of a given metric type, like GaugeVec, // unexported). It is used as a building block for implementations of vectors of
// CounterVec, SummaryVec, and HistogramVec. It is exported so that it can be // a given metric type, like GaugeVec, CounterVec, SummaryVec, and HistogramVec.
// used for custom Metric implementations. // It also handles label currying.
// type metricVec struct {
// To create a FooVec for custom Metric Foo, embed a pointer to MetricVec in
// FooVec and initialize it with NewMetricVec. Implement wrappers for
// GetMetricWithLabelValues and GetMetricWith that return (Foo, error) rather
// than (Metric, error). Similarly, create a wrapper for CurryWith that returns
// (*FooVec, error) rather than (*MetricVec, error). It is recommended to also
// add the convenience methods WithLabelValues, With, and MustCurryWith, which
// panic instead of returning errors. See also the MetricVec example.
type MetricVec struct {
*metricMap *metricMap
curry []curriedLabelValue curry []curriedLabelValue
...@@ -43,9 +35,9 @@ type MetricVec struct { ...@@ -43,9 +35,9 @@ type MetricVec struct {
hashAddByte func(h uint64, b byte) uint64 hashAddByte func(h uint64, b byte) uint64
} }
// NewMetricVec returns an initialized metricVec. // newMetricVec returns an initialized metricVec.
func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec { func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec {
return &MetricVec{ return &metricVec{
metricMap: &metricMap{ metricMap: &metricMap{
metrics: map[uint64][]metricWithLabelValues{}, metrics: map[uint64][]metricWithLabelValues{},
desc: desc, desc: desc,
...@@ -71,7 +63,7 @@ func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec { ...@@ -71,7 +63,7 @@ func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
// latter has a much more readable (albeit more verbose) syntax, but it comes // latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map). // with a performance overhead (for creating and processing the Labels map).
// See also the CounterVec example. // See also the CounterVec example.
func (m *MetricVec) DeleteLabelValues(lvs ...string) bool { func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
h, err := m.hashLabelValues(lvs) h, err := m.hashLabelValues(lvs)
if err != nil { if err != nil {
return false return false
...@@ -90,7 +82,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool { ...@@ -90,7 +82,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
// //
// This method is used for the same purpose as DeleteLabelValues(...string). See // This method is used for the same purpose as DeleteLabelValues(...string). See
// there for pros and cons of the two methods. // there for pros and cons of the two methods.
func (m *MetricVec) Delete(labels Labels) bool { func (m *metricVec) Delete(labels Labels) bool {
h, err := m.hashLabels(labels) h, err := m.hashLabels(labels)
if err != nil { if err != nil {
return false return false
...@@ -103,32 +95,15 @@ func (m *MetricVec) Delete(labels Labels) bool { ...@@ -103,32 +95,15 @@ func (m *MetricVec) Delete(labels Labels) bool {
// show up in GoDoc. // show up in GoDoc.
// Describe implements Collector. // Describe implements Collector.
func (m *MetricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) } func (m *metricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) }
// Collect implements Collector. // Collect implements Collector.
func (m *MetricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) } func (m *metricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) }
// Reset deletes all metrics in this vector. // Reset deletes all metrics in this vector.
func (m *MetricVec) Reset() { m.metricMap.Reset() } func (m *metricVec) Reset() { m.metricMap.Reset() }
// CurryWith returns a vector curried with the provided labels, i.e. the func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
// returned vector has those labels pre-set for all labeled operations performed
// on it. The cardinality of the curried vector is reduced accordingly. The
// order of the remaining labels stays the same (just with the curried labels
// taken out of the sequence – which is relevant for the
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
// vector, but only with labels not yet used for currying before.
//
// The metrics contained in the MetricVec are shared between the curried and
// uncurried vectors. They are just accessed differently. Curried and uncurried
// vectors behave identically in terms of collection. Only one must be
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
//
// Note that CurryWith is usually not called directly but through a wrapper
// around MetricVec, implementing a vector for a specific Metric
// implementation, for example GaugeVec.
func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
var ( var (
newCurry []curriedLabelValue newCurry []curriedLabelValue
oldCurry = m.curry oldCurry = m.curry
...@@ -153,7 +128,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) { ...@@ -153,7 +128,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
return nil, fmt.Errorf("%d unknown label(s) found during currying", l) return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
} }
return &MetricVec{ return &metricVec{
metricMap: m.metricMap, metricMap: m.metricMap,
curry: newCurry, curry: newCurry,
hashAdd: m.hashAdd, hashAdd: m.hashAdd,
...@@ -161,34 +136,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) { ...@@ -161,34 +136,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
}, nil }, nil
} }
// GetMetricWithLabelValues returns the Metric for the given slice of label func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
// values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Metric is created (by
// calling the newMetric function provided during construction of the
// MetricVec).
//
// It is possible to call this method without using the returned Metric to only
// create the new Metric but leave it in its initial state.
//
// Keeping the Metric for later use is possible (and should be considered if
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
// Delete can be used to delete the Metric from the MetricVec. In that case, the
// Metric will still exist, but it will not be exported anymore, even if a
// Metric with the same label values is created later.
//
// An error is returned if the number of label values is not the same as the
// number of variable labels in Desc (minus any curried labels).
//
// Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
// an alternative to avoid that type of mistake. For higher label numbers, the
// latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map).
//
// Note that GetMetricWithLabelValues is usually not called directly but through
// a wrapper around MetricVec, implementing a vector for a specific Metric
// implementation, for example GaugeVec.
func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
h, err := m.hashLabelValues(lvs) h, err := m.hashLabelValues(lvs)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -197,23 +145,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) { ...@@ -197,23 +145,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
} }
// GetMetricWith returns the Metric for the given Labels map (the label names func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
// must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Metric is created. Implications of
// creating a Metric without using it and keeping the Metric for later use
// are the same as for GetMetricWithLabelValues.
//
// An error is returned if the number and names of the Labels are inconsistent
// with those of the variable labels in Desc (minus any curried labels).
//
// This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods.
//
// Note that GetMetricWith is usually not called directly but through a wrapper
// around MetricVec, implementing a vector for a specific Metric implementation,
// for example GaugeVec.
func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
h, err := m.hashLabels(labels) h, err := m.hashLabels(labels)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -222,7 +154,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) { ...@@ -222,7 +154,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
} }
func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) { func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil { if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
return 0, err return 0, err
} }
...@@ -245,7 +177,7 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) { ...@@ -245,7 +177,7 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
return h, nil return h, nil
} }
func (m *MetricVec) hashLabels(labels Labels) (uint64, error) { func (m *metricVec) hashLabels(labels Labels) (uint64, error) {
if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil { if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
return 0, err return 0, err
} }
...@@ -344,9 +276,7 @@ func (m *metricMap) deleteByHashWithLabelValues( ...@@ -344,9 +276,7 @@ func (m *metricMap) deleteByHashWithLabelValues(
} }
if len(metrics) > 1 { if len(metrics) > 1 {
old := metrics
m.metrics[h] = append(metrics[:i], metrics[i+1:]...) m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
old[len(old)-1] = metricWithLabelValues{}
} else { } else {
delete(m.metrics, h) delete(m.metrics, h)
} }
...@@ -372,9 +302,7 @@ func (m *metricMap) deleteByHashWithLabels( ...@@ -372,9 +302,7 @@ func (m *metricMap) deleteByHashWithLabels(
} }
if len(metrics) > 1 { if len(metrics) > 1 {
old := metrics
m.metrics[h] = append(metrics[:i], metrics[i+1:]...) m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
old[len(old)-1] = metricWithLabelValues{}
} else { } else {
delete(m.metrics, h) delete(m.metrics, h)
} }
......
...@@ -17,7 +17,6 @@ import ( ...@@ -17,7 +17,6 @@ import (
"fmt" "fmt"
"sort" "sort"
//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
...@@ -28,13 +27,10 @@ import ( ...@@ -28,13 +27,10 @@ import (
// registered with the wrapped Registerer in a modified way. The modified // registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided Labels to all Metrics it collects (as // Collector adds the provided Labels to all Metrics it collects (as
// ConstLabels). The Metrics collected by the unmodified Collector must not // ConstLabels). The Metrics collected by the unmodified Collector must not
// duplicate any of those labels. Wrapping a nil value is valid, resulting // duplicate any of those labels.
// in a no-op Registerer.
// //
// WrapRegistererWith provides a way to add fixed labels to a subset of // WrapRegistererWith provides a way to add fixed labels to a subset of
// Collectors. It should not be used to add fixed labels to all metrics // Collectors. It should not be used to add fixed labels to all metrics exposed.
// exposed. See also
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
// //
// Conflicts between Collectors registered through the original Registerer with // Conflicts between Collectors registered through the original Registerer with
// Collectors registered through the wrapping Registerer will still be // Collectors registered through the wrapping Registerer will still be
...@@ -54,7 +50,6 @@ func WrapRegistererWith(labels Labels, reg Registerer) Registerer { ...@@ -54,7 +50,6 @@ func WrapRegistererWith(labels Labels, reg Registerer) Registerer {
// Registerer. Collectors registered with the returned Registerer will be // Registerer. Collectors registered with the returned Registerer will be
// registered with the wrapped Registerer in a modified way. The modified // registered with the wrapped Registerer in a modified way. The modified
// Collector adds the provided prefix to the name of all Metrics it collects. // Collector adds the provided prefix to the name of all Metrics it collects.
// Wrapping a nil value is valid, resulting in a no-op Registerer.
// //
// WrapRegistererWithPrefix is useful to have one place to prefix all metrics of // WrapRegistererWithPrefix is useful to have one place to prefix all metrics of
// a sub-system. To make this work, register metrics of the sub-system with the // a sub-system. To make this work, register metrics of the sub-system with the
...@@ -85,9 +80,6 @@ type wrappingRegisterer struct { ...@@ -85,9 +80,6 @@ type wrappingRegisterer struct {
} }
func (r *wrappingRegisterer) Register(c Collector) error { func (r *wrappingRegisterer) Register(c Collector) error {
if r.wrappedRegisterer == nil {
return nil
}
return r.wrappedRegisterer.Register(&wrappingCollector{ return r.wrappedRegisterer.Register(&wrappingCollector{
wrappedCollector: c, wrappedCollector: c,
prefix: r.prefix, prefix: r.prefix,
...@@ -96,9 +88,6 @@ func (r *wrappingRegisterer) Register(c Collector) error { ...@@ -96,9 +88,6 @@ func (r *wrappingRegisterer) Register(c Collector) error {
} }
func (r *wrappingRegisterer) MustRegister(cs ...Collector) { func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
if r.wrappedRegisterer == nil {
return
}
for _, c := range cs { for _, c := range cs {
if err := r.Register(c); err != nil { if err := r.Register(c); err != nil {
panic(err) panic(err)
...@@ -107,9 +96,6 @@ func (r *wrappingRegisterer) MustRegister(cs ...Collector) { ...@@ -107,9 +96,6 @@ func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
} }
func (r *wrappingRegisterer) Unregister(c Collector) bool { func (r *wrappingRegisterer) Unregister(c Collector) bool {
if r.wrappedRegisterer == nil {
return false
}
return r.wrappedRegisterer.Unregister(&wrappingCollector{ return r.wrappedRegisterer.Unregister(&wrappingCollector{
wrappedCollector: c, wrappedCollector: c,
prefix: r.prefix, prefix: r.prefix,
......
...@@ -164,7 +164,7 @@ func (sd *SampleDecoder) Decode(s *model.Vector) error { ...@@ -164,7 +164,7 @@ func (sd *SampleDecoder) Decode(s *model.Vector) error {
} }
// ExtractSamples builds a slice of samples from the provided metric // ExtractSamples builds a slice of samples from the provided metric
// families. If an error occurs during sample extraction, it continues to // families. If an error occurrs during sample extraction, it continues to
// extract from the remaining metric families. The returned error is the last // extract from the remaining metric families. The returned error is the last
// error that has occurred. // error that has occurred.
func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) { func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) {
......
...@@ -299,17 +299,6 @@ func (p *TextParser) startLabelName() stateFn { ...@@ -299,17 +299,6 @@ func (p *TextParser) startLabelName() stateFn {
p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte)) p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
return nil return nil
} }
// Check for duplicate label names.
labels := make(map[string]struct{})
for _, l := range p.currentMetric.Label {
lName := l.GetName()
if _, exists := labels[lName]; !exists {
labels[lName] = struct{}{}
} else {
p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName()))
return nil
}
}
return p.startLabelValue return p.startLabelValue
} }
......
...@@ -20,7 +20,7 @@ const ( ...@@ -20,7 +20,7 @@ const (
prime64 = 1099511628211 prime64 = 1099511628211
) )
// hashNew initializes a new fnv64a hash value. // hashNew initializies a new fnv64a hash value.
func hashNew() uint64 { func hashNew() uint64 {
return offset64 return offset64
} }
......
...@@ -45,14 +45,6 @@ const ( ...@@ -45,14 +45,6 @@ const (
// scrape a target. // scrape a target.
MetricsPathLabel = "__metrics_path__" MetricsPathLabel = "__metrics_path__"
// ScrapeIntervalLabel is the name of the label that holds the scrape interval
// used to scrape a target.
ScrapeIntervalLabel = "__scrape_interval__"
// ScrapeTimeoutLabel is the name of the label that holds the scrape
// timeout used to scrape a target.
ScrapeTimeoutLabel = "__scrape_timeout__"
// ReservedLabelPrefix is a prefix which is not legal in user-supplied // ReservedLabelPrefix is a prefix which is not legal in user-supplied
// label names. // label names.
ReservedLabelPrefix = "__" ReservedLabelPrefix = "__"
......
...@@ -14,8 +14,6 @@ ...@@ -14,8 +14,6 @@
package model package model
import ( import (
"encoding/json"
"errors"
"fmt" "fmt"
"math" "math"
"regexp" "regexp"
...@@ -183,118 +181,73 @@ func (d *Duration) Type() string { ...@@ -183,118 +181,73 @@ func (d *Duration) Type() string {
return "duration" return "duration"
} }
var durationRE = regexp.MustCompile("^(([0-9]+)y)?(([0-9]+)w)?(([0-9]+)d)?(([0-9]+)h)?(([0-9]+)m)?(([0-9]+)s)?(([0-9]+)ms)?$") var durationRE = regexp.MustCompile("^([0-9]+)(y|w|d|h|m|s|ms)$")
// ParseDuration parses a string into a time.Duration, assuming that a year // ParseDuration parses a string into a time.Duration, assuming that a year
// always has 365d, a week always has 7d, and a day always has 24h. // always has 365d, a week always has 7d, and a day always has 24h.
func ParseDuration(durationStr string) (Duration, error) { func ParseDuration(durationStr string) (Duration, error) {
switch durationStr {
case "0":
// Allow 0 without a unit.
return 0, nil
case "":
return 0, fmt.Errorf("empty duration string")
}
matches := durationRE.FindStringSubmatch(durationStr) matches := durationRE.FindStringSubmatch(durationStr)
if matches == nil { if len(matches) != 3 {
return 0, fmt.Errorf("not a valid duration string: %q", durationStr) return 0, fmt.Errorf("not a valid duration string: %q", durationStr)
} }
var dur time.Duration var (
n, _ = strconv.Atoi(matches[1])
// Parse the match at pos `pos` in the regex and use `mult` to turn that dur = time.Duration(n) * time.Millisecond
// into ms, then add that value to the total parsed duration. )
var overflowErr error switch unit := matches[2]; unit {
m := func(pos int, mult time.Duration) { case "y":
if matches[pos] == "" { dur *= 1000 * 60 * 60 * 24 * 365
return case "w":
} dur *= 1000 * 60 * 60 * 24 * 7
n, _ := strconv.Atoi(matches[pos]) case "d":
dur *= 1000 * 60 * 60 * 24
// Check if the provided duration overflows time.Duration (> ~ 290years). case "h":
if n > int((1<<63-1)/mult/time.Millisecond) { dur *= 1000 * 60 * 60
overflowErr = errors.New("duration out of range") case "m":
} dur *= 1000 * 60
d := time.Duration(n) * time.Millisecond case "s":
dur += d * mult dur *= 1000
case "ms":
if dur < 0 { // Value already correct
overflowErr = errors.New("duration out of range") default:
} return 0, fmt.Errorf("invalid time unit in duration string: %q", unit)
} }
return Duration(dur), nil
m(2, 1000*60*60*24*365) // y
m(4, 1000*60*60*24*7) // w
m(6, 1000*60*60*24) // d
m(8, 1000*60*60) // h
m(10, 1000*60) // m
m(12, 1000) // s
m(14, 1) // ms
return Duration(dur), overflowErr
} }
func (d Duration) String() string { func (d Duration) String() string {
var ( var (
ms = int64(time.Duration(d) / time.Millisecond) ms = int64(time.Duration(d) / time.Millisecond)
r = "" unit = "ms"
) )
if ms == 0 { if ms == 0 {
return "0s" return "0s"
} }
factors := map[string]int64{
f := func(unit string, mult int64, exact bool) { "y": 1000 * 60 * 60 * 24 * 365,
if exact && ms%mult != 0 { "w": 1000 * 60 * 60 * 24 * 7,
return "d": 1000 * 60 * 60 * 24,
} "h": 1000 * 60 * 60,
if v := ms / mult; v > 0 { "m": 1000 * 60,
r += fmt.Sprintf("%d%s", v, unit) "s": 1000,
ms -= v * mult "ms": 1,
}
} }
// Only format years and weeks if the remainder is zero, as it is often switch int64(0) {
// easier to read 90d than 12w6d. case ms % factors["y"]:
f("y", 1000*60*60*24*365, true) unit = "y"
f("w", 1000*60*60*24*7, true) case ms % factors["w"]:
unit = "w"
f("d", 1000*60*60*24, false) case ms % factors["d"]:
f("h", 1000*60*60, false) unit = "d"
f("m", 1000*60, false) case ms % factors["h"]:
f("s", 1000, false) unit = "h"
f("ms", 1, false) case ms % factors["m"]:
unit = "m"
return r case ms % factors["s"]:
} unit = "s"
// MarshalJSON implements the json.Marshaler interface.
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (d *Duration) UnmarshalJSON(bytes []byte) error {
var s string
if err := json.Unmarshal(bytes, &s); err != nil {
return err
}
dur, err := ParseDuration(s)
if err != nil {
return err
} }
*d = dur return fmt.Sprintf("%v%v", ms/factors[unit], unit)
return nil
}
// MarshalText implements the encoding.TextMarshaler interface.
func (d *Duration) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
func (d *Duration) UnmarshalText(text []byte) error {
var err error
*d, err = ParseDuration(string(text))
return err
} }
// MarshalYAML implements the yaml.Marshaler interface. // MarshalYAML implements the yaml.Marshaler interface.
......
---
linters: linters:
enable: enable:
- golint - staticcheck
- govet
## Prometheus Community Code of Conduct
Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
...@@ -69,21 +69,12 @@ else ...@@ -69,21 +69,12 @@ else
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
endif endif
GOTEST := $(GO) test PROMU_VERSION ?= 0.4.0
GOTEST_DIR :=
ifneq ($(CIRCLE_JOB),)
ifneq ($(shell which gotestsum),)
GOTEST_DIR := test-results
GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
endif
endif
PROMU_VERSION ?= 0.7.0
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
GOLANGCI_LINT := GOLANGCI_LINT :=
GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_OPTS ?=
GOLANGCI_LINT_VERSION ?= v1.18.0 GOLANGCI_LINT_VERSION ?= v1.16.0
# golangci-lint only supports linux, darwin and windows platforms on i386/amd64. # golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
# windows isn't included here because of the path separator being different. # windows isn't included here because of the path separator being different.
ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
...@@ -95,8 +86,7 @@ endif ...@@ -95,8 +86,7 @@ endif
PREFIX ?= $(shell pwd) PREFIX ?= $(shell pwd)
BIN_DIR ?= $(shell pwd) BIN_DIR ?= $(shell pwd)
DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD))
DOCKERFILE_PATH ?= ./Dockerfile DOCKERFILE_PATH ?= ./
DOCKERBUILD_CONTEXT ?= ./
DOCKER_REPO ?= prom DOCKER_REPO ?= prom
DOCKER_ARCHS ?= amd64 DOCKER_ARCHS ?= amd64
...@@ -150,29 +140,15 @@ else ...@@ -150,29 +140,15 @@ else
$(GO) get $(GOOPTS) -t ./... $(GO) get $(GOOPTS) -t ./...
endif endif
.PHONY: update-go-deps
update-go-deps:
@echo ">> updating Go dependencies"
@for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \
$(GO) get $$m; \
done
GO111MODULE=$(GO111MODULE) $(GO) mod tidy
ifneq (,$(wildcard vendor))
GO111MODULE=$(GO111MODULE) $(GO) mod vendor
endif
.PHONY: common-test-short .PHONY: common-test-short
common-test-short: $(GOTEST_DIR) common-test-short:
@echo ">> running short tests" @echo ">> running short tests"
GO111MODULE=$(GO111MODULE) $(GOTEST) -short $(GOOPTS) $(pkgs) GO111MODULE=$(GO111MODULE) $(GO) test -short $(GOOPTS) $(pkgs)
.PHONY: common-test .PHONY: common-test
common-test: $(GOTEST_DIR) common-test:
@echo ">> running all tests" @echo ">> running all tests"
GO111MODULE=$(GO111MODULE) $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) GO111MODULE=$(GO111MODULE) $(GO) test $(test-flags) $(GOOPTS) $(pkgs)
$(GOTEST_DIR):
@mkdir -p $@
.PHONY: common-format .PHONY: common-format
common-format: common-format:
...@@ -224,7 +200,7 @@ endif ...@@ -224,7 +200,7 @@ endif
.PHONY: common-build .PHONY: common-build
common-build: promu common-build: promu
@echo ">> building binaries" @echo ">> building binaries"
GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) GO111MODULE=$(GO111MODULE) $(PROMU) build --prefix $(PREFIX)
.PHONY: common-tarball .PHONY: common-tarball
common-tarball: promu common-tarball: promu
...@@ -235,22 +211,19 @@ common-tarball: promu ...@@ -235,22 +211,19 @@ common-tarball: promu
common-docker: $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS)
$(BUILD_DOCKER_ARCHS): common-docker-%: $(BUILD_DOCKER_ARCHS): common-docker-%:
docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \ docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
-f $(DOCKERFILE_PATH) \
--build-arg ARCH="$*" \ --build-arg ARCH="$*" \
--build-arg OS="linux" \ --build-arg OS="linux" \
$(DOCKERBUILD_CONTEXT) $(DOCKERFILE_PATH)
.PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
common-docker-publish: $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
$(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
.PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
common-docker-tag-latest: $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
$(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
.PHONY: common-docker-manifest .PHONY: common-docker-manifest
common-docker-manifest: common-docker-manifest:
......
# Reporting a security issue
The Prometheus security policy, including how to report vulnerabilities, can be
found here:
https://prometheus.io/docs/operating/security/
...@@ -36,7 +36,7 @@ type ARPEntry struct { ...@@ -36,7 +36,7 @@ type ARPEntry struct {
func (fs FS) GatherARPEntries() ([]ARPEntry, error) { func (fs FS) GatherARPEntries() ([]ARPEntry, error) {
data, err := ioutil.ReadFile(fs.proc.Path("net/arp")) data, err := ioutil.ReadFile(fs.proc.Path("net/arp"))
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading arp %q: %w", fs.proc.Path("net/arp"), err) return nil, fmt.Errorf("error reading arp %s: %s", fs.proc.Path("net/arp"), err)
} }
return parseARPEntries(data) return parseARPEntries(data)
...@@ -59,7 +59,7 @@ func parseARPEntries(data []byte) ([]ARPEntry, error) { ...@@ -59,7 +59,7 @@ func parseARPEntries(data []byte) ([]ARPEntry, error) {
} else if width == expectedDataWidth { } else if width == expectedDataWidth {
entry, err := parseARPEntry(columns) entry, err := parseARPEntry(columns)
if err != nil { if err != nil {
return []ARPEntry{}, fmt.Errorf("failed to parse ARP entry: %w", err) return []ARPEntry{}, fmt.Errorf("failed to parse ARP entry: %s", err)
} }
entries = append(entries, entry) entries = append(entries, entry)
} else { } else {
......
...@@ -74,7 +74,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) { ...@@ -74,7 +74,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
for i := 0; i < arraySize; i++ { for i := 0; i < arraySize; i++ {
sizes[i], err = strconv.ParseFloat(parts[i+4], 64) sizes[i], err = strconv.ParseFloat(parts[i+4], 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid value in buddyinfo: %w", err) return nil, fmt.Errorf("invalid value in buddyinfo: %s", err)
} }
} }
......
...@@ -11,16 +11,11 @@ ...@@ -11,16 +11,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// +build linux
package procfs package procfs
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"errors"
"fmt"
"regexp"
"strconv" "strconv"
"strings" "strings"
...@@ -57,11 +52,6 @@ type CPUInfo struct { ...@@ -57,11 +52,6 @@ type CPUInfo struct {
PowerManagement string PowerManagement string
} }
var (
cpuinfoClockRegexp = regexp.MustCompile(`([\d.]+)`)
cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`)
)
// CPUInfo returns information about current system CPUs. // CPUInfo returns information about current system CPUs.
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
func (fs FS) CPUInfo() ([]CPUInfo, error) { func (fs FS) CPUInfo() ([]CPUInfo, error) {
...@@ -72,26 +62,14 @@ func (fs FS) CPUInfo() ([]CPUInfo, error) { ...@@ -72,26 +62,14 @@ func (fs FS) CPUInfo() ([]CPUInfo, error) {
return parseCPUInfo(data) return parseCPUInfo(data)
} }
func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { // parseCPUInfo parses data from /proc/cpuinfo
func parseCPUInfo(info []byte) ([]CPUInfo, error) {
cpuinfo := []CPUInfo{}
i := -1
scanner := bufio.NewScanner(bytes.NewReader(info)) scanner := bufio.NewScanner(bytes.NewReader(info))
// find the first "processor" line
firstLine := firstNonEmptyLine(scanner)
if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
field := strings.SplitN(firstLine, ": ", 2)
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
firstcpu := CPUInfo{Processor: uint(v)}
cpuinfo := []CPUInfo{firstcpu}
i := 0
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if !strings.Contains(line, ":") { if strings.TrimSpace(line) == "" {
continue continue
} }
field := strings.SplitN(line, ": ", 2) field := strings.SplitN(line, ": ", 2)
...@@ -104,7 +82,7 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { ...@@ -104,7 +82,7 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
return nil, err return nil, err
} }
cpuinfo[i].Processor = uint(v) cpuinfo[i].Processor = uint(v)
case "vendor", "vendor_id": case "vendor_id":
cpuinfo[i].VendorID = field[1] cpuinfo[i].VendorID = field[1]
case "cpu family": case "cpu family":
cpuinfo[i].CPUFamily = field[1] cpuinfo[i].CPUFamily = field[1]
...@@ -185,297 +163,5 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) { ...@@ -185,297 +163,5 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
} }
} }
return cpuinfo, nil return cpuinfo, nil
}
func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
scanner := bufio.NewScanner(bytes.NewReader(info))
firstLine := firstNonEmptyLine(scanner)
match, _ := regexp.MatchString("^[Pp]rocessor", firstLine)
if !match || !strings.Contains(firstLine, ":") {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
field := strings.SplitN(firstLine, ": ", 2)
cpuinfo := []CPUInfo{}
featuresLine := ""
commonCPUInfo := CPUInfo{}
i := 0
if strings.TrimSpace(field[0]) == "Processor" {
commonCPUInfo = CPUInfo{ModelName: field[1]}
i = -1
} else {
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
firstcpu := CPUInfo{Processor: uint(v)}
cpuinfo = []CPUInfo{firstcpu}
}
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, ":") {
continue
}
field := strings.SplitN(line, ": ", 2)
switch strings.TrimSpace(field[0]) {
case "processor":
cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor
i++
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
cpuinfo[i].Processor = uint(v)
case "BogoMIPS":
if i == -1 {
cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor
i++
cpuinfo[i].Processor = 0
}
v, err := strconv.ParseFloat(field[1], 64)
if err != nil {
return nil, err
}
cpuinfo[i].BogoMips = v
case "Features":
featuresLine = line
case "model name":
cpuinfo[i].ModelName = field[1]
}
}
fields := strings.SplitN(featuresLine, ": ", 2)
for i := range cpuinfo {
cpuinfo[i].Flags = strings.Fields(fields[1])
}
return cpuinfo, nil
}
func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
scanner := bufio.NewScanner(bytes.NewReader(info))
firstLine := firstNonEmptyLine(scanner)
if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
field := strings.SplitN(firstLine, ": ", 2)
cpuinfo := []CPUInfo{}
commonCPUInfo := CPUInfo{VendorID: field[1]}
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, ":") {
continue
}
field := strings.SplitN(line, ": ", 2)
switch strings.TrimSpace(field[0]) {
case "bogomips per cpu":
v, err := strconv.ParseFloat(field[1], 64)
if err != nil {
return nil, err
}
commonCPUInfo.BogoMips = v
case "features":
commonCPUInfo.Flags = strings.Fields(field[1])
}
if strings.HasPrefix(line, "processor") {
match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
if len(match) < 2 {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
cpu := commonCPUInfo
v, err := strconv.ParseUint(match[1], 0, 32)
if err != nil {
return nil, err
}
cpu.Processor = uint(v)
cpuinfo = append(cpuinfo, cpu)
}
if strings.HasPrefix(line, "cpu number") {
break
}
}
i := 0
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, ":") {
continue
}
field := strings.SplitN(line, ": ", 2)
switch strings.TrimSpace(field[0]) {
case "cpu number":
i++
case "cpu MHz dynamic":
clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
v, err := strconv.ParseFloat(clock, 64)
if err != nil {
return nil, err
}
cpuinfo[i].CPUMHz = v
case "physical id":
cpuinfo[i].PhysicalID = field[1]
case "core id":
cpuinfo[i].CoreID = field[1]
case "cpu cores":
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
cpuinfo[i].CPUCores = uint(v)
case "siblings":
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
cpuinfo[i].Siblings = uint(v)
}
}
return cpuinfo, nil
}
func parseCPUInfoMips(info []byte) ([]CPUInfo, error) {
scanner := bufio.NewScanner(bytes.NewReader(info))
// find the first "processor" line
firstLine := firstNonEmptyLine(scanner)
if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
field := strings.SplitN(firstLine, ": ", 2)
cpuinfo := []CPUInfo{}
systemType := field[1]
i := 0
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, ":") {
continue
}
field := strings.SplitN(line, ": ", 2)
switch strings.TrimSpace(field[0]) {
case "processor":
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
i = int(v)
cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
cpuinfo[i].Processor = uint(v)
cpuinfo[i].VendorID = systemType
case "cpu model":
cpuinfo[i].ModelName = field[1]
case "BogoMIPS":
v, err := strconv.ParseFloat(field[1], 64)
if err != nil {
return nil, err
}
cpuinfo[i].BogoMips = v
}
}
return cpuinfo, nil
}
func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
scanner := bufio.NewScanner(bytes.NewReader(info))
firstLine := firstNonEmptyLine(scanner)
if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
field := strings.SplitN(firstLine, ": ", 2)
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
firstcpu := CPUInfo{Processor: uint(v)}
cpuinfo := []CPUInfo{firstcpu}
i := 0
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, ":") {
continue
}
field := strings.SplitN(line, ": ", 2)
switch strings.TrimSpace(field[0]) {
case "processor":
cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
i++
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
cpuinfo[i].Processor = uint(v)
case "cpu":
cpuinfo[i].VendorID = field[1]
case "clock":
clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
v, err := strconv.ParseFloat(clock, 64)
if err != nil {
return nil, err
}
cpuinfo[i].CPUMHz = v
}
}
return cpuinfo, nil
}
func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) {
scanner := bufio.NewScanner(bytes.NewReader(info))
firstLine := firstNonEmptyLine(scanner)
if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
}
field := strings.SplitN(firstLine, ": ", 2)
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
firstcpu := CPUInfo{Processor: uint(v)}
cpuinfo := []CPUInfo{firstcpu}
i := 0
for scanner.Scan() {
line := scanner.Text()
if !strings.Contains(line, ":") {
continue
}
field := strings.SplitN(line, ": ", 2)
switch strings.TrimSpace(field[0]) {
case "processor":
v, err := strconv.ParseUint(field[1], 0, 32)
if err != nil {
return nil, err
}
i = int(v)
cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
cpuinfo[i].Processor = uint(v)
case "hart":
cpuinfo[i].CoreID = field[1]
case "isa":
cpuinfo[i].ModelName = field[1]
}
}
return cpuinfo, nil
}
func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { // nolint:unused,deadcode
return nil, errors.New("not implemented")
}
// firstNonEmptyLine advances the scanner to the first non-empty line
// and returns the contents of that line
func firstNonEmptyLine(scanner *bufio.Scanner) string {
for scanner.Scan() {
line := scanner.Text()
if strings.TrimSpace(line) != "" {
return line
}
}
return ""
} }
// Copyright 2020 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 linux
// +build mips mipsle mips64 mips64le
package procfs
var parseCPUInfo = parseCPUInfoMips
// Copyright 2020 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 linux
// +build !386,!amd64,!arm,!arm64,!mips,!mips64,!mips64le,!mipsle,!ppc64,!ppc64le,!riscv64,!s390x
package procfs
var parseCPUInfo = parseCPUInfoDummy
// Copyright 2020 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 linux
// +build ppc64 ppc64le
package procfs
var parseCPUInfo = parseCPUInfoPPC
// Copyright 2020 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 linux
// +build riscv riscv64
package procfs
var parseCPUInfo = parseCPUInfoRISCV
// Copyright 2020 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 linux
// +build 386 amd64
package procfs
var parseCPUInfo = parseCPUInfoX86
...@@ -14,10 +14,10 @@ ...@@ -14,10 +14,10 @@
package procfs package procfs
import ( import (
"bufio"
"bytes" "bytes"
"fmt" "fmt"
"io" "io/ioutil"
"strconv"
"strings" "strings"
"github.com/prometheus/procfs/internal/util" "github.com/prometheus/procfs/internal/util"
...@@ -52,102 +52,80 @@ type Crypto struct { ...@@ -52,102 +52,80 @@ type Crypto struct {
// structs containing the relevant info. More information available here: // structs containing the relevant info. More information available here:
// https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html // https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html
func (fs FS) Crypto() ([]Crypto, error) { func (fs FS) Crypto() ([]Crypto, error) {
path := fs.proc.Path("crypto") data, err := ioutil.ReadFile(fs.proc.Path("crypto"))
b, err := util.ReadFileNoStat(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading crypto %q: %w", path, err) return nil, fmt.Errorf("error parsing crypto %s: %s", fs.proc.Path("crypto"), err)
} }
crypto, err := parseCrypto(data)
crypto, err := parseCrypto(bytes.NewReader(b))
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing crypto %q: %w", path, err) return nil, fmt.Errorf("error parsing crypto %s: %s", fs.proc.Path("crypto"), err)
} }
return crypto, nil return crypto, nil
} }
// parseCrypto parses a /proc/crypto stream into Crypto elements. func parseCrypto(cryptoData []byte) ([]Crypto, error) {
func parseCrypto(r io.Reader) ([]Crypto, error) { crypto := []Crypto{}
var out []Crypto
s := bufio.NewScanner(r) cryptoBlocks := bytes.Split(cryptoData, []byte("\n\n"))
for s.Scan() {
text := s.Text()
switch {
case strings.HasPrefix(text, "name"):
// Each crypto element begins with its name.
out = append(out, Crypto{})
case text == "":
continue
}
kv := strings.Split(text, ":") for _, block := range cryptoBlocks {
if len(kv) != 2 { var newCryptoElem Crypto
return nil, fmt.Errorf("malformed crypto line: %q", text)
}
k := strings.TrimSpace(kv[0])
v := strings.TrimSpace(kv[1])
// Parse the key/value pair into the currently focused element.
c := &out[len(out)-1]
if err := c.parseKV(k, v); err != nil {
return nil, err
}
}
if err := s.Err(); err != nil { lines := strings.Split(string(block), "\n")
return nil, err for _, line := range lines {
if strings.TrimSpace(line) == "" || line[0] == ' ' {
continue
} }
fields := strings.Split(line, ":")
key := strings.TrimSpace(fields[0])
value := strings.TrimSpace(fields[1])
vp := util.NewValueParser(value)
return out, nil switch strings.TrimSpace(key) {
}
// parseKV parses a key/value pair into the appropriate field of c.
func (c *Crypto) parseKV(k, v string) error {
vp := util.NewValueParser(v)
switch k {
case "async": case "async":
// Interpret literal yes as true. b, err := strconv.ParseBool(value)
c.Async = v == "yes" if err == nil {
newCryptoElem.Async = b
}
case "blocksize": case "blocksize":
c.Blocksize = vp.PUInt64() newCryptoElem.Blocksize = vp.PUInt64()
case "chunksize": case "chunksize":
c.Chunksize = vp.PUInt64() newCryptoElem.Chunksize = vp.PUInt64()
case "digestsize": case "digestsize":
c.Digestsize = vp.PUInt64() newCryptoElem.Digestsize = vp.PUInt64()
case "driver": case "driver":
c.Driver = v newCryptoElem.Driver = value
case "geniv": case "geniv":
c.Geniv = v newCryptoElem.Geniv = value
case "internal": case "internal":
c.Internal = v newCryptoElem.Internal = value
case "ivsize": case "ivsize":
c.Ivsize = vp.PUInt64() newCryptoElem.Ivsize = vp.PUInt64()
case "maxauthsize": case "maxauthsize":
c.Maxauthsize = vp.PUInt64() newCryptoElem.Maxauthsize = vp.PUInt64()
case "max keysize": case "max keysize":
c.MaxKeysize = vp.PUInt64() newCryptoElem.MaxKeysize = vp.PUInt64()
case "min keysize": case "min keysize":
c.MinKeysize = vp.PUInt64() newCryptoElem.MinKeysize = vp.PUInt64()
case "module": case "module":
c.Module = v newCryptoElem.Module = value
case "name": case "name":
c.Name = v newCryptoElem.Name = value
case "priority": case "priority":
c.Priority = vp.PInt64() newCryptoElem.Priority = vp.PInt64()
case "refcnt": case "refcnt":
c.Refcnt = vp.PInt64() newCryptoElem.Refcnt = vp.PInt64()
case "seedsize": case "seedsize":
c.Seedsize = vp.PUInt64() newCryptoElem.Seedsize = vp.PUInt64()
case "selftest": case "selftest":
c.Selftest = v newCryptoElem.Selftest = value
case "type": case "type":
c.Type = v newCryptoElem.Type = value
case "walksize": case "walksize":
c.Walksize = vp.PUInt64() newCryptoElem.Walksize = vp.PUInt64()
} }
}
return vp.Err() crypto = append(crypto, newCryptoElem)
}
return crypto, nil
} }
module github.com/prometheus/procfs module github.com/prometheus/procfs
go 1.13 go 1.12
require ( require (
github.com/google/go-cmp v0.5.4 github.com/google/go-cmp v0.3.1
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
) )
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
...@@ -39,10 +39,10 @@ type FS string ...@@ -39,10 +39,10 @@ type FS string
func NewFS(mountPoint string) (FS, error) { func NewFS(mountPoint string) (FS, error) {
info, err := os.Stat(mountPoint) info, err := os.Stat(mountPoint)
if err != nil { if err != nil {
return "", fmt.Errorf("could not read %q: %w", mountPoint, err) return "", fmt.Errorf("could not read %s: %s", mountPoint, err)
} }
if !info.IsDir() { if !info.IsDir() {
return "", fmt.Errorf("mount point %q is not a directory", mountPoint) return "", fmt.Errorf("mount point %s is not a directory", mountPoint)
} }
return FS(mountPoint), nil return FS(mountPoint), nil
......
...@@ -73,15 +73,6 @@ func ReadUintFromFile(path string) (uint64, error) { ...@@ -73,15 +73,6 @@ func ReadUintFromFile(path string) (uint64, error) {
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
} }
// ReadIntFromFile reads a file and attempts to parse a int64 from it.
func ReadIntFromFile(path string) (int64, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return 0, err
}
return strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
}
// ParseBool parses a string into a boolean pointer. // ParseBool parses a string into a boolean pointer.
func ParseBool(b string) *bool { func ParseBool(b string) *bool {
var truth bool var truth bool
......
// Copyright 2020 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 procfs
import (
"os"
"github.com/prometheus/procfs/internal/util"
)
// KernelRandom contains information about to the kernel's random number generator.
type KernelRandom struct {
// EntropyAvaliable gives the available entropy, in bits.
EntropyAvaliable *uint64
// PoolSize gives the size of the entropy pool, in bits.
PoolSize *uint64
// URandomMinReseedSeconds is the number of seconds after which the DRNG will be reseeded.
URandomMinReseedSeconds *uint64
// WriteWakeupThreshold the number of bits of entropy below which we wake up processes
// that do a select(2) or poll(2) for write access to /dev/random.
WriteWakeupThreshold *uint64
// ReadWakeupThreshold is the number of bits of entropy required for waking up processes that sleep
// waiting for entropy from /dev/random.
ReadWakeupThreshold *uint64
}
// KernelRandom returns values from /proc/sys/kernel/random.
func (fs FS) KernelRandom() (KernelRandom, error) {
random := KernelRandom{}
for file, p := range map[string]**uint64{
"entropy_avail": &random.EntropyAvaliable,
"poolsize": &random.PoolSize,
"urandom_min_reseed_secs": &random.URandomMinReseedSeconds,
"write_wakeup_threshold": &random.WriteWakeupThreshold,
"read_wakeup_threshold": &random.ReadWakeupThreshold,
} {
val, err := util.ReadUintFromFile(fs.proc.Path("sys", "kernel", "random", file))
if os.IsNotExist(err) {
continue
}
if err != nil {
return random, err
}
*p = &val
}
return random, nil
}
// Copyright 2019 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.
package procfs
import (
"fmt"
"strconv"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// LoadAvg represents an entry in /proc/loadavg
type LoadAvg struct {
Load1 float64
Load5 float64
Load15 float64
}
// LoadAvg returns loadavg from /proc.
func (fs FS) LoadAvg() (*LoadAvg, error) {
path := fs.proc.Path("loadavg")
data, err := util.ReadFileNoStat(path)
if err != nil {
return nil, err
}
return parseLoad(data)
}
// Parse /proc loadavg and return 1m, 5m and 15m.
func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
loads := make([]float64, 3)
parts := strings.Fields(string(loadavgBytes))
if len(parts) < 3 {
return nil, fmt.Errorf("malformed loadavg line: too few fields in loadavg string: %q", string(loadavgBytes))
}
var err error
for i, load := range parts[0:3] {
loads[i], err = strconv.ParseFloat(load, 64)
if err != nil {
return nil, fmt.Errorf("could not parse load %q: %w", load, err)
}
}
return &LoadAvg{
Load1: loads[0],
Load5: loads[1],
Load15: loads[2],
}, nil
}
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
var ( var (
statusLineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`) statusLineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`) recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
componentDeviceRE = regexp.MustCompile(`(.*)\[\d+\]`)
) )
// MDStat holds info parsed from /proc/mdstat. // MDStat holds info parsed from /proc/mdstat.
...@@ -45,8 +44,6 @@ type MDStat struct { ...@@ -45,8 +44,6 @@ type MDStat struct {
BlocksTotal int64 BlocksTotal int64
// Number of blocks on the device that are in sync. // Number of blocks on the device that are in sync.
BlocksSynced int64 BlocksSynced int64
// Name of md component devices
Devices []string
} }
// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of // MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
...@@ -55,11 +52,11 @@ type MDStat struct { ...@@ -55,11 +52,11 @@ type MDStat struct {
func (fs FS) MDStat() ([]MDStat, error) { func (fs FS) MDStat() ([]MDStat, error) {
data, err := ioutil.ReadFile(fs.proc.Path("mdstat")) data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
} }
mdstat, err := parseMDStat(data) mdstat, err := parseMDStat(data)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing mdstat %q: %w", fs.proc.Path("mdstat"), err) return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
} }
return mdstat, nil return mdstat, nil
} }
...@@ -85,7 +82,10 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { ...@@ -85,7 +82,10 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
state := deviceFields[2] // active or inactive state := deviceFields[2] // active or inactive
if len(lines) <= i+3 { if len(lines) <= i+3 {
return nil, fmt.Errorf("error parsing %q: too few lines for md device", mdName) return nil, fmt.Errorf(
"error parsing %s: too few lines for md device",
mdName,
)
} }
// Failed disks have the suffix (F) & Spare disks have the suffix (S). // Failed disks have the suffix (F) & Spare disks have the suffix (S).
...@@ -94,7 +94,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { ...@@ -94,7 +94,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
active, total, size, err := evalStatusLine(lines[i], lines[i+1]) active, total, size, err := evalStatusLine(lines[i], lines[i+1])
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing md device lines: %w", err) return nil, fmt.Errorf("error parsing md device lines: %s", err)
} }
syncLineIdx := i + 2 syncLineIdx := i + 2
...@@ -107,14 +107,11 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { ...@@ -107,14 +107,11 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
syncedBlocks := size syncedBlocks := size
recovering := strings.Contains(lines[syncLineIdx], "recovery") recovering := strings.Contains(lines[syncLineIdx], "recovery")
resyncing := strings.Contains(lines[syncLineIdx], "resync") resyncing := strings.Contains(lines[syncLineIdx], "resync")
checking := strings.Contains(lines[syncLineIdx], "check")
// Append recovery and resyncing state info. // Append recovery and resyncing state info.
if recovering || resyncing || checking { if recovering || resyncing {
if recovering { if recovering {
state = "recovering" state = "recovering"
} else if checking {
state = "checking"
} else { } else {
state = "resyncing" state = "resyncing"
} }
...@@ -126,7 +123,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { ...@@ -126,7 +123,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
} else { } else {
syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx]) syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing sync line in md device %q: %w", mdName, err) return nil, fmt.Errorf("error parsing sync line in md device %s: %s", mdName, err)
} }
} }
} }
...@@ -140,7 +137,6 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) { ...@@ -140,7 +137,6 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
DisksTotal: total, DisksTotal: total,
BlocksTotal: size, BlocksTotal: size,
BlocksSynced: syncedBlocks, BlocksSynced: syncedBlocks,
Devices: evalComponentDevices(deviceFields),
}) })
} }
...@@ -152,7 +148,7 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, e ...@@ -152,7 +148,7 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, e
sizeStr := strings.Fields(statusLine)[0] sizeStr := strings.Fields(statusLine)[0]
size, err = strconv.ParseInt(sizeStr, 10, 64) size, err = strconv.ParseInt(sizeStr, 10, 64)
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err) return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
} }
if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") { if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
...@@ -172,12 +168,12 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, e ...@@ -172,12 +168,12 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, e
total, err = strconv.ParseInt(matches[2], 10, 64) total, err = strconv.ParseInt(matches[2], 10, 64)
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err) return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
} }
active, err = strconv.ParseInt(matches[3], 10, 64) active, err = strconv.ParseInt(matches[3], 10, 64)
if err != nil { if err != nil {
return 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err) return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
} }
return active, total, size, nil return active, total, size, nil
...@@ -191,23 +187,8 @@ func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) { ...@@ -191,23 +187,8 @@ func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) {
syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64) syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
if err != nil { if err != nil {
return 0, fmt.Errorf("error parsing int from recoveryLine %q: %w", recoveryLine, err) return 0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine)
} }
return syncedBlocks, nil return syncedBlocks, nil
} }
func evalComponentDevices(deviceFields []string) []string {
mdComponentDevices := make([]string, 0)
if len(deviceFields) > 3 {
for _, field := range deviceFields[4:] {
match := componentDeviceRE.FindStringSubmatch(field)
if match == nil {
continue
}
mdComponentDevices = append(mdComponentDevices, match[1])
}
}
return mdComponentDevices
}
...@@ -28,9 +28,9 @@ import ( ...@@ -28,9 +28,9 @@ import (
type Meminfo struct { type Meminfo struct {
// Total usable ram (i.e. physical ram minus a few reserved // Total usable ram (i.e. physical ram minus a few reserved
// bits and the kernel binary code) // bits and the kernel binary code)
MemTotal *uint64 MemTotal uint64
// The sum of LowFree+HighFree // The sum of LowFree+HighFree
MemFree *uint64 MemFree uint64
// An estimate of how much memory is available for starting // An estimate of how much memory is available for starting
// new applications, without swapping. Calculated from // new applications, without swapping. Calculated from
// MemFree, SReclaimable, the size of the file LRU lists, and // MemFree, SReclaimable, the size of the file LRU lists, and
...@@ -39,59 +39,59 @@ type Meminfo struct { ...@@ -39,59 +39,59 @@ type Meminfo struct {
// well, and that not all reclaimable slab will be // well, and that not all reclaimable slab will be
// reclaimable, due to items being in use. The impact of those // reclaimable, due to items being in use. The impact of those
// factors will vary from system to system. // factors will vary from system to system.
MemAvailable *uint64 MemAvailable uint64
// Relatively temporary storage for raw disk blocks shouldn't // Relatively temporary storage for raw disk blocks shouldn't
// get tremendously large (20MB or so) // get tremendously large (20MB or so)
Buffers *uint64 Buffers uint64
Cached *uint64 Cached uint64
// Memory that once was swapped out, is swapped back in but // Memory that once was swapped out, is swapped back in but
// still also is in the swapfile (if memory is needed it // still also is in the swapfile (if memory is needed it
// doesn't need to be swapped out AGAIN because it is already // doesn't need to be swapped out AGAIN because it is already
// in the swapfile. This saves I/O) // in the swapfile. This saves I/O)
SwapCached *uint64 SwapCached uint64
// Memory that has been used more recently and usually not // Memory that has been used more recently and usually not
// reclaimed unless absolutely necessary. // reclaimed unless absolutely necessary.
Active *uint64 Active uint64
// Memory which has been less recently used. It is more // Memory which has been less recently used. It is more
// eligible to be reclaimed for other purposes // eligible to be reclaimed for other purposes
Inactive *uint64 Inactive uint64
ActiveAnon *uint64 ActiveAnon uint64
InactiveAnon *uint64 InactiveAnon uint64
ActiveFile *uint64 ActiveFile uint64
InactiveFile *uint64 InactiveFile uint64
Unevictable *uint64 Unevictable uint64
Mlocked *uint64 Mlocked uint64
// total amount of swap space available // total amount of swap space available
SwapTotal *uint64 SwapTotal uint64
// Memory which has been evicted from RAM, and is temporarily // Memory which has been evicted from RAM, and is temporarily
// on the disk // on the disk
SwapFree *uint64 SwapFree uint64
// Memory which is waiting to get written back to the disk // Memory which is waiting to get written back to the disk
Dirty *uint64 Dirty uint64
// Memory which is actively being written back to the disk // Memory which is actively being written back to the disk
Writeback *uint64 Writeback uint64
// Non-file backed pages mapped into userspace page tables // Non-file backed pages mapped into userspace page tables
AnonPages *uint64 AnonPages uint64
// files which have been mapped, such as libraries // files which have been mapped, such as libraries
Mapped *uint64 Mapped uint64
Shmem *uint64 Shmem uint64
// in-kernel data structures cache // in-kernel data structures cache
Slab *uint64 Slab uint64
// Part of Slab, that might be reclaimed, such as caches // Part of Slab, that might be reclaimed, such as caches
SReclaimable *uint64 SReclaimable uint64
// Part of Slab, that cannot be reclaimed on memory pressure // Part of Slab, that cannot be reclaimed on memory pressure
SUnreclaim *uint64 SUnreclaim uint64
KernelStack *uint64 KernelStack uint64
// amount of memory dedicated to the lowest level of page // amount of memory dedicated to the lowest level of page
// tables. // tables.
PageTables *uint64 PageTables uint64
// NFS pages sent to the server, but not yet committed to // NFS pages sent to the server, but not yet committed to
// stable storage // stable storage
NFSUnstable *uint64 NFSUnstable uint64
// Memory used for block device "bounce buffers" // Memory used for block device "bounce buffers"
Bounce *uint64 Bounce uint64
// Memory used by FUSE for temporary writeback buffers // Memory used by FUSE for temporary writeback buffers
WritebackTmp *uint64 WritebackTmp uint64
// Based on the overcommit ratio ('vm.overcommit_ratio'), // Based on the overcommit ratio ('vm.overcommit_ratio'),
// this is the total amount of memory currently available to // this is the total amount of memory currently available to
// be allocated on the system. This limit is only adhered to // be allocated on the system. This limit is only adhered to
...@@ -105,7 +105,7 @@ type Meminfo struct { ...@@ -105,7 +105,7 @@ type Meminfo struct {
// yield a CommitLimit of 7.3G. // yield a CommitLimit of 7.3G.
// For more details, see the memory overcommit documentation // For more details, see the memory overcommit documentation
// in vm/overcommit-accounting. // in vm/overcommit-accounting.
CommitLimit *uint64 CommitLimit uint64
// The amount of memory presently allocated on the system. // The amount of memory presently allocated on the system.
// The committed memory is a sum of all of the memory which // The committed memory is a sum of all of the memory which
// has been allocated by processes, even if it has not been // has been allocated by processes, even if it has not been
...@@ -119,27 +119,27 @@ type Meminfo struct { ...@@ -119,27 +119,27 @@ type Meminfo struct {
// This is useful if one needs to guarantee that processes will // This is useful if one needs to guarantee that processes will
// not fail due to lack of memory once that memory has been // not fail due to lack of memory once that memory has been
// successfully allocated. // successfully allocated.
CommittedAS *uint64 CommittedAS uint64
// total size of vmalloc memory area // total size of vmalloc memory area
VmallocTotal *uint64 VmallocTotal uint64
// amount of vmalloc area which is used // amount of vmalloc area which is used
VmallocUsed *uint64 VmallocUsed uint64
// largest contiguous block of vmalloc area which is free // largest contiguous block of vmalloc area which is free
VmallocChunk *uint64 VmallocChunk uint64
HardwareCorrupted *uint64 HardwareCorrupted uint64
AnonHugePages *uint64 AnonHugePages uint64
ShmemHugePages *uint64 ShmemHugePages uint64
ShmemPmdMapped *uint64 ShmemPmdMapped uint64
CmaTotal *uint64 CmaTotal uint64
CmaFree *uint64 CmaFree uint64
HugePagesTotal *uint64 HugePagesTotal uint64
HugePagesFree *uint64 HugePagesFree uint64
HugePagesRsvd *uint64 HugePagesRsvd uint64
HugePagesSurp *uint64 HugePagesSurp uint64
Hugepagesize *uint64 Hugepagesize uint64
DirectMap4k *uint64 DirectMap4k uint64
DirectMap2M *uint64 DirectMap2M uint64
DirectMap1G *uint64 DirectMap1G uint64
} }
// Meminfo returns an information about current kernel/system memory statistics. // Meminfo returns an information about current kernel/system memory statistics.
...@@ -152,7 +152,7 @@ func (fs FS) Meminfo() (Meminfo, error) { ...@@ -152,7 +152,7 @@ func (fs FS) Meminfo() (Meminfo, error) {
m, err := parseMemInfo(bytes.NewReader(b)) m, err := parseMemInfo(bytes.NewReader(b))
if err != nil { if err != nil {
return Meminfo{}, fmt.Errorf("failed to parse meminfo: %w", err) return Meminfo{}, fmt.Errorf("failed to parse meminfo: %v", err)
} }
return *m, nil return *m, nil
...@@ -175,101 +175,101 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) { ...@@ -175,101 +175,101 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) {
switch fields[0] { switch fields[0] {
case "MemTotal:": case "MemTotal:":
m.MemTotal = &v m.MemTotal = v
case "MemFree:": case "MemFree:":
m.MemFree = &v m.MemFree = v
case "MemAvailable:": case "MemAvailable:":
m.MemAvailable = &v m.MemAvailable = v
case "Buffers:": case "Buffers:":
m.Buffers = &v m.Buffers = v
case "Cached:": case "Cached:":
m.Cached = &v m.Cached = v
case "SwapCached:": case "SwapCached:":
m.SwapCached = &v m.SwapCached = v
case "Active:": case "Active:":
m.Active = &v m.Active = v
case "Inactive:": case "Inactive:":
m.Inactive = &v m.Inactive = v
case "Active(anon):": case "Active(anon):":
m.ActiveAnon = &v m.ActiveAnon = v
case "Inactive(anon):": case "Inactive(anon):":
m.InactiveAnon = &v m.InactiveAnon = v
case "Active(file):": case "Active(file):":
m.ActiveFile = &v m.ActiveFile = v
case "Inactive(file):": case "Inactive(file):":
m.InactiveFile = &v m.InactiveFile = v
case "Unevictable:": case "Unevictable:":
m.Unevictable = &v m.Unevictable = v
case "Mlocked:": case "Mlocked:":
m.Mlocked = &v m.Mlocked = v
case "SwapTotal:": case "SwapTotal:":
m.SwapTotal = &v m.SwapTotal = v
case "SwapFree:": case "SwapFree:":
m.SwapFree = &v m.SwapFree = v
case "Dirty:": case "Dirty:":
m.Dirty = &v m.Dirty = v
case "Writeback:": case "Writeback:":
m.Writeback = &v m.Writeback = v
case "AnonPages:": case "AnonPages:":
m.AnonPages = &v m.AnonPages = v
case "Mapped:": case "Mapped:":
m.Mapped = &v m.Mapped = v
case "Shmem:": case "Shmem:":
m.Shmem = &v m.Shmem = v
case "Slab:": case "Slab:":
m.Slab = &v m.Slab = v
case "SReclaimable:": case "SReclaimable:":
m.SReclaimable = &v m.SReclaimable = v
case "SUnreclaim:": case "SUnreclaim:":
m.SUnreclaim = &v m.SUnreclaim = v
case "KernelStack:": case "KernelStack:":
m.KernelStack = &v m.KernelStack = v
case "PageTables:": case "PageTables:":
m.PageTables = &v m.PageTables = v
case "NFS_Unstable:": case "NFS_Unstable:":
m.NFSUnstable = &v m.NFSUnstable = v
case "Bounce:": case "Bounce:":
m.Bounce = &v m.Bounce = v
case "WritebackTmp:": case "WritebackTmp:":
m.WritebackTmp = &v m.WritebackTmp = v
case "CommitLimit:": case "CommitLimit:":
m.CommitLimit = &v m.CommitLimit = v
case "Committed_AS:": case "Committed_AS:":
m.CommittedAS = &v m.CommittedAS = v
case "VmallocTotal:": case "VmallocTotal:":
m.VmallocTotal = &v m.VmallocTotal = v
case "VmallocUsed:": case "VmallocUsed:":
m.VmallocUsed = &v m.VmallocUsed = v
case "VmallocChunk:": case "VmallocChunk:":
m.VmallocChunk = &v m.VmallocChunk = v
case "HardwareCorrupted:": case "HardwareCorrupted:":
m.HardwareCorrupted = &v m.HardwareCorrupted = v
case "AnonHugePages:": case "AnonHugePages:":
m.AnonHugePages = &v m.AnonHugePages = v
case "ShmemHugePages:": case "ShmemHugePages:":
m.ShmemHugePages = &v m.ShmemHugePages = v
case "ShmemPmdMapped:": case "ShmemPmdMapped:":
m.ShmemPmdMapped = &v m.ShmemPmdMapped = v
case "CmaTotal:": case "CmaTotal:":
m.CmaTotal = &v m.CmaTotal = v
case "CmaFree:": case "CmaFree:":
m.CmaFree = &v m.CmaFree = v
case "HugePages_Total:": case "HugePages_Total:":
m.HugePagesTotal = &v m.HugePagesTotal = v
case "HugePages_Free:": case "HugePages_Free:":
m.HugePagesFree = &v m.HugePagesFree = v
case "HugePages_Rsvd:": case "HugePages_Rsvd:":
m.HugePagesRsvd = &v m.HugePagesRsvd = v
case "HugePages_Surp:": case "HugePages_Surp:":
m.HugePagesSurp = &v m.HugePagesSurp = v
case "Hugepagesize:": case "Hugepagesize:":
m.Hugepagesize = &v m.Hugepagesize = v
case "DirectMap4k:": case "DirectMap4k:":
m.DirectMap4k = &v m.DirectMap4k = v
case "DirectMap2M:": case "DirectMap2M:":
m.DirectMap2M = &v m.DirectMap2M = v
case "DirectMap1G:": case "DirectMap1G:":
m.DirectMap1G = &v m.DirectMap1G = v
} }
} }
......
...@@ -29,10 +29,10 @@ import ( ...@@ -29,10 +29,10 @@ import (
// is described in the following man page. // is described in the following man page.
// http://man7.org/linux/man-pages/man5/proc.5.html // http://man7.org/linux/man-pages/man5/proc.5.html
type MountInfo struct { type MountInfo struct {
// Unique ID for the mount // Unique Id for the mount
MountID int MountId int
// The ID of the parent mount // The Id of the parent mount
ParentID int ParentId int
// The value of `st_dev` for the files on this FS // The value of `st_dev` for the files on this FS
MajorMinorVer string MajorMinorVer string
// The pathname of the directory in the FS that forms // The pathname of the directory in the FS that forms
...@@ -77,7 +77,7 @@ func parseMountInfoString(mountString string) (*MountInfo, error) { ...@@ -77,7 +77,7 @@ func parseMountInfoString(mountString string) (*MountInfo, error) {
mountInfo := strings.Split(mountString, " ") mountInfo := strings.Split(mountString, " ")
mountInfoLength := len(mountInfo) mountInfoLength := len(mountInfo)
if mountInfoLength < 10 { if mountInfoLength < 11 {
return nil, fmt.Errorf("couldn't find enough fields in mount string: %s", mountString) return nil, fmt.Errorf("couldn't find enough fields in mount string: %s", mountString)
} }
...@@ -96,11 +96,11 @@ func parseMountInfoString(mountString string) (*MountInfo, error) { ...@@ -96,11 +96,11 @@ func parseMountInfoString(mountString string) (*MountInfo, error) {
SuperOptions: mountOptionsParser(mountInfo[mountInfoLength-1]), SuperOptions: mountOptionsParser(mountInfo[mountInfoLength-1]),
} }
mount.MountID, err = strconv.Atoi(mountInfo[0]) mount.MountId, err = strconv.Atoi(mountInfo[0])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse mount ID") return nil, fmt.Errorf("failed to parse mount ID")
} }
mount.ParentID, err = strconv.Atoi(mountInfo[1]) mount.ParentId, err = strconv.Atoi(mountInfo[1])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse parent ID") return nil, fmt.Errorf("failed to parse parent ID")
} }
...@@ -144,7 +144,7 @@ func mountOptionsParseOptionalFields(o []string) (map[string]string, error) { ...@@ -144,7 +144,7 @@ func mountOptionsParseOptionalFields(o []string) (map[string]string, error) {
return optionalFields, nil return optionalFields, nil
} }
// mountOptionsParser parses the mount options, superblock options. // Parses the mount options, superblock options.
func mountOptionsParser(mountOptions string) map[string]string { func mountOptionsParser(mountOptions string) map[string]string {
opts := make(map[string]string) opts := make(map[string]string)
options := strings.Split(mountOptions, ",") options := strings.Split(mountOptions, ",")
...@@ -161,7 +161,7 @@ func mountOptionsParser(mountOptions string) map[string]string { ...@@ -161,7 +161,7 @@ func mountOptionsParser(mountOptions string) map[string]string {
return opts return opts
} }
// GetMounts retrieves mountinfo information from `/proc/self/mountinfo`. // Retrieves mountinfo information from `/proc/self/mountinfo`.
func GetMounts() ([]*MountInfo, error) { func GetMounts() ([]*MountInfo, error) {
data, err := util.ReadFileNoStat("/proc/self/mountinfo") data, err := util.ReadFileNoStat("/proc/self/mountinfo")
if err != nil { if err != nil {
...@@ -170,7 +170,7 @@ func GetMounts() ([]*MountInfo, error) { ...@@ -170,7 +170,7 @@ func GetMounts() ([]*MountInfo, error) {
return parseMountInfo(data) return parseMountInfo(data)
} }
// GetProcMounts retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`. // Retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`.
func GetProcMounts(pid int) ([]*MountInfo, error) { func GetProcMounts(pid int) ([]*MountInfo, error) {
data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/mountinfo", pid)) data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil { if err != nil {
......
...@@ -186,8 +186,6 @@ type NFSOperationStats struct { ...@@ -186,8 +186,6 @@ type NFSOperationStats struct {
CumulativeTotalResponseMilliseconds uint64 CumulativeTotalResponseMilliseconds uint64
// Duration from when a request was enqueued to when it was completely handled. // Duration from when a request was enqueued to when it was completely handled.
CumulativeTotalRequestMilliseconds uint64 CumulativeTotalRequestMilliseconds uint64
// The count of operations that complete with tk_status < 0. These statuses usually indicate error conditions.
Errors uint64
} }
// A NFSTransportStats contains statistics for the NFS mount RPC requests and // A NFSTransportStats contains statistics for the NFS mount RPC requests and
...@@ -338,12 +336,12 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e ...@@ -338,12 +336,12 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
if len(ss) == 0 { if len(ss) == 0 {
break break
} }
switch ss[0] {
case fieldOpts:
if len(ss) < 2 { if len(ss) < 2 {
return nil, fmt.Errorf("not enough information for NFS stats: %v", ss) return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
} }
switch ss[0] {
case fieldOpts:
if stats.Opts == nil { if stats.Opts == nil {
stats.Opts = map[string]string{} stats.Opts = map[string]string{}
} }
...@@ -356,9 +354,6 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e ...@@ -356,9 +354,6 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
} }
} }
case fieldAge: case fieldAge:
if len(ss) < 2 {
return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
}
// Age integer is in seconds // Age integer is in seconds
d, err := time.ParseDuration(ss[1] + "s") d, err := time.ParseDuration(ss[1] + "s")
if err != nil { if err != nil {
...@@ -367,9 +362,6 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e ...@@ -367,9 +362,6 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
stats.Age = d stats.Age = d
case fieldBytes: case fieldBytes:
if len(ss) < 2 {
return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
}
bstats, err := parseNFSBytesStats(ss[1:]) bstats, err := parseNFSBytesStats(ss[1:])
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -377,9 +369,6 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e ...@@ -377,9 +369,6 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
stats.Bytes = *bstats stats.Bytes = *bstats
case fieldEvents: case fieldEvents:
if len(ss) < 2 {
return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
}
estats, err := parseNFSEventsStats(ss[1:]) estats, err := parseNFSEventsStats(ss[1:])
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -505,8 +494,8 @@ func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) { ...@@ -505,8 +494,8 @@ func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
// line is reached. // line is reached.
func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
const ( const (
// Minimum number of expected fields in each per-operation statistics set // Number of expected fields in each per-operation statistics set
minFields = 9 numFields = 9
) )
var ops []NFSOperationStats var ops []NFSOperationStats
...@@ -519,12 +508,12 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { ...@@ -519,12 +508,12 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
break break
} }
if len(ss) < minFields { if len(ss) != numFields {
return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss) return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
} }
// Skip string operation name for integers // Skip string operation name for integers
ns := make([]uint64, 0, minFields-1) ns := make([]uint64, 0, numFields-1)
for _, st := range ss[1:] { for _, st := range ss[1:] {
n, err := strconv.ParseUint(st, 10, 64) n, err := strconv.ParseUint(st, 10, 64)
if err != nil { if err != nil {
...@@ -534,7 +523,7 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { ...@@ -534,7 +523,7 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
ns = append(ns, n) ns = append(ns, n)
} }
opStats := NFSOperationStats{ ops = append(ops, NFSOperationStats{
Operation: strings.TrimSuffix(ss[0], ":"), Operation: strings.TrimSuffix(ss[0], ":"),
Requests: ns[0], Requests: ns[0],
Transmissions: ns[1], Transmissions: ns[1],
...@@ -544,13 +533,7 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) { ...@@ -544,13 +533,7 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
CumulativeQueueMilliseconds: ns[5], CumulativeQueueMilliseconds: ns[5],
CumulativeTotalResponseMilliseconds: ns[6], CumulativeTotalResponseMilliseconds: ns[6],
CumulativeTotalRequestMilliseconds: ns[7], CumulativeTotalRequestMilliseconds: ns[7],
} })
if len(ns) > 8 {
opStats.Errors = ns[8]
}
ops = append(ops, opStats)
} }
return ops, s.Err() return ops, s.Err()
......
// Copyright 2020 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.
package procfs
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// A ConntrackStatEntry represents one line from net/stat/nf_conntrack
// and contains netfilter conntrack statistics at one CPU core
type ConntrackStatEntry struct {
Entries uint64
Found uint64
Invalid uint64
Ignore uint64
Insert uint64
InsertFailed uint64
Drop uint64
EarlyDrop uint64
SearchRestart uint64
}
// ConntrackStat retrieves netfilter's conntrack statistics, split by CPU cores
func (fs FS) ConntrackStat() ([]ConntrackStatEntry, error) {
return readConntrackStat(fs.proc.Path("net", "stat", "nf_conntrack"))
}
// Parses a slice of ConntrackStatEntries from the given filepath
func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
// This file is small and can be read with one syscall.
b, err := util.ReadFileNoStat(path)
if err != nil {
// Do not wrap this error so the caller can detect os.IsNotExist and
// similar conditions.
return nil, err
}
stat, err := parseConntrackStat(bytes.NewReader(b))
if err != nil {
return nil, fmt.Errorf("failed to read conntrack stats from %q: %w", path, err)
}
return stat, nil
}
// Reads the contents of a conntrack statistics file and parses a slice of ConntrackStatEntries
func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
var entries []ConntrackStatEntry
scanner := bufio.NewScanner(r)
scanner.Scan()
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
conntrackEntry, err := parseConntrackStatEntry(fields)
if err != nil {
return nil, err
}
entries = append(entries, *conntrackEntry)
}
return entries, nil
}
// Parses a ConntrackStatEntry from given array of fields
func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
if len(fields) != 17 {
return nil, fmt.Errorf("invalid conntrackstat entry, missing fields")
}
entry := &ConntrackStatEntry{}
entries, err := parseConntrackStatField(fields[0])
if err != nil {
return nil, err
}
entry.Entries = entries
found, err := parseConntrackStatField(fields[2])
if err != nil {
return nil, err
}
entry.Found = found
invalid, err := parseConntrackStatField(fields[4])
if err != nil {
return nil, err
}
entry.Invalid = invalid
ignore, err := parseConntrackStatField(fields[5])
if err != nil {
return nil, err
}
entry.Ignore = ignore
insert, err := parseConntrackStatField(fields[8])
if err != nil {
return nil, err
}
entry.Insert = insert
insertFailed, err := parseConntrackStatField(fields[9])
if err != nil {
return nil, err
}
entry.InsertFailed = insertFailed
drop, err := parseConntrackStatField(fields[10])
if err != nil {
return nil, err
}
entry.Drop = drop
earlyDrop, err := parseConntrackStatField(fields[11])
if err != nil {
return nil, err
}
entry.EarlyDrop = earlyDrop
searchRestart, err := parseConntrackStatField(fields[16])
if err != nil {
return nil, err
}
entry.SearchRestart = searchRestart
return entry, nil
}
// Parses a uint64 from given hex in string
func parseConntrackStatField(field string) (uint64, error) {
val, err := strconv.ParseUint(field, 16, 64)
if err != nil {
return 0, fmt.Errorf("couldn't parse %q field: %w", field, err)
}
return val, err
}
// Copyright 2020 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.
package procfs
import (
"bufio"
"encoding/hex"
"fmt"
"io"
"net"
"os"
"strconv"
"strings"
)
const (
// readLimit is used by io.LimitReader while reading the content of the
// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
// as each line represents a single used socket.
// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
// With e.g. 150 Byte per line and the maximum number of 65535,
// the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP.
readLimit = 4294967296 // Byte -> 4 GiB
)
// this contains generic data structures for both udp and tcp sockets
type (
// NetIPSocket represents the contents of /proc/net/{t,u}dp{,6} file without the header.
NetIPSocket []*netIPSocketLine
// NetIPSocketSummary provides already computed values like the total queue lengths or
// the total number of used sockets. In contrast to NetIPSocket it does not collect
// the parsed lines into a slice.
NetIPSocketSummary struct {
// TxQueueLength shows the total queue length of all parsed tx_queue lengths.
TxQueueLength uint64
// RxQueueLength shows the total queue length of all parsed rx_queue lengths.
RxQueueLength uint64
// UsedSockets shows the total number of parsed lines representing the
// number of used sockets.
UsedSockets uint64
}
// netIPSocketLine represents the fields parsed from a single line
// in /proc/net/{t,u}dp{,6}. Fields which are not used by IPSocket are skipped.
// For the proc file format details, see https://linux.die.net/man/5/proc.
netIPSocketLine struct {
Sl uint64
LocalAddr net.IP
LocalPort uint64
RemAddr net.IP
RemPort uint64
St uint64
TxQueue uint64
RxQueue uint64
UID uint64
}
)
func newNetIPSocket(file string) (NetIPSocket, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
var netIPSocket NetIPSocket
lr := io.LimitReader(f, readLimit)
s := bufio.NewScanner(lr)
s.Scan() // skip first line with headers
for s.Scan() {
fields := strings.Fields(s.Text())
line, err := parseNetIPSocketLine(fields)
if err != nil {
return nil, err
}
netIPSocket = append(netIPSocket, line)
}
if err := s.Err(); err != nil {
return nil, err
}
return netIPSocket, nil
}
// newNetIPSocketSummary creates a new NetIPSocket{,6} from the contents of the given file.
func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
var netIPSocketSummary NetIPSocketSummary
lr := io.LimitReader(f, readLimit)
s := bufio.NewScanner(lr)
s.Scan() // skip first line with headers
for s.Scan() {
fields := strings.Fields(s.Text())
line, err := parseNetIPSocketLine(fields)
if err != nil {
return nil, err
}
netIPSocketSummary.TxQueueLength += line.TxQueue
netIPSocketSummary.RxQueueLength += line.RxQueue
netIPSocketSummary.UsedSockets++
}
if err := s.Err(); err != nil {
return nil, err
}
return &netIPSocketSummary, nil
}
// the /proc/net/{t,u}dp{,6} files are network byte order for ipv4 and for ipv6 the address is four words consisting of four bytes each. In each of those four words the four bytes are written in reverse order.
func parseIP(hexIP string) (net.IP, error) {
var byteIP []byte
byteIP, err := hex.DecodeString(hexIP)
if err != nil {
return nil, fmt.Errorf("cannot parse address field in socket line %q", hexIP)
}
switch len(byteIP) {
case 4:
return net.IP{byteIP[3], byteIP[2], byteIP[1], byteIP[0]}, nil
case 16:
i := net.IP{
byteIP[3], byteIP[2], byteIP[1], byteIP[0],
byteIP[7], byteIP[6], byteIP[5], byteIP[4],
byteIP[11], byteIP[10], byteIP[9], byteIP[8],
byteIP[15], byteIP[14], byteIP[13], byteIP[12],
}
return i, nil
default:
return nil, fmt.Errorf("Unable to parse IP %s", hexIP)
}
}
// parseNetIPSocketLine parses a single line, represented by a list of fields.
func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) {
line := &netIPSocketLine{}
if len(fields) < 8 {
return nil, fmt.Errorf(
"cannot parse net socket line as it has less then 8 columns %q",
strings.Join(fields, " "),
)
}
var err error // parse error
// sl
s := strings.Split(fields[0], ":")
if len(s) != 2 {
return nil, fmt.Errorf("cannot parse sl field in socket line %q", fields[0])
}
if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
return nil, fmt.Errorf("cannot parse sl value in socket line: %w", err)
}
// local_address
l := strings.Split(fields[1], ":")
if len(l) != 2 {
return nil, fmt.Errorf("cannot parse local_address field in socket line %q", fields[1])
}
if line.LocalAddr, err = parseIP(l[0]); err != nil {
return nil, err
}
if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse local_address port value in socket line: %w", err)
}
// remote_address
r := strings.Split(fields[2], ":")
if len(r) != 2 {
return nil, fmt.Errorf("cannot parse rem_address field in socket line %q", fields[1])
}
if line.RemAddr, err = parseIP(r[0]); err != nil {
return nil, err
}
if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse rem_address port value in socket line: %w", err)
}
// st
if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse st value in socket line: %w", err)
}
// tx_queue and rx_queue
q := strings.Split(fields[4], ":")
if len(q) != 2 {
return nil, fmt.Errorf(
"cannot parse tx/rx queues in socket line as it has a missing colon %q",
fields[4],
)
}
if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse tx_queue value in socket line: %w", err)
}
if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse rx_queue value in socket line: %w", err)
}
// uid
if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
return nil, fmt.Errorf("cannot parse uid value in socket line: %w", err)
}
return line, nil
}
// Copyright 2020 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.
package procfs
import (
"bufio"
"bytes"
"fmt"
"strconv"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// NetProtocolStats stores the contents from /proc/net/protocols
type NetProtocolStats map[string]NetProtocolStatLine
// NetProtocolStatLine contains a single line parsed from /proc/net/protocols. We
// only care about the first six columns as the rest are not likely to change
// and only serve to provide a set of capabilities for each protocol.
type NetProtocolStatLine struct {
Name string // 0 The name of the protocol
Size uint64 // 1 The size, in bytes, of a given protocol structure. e.g. sizeof(struct tcp_sock) or sizeof(struct unix_sock)
Sockets int64 // 2 Number of sockets in use by this protocol
Memory int64 // 3 Number of 4KB pages allocated by all sockets of this protocol
Pressure int // 4 This is either yes, no, or NI (not implemented). For the sake of simplicity we treat NI as not experiencing memory pressure.
MaxHeader uint64 // 5 Protocol specific max header size
Slab bool // 6 Indicates whether or not memory is allocated from the SLAB
ModuleName string // 7 The name of the module that implemented this protocol or "kernel" if not from a module
Capabilities NetProtocolCapabilities
}
// NetProtocolCapabilities contains a list of capabilities for each protocol
type NetProtocolCapabilities struct {
Close bool // 8
Connect bool // 9
Disconnect bool // 10
Accept bool // 11
IoCtl bool // 12
Init bool // 13
Destroy bool // 14
Shutdown bool // 15
SetSockOpt bool // 16
GetSockOpt bool // 17
SendMsg bool // 18
RecvMsg bool // 19
SendPage bool // 20
Bind bool // 21
BacklogRcv bool // 22
Hash bool // 23
UnHash bool // 24
GetPort bool // 25
EnterMemoryPressure bool // 26
}
// NetProtocols reads stats from /proc/net/protocols and returns a map of
// PortocolStatLine entries. As of this writing no official Linux Documentation
// exists, however the source is fairly self-explanatory and the format seems
// stable since its introduction in 2.6.12-rc2
// Linux 2.6.12-rc2 - https://elixir.bootlin.com/linux/v2.6.12-rc2/source/net/core/sock.c#L1452
// Linux 5.10 - https://elixir.bootlin.com/linux/v5.10.4/source/net/core/sock.c#L3586
func (fs FS) NetProtocols() (NetProtocolStats, error) {
data, err := util.ReadFileNoStat(fs.proc.Path("net/protocols"))
if err != nil {
return NetProtocolStats{}, err
}
return parseNetProtocols(bufio.NewScanner(bytes.NewReader(data)))
}
func parseNetProtocols(s *bufio.Scanner) (NetProtocolStats, error) {
nps := NetProtocolStats{}
// Skip the header line
s.Scan()
for s.Scan() {
line, err := nps.parseLine(s.Text())
if err != nil {
return NetProtocolStats{}, err
}
nps[line.Name] = *line
}
return nps, nil
}
func (ps NetProtocolStats) parseLine(rawLine string) (*NetProtocolStatLine, error) {
line := &NetProtocolStatLine{Capabilities: NetProtocolCapabilities{}}
var err error
const enabled = "yes"
const disabled = "no"
fields := strings.Fields(rawLine)
line.Name = fields[0]
line.Size, err = strconv.ParseUint(fields[1], 10, 64)
if err != nil {
return nil, err
}
line.Sockets, err = strconv.ParseInt(fields[2], 10, 64)
if err != nil {
return nil, err
}
line.Memory, err = strconv.ParseInt(fields[3], 10, 64)
if err != nil {
return nil, err
}
if fields[4] == enabled {
line.Pressure = 1
} else if fields[4] == disabled {
line.Pressure = 0
} else {
line.Pressure = -1
}
line.MaxHeader, err = strconv.ParseUint(fields[5], 10, 64)
if err != nil {
return nil, err
}
if fields[6] == enabled {
line.Slab = true
} else if fields[6] == disabled {
line.Slab = false
} else {
return nil, fmt.Errorf("unable to parse capability for protocol: %s", line.Name)
}
line.ModuleName = fields[7]
err = line.Capabilities.parseCapabilities(fields[8:])
if err != nil {
return nil, err
}
return line, nil
}
func (pc *NetProtocolCapabilities) parseCapabilities(capabilities []string) error {
// The capabilities are all bools so we can loop over to map them
capabilityFields := [...]*bool{
&pc.Close,
&pc.Connect,
&pc.Disconnect,
&pc.Accept,
&pc.IoCtl,
&pc.Init,
&pc.Destroy,
&pc.Shutdown,
&pc.SetSockOpt,
&pc.GetSockOpt,
&pc.SendMsg,
&pc.RecvMsg,
&pc.SendPage,
&pc.Bind,
&pc.BacklogRcv,
&pc.Hash,
&pc.UnHash,
&pc.GetPort,
&pc.EnterMemoryPressure,
}
for i := 0; i < len(capabilities); i++ {
if capabilities[i] == "y" {
*capabilityFields[i] = true
} else if capabilities[i] == "n" {
*capabilityFields[i] = false
} else {
return fmt.Errorf("unable to parse capability block for protocol: position %d", i)
}
}
return nil
}
...@@ -70,7 +70,7 @@ func readSockstat(name string) (*NetSockstat, error) { ...@@ -70,7 +70,7 @@ func readSockstat(name string) (*NetSockstat, error) {
stat, err := parseSockstat(bytes.NewReader(b)) stat, err := parseSockstat(bytes.NewReader(b))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read sockstats from %q: %w", name, err) return nil, fmt.Errorf("failed to read sockstats from %q: %v", name, err)
} }
return stat, nil return stat, nil
...@@ -90,7 +90,7 @@ func parseSockstat(r io.Reader) (*NetSockstat, error) { ...@@ -90,7 +90,7 @@ func parseSockstat(r io.Reader) (*NetSockstat, error) {
// The remaining fields are key/value pairs. // The remaining fields are key/value pairs.
kvs, err := parseSockstatKVs(fields[1:]) kvs, err := parseSockstatKVs(fields[1:])
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing sockstat key/value pairs from %q: %w", s.Text(), err) return nil, fmt.Errorf("error parsing sockstat key/value pairs from %q: %v", s.Text(), err)
} }
// The first field is the protocol. We must trim its colon suffix. // The first field is the protocol. We must trim its colon suffix.
......
...@@ -14,89 +14,78 @@ ...@@ -14,89 +14,78 @@
package procfs package procfs
import ( import (
"bufio"
"bytes"
"fmt" "fmt"
"io" "io/ioutil"
"strconv" "strconv"
"strings" "strings"
"github.com/prometheus/procfs/internal/util"
) )
// For the proc file format details, // For the proc file format details,
// See: // see https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
// * Linux 2.6.23 https://elixir.bootlin.com/linux/v2.6.23/source/net/core/dev.c#L2343
// * Linux 4.17 https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
// and https://elixir.bootlin.com/linux/v4.17/source/include/linux/netdevice.h#L2810. // and https://elixir.bootlin.com/linux/v4.17/source/include/linux/netdevice.h#L2810.
// SoftnetStat contains a single row of data from /proc/net/softnet_stat // SoftnetEntry contains a single row of data from /proc/net/softnet_stat
type SoftnetStat struct { type SoftnetEntry struct {
// Number of processed packets // Number of processed packets
Processed uint32 Processed uint
// Number of dropped packets // Number of dropped packets
Dropped uint32 Dropped uint
// Number of times processing packets ran out of quota // Number of times processing packets ran out of quota
TimeSqueezed uint32 TimeSqueezed uint
} }
var softNetProcFile = "net/softnet_stat" // GatherSoftnetStats reads /proc/net/softnet_stat, parse the relevant columns,
// and then return a slice of SoftnetEntry's.
// NetSoftnetStat reads data from /proc/net/softnet_stat. func (fs FS) GatherSoftnetStats() ([]SoftnetEntry, error) {
func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) { data, err := ioutil.ReadFile(fs.proc.Path("net/softnet_stat"))
b, err := util.ReadFileNoStat(fs.proc.Path(softNetProcFile))
if err != nil {
return nil, err
}
entries, err := parseSoftnet(bytes.NewReader(b))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse /proc/net/softnet_stat: %w", err) return nil, fmt.Errorf("error reading softnet %s: %s", fs.proc.Path("net/softnet_stat"), err)
} }
return entries, nil return parseSoftnetEntries(data)
} }
func parseSoftnet(r io.Reader) ([]SoftnetStat, error) { func parseSoftnetEntries(data []byte) ([]SoftnetEntry, error) {
const minColumns = 9 lines := strings.Split(string(data), "\n")
entries := make([]SoftnetEntry, 0)
s := bufio.NewScanner(r) var err error
const (
var stats []SoftnetStat expectedColumns = 11
for s.Scan() { )
columns := strings.Fields(s.Text()) for _, line := range lines {
columns := strings.Fields(line)
width := len(columns) width := len(columns)
if width == 0 {
if width < minColumns { continue
return nil, fmt.Errorf("%d columns were detected, but at least %d were expected", width, minColumns)
} }
if width != expectedColumns {
// We only parse the first three columns at the moment. return []SoftnetEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedColumns)
us, err := parseHexUint32s(columns[0:3])
if err != nil {
return nil, err
} }
var entry SoftnetEntry
stats = append(stats, SoftnetStat{ if entry, err = parseSoftnetEntry(columns); err != nil {
Processed: us[0], return []SoftnetEntry{}, err
Dropped: us[1], }
TimeSqueezed: us[2], entries = append(entries, entry)
})
} }
return stats, nil return entries, nil
} }
func parseHexUint32s(ss []string) ([]uint32, error) { func parseSoftnetEntry(columns []string) (SoftnetEntry, error) {
us := make([]uint32, 0, len(ss)) var err error
for _, s := range ss { var processed, dropped, timeSqueezed uint64
u, err := strconv.ParseUint(s, 16, 32) if processed, err = strconv.ParseUint(columns[0], 16, 32); err != nil {
if err != nil { return SoftnetEntry{}, fmt.Errorf("Unable to parse column 0: %s", err)
return nil, err
} }
if dropped, err = strconv.ParseUint(columns[1], 16, 32); err != nil {
us = append(us, uint32(u)) return SoftnetEntry{}, fmt.Errorf("Unable to parse column 1: %s", err)
} }
if timeSqueezed, err = strconv.ParseUint(columns[2], 16, 32); err != nil {
return us, nil return SoftnetEntry{}, fmt.Errorf("Unable to parse column 2: %s", err)
}
return SoftnetEntry{
Processed: uint(processed),
Dropped: uint(dropped),
TimeSqueezed: uint(timeSqueezed),
}, nil
} }
// Copyright 2020 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.
package procfs
type (
// NetTCP represents the contents of /proc/net/tcp{,6} file without the header.
NetTCP []*netIPSocketLine
// NetTCPSummary provides already computed values like the total queue lengths or
// the total number of used sockets. In contrast to NetTCP it does not collect
// the parsed lines into a slice.
NetTCPSummary NetIPSocketSummary
)
// NetTCP returns the IPv4 kernel/networking statistics for TCP datagrams
// read from /proc/net/tcp.
func (fs FS) NetTCP() (NetTCP, error) {
return newNetTCP(fs.proc.Path("net/tcp"))
}
// NetTCP6 returns the IPv6 kernel/networking statistics for TCP datagrams
// read from /proc/net/tcp6.
func (fs FS) NetTCP6() (NetTCP, error) {
return newNetTCP(fs.proc.Path("net/tcp6"))
}
// NetTCPSummary returns already computed statistics like the total queue lengths
// for TCP datagrams read from /proc/net/tcp.
func (fs FS) NetTCPSummary() (*NetTCPSummary, error) {
return newNetTCPSummary(fs.proc.Path("net/tcp"))
}
// NetTCP6Summary returns already computed statistics like the total queue lengths
// for TCP datagrams read from /proc/net/tcp6.
func (fs FS) NetTCP6Summary() (*NetTCPSummary, error) {
return newNetTCPSummary(fs.proc.Path("net/tcp6"))
}
// newNetTCP creates a new NetTCP{,6} from the contents of the given file.
func newNetTCP(file string) (NetTCP, error) {
n, err := newNetIPSocket(file)
n1 := NetTCP(n)
return n1, err
}
func newNetTCPSummary(file string) (*NetTCPSummary, error) {
n, err := newNetIPSocketSummary(file)
if n == nil {
return nil, err
}
n1 := NetTCPSummary(*n)
return &n1, err
}
// Copyright 2020 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.
package procfs
type (
// NetUDP represents the contents of /proc/net/udp{,6} file without the header.
NetUDP []*netIPSocketLine
// NetUDPSummary provides already computed values like the total queue lengths or
// the total number of used sockets. In contrast to NetUDP it does not collect
// the parsed lines into a slice.
NetUDPSummary NetIPSocketSummary
)
// NetUDP returns the IPv4 kernel/networking statistics for UDP datagrams
// read from /proc/net/udp.
func (fs FS) NetUDP() (NetUDP, error) {
return newNetUDP(fs.proc.Path("net/udp"))
}
// NetUDP6 returns the IPv6 kernel/networking statistics for UDP datagrams
// read from /proc/net/udp6.
func (fs FS) NetUDP6() (NetUDP, error) {
return newNetUDP(fs.proc.Path("net/udp6"))
}
// NetUDPSummary returns already computed statistics like the total queue lengths
// for UDP datagrams read from /proc/net/udp.
func (fs FS) NetUDPSummary() (*NetUDPSummary, error) {
return newNetUDPSummary(fs.proc.Path("net/udp"))
}
// NetUDP6Summary returns already computed statistics like the total queue lengths
// for UDP datagrams read from /proc/net/udp6.
func (fs FS) NetUDP6Summary() (*NetUDPSummary, error) {
return newNetUDPSummary(fs.proc.Path("net/udp6"))
}
// newNetUDP creates a new NetUDP{,6} from the contents of the given file.
func newNetUDP(file string) (NetUDP, error) {
n, err := newNetIPSocket(file)
n1 := NetUDP(n)
return n1, err
}
func newNetUDPSummary(file string) (*NetUDPSummary, error) {
n, err := newNetIPSocketSummary(file)
if n == nil {
return nil, err
}
n1 := NetUDPSummary(*n)
return &n1, err
}
...@@ -15,6 +15,7 @@ package procfs ...@@ -15,6 +15,7 @@ package procfs
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
...@@ -26,14 +27,24 @@ import ( ...@@ -26,14 +27,24 @@ import (
// see https://elixir.bootlin.com/linux/v4.17/source/net/unix/af_unix.c#L2815 // see https://elixir.bootlin.com/linux/v4.17/source/net/unix/af_unix.c#L2815
// and https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/net.h#L48. // and https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/net.h#L48.
// Constants for the various /proc/net/unix enumerations. const (
// TODO: match against x/sys/unix or similar? netUnixKernelPtrIdx = iota
netUnixRefCountIdx
_
netUnixFlagsIdx
netUnixTypeIdx
netUnixStateIdx
netUnixInodeIdx
// Inode and Path are optional.
netUnixStaticFieldsCnt = 6
)
const ( const (
netUnixTypeStream = 1 netUnixTypeStream = 1
netUnixTypeDgram = 2 netUnixTypeDgram = 2
netUnixTypeSeqpacket = 5 netUnixTypeSeqpacket = 5
netUnixFlagDefault = 0
netUnixFlagListen = 1 << 16 netUnixFlagListen = 1 << 16
netUnixStateUnconnected = 1 netUnixStateUnconnected = 1
...@@ -42,127 +53,129 @@ const ( ...@@ -42,127 +53,129 @@ const (
netUnixStateDisconnected = 4 netUnixStateDisconnected = 4
) )
// NetUNIXType is the type of the type field. var errInvalidKernelPtrFmt = errors.New("Invalid Num(the kernel table slot number) format")
type NetUNIXType uint64
// NetUNIXFlags is the type of the flags field. // NetUnixType is the type of the type field.
type NetUNIXFlags uint64 type NetUnixType uint64
// NetUNIXState is the type of the state field. // NetUnixFlags is the type of the flags field.
type NetUNIXState uint64 type NetUnixFlags uint64
// NetUNIXLine represents a line of /proc/net/unix. // NetUnixState is the type of the state field.
type NetUNIXLine struct { type NetUnixState uint64
// NetUnixLine represents a line of /proc/net/unix.
type NetUnixLine struct {
KernelPtr string KernelPtr string
RefCount uint64 RefCount uint64
Protocol uint64 Protocol uint64
Flags NetUNIXFlags Flags NetUnixFlags
Type NetUNIXType Type NetUnixType
State NetUNIXState State NetUnixState
Inode uint64 Inode uint64
Path string Path string
} }
// NetUNIX holds the data read from /proc/net/unix. // NetUnix holds the data read from /proc/net/unix.
type NetUNIX struct { type NetUnix struct {
Rows []*NetUNIXLine Rows []*NetUnixLine
} }
// NetUNIX returns data read from /proc/net/unix. // NewNetUnix returns data read from /proc/net/unix.
func (fs FS) NetUNIX() (*NetUNIX, error) { func NewNetUnix() (*NetUnix, error) {
return readNetUNIX(fs.proc.Path("net/unix")) fs, err := NewFS(DefaultMountPoint)
if err != nil {
return nil, err
}
return fs.NewNetUnix()
} }
// readNetUNIX reads data in /proc/net/unix format from the specified file. // NewNetUnix returns data read from /proc/net/unix.
func readNetUNIX(file string) (*NetUNIX, error) { func (fs FS) NewNetUnix() (*NetUnix, error) {
// This file could be quite large and a streaming read is desirable versus return NewNetUnixByPath(fs.proc.Path("net/unix"))
// reading the entire contents at once. }
f, err := os.Open(file)
// NewNetUnixByPath returns data read from /proc/net/unix by file path.
// It might returns an error with partial parsed data, if an error occur after some data parsed.
func NewNetUnixByPath(path string) (*NetUnix, error) {
f, err := os.Open(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer f.Close() defer f.Close()
return NewNetUnixByReader(f)
return parseNetUNIX(f)
} }
// parseNetUNIX creates a NetUnix structure from the incoming stream. // NewNetUnixByReader returns data read from /proc/net/unix by a reader.
func parseNetUNIX(r io.Reader) (*NetUNIX, error) { // It might returns an error with partial parsed data, if an error occur after some data parsed.
// Begin scanning by checking for the existence of Inode. func NewNetUnixByReader(reader io.Reader) (*NetUnix, error) {
s := bufio.NewScanner(r) nu := &NetUnix{
s.Scan() Rows: make([]*NetUnixLine, 0, 32),
}
scanner := bufio.NewScanner(reader)
// Omit the header line.
scanner.Scan()
header := scanner.Text()
// From the man page of proc(5), it does not contain an Inode field, // From the man page of proc(5), it does not contain an Inode field,
// but in actually it exists. This code works for both cases. // but in actually it exists.
hasInode := strings.Contains(s.Text(), "Inode") // This code works for both cases.
hasInode := strings.Contains(header, "Inode")
// Expect a minimum number of fields, but Inode and Path are optional: minFieldsCnt := netUnixStaticFieldsCnt
// Num RefCount Protocol Flags Type St Inode Path
minFields := 6
if hasInode { if hasInode {
minFields++ minFieldsCnt++
} }
for scanner.Scan() {
var nu NetUNIX line := scanner.Text()
for s.Scan() { item, err := nu.parseLine(line, hasInode, minFieldsCnt)
line := s.Text()
item, err := nu.parseLine(line, hasInode, minFields)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse /proc/net/unix data %q: %w", line, err) return nu, err
} }
nu.Rows = append(nu.Rows, item) nu.Rows = append(nu.Rows, item)
} }
if err := s.Err(); err != nil { return nu, scanner.Err()
return nil, fmt.Errorf("failed to scan /proc/net/unix data: %w", err)
}
return &nu, nil
} }
func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine, error) { func (u *NetUnix) parseLine(line string, hasInode bool, minFieldsCnt int) (*NetUnixLine, error) {
fields := strings.Fields(line) fields := strings.Fields(line)
fieldsLen := len(fields)
l := len(fields) if fieldsLen < minFieldsCnt {
if l < min { return nil, fmt.Errorf(
return nil, fmt.Errorf("expected at least %d fields but got %d", min, l) "Parse Unix domain failed: expect at least %d fields but got %d",
minFieldsCnt, fieldsLen)
} }
kernelPtr, err := u.parseKernelPtr(fields[netUnixKernelPtrIdx])
// Field offsets are as follows:
// Num RefCount Protocol Flags Type St Inode Path
kernelPtr := strings.TrimSuffix(fields[0], ":")
users, err := u.parseUsers(fields[1])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse ref count %q: %w", fields[1], err) return nil, fmt.Errorf("Parse Unix domain num(%s) failed: %s", fields[netUnixKernelPtrIdx], err)
} }
users, err := u.parseUsers(fields[netUnixRefCountIdx])
flags, err := u.parseFlags(fields[3])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse flags %q: %w", fields[3], err) return nil, fmt.Errorf("Parse Unix domain ref count(%s) failed: %s", fields[netUnixRefCountIdx], err)
} }
flags, err := u.parseFlags(fields[netUnixFlagsIdx])
typ, err := u.parseType(fields[4])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse type %q: %w", fields[4], err) return nil, fmt.Errorf("Parse Unix domain flags(%s) failed: %s", fields[netUnixFlagsIdx], err)
} }
typ, err := u.parseType(fields[netUnixTypeIdx])
state, err := u.parseState(fields[5])
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse state %q: %w", fields[5], err) return nil, fmt.Errorf("Parse Unix domain type(%s) failed: %s", fields[netUnixTypeIdx], err)
}
state, err := u.parseState(fields[netUnixStateIdx])
if err != nil {
return nil, fmt.Errorf("Parse Unix domain state(%s) failed: %s", fields[netUnixStateIdx], err)
} }
var inode uint64 var inode uint64
if hasInode { if hasInode {
inode, err = u.parseInode(fields[6]) inodeStr := fields[netUnixInodeIdx]
inode, err = u.parseInode(inodeStr)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse inode %q: %w", fields[6], err) return nil, fmt.Errorf("Parse Unix domain inode(%s) failed: %s", inodeStr, err)
} }
} }
n := &NetUNIXLine{ nuLine := &NetUnixLine{
KernelPtr: kernelPtr, KernelPtr: kernelPtr,
RefCount: users, RefCount: users,
Type: typ, Type: typ,
...@@ -172,56 +185,57 @@ func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine, ...@@ -172,56 +185,57 @@ func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine,
} }
// Path field is optional. // Path field is optional.
if l > min { if fieldsLen > minFieldsCnt {
// Path occurs at either index 6 or 7 depending on whether inode is pathIdx := netUnixInodeIdx + 1
// already present.
pathIdx := 7
if !hasInode { if !hasInode {
pathIdx-- pathIdx--
} }
nuLine.Path = fields[pathIdx]
n.Path = fields[pathIdx]
} }
return n, nil return nuLine, nil
}
func (u NetUnix) parseKernelPtr(str string) (string, error) {
if !strings.HasSuffix(str, ":") {
return "", errInvalidKernelPtrFmt
}
return str[:len(str)-1], nil
} }
func (u NetUNIX) parseUsers(s string) (uint64, error) { func (u NetUnix) parseUsers(hexStr string) (uint64, error) {
return strconv.ParseUint(s, 16, 32) return strconv.ParseUint(hexStr, 16, 32)
} }
func (u NetUNIX) parseType(s string) (NetUNIXType, error) { func (u NetUnix) parseType(hexStr string) (NetUnixType, error) {
typ, err := strconv.ParseUint(s, 16, 16) typ, err := strconv.ParseUint(hexStr, 16, 16)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return NetUnixType(typ), nil
return NetUNIXType(typ), nil
} }
func (u NetUNIX) parseFlags(s string) (NetUNIXFlags, error) { func (u NetUnix) parseFlags(hexStr string) (NetUnixFlags, error) {
flags, err := strconv.ParseUint(s, 16, 32) flags, err := strconv.ParseUint(hexStr, 16, 32)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return NetUnixFlags(flags), nil
return NetUNIXFlags(flags), nil
} }
func (u NetUNIX) parseState(s string) (NetUNIXState, error) { func (u NetUnix) parseState(hexStr string) (NetUnixState, error) {
st, err := strconv.ParseInt(s, 16, 8) st, err := strconv.ParseInt(hexStr, 16, 8)
if err != nil { if err != nil {
return 0, err return 0, err
} }
return NetUnixState(st), nil
return NetUNIXState(st), nil
} }
func (u NetUNIX) parseInode(s string) (uint64, error) { func (u NetUnix) parseInode(inodeStr string) (uint64, error) {
return strconv.ParseUint(s, 10, 64) return strconv.ParseUint(inodeStr, 10, 64)
} }
func (t NetUNIXType) String() string { func (t NetUnixType) String() string {
switch t { switch t {
case netUnixTypeStream: case netUnixTypeStream:
return "stream" return "stream"
...@@ -233,7 +247,7 @@ func (t NetUNIXType) String() string { ...@@ -233,7 +247,7 @@ func (t NetUNIXType) String() string {
return "unknown" return "unknown"
} }
func (f NetUNIXFlags) String() string { func (f NetUnixFlags) String() string {
switch f { switch f {
case netUnixFlagListen: case netUnixFlagListen:
return "listen" return "listen"
...@@ -242,7 +256,7 @@ func (f NetUNIXFlags) String() string { ...@@ -242,7 +256,7 @@ func (f NetUNIXFlags) String() string {
} }
} }
func (s NetUNIXState) String() string { func (s NetUnixState) String() string {
switch s { switch s {
case netUnixStateUnconnected: case netUnixStateUnconnected:
return "unconnected" return "unconnected"
......
...@@ -105,7 +105,7 @@ func (fs FS) AllProcs() (Procs, error) { ...@@ -105,7 +105,7 @@ func (fs FS) AllProcs() (Procs, error) {
names, err := d.Readdirnames(-1) names, err := d.Readdirnames(-1)
if err != nil { if err != nil {
return Procs{}, fmt.Errorf("could not read %q: %w", d.Name(), err) return Procs{}, fmt.Errorf("could not read %s: %s", d.Name(), err)
} }
p := Procs{} p := Procs{}
...@@ -134,27 +134,6 @@ func (p Proc) CmdLine() ([]string, error) { ...@@ -134,27 +134,6 @@ func (p Proc) CmdLine() ([]string, error) {
return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil
} }
// Wchan returns the wchan (wait channel) of a process.
func (p Proc) Wchan() (string, error) {
f, err := os.Open(p.path("wchan"))
if err != nil {
return "", err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return "", err
}
wchan := string(data)
if wchan == "" || wchan == "0" {
return "", nil
}
return wchan, nil
}
// Comm returns the command name of a process. // Comm returns the command name of a process.
func (p Proc) Comm() (string, error) { func (p Proc) Comm() (string, error) {
data, err := util.ReadFileNoStat(p.path("comm")) data, err := util.ReadFileNoStat(p.path("comm"))
...@@ -206,7 +185,7 @@ func (p Proc) FileDescriptors() ([]uintptr, error) { ...@@ -206,7 +185,7 @@ func (p Proc) FileDescriptors() ([]uintptr, error) {
for i, n := range names { for i, n := range names {
fd, err := strconv.ParseInt(n, 10, 32) fd, err := strconv.ParseInt(n, 10, 32)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse fd %q: %w", n, err) return nil, fmt.Errorf("could not parse fd %s: %s", n, err)
} }
fds[i] = uintptr(fd) fds[i] = uintptr(fd)
} }
...@@ -278,7 +257,7 @@ func (p Proc) fileDescriptors() ([]string, error) { ...@@ -278,7 +257,7 @@ func (p Proc) fileDescriptors() ([]string, error) {
names, err := d.Readdirnames(-1) names, err := d.Readdirnames(-1)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not read %q: %w", d.Name(), err) return nil, fmt.Errorf("could not read %s: %s", d.Name(), err)
} }
return names, nil return names, nil
......
// Copyright 2020 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.
package procfs
import (
"bufio"
"bytes"
"fmt"
"strconv"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// Cgroup models one line from /proc/[pid]/cgroup. Each Cgroup struct describes the the placement of a PID inside a
// specific control hierarchy. The kernel has two cgroup APIs, v1 and v2. v1 has one hierarchy per available resource
// controller, while v2 has one unified hierarchy shared by all controllers. Regardless of v1 or v2, all hierarchies
// contain all running processes, so the question answerable with a Cgroup struct is 'where is this process in
// this hierarchy' (where==what path on the specific cgroupfs). By prefixing this path with the mount point of
// *this specific* hierarchy, you can locate the relevant pseudo-files needed to read/set the data for this PID
// in this hierarchy
//
// Also see http://man7.org/linux/man-pages/man7/cgroups.7.html
type Cgroup struct {
// HierarchyID that can be matched to a named hierarchy using /proc/cgroups. Cgroups V2 only has one
// hierarchy, so HierarchyID is always 0. For cgroups v1 this is a unique ID number
HierarchyID int
// Controllers using this hierarchy of processes. Controllers are also known as subsystems. For
// Cgroups V2 this may be empty, as all active controllers use the same hierarchy
Controllers []string
// Path of this control group, relative to the mount point of the cgroupfs representing this specific
// hierarchy
Path string
}
// parseCgroupString parses each line of the /proc/[pid]/cgroup file
// Line format is hierarchyID:[controller1,controller2]:path
func parseCgroupString(cgroupStr string) (*Cgroup, error) {
var err error
fields := strings.SplitN(cgroupStr, ":", 3)
if len(fields) < 3 {
return nil, fmt.Errorf("at least 3 fields required, found %d fields in cgroup string: %s", len(fields), cgroupStr)
}
cgroup := &Cgroup{
Path: fields[2],
Controllers: nil,
}
cgroup.HierarchyID, err = strconv.Atoi(fields[0])
if err != nil {
return nil, fmt.Errorf("failed to parse hierarchy ID")
}
if fields[1] != "" {
ssNames := strings.Split(fields[1], ",")
cgroup.Controllers = append(cgroup.Controllers, ssNames...)
}
return cgroup, nil
}
// parseCgroups reads each line of the /proc/[pid]/cgroup file
func parseCgroups(data []byte) ([]Cgroup, error) {
var cgroups []Cgroup
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
mountString := scanner.Text()
parsedMounts, err := parseCgroupString(mountString)
if err != nil {
return nil, err
}
cgroups = append(cgroups, *parsedMounts)
}
err := scanner.Err()
return cgroups, err
}
// Cgroups reads from /proc/<pid>/cgroups and returns a []*Cgroup struct locating this PID in each process
// control hierarchy running on this system. On every system (v1 and v2), all hierarchies contain all processes,
// so the len of the returned struct is equal to the number of active hierarchies on this system
func (p Proc) Cgroups() ([]Cgroup, error) {
data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/cgroup", p.PID))
if err != nil {
return nil, err
}
return parseCgroups(data)
}
...@@ -16,7 +16,6 @@ package procfs ...@@ -16,7 +16,6 @@ package procfs
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"fmt"
"regexp" "regexp"
"github.com/prometheus/procfs/internal/util" "github.com/prometheus/procfs/internal/util"
...@@ -28,7 +27,6 @@ var ( ...@@ -28,7 +27,6 @@ var (
rFlags = regexp.MustCompile(`^flags:\s+(\d+)$`) rFlags = regexp.MustCompile(`^flags:\s+(\d+)$`)
rMntID = regexp.MustCompile(`^mnt_id:\s+(\d+)$`) rMntID = regexp.MustCompile(`^mnt_id:\s+(\d+)$`)
rInotify = regexp.MustCompile(`^inotify`) rInotify = regexp.MustCompile(`^inotify`)
rInotifyParts = regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)(?:\s+mask:([0-9a-f]+))?`)
) )
// ProcFDInfo contains represents file descriptor information. // ProcFDInfo contains represents file descriptor information.
...@@ -41,7 +39,7 @@ type ProcFDInfo struct { ...@@ -41,7 +39,7 @@ type ProcFDInfo struct {
Flags string Flags string
// Mount point ID // Mount point ID
MntID string MntID string
// List of inotify lines (structured) in the fdinfo file (kernel 3.8+ only) // List of inotify lines (structed) in the fdinfo file (kernel 3.8+ only)
InotifyInfos []InotifyInfo InotifyInfos []InotifyInfo
} }
...@@ -98,21 +96,15 @@ type InotifyInfo struct { ...@@ -98,21 +96,15 @@ type InotifyInfo struct {
// InotifyInfo constructor. Only available on kernel 3.8+. // InotifyInfo constructor. Only available on kernel 3.8+.
func parseInotifyInfo(line string) (*InotifyInfo, error) { func parseInotifyInfo(line string) (*InotifyInfo, error) {
m := rInotifyParts.FindStringSubmatch(line) r := regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)\s+mask:([0-9a-f]+)`)
if len(m) >= 4 { m := r.FindStringSubmatch(line)
var mask string
if len(m) == 5 {
mask = m[4]
}
i := &InotifyInfo{ i := &InotifyInfo{
WD: m[1], WD: m[1],
Ino: m[2], Ino: m[2],
Sdev: m[3], Sdev: m[3],
Mask: mask, Mask: m[4],
} }
return i, nil return i, nil
}
return nil, fmt.Errorf("invalid inode entry: %q", line)
} }
// ProcFDInfos represents a list of ProcFDInfo structs. // ProcFDInfos represents a list of ProcFDInfo structs.
......
...@@ -26,55 +26,55 @@ import ( ...@@ -26,55 +26,55 @@ import (
// http://man7.org/linux/man-pages/man2/getrlimit.2.html. // http://man7.org/linux/man-pages/man2/getrlimit.2.html.
type ProcLimits struct { type ProcLimits struct {
// CPU time limit in seconds. // CPU time limit in seconds.
CPUTime uint64 CPUTime int64
// Maximum size of files that the process may create. // Maximum size of files that the process may create.
FileSize uint64 FileSize int64
// Maximum size of the process's data segment (initialized data, // Maximum size of the process's data segment (initialized data,
// uninitialized data, and heap). // uninitialized data, and heap).
DataSize uint64 DataSize int64
// Maximum size of the process stack in bytes. // Maximum size of the process stack in bytes.
StackSize uint64 StackSize int64
// Maximum size of a core file. // Maximum size of a core file.
CoreFileSize uint64 CoreFileSize int64
// Limit of the process's resident set in pages. // Limit of the process's resident set in pages.
ResidentSet uint64 ResidentSet int64
// Maximum number of processes that can be created for the real user ID of // Maximum number of processes that can be created for the real user ID of
// the calling process. // the calling process.
Processes uint64 Processes int64
// Value one greater than the maximum file descriptor number that can be // Value one greater than the maximum file descriptor number that can be
// opened by this process. // opened by this process.
OpenFiles uint64 OpenFiles int64
// Maximum number of bytes of memory that may be locked into RAM. // Maximum number of bytes of memory that may be locked into RAM.
LockedMemory uint64 LockedMemory int64
// Maximum size of the process's virtual memory address space in bytes. // Maximum size of the process's virtual memory address space in bytes.
AddressSpace uint64 AddressSpace int64
// Limit on the combined number of flock(2) locks and fcntl(2) leases that // Limit on the combined number of flock(2) locks and fcntl(2) leases that
// this process may establish. // this process may establish.
FileLocks uint64 FileLocks int64
// Limit of signals that may be queued for the real user ID of the calling // Limit of signals that may be queued for the real user ID of the calling
// process. // process.
PendingSignals uint64 PendingSignals int64
// Limit on the number of bytes that can be allocated for POSIX message // Limit on the number of bytes that can be allocated for POSIX message
// queues for the real user ID of the calling process. // queues for the real user ID of the calling process.
MsqqueueSize uint64 MsqqueueSize int64
// Limit of the nice priority set using setpriority(2) or nice(2). // Limit of the nice priority set using setpriority(2) or nice(2).
NicePriority uint64 NicePriority int64
// Limit of the real-time priority set using sched_setscheduler(2) or // Limit of the real-time priority set using sched_setscheduler(2) or
// sched_setparam(2). // sched_setparam(2).
RealtimePriority uint64 RealtimePriority int64
// Limit (in microseconds) on the amount of CPU time that a process // Limit (in microseconds) on the amount of CPU time that a process
// scheduled under a real-time scheduling policy may consume without making // scheduled under a real-time scheduling policy may consume without making
// a blocking system call. // a blocking system call.
RealtimeTimeout uint64 RealtimeTimeout int64
} }
const ( const (
limitsFields = 4 limitsFields = 3
limitsUnlimited = "unlimited" limitsUnlimited = "unlimited"
) )
var ( var (
limitsMatch = regexp.MustCompile(`(Max \w+\s{0,1}?\w*\s{0,1}\w*)\s{2,}(\w+)\s+(\w+)`) limitsDelimiter = regexp.MustCompile(" +")
) )
// NewLimits returns the current soft limits of the process. // NewLimits returns the current soft limits of the process.
...@@ -96,49 +96,46 @@ func (p Proc) Limits() (ProcLimits, error) { ...@@ -96,49 +96,46 @@ func (p Proc) Limits() (ProcLimits, error) {
l = ProcLimits{} l = ProcLimits{}
s = bufio.NewScanner(f) s = bufio.NewScanner(f)
) )
s.Scan() // Skip limits header
for s.Scan() { for s.Scan() {
//fields := limitsMatch.Split(s.Text(), limitsFields) fields := limitsDelimiter.Split(s.Text(), limitsFields)
fields := limitsMatch.FindStringSubmatch(s.Text())
if len(fields) != limitsFields { if len(fields) != limitsFields {
return ProcLimits{}, fmt.Errorf("couldn't parse %q line %q", f.Name(), s.Text()) return ProcLimits{}, fmt.Errorf(
"couldn't parse %s line %s", f.Name(), s.Text())
} }
switch fields[1] { switch fields[0] {
case "Max cpu time": case "Max cpu time":
l.CPUTime, err = parseUint(fields[2]) l.CPUTime, err = parseInt(fields[1])
case "Max file size": case "Max file size":
l.FileSize, err = parseUint(fields[2]) l.FileSize, err = parseInt(fields[1])
case "Max data size": case "Max data size":
l.DataSize, err = parseUint(fields[2]) l.DataSize, err = parseInt(fields[1])
case "Max stack size": case "Max stack size":
l.StackSize, err = parseUint(fields[2]) l.StackSize, err = parseInt(fields[1])
case "Max core file size": case "Max core file size":
l.CoreFileSize, err = parseUint(fields[2]) l.CoreFileSize, err = parseInt(fields[1])
case "Max resident set": case "Max resident set":
l.ResidentSet, err = parseUint(fields[2]) l.ResidentSet, err = parseInt(fields[1])
case "Max processes": case "Max processes":
l.Processes, err = parseUint(fields[2]) l.Processes, err = parseInt(fields[1])
case "Max open files": case "Max open files":
l.OpenFiles, err = parseUint(fields[2]) l.OpenFiles, err = parseInt(fields[1])
case "Max locked memory": case "Max locked memory":
l.LockedMemory, err = parseUint(fields[2]) l.LockedMemory, err = parseInt(fields[1])
case "Max address space": case "Max address space":
l.AddressSpace, err = parseUint(fields[2]) l.AddressSpace, err = parseInt(fields[1])
case "Max file locks": case "Max file locks":
l.FileLocks, err = parseUint(fields[2]) l.FileLocks, err = parseInt(fields[1])
case "Max pending signals": case "Max pending signals":
l.PendingSignals, err = parseUint(fields[2]) l.PendingSignals, err = parseInt(fields[1])
case "Max msgqueue size": case "Max msgqueue size":
l.MsqqueueSize, err = parseUint(fields[2]) l.MsqqueueSize, err = parseInt(fields[1])
case "Max nice priority": case "Max nice priority":
l.NicePriority, err = parseUint(fields[2]) l.NicePriority, err = parseInt(fields[1])
case "Max realtime priority": case "Max realtime priority":
l.RealtimePriority, err = parseUint(fields[2]) l.RealtimePriority, err = parseInt(fields[1])
case "Max realtime timeout": case "Max realtime timeout":
l.RealtimeTimeout, err = parseUint(fields[2]) l.RealtimeTimeout, err = parseInt(fields[1])
} }
if err != nil { if err != nil {
return ProcLimits{}, err return ProcLimits{}, err
...@@ -148,13 +145,13 @@ func (p Proc) Limits() (ProcLimits, error) { ...@@ -148,13 +145,13 @@ func (p Proc) Limits() (ProcLimits, error) {
return l, s.Err() return l, s.Err()
} }
func parseUint(s string) (uint64, error) { func parseInt(s string) (int64, error) {
if s == limitsUnlimited { if s == limitsUnlimited {
return 18446744073709551615, nil return -1, nil
} }
i, err := strconv.ParseUint(s, 10, 64) i, err := strconv.ParseInt(s, 10, 64)
if err != nil { if err != nil {
return 0, fmt.Errorf("couldn't parse value %q: %w", s, err) return 0, fmt.Errorf("couldn't parse value %s: %s", s, err)
} }
return i, nil return i, nil
} }
// Copyright 2019 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 aix darwin dragonfly freebsd linux netbsd openbsd solaris
package procfs
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"golang.org/x/sys/unix"
)
// ProcMapPermissions contains permission settings read from /proc/[pid]/maps
type ProcMapPermissions struct {
// mapping has the [R]ead flag set
Read bool
// mapping has the [W]rite flag set
Write bool
// mapping has the [X]ecutable flag set
Execute bool
// mapping has the [S]hared flag set
Shared bool
// mapping is marked as [P]rivate (copy on write)
Private bool
}
// ProcMap contains the process memory-mappings of the process,
// read from /proc/[pid]/maps
type ProcMap struct {
// The start address of current mapping.
StartAddr uintptr
// The end address of the current mapping
EndAddr uintptr
// The permissions for this mapping
Perms *ProcMapPermissions
// The current offset into the file/fd (e.g., shared libs)
Offset int64
// Device owner of this mapping (major:minor) in Mkdev format.
Dev uint64
// The inode of the device above
Inode uint64
// The file or psuedofile (or empty==anonymous)
Pathname string
}
// parseDevice parses the device token of a line and converts it to a dev_t
// (mkdev) like structure.
func parseDevice(s string) (uint64, error) {
toks := strings.Split(s, ":")
if len(toks) < 2 {
return 0, fmt.Errorf("unexpected number of fields")
}
major, err := strconv.ParseUint(toks[0], 16, 0)
if err != nil {
return 0, err
}
minor, err := strconv.ParseUint(toks[1], 16, 0)
if err != nil {
return 0, err
}
return unix.Mkdev(uint32(major), uint32(minor)), nil
}
// parseAddress just converts a hex-string to a uintptr
func parseAddress(s string) (uintptr, error) {
a, err := strconv.ParseUint(s, 16, 0)
if err != nil {
return 0, err
}
return uintptr(a), nil
}
// parseAddresses parses the start-end address
func parseAddresses(s string) (uintptr, uintptr, error) {
toks := strings.Split(s, "-")
if len(toks) < 2 {
return 0, 0, fmt.Errorf("invalid address")
}
saddr, err := parseAddress(toks[0])
if err != nil {
return 0, 0, err
}
eaddr, err := parseAddress(toks[1])
if err != nil {
return 0, 0, err
}
return saddr, eaddr, nil
}
// parsePermissions parses a token and returns any that are set.
func parsePermissions(s string) (*ProcMapPermissions, error) {
if len(s) < 4 {
return nil, fmt.Errorf("invalid permissions token")
}
perms := ProcMapPermissions{}
for _, ch := range s {
switch ch {
case 'r':
perms.Read = true
case 'w':
perms.Write = true
case 'x':
perms.Execute = true
case 'p':
perms.Private = true
case 's':
perms.Shared = true
}
}
return &perms, nil
}
// parseProcMap will attempt to parse a single line within a proc/[pid]/maps
// buffer.
func parseProcMap(text string) (*ProcMap, error) {
fields := strings.Fields(text)
if len(fields) < 5 {
return nil, fmt.Errorf("truncated procmap entry")
}
saddr, eaddr, err := parseAddresses(fields[0])
if err != nil {
return nil, err
}
perms, err := parsePermissions(fields[1])
if err != nil {
return nil, err
}
offset, err := strconv.ParseInt(fields[2], 16, 0)
if err != nil {
return nil, err
}
device, err := parseDevice(fields[3])
if err != nil {
return nil, err
}
inode, err := strconv.ParseUint(fields[4], 10, 0)
if err != nil {
return nil, err
}
pathname := ""
if len(fields) >= 5 {
pathname = strings.Join(fields[5:], " ")
}
return &ProcMap{
StartAddr: saddr,
EndAddr: eaddr,
Perms: perms,
Offset: offset,
Dev: device,
Inode: inode,
Pathname: pathname,
}, nil
}
// ProcMaps reads from /proc/[pid]/maps to get the memory-mappings of the
// process.
func (p Proc) ProcMaps() ([]*ProcMap, error) {
file, err := os.Open(p.path("maps"))
if err != nil {
return nil, err
}
defer file.Close()
maps := []*ProcMap{}
scan := bufio.NewScanner(file)
for scan.Scan() {
m, err := parseProcMap(scan.Text())
if err != nil {
return nil, err
}
maps = append(maps, m)
}
return maps, nil
}
...@@ -40,7 +40,7 @@ func (p Proc) Namespaces() (Namespaces, error) { ...@@ -40,7 +40,7 @@ func (p Proc) Namespaces() (Namespaces, error) {
names, err := d.Readdirnames(-1) names, err := d.Readdirnames(-1)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read contents of ns dir: %w", err) return nil, fmt.Errorf("failed to read contents of ns dir: %v", err)
} }
ns := make(Namespaces, len(names)) ns := make(Namespaces, len(names))
...@@ -52,13 +52,13 @@ func (p Proc) Namespaces() (Namespaces, error) { ...@@ -52,13 +52,13 @@ func (p Proc) Namespaces() (Namespaces, error) {
fields := strings.SplitN(target, ":", 2) fields := strings.SplitN(target, ":", 2)
if len(fields) != 2 { if len(fields) != 2 {
return nil, fmt.Errorf("failed to parse namespace type and inode from %q", target) return nil, fmt.Errorf("failed to parse namespace type and inode from '%v'", target)
} }
typ := fields[0] typ := fields[0]
inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32) inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse inode from %q: %w", fields[1], err) return nil, fmt.Errorf("failed to parse inode from '%v': %v", fields[1], err)
} }
ns[name] = Namespace{typ, uint32(inode)} ns[name] = Namespace{typ, uint32(inode)}
......
...@@ -59,7 +59,7 @@ type PSIStats struct { ...@@ -59,7 +59,7 @@ type PSIStats struct {
func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) { func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) {
data, err := util.ReadFileNoStat(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource))) data, err := util.ReadFileNoStat(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
if err != nil { if err != nil {
return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %q: %w", resource, err) return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %s", resource)
} }
return parsePSIStats(resource, bytes.NewReader(data)) return parsePSIStats(resource, bytes.NewReader(data))
......
// Copyright 2020 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 procfs
import (
"bufio"
"errors"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"github.com/prometheus/procfs/internal/util"
)
var (
// match the header line before each mapped zone in /proc/pid/smaps
procSMapsHeaderLine = regexp.MustCompile(`^[a-f0-9].*$`)
)
type ProcSMapsRollup struct {
// Amount of the mapping that is currently resident in RAM
Rss uint64
// Process's proportional share of this mapping
Pss uint64
// Size in bytes of clean shared pages
SharedClean uint64
// Size in bytes of dirty shared pages
SharedDirty uint64
// Size in bytes of clean private pages
PrivateClean uint64
// Size in bytes of dirty private pages
PrivateDirty uint64
// Amount of memory currently marked as referenced or accessed
Referenced uint64
// Amount of memory that does not belong to any file
Anonymous uint64
// Amount would-be-anonymous memory currently on swap
Swap uint64
// Process's proportional memory on swap
SwapPss uint64
}
// ProcSMapsRollup reads from /proc/[pid]/smaps_rollup to get summed memory information of the
// process.
//
// If smaps_rollup does not exists (require kernel >= 4.15), the content of /proc/pid/smaps will
// we read and summed.
func (p Proc) ProcSMapsRollup() (ProcSMapsRollup, error) {
data, err := util.ReadFileNoStat(p.path("smaps_rollup"))
if err != nil && os.IsNotExist(err) {
return p.procSMapsRollupManual()
}
if err != nil {
return ProcSMapsRollup{}, err
}
lines := strings.Split(string(data), "\n")
smaps := ProcSMapsRollup{}
// skip first line which don't contains information we need
lines = lines[1:]
for _, line := range lines {
if line == "" {
continue
}
if err := smaps.parseLine(line); err != nil {
return ProcSMapsRollup{}, err
}
}
return smaps, nil
}
// Read /proc/pid/smaps and do the roll-up in Go code.
func (p Proc) procSMapsRollupManual() (ProcSMapsRollup, error) {
file, err := os.Open(p.path("smaps"))
if err != nil {
return ProcSMapsRollup{}, err
}
defer file.Close()
smaps := ProcSMapsRollup{}
scan := bufio.NewScanner(file)
for scan.Scan() {
line := scan.Text()
if procSMapsHeaderLine.MatchString(line) {
continue
}
if err := smaps.parseLine(line); err != nil {
return ProcSMapsRollup{}, err
}
}
return smaps, nil
}
func (s *ProcSMapsRollup) parseLine(line string) error {
kv := strings.SplitN(line, ":", 2)
if len(kv) != 2 {
fmt.Println(line)
return errors.New("invalid net/dev line, missing colon")
}
k := kv[0]
if k == "VmFlags" {
return nil
}
v := strings.TrimSpace(kv[1])
v = strings.TrimRight(v, " kB")
vKBytes, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return err
}
vBytes := vKBytes * 1024
s.addValue(k, v, vKBytes, vBytes)
return nil
}
func (s *ProcSMapsRollup) addValue(k string, vString string, vUint uint64, vUintBytes uint64) {
switch k {
case "Rss":
s.Rss += vUintBytes
case "Pss":
s.Pss += vUintBytes
case "Shared_Clean":
s.SharedClean += vUintBytes
case "Shared_Dirty":
s.SharedDirty += vUintBytes
case "Private_Clean":
s.PrivateClean += vUintBytes
case "Private_Dirty":
s.PrivateDirty += vUintBytes
case "Referenced":
s.Referenced += vUintBytes
case "Anonymous":
s.Anonymous += vUintBytes
case "Swap":
s.Swap += vUintBytes
case "SwapPss":
s.SwapPss += vUintBytes
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment