Browse code

vendor: github.com/prometheus/client_golang v1.17.0

full diffs:

- https://github.com/prometheus/client_golang/compare/v1.14.0...v1.17.0
- https://github.com/prometheus/client_model/compare/v0.3.0...v0.5.0
- https://github.com/prometheus/common/compare/v0.42.0...v0.44.0
- https://github.com/prometheus/procfs/compare/v0.9.0...v0.11.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2023/12/22 03:13:04
Showing 79 changed files
... ...
@@ -81,7 +81,7 @@ require (
81 81
 	github.com/opencontainers/selinux v1.11.0
82 82
 	github.com/pelletier/go-toml v1.9.5
83 83
 	github.com/pkg/errors v0.9.1
84
-	github.com/prometheus/client_golang v1.14.0
84
+	github.com/prometheus/client_golang v1.17.0
85 85
 	github.com/rootless-containers/rootlesskit/v2 v2.0.1
86 86
 	github.com/sirupsen/logrus v1.9.3
87 87
 	github.com/spf13/cobra v1.8.0
... ...
@@ -173,9 +173,9 @@ require (
173 173
 	github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect
174 174
 	github.com/package-url/packageurl-go v0.1.1-0.20220428063043-89078438f170 // indirect
175 175
 	github.com/philhofer/fwd v1.1.2 // indirect
176
-	github.com/prometheus/client_model v0.3.0 // indirect
177
-	github.com/prometheus/common v0.42.0 // indirect
178
-	github.com/prometheus/procfs v0.9.0 // indirect
176
+	github.com/prometheus/client_model v0.5.0 // indirect
177
+	github.com/prometheus/common v0.44.0 // indirect
178
+	github.com/prometheus/procfs v0.11.1 // indirect
179 179
 	github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
180 180
 	github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
181 181
 	github.com/shibumi/go-pathspec v1.3.0 // indirect
... ...
@@ -1056,16 +1056,16 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ
1056 1056
 github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
1057 1057
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
1058 1058
 github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
1059
-github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
1060
-github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
1059
+github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
1060
+github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
1061 1061
 github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
1062 1062
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
1063 1063
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
1064 1064
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
1065 1065
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
1066 1066
 github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
1067
-github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
1068
-github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
1067
+github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
1068
+github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
1069 1069
 github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
1070 1070
 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
1071 1071
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
... ...
@@ -1076,8 +1076,8 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+
1076 1076
 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
1077 1077
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
1078 1078
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
1079
-github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
1080
-github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
1079
+github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
1080
+github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
1081 1081
 github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
1082 1082
 github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
1083 1083
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
... ...
@@ -1090,8 +1090,8 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
1090 1090
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
1091 1091
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
1092 1092
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
1093
-github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
1094
-github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
1093
+github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
1094
+github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
1095 1095
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
1096 1096
 github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
1097 1097
 github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k=
... ...
@@ -20,6 +20,7 @@ import (
20 20
 	"time"
21 21
 
22 22
 	dto "github.com/prometheus/client_model/go"
23
+	"google.golang.org/protobuf/types/known/timestamppb"
23 24
 )
24 25
 
25 26
 // Counter is a Metric that represents a single numerical value that only ever
... ...
@@ -59,6 +60,18 @@ type ExemplarAdder interface {
59 59
 // CounterOpts is an alias for Opts. See there for doc comments.
60 60
 type CounterOpts Opts
61 61
 
62
+// CounterVecOpts bundles the options to create a CounterVec metric.
63
+// It is mandatory to set CounterOpts, see there for mandatory fields. VariableLabels
64
+// is optional and can safely be left to its default value.
65
+type CounterVecOpts struct {
66
+	CounterOpts
67
+
68
+	// VariableLabels are used to partition the metric vector by the given set
69
+	// of labels. Each label value will be constrained with the optional Constraint
70
+	// function, if provided.
71
+	VariableLabels ConstrainableLabels
72
+}
73
+
62 74
 // NewCounter creates a new Counter based on the provided CounterOpts.
63 75
 //
64 76
 // The returned implementation also implements ExemplarAdder. It is safe to
... ...
@@ -78,8 +91,12 @@ func NewCounter(opts CounterOpts) Counter {
78 78
 		nil,
79 79
 		opts.ConstLabels,
80 80
 	)
81
-	result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: time.Now}
81
+	if opts.now == nil {
82
+		opts.now = time.Now
83
+	}
84
+	result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: opts.now}
82 85
 	result.init(result) // Init self-collection.
86
+	result.createdTs = timestamppb.New(opts.now())
83 87
 	return result
84 88
 }
85 89
 
... ...
@@ -94,10 +111,12 @@ type counter struct {
94 94
 	selfCollector
95 95
 	desc *Desc
96 96
 
97
+	createdTs  *timestamppb.Timestamp
97 98
 	labelPairs []*dto.LabelPair
98 99
 	exemplar   atomic.Value // Containing nil or a *dto.Exemplar.
99 100
 
100
-	now func() time.Time // To mock out time.Now() for testing.
101
+	// now is for testing purposes, by default it's time.Now.
102
+	now func() time.Time
101 103
 }
102 104
 
103 105
 func (c *counter) Desc() *Desc {
... ...
@@ -147,8 +166,7 @@ func (c *counter) Write(out *dto.Metric) error {
147 147
 		exemplar = e.(*dto.Exemplar)
148 148
 	}
149 149
 	val := c.get()
150
-
151
-	return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
150
+	return populateMetric(CounterValue, val, c.labelPairs, exemplar, out, c.createdTs)
152 151
 }
153 152
 
154 153
 func (c *counter) updateExemplar(v float64, l Labels) {
... ...
@@ -174,19 +192,31 @@ type CounterVec struct {
174 174
 // NewCounterVec creates a new CounterVec based on the provided CounterOpts and
175 175
 // partitioned by the given label names.
176 176
 func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
177
-	desc := NewDesc(
177
+	return V2.NewCounterVec(CounterVecOpts{
178
+		CounterOpts:    opts,
179
+		VariableLabels: UnconstrainedLabels(labelNames),
180
+	})
181
+}
182
+
183
+// NewCounterVec creates a new CounterVec based on the provided CounterVecOpts.
184
+func (v2) NewCounterVec(opts CounterVecOpts) *CounterVec {
185
+	desc := V2.NewDesc(
178 186
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
179 187
 		opts.Help,
180
-		labelNames,
188
+		opts.VariableLabels,
181 189
 		opts.ConstLabels,
182 190
 	)
191
+	if opts.now == nil {
192
+		opts.now = time.Now
193
+	}
183 194
 	return &CounterVec{
184 195
 		MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
185
-			if len(lvs) != len(desc.variableLabels) {
186
-				panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
196
+			if len(lvs) != len(desc.variableLabels.names) {
197
+				panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, lvs))
187 198
 			}
188
-			result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
199
+			result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: opts.now}
189 200
 			result.init(result) // Init self-collection.
201
+			result.createdTs = timestamppb.New(opts.now())
190 202
 			return result
191 203
 		}),
192 204
 	}
... ...
@@ -14,20 +14,16 @@
14 14
 package prometheus
15 15
 
16 16
 import (
17
-	"errors"
18 17
 	"fmt"
19 18
 	"sort"
20 19
 	"strings"
21 20
 
22 21
 	"github.com/cespare/xxhash/v2"
23
-
24
-	"github.com/prometheus/client_golang/prometheus/internal"
25
-
26
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
27
-	"github.com/golang/protobuf/proto"
22
+	dto "github.com/prometheus/client_model/go"
28 23
 	"github.com/prometheus/common/model"
24
+	"google.golang.org/protobuf/proto"
29 25
 
30
-	dto "github.com/prometheus/client_model/go"
26
+	"github.com/prometheus/client_golang/prometheus/internal"
31 27
 )
32 28
 
33 29
 // Desc is the descriptor used by every Prometheus Metric. It is essentially
... ...
@@ -54,9 +50,9 @@ type Desc struct {
54 54
 	// constLabelPairs contains precalculated DTO label pairs based on
55 55
 	// the constant labels.
56 56
 	constLabelPairs []*dto.LabelPair
57
-	// variableLabels contains names of labels for which the metric
58
-	// maintains variable values.
59
-	variableLabels []string
57
+	// variableLabels contains names of labels and normalization function for
58
+	// which the metric maintains variable values.
59
+	variableLabels *compiledLabels
60 60
 	// id is a hash of the values of the ConstLabels and fqName. This
61 61
 	// must be unique among all registered descriptors and can therefore be
62 62
 	// used as an identifier of the descriptor.
... ...
@@ -80,10 +76,24 @@ type Desc struct {
80 80
 // For constLabels, the label values are constant. Therefore, they are fully
81 81
 // specified in the Desc. See the Collector example for a usage pattern.
82 82
 func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
83
+	return V2.NewDesc(fqName, help, UnconstrainedLabels(variableLabels), constLabels)
84
+}
85
+
86
+// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
87
+// and will be reported on registration time. variableLabels and constLabels can
88
+// be nil if no such labels should be set. fqName must not be empty.
89
+//
90
+// variableLabels only contain the label names and normalization functions. Their
91
+// label values are variable and therefore not part of the Desc. (They are managed
92
+// within the Metric.)
93
+//
94
+// For constLabels, the label values are constant. Therefore, they are fully
95
+// specified in the Desc. See the Collector example for a usage pattern.
96
+func (v2) NewDesc(fqName, help string, variableLabels ConstrainableLabels, constLabels Labels) *Desc {
83 97
 	d := &Desc{
84 98
 		fqName:         fqName,
85 99
 		help:           help,
86
-		variableLabels: variableLabels,
100
+		variableLabels: variableLabels.compile(),
87 101
 	}
88 102
 	if !model.IsValidMetricName(model.LabelValue(fqName)) {
89 103
 		d.err = fmt.Errorf("%q is not a valid metric name", fqName)
... ...
@@ -93,7 +103,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
93 93
 	// their sorted label names) plus the fqName (at position 0).
94 94
 	labelValues := make([]string, 1, len(constLabels)+1)
95 95
 	labelValues[0] = fqName
96
-	labelNames := make([]string, 0, len(constLabels)+len(variableLabels))
96
+	labelNames := make([]string, 0, len(constLabels)+len(d.variableLabels.names))
97 97
 	labelNameSet := map[string]struct{}{}
98 98
 	// First add only the const label names and sort them...
99 99
 	for labelName := range constLabels {
... ...
@@ -118,16 +128,16 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
118 118
 	// Now add the variable label names, but prefix them with something that
119 119
 	// cannot be in a regular label name. That prevents matching the label
120 120
 	// dimension with a different mix between preset and variable labels.
121
-	for _, labelName := range variableLabels {
122
-		if !checkLabelName(labelName) {
123
-			d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
121
+	for _, label := range d.variableLabels.names {
122
+		if !checkLabelName(label) {
123
+			d.err = fmt.Errorf("%q is not a valid label name for metric %q", label, fqName)
124 124
 			return d
125 125
 		}
126
-		labelNames = append(labelNames, "$"+labelName)
127
-		labelNameSet[labelName] = struct{}{}
126
+		labelNames = append(labelNames, "$"+label)
127
+		labelNameSet[label] = struct{}{}
128 128
 	}
129 129
 	if len(labelNames) != len(labelNameSet) {
130
-		d.err = errors.New("duplicate label names")
130
+		d.err = fmt.Errorf("duplicate label names in constant and variable labels for metric %q", fqName)
131 131
 		return d
132 132
 	}
133 133
 
... ...
@@ -179,11 +189,19 @@ func (d *Desc) String() string {
179 179
 			fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
180 180
 		)
181 181
 	}
182
+	vlStrings := make([]string, 0, len(d.variableLabels.names))
183
+	for _, vl := range d.variableLabels.names {
184
+		if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil {
185
+			vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl))
186
+		} else {
187
+			vlStrings = append(vlStrings, vl)
188
+		}
189
+	}
182 190
 	return fmt.Sprintf(
183
-		"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: %v}",
191
+		"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: {%s}}",
184 192
 		d.fqName,
185 193
 		d.help,
186 194
 		strings.Join(lpStrings, ","),
187
-		d.variableLabels,
195
+		strings.Join(vlStrings, ","),
188 196
 	)
189 197
 }
... ...
@@ -37,35 +37,35 @@
37 37
 //
38 38
 //	type metrics struct {
39 39
 //		cpuTemp  prometheus.Gauge
40
-//	  hdFailures *prometheus.CounterVec
40
+//		hdFailures *prometheus.CounterVec
41 41
 //	}
42 42
 //
43 43
 //	func NewMetrics(reg prometheus.Registerer) *metrics {
44
-//	  m := &metrics{
45
-//	    cpuTemp: prometheus.NewGauge(prometheus.GaugeOpts{
46
-//	      Name: "cpu_temperature_celsius",
47
-//	      Help: "Current temperature of the CPU.",
48
-//	    }),
49
-//	    hdFailures: prometheus.NewCounterVec(
50
-//	      prometheus.CounterOpts{
51
-//	        Name: "hd_errors_total",
52
-//	        Help: "Number of hard-disk errors.",
53
-//	      },
54
-//	      []string{"device"},
55
-//	    ),
56
-//	  }
57
-//	  reg.MustRegister(m.cpuTemp)
58
-//	  reg.MustRegister(m.hdFailures)
59
-//	  return m
44
+//		m := &metrics{
45
+//			cpuTemp: prometheus.NewGauge(prometheus.GaugeOpts{
46
+//				Name: "cpu_temperature_celsius",
47
+//				Help: "Current temperature of the CPU.",
48
+//			}),
49
+//			hdFailures: prometheus.NewCounterVec(
50
+//				prometheus.CounterOpts{
51
+//					Name: "hd_errors_total",
52
+//					Help: "Number of hard-disk errors.",
53
+//				},
54
+//				[]string{"device"},
55
+//			),
56
+//		}
57
+//		reg.MustRegister(m.cpuTemp)
58
+//		reg.MustRegister(m.hdFailures)
59
+//		return m
60 60
 //	}
61 61
 //
62 62
 //	func main() {
63
-//	  // Create a non-global registry.
64
-//	  reg := prometheus.NewRegistry()
63
+//		// Create a non-global registry.
64
+//		reg := prometheus.NewRegistry()
65 65
 //
66
-//	  // Create new metrics and register them using the custom registry.
67
-//	  m := NewMetrics(reg)
68
-//	  // Set values for the new created metrics.
66
+//		// Create new metrics and register them using the custom registry.
67
+//		m := NewMetrics(reg)
68
+//		// Set values for the new created metrics.
69 69
 //		m.cpuTemp.Set(65.3)
70 70
 //		m.hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
71 71
 //
... ...
@@ -48,7 +48,7 @@ func (e *expvarCollector) Collect(ch chan<- Metric) {
48 48
 			continue
49 49
 		}
50 50
 		var v interface{}
51
-		labels := make([]string, len(desc.variableLabels))
51
+		labels := make([]string, len(desc.variableLabels.names))
52 52
 		if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil {
53 53
 			ch <- NewInvalidMetric(desc, err)
54 54
 			continue
... ...
@@ -55,6 +55,18 @@ type Gauge interface {
55 55
 // GaugeOpts is an alias for Opts. See there for doc comments.
56 56
 type GaugeOpts Opts
57 57
 
58
+// GaugeVecOpts bundles the options to create a GaugeVec metric.
59
+// It is mandatory to set GaugeOpts, see there for mandatory fields. VariableLabels
60
+// is optional and can safely be left to its default value.
61
+type GaugeVecOpts struct {
62
+	GaugeOpts
63
+
64
+	// VariableLabels are used to partition the metric vector by the given set
65
+	// of labels. Each label value will be constrained with the optional Constraint
66
+	// function, if provided.
67
+	VariableLabels ConstrainableLabels
68
+}
69
+
58 70
 // NewGauge creates a new Gauge based on the provided GaugeOpts.
59 71
 //
60 72
 // The returned implementation is optimized for a fast Set method. If you have a
... ...
@@ -123,7 +135,7 @@ func (g *gauge) Sub(val float64) {
123 123
 
124 124
 func (g *gauge) Write(out *dto.Metric) error {
125 125
 	val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
126
-	return populateMetric(GaugeValue, val, g.labelPairs, nil, out)
126
+	return populateMetric(GaugeValue, val, g.labelPairs, nil, out, nil)
127 127
 }
128 128
 
129 129
 // GaugeVec is a Collector that bundles a set of Gauges that all share the same
... ...
@@ -138,16 +150,24 @@ type GaugeVec struct {
138 138
 // NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
139 139
 // partitioned by the given label names.
140 140
 func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
141
-	desc := NewDesc(
141
+	return V2.NewGaugeVec(GaugeVecOpts{
142
+		GaugeOpts:      opts,
143
+		VariableLabels: UnconstrainedLabels(labelNames),
144
+	})
145
+}
146
+
147
+// NewGaugeVec creates a new GaugeVec based on the provided GaugeVecOpts.
148
+func (v2) NewGaugeVec(opts GaugeVecOpts) *GaugeVec {
149
+	desc := V2.NewDesc(
142 150
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
143 151
 		opts.Help,
144
-		labelNames,
152
+		opts.VariableLabels,
145 153
 		opts.ConstLabels,
146 154
 	)
147 155
 	return &GaugeVec{
148 156
 		MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
149
-			if len(lvs) != len(desc.variableLabels) {
150
-				panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
157
+			if len(lvs) != len(desc.variableLabels.names) {
158
+				panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, lvs))
151 159
 			}
152 160
 			result := &gauge{desc: desc, labelPairs: MakeLabelPairs(desc, lvs)}
153 161
 			result.init(result) // Init self-collection.
... ...
@@ -23,11 +23,10 @@ import (
23 23
 	"strings"
24 24
 	"sync"
25 25
 
26
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
27
-	"github.com/golang/protobuf/proto"
28
-	dto "github.com/prometheus/client_model/go"
29
-
30 26
 	"github.com/prometheus/client_golang/prometheus/internal"
27
+
28
+	dto "github.com/prometheus/client_model/go"
29
+	"google.golang.org/protobuf/proto"
31 30
 )
32 31
 
33 32
 const (
... ...
@@ -22,10 +22,10 @@ import (
22 22
 	"sync/atomic"
23 23
 	"time"
24 24
 
25
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
26
-	"github.com/golang/protobuf/proto"
27
-
28 25
 	dto "github.com/prometheus/client_model/go"
26
+
27
+	"google.golang.org/protobuf/proto"
28
+	"google.golang.org/protobuf/types/known/timestamppb"
29 29
 )
30 30
 
31 31
 // nativeHistogramBounds for the frac of observed values. Only relevant for
... ...
@@ -392,7 +392,7 @@ type HistogramOpts struct {
392 392
 	// zero, it is replaced by default buckets. The default buckets are
393 393
 	// DefBuckets if no buckets for a native histogram (see below) are used,
394 394
 	// otherwise the default is no buckets. (In other words, if you want to
395
-	// use both reguler buckets and buckets for a native histogram, you have
395
+	// use both regular buckets and buckets for a native histogram, you have
396 396
 	// to define the regular buckets here explicitly.)
397 397
 	Buckets []float64
398 398
 
... ...
@@ -402,7 +402,7 @@ type HistogramOpts struct {
402 402
 	// Histogram by a Prometheus server with that feature enabled (requires
403 403
 	// Prometheus v2.40+). Sparse buckets are exponential buckets covering
404 404
 	// the whole float64 range (with the exception of the “zero” bucket, see
405
-	// SparseBucketsZeroThreshold below). From any one bucket to the next,
405
+	// NativeHistogramZeroThreshold below). From any one bucket to the next,
406 406
 	// the width of the bucket grows by a constant
407 407
 	// factor. NativeHistogramBucketFactor provides an upper bound for this
408 408
 	// factor (exception see below). The smaller
... ...
@@ -414,8 +414,8 @@ type HistogramOpts struct {
414 414
 	// and 2, same as between 2 and 4, and 4 and 8, etc.).
415 415
 	//
416 416
 	// Details about the actually used factor: The factor is calculated as
417
-	// 2^(2^n), where n is an integer number between (and including) -8 and
418
-	// 4. n is chosen so that the resulting factor is the largest that is
417
+	// 2^(2^-n), where n is an integer number between (and including) -4 and
418
+	// 8. n is chosen so that the resulting factor is the largest that is
419 419
 	// still smaller or equal to NativeHistogramBucketFactor. Note that the
420 420
 	// smallest possible factor is therefore approx. 1.00271 (i.e. 2^(2^-8)
421 421
 	// ). If NativeHistogramBucketFactor is greater than 1 but smaller than
... ...
@@ -429,12 +429,12 @@ type HistogramOpts struct {
429 429
 	// a major version bump.
430 430
 	NativeHistogramBucketFactor float64
431 431
 	// All observations with an absolute value of less or equal
432
-	// NativeHistogramZeroThreshold are accumulated into a “zero”
433
-	// bucket. For best results, this should be close to a bucket
434
-	// boundary. This is usually the case if picking a power of two. If
432
+	// NativeHistogramZeroThreshold are accumulated into a “zero” bucket.
433
+	// For best results, this should be close to a bucket boundary. This is
434
+	// usually the case if picking a power of two. If
435 435
 	// NativeHistogramZeroThreshold is left at zero,
436
-	// DefSparseBucketsZeroThreshold is used as the threshold. To configure
437
-	// a zero bucket with an actual threshold of zero (i.e. only
436
+	// DefNativeHistogramZeroThreshold is used as the threshold. To
437
+	// configure a zero bucket with an actual threshold of zero (i.e. only
438 438
 	// observations of precisely zero will go into the zero bucket), set
439 439
 	// NativeHistogramZeroThreshold to the NativeHistogramZeroThresholdZero
440 440
 	// constant (or any negative float value).
... ...
@@ -447,26 +447,46 @@ type HistogramOpts struct {
447 447
 	// Histogram are sufficiently wide-spread. In particular, this could be
448 448
 	// used as a DoS attack vector. Where the observed values depend on
449 449
 	// external inputs, it is highly recommended to set a
450
-	// NativeHistogramMaxBucketNumber.)  Once the set
450
+	// NativeHistogramMaxBucketNumber.) Once the set
451 451
 	// NativeHistogramMaxBucketNumber is exceeded, the following strategy is
452
-	// enacted: First, if the last reset (or the creation) of the histogram
453
-	// is at least NativeHistogramMinResetDuration ago, then the whole
454
-	// histogram is reset to its initial state (including regular
455
-	// buckets). If less time has passed, or if
456
-	// NativeHistogramMinResetDuration is zero, no reset is
457
-	// performed. Instead, the zero threshold is increased sufficiently to
458
-	// reduce the number of buckets to or below
459
-	// NativeHistogramMaxBucketNumber, but not to more than
460
-	// NativeHistogramMaxZeroThreshold. Thus, if
461
-	// NativeHistogramMaxZeroThreshold is already at or below the current
462
-	// zero threshold, nothing happens at this step. After that, if the
463
-	// number of buckets still exceeds NativeHistogramMaxBucketNumber, the
464
-	// resolution of the histogram is reduced by doubling the width of the
465
-	// sparse buckets (up to a growth factor between one bucket to the next
466
-	// of 2^(2^4) = 65536, see above).
452
+	// enacted:
453
+	//  - First, if the last reset (or the creation) of the histogram is at
454
+	//    least NativeHistogramMinResetDuration ago, then the whole
455
+	//    histogram is reset to its initial state (including regular
456
+	//    buckets).
457
+	//  - If less time has passed, or if NativeHistogramMinResetDuration is
458
+	//    zero, no reset is performed. Instead, the zero threshold is
459
+	//    increased sufficiently to reduce the number of buckets to or below
460
+	//    NativeHistogramMaxBucketNumber, but not to more than
461
+	//    NativeHistogramMaxZeroThreshold. Thus, if
462
+	//    NativeHistogramMaxZeroThreshold is already at or below the current
463
+	//    zero threshold, nothing happens at this step.
464
+	//  - After that, if the number of buckets still exceeds
465
+	//    NativeHistogramMaxBucketNumber, the resolution of the histogram is
466
+	//    reduced by doubling the width of the sparse buckets (up to a
467
+	//    growth factor between one bucket to the next of 2^(2^4) = 65536,
468
+	//    see above).
469
+	//  - Any increased zero threshold or reduced resolution is reset back
470
+	//    to their original values once NativeHistogramMinResetDuration has
471
+	//    passed (since the last reset or the creation of the histogram).
467 472
 	NativeHistogramMaxBucketNumber  uint32
468 473
 	NativeHistogramMinResetDuration time.Duration
469 474
 	NativeHistogramMaxZeroThreshold float64
475
+
476
+	// now is for testing purposes, by default it's time.Now.
477
+	now func() time.Time
478
+}
479
+
480
+// HistogramVecOpts bundles the options to create a HistogramVec metric.
481
+// It is mandatory to set HistogramOpts, see there for mandatory fields. VariableLabels
482
+// is optional and can safely be left to its default value.
483
+type HistogramVecOpts struct {
484
+	HistogramOpts
485
+
486
+	// VariableLabels are used to partition the metric vector by the given set
487
+	// of labels. Each label value will be constrained with the optional Constraint
488
+	// function, if provided.
489
+	VariableLabels ConstrainableLabels
470 490
 }
471 491
 
472 492
 // NewHistogram creates a new Histogram based on the provided HistogramOpts. It
... ...
@@ -488,11 +508,11 @@ func NewHistogram(opts HistogramOpts) Histogram {
488 488
 }
489 489
 
490 490
 func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
491
-	if len(desc.variableLabels) != len(labelValues) {
492
-		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
491
+	if len(desc.variableLabels.names) != len(labelValues) {
492
+		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
493 493
 	}
494 494
 
495
-	for _, n := range desc.variableLabels {
495
+	for _, n := range desc.variableLabels.names {
496 496
 		if n == bucketLabel {
497 497
 			panic(errBucketLabelNotAllowed)
498 498
 		}
... ...
@@ -503,6 +523,10 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
503 503
 		}
504 504
 	}
505 505
 
506
+	if opts.now == nil {
507
+		opts.now = time.Now
508
+	}
509
+
506 510
 	h := &histogram{
507 511
 		desc:                            desc,
508 512
 		upperBounds:                     opts.Buckets,
... ...
@@ -510,8 +534,8 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
510 510
 		nativeHistogramMaxBuckets:       opts.NativeHistogramMaxBucketNumber,
511 511
 		nativeHistogramMaxZeroThreshold: opts.NativeHistogramMaxZeroThreshold,
512 512
 		nativeHistogramMinResetDuration: opts.NativeHistogramMinResetDuration,
513
-		lastResetTime:                   time.Now(),
514
-		now:                             time.Now,
513
+		lastResetTime:                   opts.now(),
514
+		now:                             opts.now,
515 515
 	}
516 516
 	if len(h.upperBounds) == 0 && opts.NativeHistogramBucketFactor <= 1 {
517 517
 		h.upperBounds = DefBuckets
... ...
@@ -544,16 +568,12 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
544 544
 	}
545 545
 	// Finally we know the final length of h.upperBounds and can make buckets
546 546
 	// for both counts as well as exemplars:
547
-	h.counts[0] = &histogramCounts{
548
-		buckets:                          make([]uint64, len(h.upperBounds)),
549
-		nativeHistogramZeroThresholdBits: math.Float64bits(h.nativeHistogramZeroThreshold),
550
-		nativeHistogramSchema:            h.nativeHistogramSchema,
551
-	}
552
-	h.counts[1] = &histogramCounts{
553
-		buckets:                          make([]uint64, len(h.upperBounds)),
554
-		nativeHistogramZeroThresholdBits: math.Float64bits(h.nativeHistogramZeroThreshold),
555
-		nativeHistogramSchema:            h.nativeHistogramSchema,
556
-	}
547
+	h.counts[0] = &histogramCounts{buckets: make([]uint64, len(h.upperBounds))}
548
+	atomic.StoreUint64(&h.counts[0].nativeHistogramZeroThresholdBits, math.Float64bits(h.nativeHistogramZeroThreshold))
549
+	atomic.StoreInt32(&h.counts[0].nativeHistogramSchema, h.nativeHistogramSchema)
550
+	h.counts[1] = &histogramCounts{buckets: make([]uint64, len(h.upperBounds))}
551
+	atomic.StoreUint64(&h.counts[1].nativeHistogramZeroThresholdBits, math.Float64bits(h.nativeHistogramZeroThreshold))
552
+	atomic.StoreInt32(&h.counts[1].nativeHistogramSchema, h.nativeHistogramSchema)
557 553
 	h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
558 554
 
559 555
 	h.init(h) // Init self-collection.
... ...
@@ -632,8 +652,8 @@ func (hc *histogramCounts) observe(v float64, bucket int, doSparse bool) {
632 632
 			if frac == 0.5 {
633 633
 				key--
634 634
 			}
635
-			div := 1 << -schema
636
-			key = (key + div - 1) / div
635
+			offset := (1 << -schema) - 1
636
+			key = (key + offset) >> -schema
637 637
 		}
638 638
 		if isInf {
639 639
 			key++
... ...
@@ -694,9 +714,11 @@ type histogram struct {
694 694
 	nativeHistogramMaxZeroThreshold float64
695 695
 	nativeHistogramMaxBuckets       uint32
696 696
 	nativeHistogramMinResetDuration time.Duration
697
-	lastResetTime                   time.Time // Protected by mtx.
697
+	// lastResetTime is protected by mtx. It is also used as created timestamp.
698
+	lastResetTime time.Time
698 699
 
699
-	now func() time.Time // To mock out time.Now() for testing.
700
+	// now is for testing purposes, by default it's time.Now.
701
+	now func() time.Time
700 702
 }
701 703
 
702 704
 func (h *histogram) Desc() *Desc {
... ...
@@ -735,9 +757,10 @@ func (h *histogram) Write(out *dto.Metric) error {
735 735
 	waitForCooldown(count, coldCounts)
736 736
 
737 737
 	his := &dto.Histogram{
738
-		Bucket:      make([]*dto.Bucket, len(h.upperBounds)),
739
-		SampleCount: proto.Uint64(count),
740
-		SampleSum:   proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
738
+		Bucket:           make([]*dto.Bucket, len(h.upperBounds)),
739
+		SampleCount:      proto.Uint64(count),
740
+		SampleSum:        proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
741
+		CreatedTimestamp: timestamppb.New(h.lastResetTime),
741 742
 	}
742 743
 	out.Histogram = his
743 744
 	out.Label = h.labelPairs
... ...
@@ -775,6 +798,16 @@ func (h *histogram) Write(out *dto.Metric) error {
775 775
 		his.ZeroCount = proto.Uint64(zeroBucket)
776 776
 		his.NegativeSpan, his.NegativeDelta = makeBuckets(&coldCounts.nativeHistogramBucketsNegative)
777 777
 		his.PositiveSpan, his.PositiveDelta = makeBuckets(&coldCounts.nativeHistogramBucketsPositive)
778
+
779
+		// Add a no-op span to a histogram without observations and with
780
+		// a zero threshold of zero. Otherwise, a native histogram would
781
+		// look like a classic histogram to scrapers.
782
+		if *his.ZeroThreshold == 0 && *his.ZeroCount == 0 && len(his.PositiveSpan) == 0 && len(his.NegativeSpan) == 0 {
783
+			his.PositiveSpan = []*dto.BucketSpan{{
784
+				Offset: proto.Int32(0),
785
+				Length: proto.Uint32(0),
786
+			}}
787
+		}
778 788
 	}
779 789
 	addAndResetCounts(hotCounts, coldCounts)
780 790
 	return nil
... ...
@@ -810,7 +843,7 @@ func (h *histogram) observe(v float64, bucket int) {
810 810
 	}
811 811
 }
812 812
 
813
-// limitSparsebuckets applies a strategy to limit the number of populated sparse
813
+// limitBuckets applies a strategy to limit the number of populated sparse
814 814
 // buckets. It's generally best effort, and there are situations where the
815 815
 // number can go higher (if even the lowest resolution isn't enough to reduce
816 816
 // the number sufficiently, or if the provided counts aren't fully updated yet
... ...
@@ -847,20 +880,23 @@ func (h *histogram) limitBuckets(counts *histogramCounts, value float64, bucket
847 847
 	h.doubleBucketWidth(hotCounts, coldCounts)
848 848
 }
849 849
 
850
-// maybeReset resests the whole histogram if at least h.nativeHistogramMinResetDuration
850
+// maybeReset resets the whole histogram if at least h.nativeHistogramMinResetDuration
851 851
 // has been passed. It returns true if the histogram has been reset. The caller
852 852
 // must have locked h.mtx.
853
-func (h *histogram) maybeReset(hot, cold *histogramCounts, coldIdx uint64, value float64, bucket int) bool {
853
+func (h *histogram) maybeReset(
854
+	hot, cold *histogramCounts, coldIdx uint64, value float64, bucket int,
855
+) bool {
854 856
 	// We are using the possibly mocked h.now() rather than
855 857
 	// time.Since(h.lastResetTime) to enable testing.
856
-	if h.nativeHistogramMinResetDuration == 0 || h.now().Sub(h.lastResetTime) < h.nativeHistogramMinResetDuration {
858
+	if h.nativeHistogramMinResetDuration == 0 ||
859
+		h.now().Sub(h.lastResetTime) < h.nativeHistogramMinResetDuration {
857 860
 		return false
858 861
 	}
859 862
 	// Completely reset coldCounts.
860 863
 	h.resetCounts(cold)
861 864
 	// Repeat the latest observation to not lose it completely.
862 865
 	cold.observe(value, bucket, true)
863
-	// Make coldCounts the new hot counts while ressetting countAndHotIdx.
866
+	// Make coldCounts the new hot counts while resetting countAndHotIdx.
864 867
 	n := atomic.SwapUint64(&h.countAndHotIdx, (coldIdx<<63)+1)
865 868
 	count := n & ((1 << 63) - 1)
866 869
 	waitForCooldown(count, hot)
... ...
@@ -1034,15 +1070,23 @@ type HistogramVec struct {
1034 1034
 // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
1035 1035
 // partitioned by the given label names.
1036 1036
 func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
1037
-	desc := NewDesc(
1037
+	return V2.NewHistogramVec(HistogramVecOpts{
1038
+		HistogramOpts:  opts,
1039
+		VariableLabels: UnconstrainedLabels(labelNames),
1040
+	})
1041
+}
1042
+
1043
+// NewHistogramVec creates a new HistogramVec based on the provided HistogramVecOpts.
1044
+func (v2) NewHistogramVec(opts HistogramVecOpts) *HistogramVec {
1045
+	desc := V2.NewDesc(
1038 1046
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
1039 1047
 		opts.Help,
1040
-		labelNames,
1048
+		opts.VariableLabels,
1041 1049
 		opts.ConstLabels,
1042 1050
 	)
1043 1051
 	return &HistogramVec{
1044 1052
 		MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
1045
-			return newHistogram(desc, opts, lvs...)
1053
+			return newHistogram(desc, opts.HistogramOpts, lvs...)
1046 1054
 		}),
1047 1055
 	}
1048 1056
 }
... ...
@@ -1161,6 +1205,7 @@ type constHistogram struct {
1161 1161
 	sum        float64
1162 1162
 	buckets    map[float64]uint64
1163 1163
 	labelPairs []*dto.LabelPair
1164
+	createdTs  *timestamppb.Timestamp
1164 1165
 }
1165 1166
 
1166 1167
 func (h *constHistogram) Desc() *Desc {
... ...
@@ -1168,7 +1213,9 @@ func (h *constHistogram) Desc() *Desc {
1168 1168
 }
1169 1169
 
1170 1170
 func (h *constHistogram) Write(out *dto.Metric) error {
1171
-	his := &dto.Histogram{}
1171
+	his := &dto.Histogram{
1172
+		CreatedTimestamp: h.createdTs,
1173
+	}
1172 1174
 
1173 1175
 	buckets := make([]*dto.Bucket, 0, len(h.buckets))
1174 1176
 
... ...
@@ -1215,7 +1262,7 @@ func NewConstHistogram(
1215 1215
 	if desc.err != nil {
1216 1216
 		return nil, desc.err
1217 1217
 	}
1218
-	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
1218
+	if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
1219 1219
 		return nil, err
1220 1220
 	}
1221 1221
 	return &constHistogram{
... ...
@@ -1309,7 +1356,7 @@ func makeBuckets(buckets *sync.Map) ([]*dto.BucketSpan, []int64) {
1309 1309
 		// Multiple spans with only small gaps in between are probably
1310 1310
 		// encoded more efficiently as one larger span with a few empty
1311 1311
 		// buckets. Needs some research to find the sweet spot. For now,
1312
-		// we assume that gaps of one ore two buckets should not create
1312
+		// we assume that gaps of one or two buckets should not create
1313 1313
 		// a new span.
1314 1314
 		iDelta := int32(i - nextI)
1315 1315
 		if n == 0 || iDelta > 2 {
... ...
@@ -14,7 +14,7 @@
14 14
 // It provides tools to compare sequences of strings and generate textual diffs.
15 15
 //
16 16
 // Maintaining `GetUnifiedDiffString` here because original repository
17
-// (https://github.com/pmezard/go-difflib) is no loger maintained.
17
+// (https://github.com/pmezard/go-difflib) is no longer maintained.
18 18
 package internal
19 19
 
20 20
 import (
... ...
@@ -32,6 +32,104 @@ import (
32 32
 // create a Desc.
33 33
 type Labels map[string]string
34 34
 
35
+// LabelConstraint normalizes label values.
36
+type LabelConstraint func(string) string
37
+
38
+// ConstrainedLabels represents a label name and its constrain function
39
+// to normalize label values. This type is commonly used when constructing
40
+// metric vector Collectors.
41
+type ConstrainedLabel struct {
42
+	Name       string
43
+	Constraint LabelConstraint
44
+}
45
+
46
+// ConstrainableLabels is an interface that allows creating of labels that can
47
+// be optionally constrained.
48
+//
49
+//	prometheus.V2().NewCounterVec(CounterVecOpts{
50
+//	  CounterOpts: {...}, // Usual CounterOpts fields
51
+//	  VariableLabels: []ConstrainedLabels{
52
+//	    {Name: "A"},
53
+//	    {Name: "B", Constraint: func(v string) string { ... }},
54
+//	  },
55
+//	})
56
+type ConstrainableLabels interface {
57
+	compile() *compiledLabels
58
+	labelNames() []string
59
+}
60
+
61
+// ConstrainedLabels represents a collection of label name -> constrain function
62
+// to normalize label values. This type is commonly used when constructing
63
+// metric vector Collectors.
64
+type ConstrainedLabels []ConstrainedLabel
65
+
66
+func (cls ConstrainedLabels) compile() *compiledLabels {
67
+	compiled := &compiledLabels{
68
+		names:            make([]string, len(cls)),
69
+		labelConstraints: map[string]LabelConstraint{},
70
+	}
71
+
72
+	for i, label := range cls {
73
+		compiled.names[i] = label.Name
74
+		if label.Constraint != nil {
75
+			compiled.labelConstraints[label.Name] = label.Constraint
76
+		}
77
+	}
78
+
79
+	return compiled
80
+}
81
+
82
+func (cls ConstrainedLabels) labelNames() []string {
83
+	names := make([]string, len(cls))
84
+	for i, label := range cls {
85
+		names[i] = label.Name
86
+	}
87
+	return names
88
+}
89
+
90
+// UnconstrainedLabels represents collection of label without any constraint on
91
+// their value. Thus, it is simply a collection of label names.
92
+//
93
+//	UnconstrainedLabels([]string{ "A", "B" })
94
+//
95
+// is equivalent to
96
+//
97
+//	ConstrainedLabels {
98
+//	  { Name: "A" },
99
+//	  { Name: "B" },
100
+//	}
101
+type UnconstrainedLabels []string
102
+
103
+func (uls UnconstrainedLabels) compile() *compiledLabels {
104
+	return &compiledLabels{
105
+		names: uls,
106
+	}
107
+}
108
+
109
+func (uls UnconstrainedLabels) labelNames() []string {
110
+	return uls
111
+}
112
+
113
+type compiledLabels struct {
114
+	names            []string
115
+	labelConstraints map[string]LabelConstraint
116
+}
117
+
118
+func (cls *compiledLabels) compile() *compiledLabels {
119
+	return cls
120
+}
121
+
122
+func (cls *compiledLabels) labelNames() []string {
123
+	return cls.names
124
+}
125
+
126
+func (cls *compiledLabels) constrain(labelName, value string) string {
127
+	if fn, ok := cls.labelConstraints[labelName]; ok && fn != nil {
128
+		return fn(value)
129
+	}
130
+	return value
131
+}
132
+
35 133
 // reservedLabelPrefix is a prefix which is not legal in user-supplied
36 134
 // label names.
37 135
 const reservedLabelPrefix = "__"
... ...
@@ -20,11 +20,9 @@ import (
20 20
 	"strings"
21 21
 	"time"
22 22
 
23
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
24
-	"github.com/golang/protobuf/proto"
25
-	"github.com/prometheus/common/model"
26
-
27 23
 	dto "github.com/prometheus/client_model/go"
24
+	"github.com/prometheus/common/model"
25
+	"google.golang.org/protobuf/proto"
28 26
 )
29 27
 
30 28
 var separatorByteSlice = []byte{model.SeparatorByte} // For convenient use with xxhash.
... ...
@@ -94,6 +92,9 @@ type Opts struct {
94 94
 	// machine_role metric). See also
95 95
 	// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
96 96
 	ConstLabels Labels
97
+
98
+	// now is for testing purposes, by default it's time.Now.
99
+	now func() time.Time
97 100
 }
98 101
 
99 102
 // BuildFQName joins the given three name components by "_". Empty name
... ...
@@ -37,6 +37,7 @@ import (
37 37
 	"fmt"
38 38
 	"io"
39 39
 	"net/http"
40
+	"strconv"
40 41
 	"strings"
41 42
 	"sync"
42 43
 	"time"
... ...
@@ -47,9 +48,10 @@ import (
47 47
 )
48 48
 
49 49
 const (
50
-	contentTypeHeader     = "Content-Type"
51
-	contentEncodingHeader = "Content-Encoding"
52
-	acceptEncodingHeader  = "Accept-Encoding"
50
+	contentTypeHeader      = "Content-Type"
51
+	contentEncodingHeader  = "Content-Encoding"
52
+	acceptEncodingHeader   = "Accept-Encoding"
53
+	processStartTimeHeader = "Process-Start-Time-Unix"
53 54
 )
54 55
 
55 56
 var gzipPool = sync.Pool{
... ...
@@ -121,6 +123,9 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO
121 121
 	}
122 122
 
123 123
 	h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
124
+		if !opts.ProcessStartTime.IsZero() {
125
+			rsp.Header().Set(processStartTimeHeader, strconv.FormatInt(opts.ProcessStartTime.Unix(), 10))
126
+		}
124 127
 		if inFlightSem != nil {
125 128
 			select {
126 129
 			case inFlightSem <- struct{}{}: // All good, carry on.
... ...
@@ -366,6 +371,14 @@ type HandlerOpts struct {
366 366
 	// (which changes the identity of the resulting series on the Prometheus
367 367
 	// server).
368 368
 	EnableOpenMetrics bool
369
+	// ProcessStartTime allows setting process start timevalue that will be exposed
370
+	// with "Process-Start-Time-Unix" response header along with the metrics
371
+	// payload. This allow callers to have efficient transformations to cumulative
372
+	// counters (e.g. OpenTelemetry) or generally _created timestamp estimation per
373
+	// scrape target.
374
+	// NOTE: This feature is experimental and not covered by OpenMetrics or Prometheus
375
+	// exposition format.
376
+	ProcessStartTime time.Time
369 377
 }
370 378
 
371 379
 // gzipAccepted returns whether the client will accept gzip-encoded content.
... ...
@@ -68,16 +68,17 @@ func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.Rou
68 68
 		o.apply(rtOpts)
69 69
 	}
70 70
 
71
-	code, method := checkLabels(counter)
71
+	// Curry the counter with dynamic labels before checking the remaining labels.
72
+	code, method := checkLabels(counter.MustCurryWith(rtOpts.emptyDynamicLabels()))
72 73
 
73 74
 	return func(r *http.Request) (*http.Response, error) {
74 75
 		resp, err := next.RoundTrip(r)
75 76
 		if err == nil {
76
-			addWithExemplar(
77
-				counter.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
78
-				1,
79
-				rtOpts.getExemplarFn(r.Context()),
80
-			)
77
+			l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
78
+			for label, resolve := range rtOpts.extraLabelsFromCtx {
79
+				l[label] = resolve(resp.Request.Context())
80
+			}
81
+			addWithExemplar(counter.With(l), 1, rtOpts.getExemplarFn(r.Context()))
81 82
 		}
82 83
 		return resp, err
83 84
 	}
... ...
@@ -110,17 +111,18 @@ func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundT
110 110
 		o.apply(rtOpts)
111 111
 	}
112 112
 
113
-	code, method := checkLabels(obs)
113
+	// Curry the observer with dynamic labels before checking the remaining labels.
114
+	code, method := checkLabels(obs.MustCurryWith(rtOpts.emptyDynamicLabels()))
114 115
 
115 116
 	return func(r *http.Request) (*http.Response, error) {
116 117
 		start := time.Now()
117 118
 		resp, err := next.RoundTrip(r)
118 119
 		if err == nil {
119
-			observeWithExemplar(
120
-				obs.With(labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)),
121
-				time.Since(start).Seconds(),
122
-				rtOpts.getExemplarFn(r.Context()),
123
-			)
120
+			l := labels(code, method, r.Method, resp.StatusCode, rtOpts.extraMethods...)
121
+			for label, resolve := range rtOpts.extraLabelsFromCtx {
122
+				l[label] = resolve(resp.Request.Context())
123
+			}
124
+			observeWithExemplar(obs.With(l), time.Since(start).Seconds(), rtOpts.getExemplarFn(r.Context()))
124 125
 		}
125 126
 		return resp, err
126 127
 	}
... ...
@@ -87,7 +87,8 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
87 87
 		o.apply(hOpts)
88 88
 	}
89 89
 
90
-	code, method := checkLabels(obs)
90
+	// Curry the observer with dynamic labels before checking the remaining labels.
91
+	code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
91 92
 
92 93
 	if code {
93 94
 		return func(w http.ResponseWriter, r *http.Request) {
... ...
@@ -95,23 +96,22 @@ func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler, op
95 95
 			d := newDelegator(w, nil)
96 96
 			next.ServeHTTP(d, r)
97 97
 
98
-			observeWithExemplar(
99
-				obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
100
-				time.Since(now).Seconds(),
101
-				hOpts.getExemplarFn(r.Context()),
102
-			)
98
+			l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
99
+			for label, resolve := range hOpts.extraLabelsFromCtx {
100
+				l[label] = resolve(r.Context())
101
+			}
102
+			observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
103 103
 		}
104 104
 	}
105 105
 
106 106
 	return func(w http.ResponseWriter, r *http.Request) {
107 107
 		now := time.Now()
108 108
 		next.ServeHTTP(w, r)
109
-
110
-		observeWithExemplar(
111
-			obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
112
-			time.Since(now).Seconds(),
113
-			hOpts.getExemplarFn(r.Context()),
114
-		)
109
+		l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
110
+		for label, resolve := range hOpts.extraLabelsFromCtx {
111
+			l[label] = resolve(r.Context())
112
+		}
113
+		observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
115 114
 	}
116 115
 }
117 116
 
... ...
@@ -138,28 +138,30 @@ func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler,
138 138
 		o.apply(hOpts)
139 139
 	}
140 140
 
141
-	code, method := checkLabels(counter)
141
+	// Curry the counter with dynamic labels before checking the remaining labels.
142
+	code, method := checkLabels(counter.MustCurryWith(hOpts.emptyDynamicLabels()))
142 143
 
143 144
 	if code {
144 145
 		return func(w http.ResponseWriter, r *http.Request) {
145 146
 			d := newDelegator(w, nil)
146 147
 			next.ServeHTTP(d, r)
147 148
 
148
-			addWithExemplar(
149
-				counter.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
150
-				1,
151
-				hOpts.getExemplarFn(r.Context()),
152
-			)
149
+			l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
150
+			for label, resolve := range hOpts.extraLabelsFromCtx {
151
+				l[label] = resolve(r.Context())
152
+			}
153
+			addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
153 154
 		}
154 155
 	}
155 156
 
156 157
 	return func(w http.ResponseWriter, r *http.Request) {
157 158
 		next.ServeHTTP(w, r)
158
-		addWithExemplar(
159
-			counter.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
160
-			1,
161
-			hOpts.getExemplarFn(r.Context()),
162
-		)
159
+
160
+		l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
161
+		for label, resolve := range hOpts.extraLabelsFromCtx {
162
+			l[label] = resolve(r.Context())
163
+		}
164
+		addWithExemplar(counter.With(l), 1, hOpts.getExemplarFn(r.Context()))
163 165
 	}
164 166
 }
165 167
 
... ...
@@ -191,16 +193,17 @@ func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Ha
191 191
 		o.apply(hOpts)
192 192
 	}
193 193
 
194
-	code, method := checkLabels(obs)
194
+	// Curry the observer with dynamic labels before checking the remaining labels.
195
+	code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
195 196
 
196 197
 	return func(w http.ResponseWriter, r *http.Request) {
197 198
 		now := time.Now()
198 199
 		d := newDelegator(w, func(status int) {
199
-			observeWithExemplar(
200
-				obs.With(labels(code, method, r.Method, status, hOpts.extraMethods...)),
201
-				time.Since(now).Seconds(),
202
-				hOpts.getExemplarFn(r.Context()),
203
-			)
200
+			l := labels(code, method, r.Method, status, hOpts.extraMethods...)
201
+			for label, resolve := range hOpts.extraLabelsFromCtx {
202
+				l[label] = resolve(r.Context())
203
+			}
204
+			observeWithExemplar(obs.With(l), time.Since(now).Seconds(), hOpts.getExemplarFn(r.Context()))
204 205
 		})
205 206
 		next.ServeHTTP(d, r)
206 207
 	}
... ...
@@ -231,28 +234,32 @@ func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler,
231 231
 		o.apply(hOpts)
232 232
 	}
233 233
 
234
-	code, method := checkLabels(obs)
234
+	// Curry the observer with dynamic labels before checking the remaining labels.
235
+	code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
236
+
235 237
 	if code {
236 238
 		return func(w http.ResponseWriter, r *http.Request) {
237 239
 			d := newDelegator(w, nil)
238 240
 			next.ServeHTTP(d, r)
239 241
 			size := computeApproximateRequestSize(r)
240
-			observeWithExemplar(
241
-				obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
242
-				float64(size),
243
-				hOpts.getExemplarFn(r.Context()),
244
-			)
242
+
243
+			l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
244
+			for label, resolve := range hOpts.extraLabelsFromCtx {
245
+				l[label] = resolve(r.Context())
246
+			}
247
+			observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
245 248
 		}
246 249
 	}
247 250
 
248 251
 	return func(w http.ResponseWriter, r *http.Request) {
249 252
 		next.ServeHTTP(w, r)
250 253
 		size := computeApproximateRequestSize(r)
251
-		observeWithExemplar(
252
-			obs.With(labels(code, method, r.Method, 0, hOpts.extraMethods...)),
253
-			float64(size),
254
-			hOpts.getExemplarFn(r.Context()),
255
-		)
254
+
255
+		l := labels(code, method, r.Method, 0, hOpts.extraMethods...)
256
+		for label, resolve := range hOpts.extraLabelsFromCtx {
257
+			l[label] = resolve(r.Context())
258
+		}
259
+		observeWithExemplar(obs.With(l), float64(size), hOpts.getExemplarFn(r.Context()))
256 260
 	}
257 261
 }
258 262
 
... ...
@@ -281,16 +288,18 @@ func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler
281 281
 		o.apply(hOpts)
282 282
 	}
283 283
 
284
-	code, method := checkLabels(obs)
284
+	// Curry the observer with dynamic labels before checking the remaining labels.
285
+	code, method := checkLabels(obs.MustCurryWith(hOpts.emptyDynamicLabels()))
285 286
 
286 287
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
287 288
 		d := newDelegator(w, nil)
288 289
 		next.ServeHTTP(d, r)
289
-		observeWithExemplar(
290
-			obs.With(labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)),
291
-			float64(d.Written()),
292
-			hOpts.getExemplarFn(r.Context()),
293
-		)
290
+
291
+		l := labels(code, method, r.Method, d.Status(), hOpts.extraMethods...)
292
+		for label, resolve := range hOpts.extraLabelsFromCtx {
293
+			l[label] = resolve(r.Context())
294
+		}
295
+		observeWithExemplar(obs.With(l), float64(d.Written()), hOpts.getExemplarFn(r.Context()))
294 296
 	})
295 297
 }
296 298
 
... ...
@@ -380,15 +389,12 @@ func isLabelCurried(c prometheus.Collector, label string) bool {
380 380
 	return true
381 381
 }
382 382
 
383
-// emptyLabels is a one-time allocation for non-partitioned metrics to avoid
384
-// unnecessary allocations on each request.
385
-var emptyLabels = prometheus.Labels{}
386
-
387 383
 func labels(code, method bool, reqMethod string, status int, extraMethods ...string) prometheus.Labels {
384
+	labels := prometheus.Labels{}
385
+
388 386
 	if !(code || method) {
389
-		return emptyLabels
387
+		return labels
390 388
 	}
391
-	labels := prometheus.Labels{}
392 389
 
393 390
 	if code {
394 391
 		labels["code"] = sanitizeCode(status)
... ...
@@ -24,14 +24,32 @@ type Option interface {
24 24
 	apply(*options)
25 25
 }
26 26
 
27
+// LabelValueFromCtx are used to compute the label value from request context.
28
+// Context can be filled with values from request through middleware.
29
+type LabelValueFromCtx func(ctx context.Context) string
30
+
27 31
 // options store options for both a handler or round tripper.
28 32
 type options struct {
29
-	extraMethods  []string
30
-	getExemplarFn func(requestCtx context.Context) prometheus.Labels
33
+	extraMethods       []string
34
+	getExemplarFn      func(requestCtx context.Context) prometheus.Labels
35
+	extraLabelsFromCtx map[string]LabelValueFromCtx
31 36
 }
32 37
 
33 38
 func defaultOptions() *options {
34
-	return &options{getExemplarFn: func(ctx context.Context) prometheus.Labels { return nil }}
39
+	return &options{
40
+		getExemplarFn:      func(ctx context.Context) prometheus.Labels { return nil },
41
+		extraLabelsFromCtx: map[string]LabelValueFromCtx{},
42
+	}
43
+}
44
+
45
+func (o *options) emptyDynamicLabels() prometheus.Labels {
46
+	labels := prometheus.Labels{}
47
+
48
+	for label := range o.extraLabelsFromCtx {
49
+		labels[label] = ""
50
+	}
51
+
52
+	return labels
35 53
 }
36 54
 
37 55
 type optionApplyFunc func(*options)
... ...
@@ -48,11 +66,19 @@ func WithExtraMethods(methods ...string) Option {
48 48
 	})
49 49
 }
50 50
 
51
-// WithExemplarFromContext adds allows to put a hook to all counter and histogram metrics.
52
-// If the hook function returns non-nil labels, exemplars will be added for that request, otherwise metric
53
-// will get instrumented without exemplar.
51
+// WithExemplarFromContext allows to inject function that will get exemplar from context that will be put to counter and histogram metrics.
52
+// If the function returns nil labels or the metric does not support exemplars, no exemplar will be added (noop), but
53
+// metric will continue to observe/increment.
54 54
 func WithExemplarFromContext(getExemplarFn func(requestCtx context.Context) prometheus.Labels) Option {
55 55
 	return optionApplyFunc(func(o *options) {
56 56
 		o.getExemplarFn = getExemplarFn
57 57
 	})
58 58
 }
59
+
60
+// WithLabelFromCtx registers a label for dynamic resolution with access to context.
61
+// See the example for ExampleInstrumentHandlerWithLabelResolver for example usage
62
+func WithLabelFromCtx(name string, valueFn LabelValueFromCtx) Option {
63
+	return optionApplyFunc(func(o *options) {
64
+		o.extraLabelsFromCtx[name] = valueFn
65
+	})
66
+}
... ...
@@ -21,18 +21,17 @@ import (
21 21
 	"path/filepath"
22 22
 	"runtime"
23 23
 	"sort"
24
+	"strconv"
24 25
 	"strings"
25 26
 	"sync"
26 27
 	"unicode/utf8"
27 28
 
28
-	"github.com/cespare/xxhash/v2"
29
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
30
-	"github.com/golang/protobuf/proto"
31
-	"github.com/prometheus/common/expfmt"
29
+	"github.com/prometheus/client_golang/prometheus/internal"
32 30
 
31
+	"github.com/cespare/xxhash/v2"
33 32
 	dto "github.com/prometheus/client_model/go"
34
-
35
-	"github.com/prometheus/client_golang/prometheus/internal"
33
+	"github.com/prometheus/common/expfmt"
34
+	"google.golang.org/protobuf/proto"
36 35
 )
37 36
 
38 37
 const (
... ...
@@ -549,7 +548,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
549 549
 			goroutineBudget--
550 550
 			runtime.Gosched()
551 551
 		}
552
-		// Once both checkedMetricChan and uncheckdMetricChan are closed
552
+		// Once both checkedMetricChan and uncheckedMetricChan are closed
553 553
 		// and drained, the contraption above will nil out cmc and umc,
554 554
 		// and then we can leave the collect loop here.
555 555
 		if cmc == nil && umc == nil {
... ...
@@ -933,6 +932,10 @@ func checkMetricConsistency(
933 933
 		h.WriteString(lp.GetValue())
934 934
 		h.Write(separatorByteSlice)
935 935
 	}
936
+	if dtoMetric.TimestampMs != nil {
937
+		h.WriteString(strconv.FormatInt(*(dtoMetric.TimestampMs), 10))
938
+		h.Write(separatorByteSlice)
939
+	}
936 940
 	hSum := h.Sum64()
937 941
 	if _, exists := metricHashes[hSum]; exists {
938 942
 		return fmt.Errorf(
... ...
@@ -960,7 +963,7 @@ func checkDescConsistency(
960 960
 	// Is the desc consistent with the content of the metric?
961 961
 	lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
962 962
 	copy(lpsFromDesc, desc.constLabelPairs)
963
-	for _, l := range desc.variableLabels {
963
+	for _, l := range desc.variableLabels.names {
964 964
 		lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
965 965
 			Name: proto.String(l),
966 966
 		})
... ...
@@ -22,11 +22,11 @@ import (
22 22
 	"sync/atomic"
23 23
 	"time"
24 24
 
25
-	"github.com/beorn7/perks/quantile"
26
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
27
-	"github.com/golang/protobuf/proto"
28
-
29 25
 	dto "github.com/prometheus/client_model/go"
26
+
27
+	"github.com/beorn7/perks/quantile"
28
+	"google.golang.org/protobuf/proto"
29
+	"google.golang.org/protobuf/types/known/timestamppb"
30 30
 )
31 31
 
32 32
 // quantileLabel is used for the label that defines the quantile in a
... ...
@@ -146,6 +146,21 @@ type SummaryOpts struct {
146 146
 	// is the internal buffer size of the underlying package
147 147
 	// "github.com/bmizerany/perks/quantile").
148 148
 	BufCap uint32
149
+
150
+	// now is for testing purposes, by default it's time.Now.
151
+	now func() time.Time
152
+}
153
+
154
+// SummaryVecOpts bundles the options to create a SummaryVec metric.
155
+// It is mandatory to set SummaryOpts, see there for mandatory fields. VariableLabels
156
+// is optional and can safely be left to its default value.
157
+type SummaryVecOpts struct {
158
+	SummaryOpts
159
+
160
+	// VariableLabels are used to partition the metric vector by the given set
161
+	// of labels. Each label value will be constrained with the optional Constraint
162
+	// function, if provided.
163
+	VariableLabels ConstrainableLabels
149 164
 }
150 165
 
151 166
 // Problem with the sliding-window decay algorithm... The Merge method of
... ...
@@ -177,11 +192,11 @@ func NewSummary(opts SummaryOpts) Summary {
177 177
 }
178 178
 
179 179
 func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
180
-	if len(desc.variableLabels) != len(labelValues) {
181
-		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
180
+	if len(desc.variableLabels.names) != len(labelValues) {
181
+		panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels.names, labelValues))
182 182
 	}
183 183
 
184
-	for _, n := range desc.variableLabels {
184
+	for _, n := range desc.variableLabels.names {
185 185
 		if n == quantileLabel {
186 186
 			panic(errQuantileLabelNotAllowed)
187 187
 		}
... ...
@@ -211,6 +226,9 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
211 211
 		opts.BufCap = DefBufCap
212 212
 	}
213 213
 
214
+	if opts.now == nil {
215
+		opts.now = time.Now
216
+	}
214 217
 	if len(opts.Objectives) == 0 {
215 218
 		// Use the lock-free implementation of a Summary without objectives.
216 219
 		s := &noObjectivesSummary{
... ...
@@ -219,6 +237,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
219 219
 			counts:     [2]*summaryCounts{{}, {}},
220 220
 		}
221 221
 		s.init(s) // Init self-collection.
222
+		s.createdTs = timestamppb.New(opts.now())
222 223
 		return s
223 224
 	}
224 225
 
... ...
@@ -234,7 +253,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
234 234
 		coldBuf:        make([]float64, 0, opts.BufCap),
235 235
 		streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets),
236 236
 	}
237
-	s.headStreamExpTime = time.Now().Add(s.streamDuration)
237
+	s.headStreamExpTime = opts.now().Add(s.streamDuration)
238 238
 	s.hotBufExpTime = s.headStreamExpTime
239 239
 
240 240
 	for i := uint32(0); i < opts.AgeBuckets; i++ {
... ...
@@ -248,6 +267,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
248 248
 	sort.Float64s(s.sortedObjectives)
249 249
 
250 250
 	s.init(s) // Init self-collection.
251
+	s.createdTs = timestamppb.New(opts.now())
251 252
 	return s
252 253
 }
253 254
 
... ...
@@ -275,6 +295,8 @@ type summary struct {
275 275
 	headStream                       *quantile.Stream
276 276
 	headStreamIdx                    int
277 277
 	headStreamExpTime, hotBufExpTime time.Time
278
+
279
+	createdTs *timestamppb.Timestamp
278 280
 }
279 281
 
280 282
 func (s *summary) Desc() *Desc {
... ...
@@ -296,7 +318,9 @@ func (s *summary) Observe(v float64) {
296 296
 }
297 297
 
298 298
 func (s *summary) Write(out *dto.Metric) error {
299
-	sum := &dto.Summary{}
299
+	sum := &dto.Summary{
300
+		CreatedTimestamp: s.createdTs,
301
+	}
300 302
 	qs := make([]*dto.Quantile, 0, len(s.objectives))
301 303
 
302 304
 	s.bufMtx.Lock()
... ...
@@ -429,6 +453,8 @@ type noObjectivesSummary struct {
429 429
 	counts [2]*summaryCounts
430 430
 
431 431
 	labelPairs []*dto.LabelPair
432
+
433
+	createdTs *timestamppb.Timestamp
432 434
 }
433 435
 
434 436
 func (s *noObjectivesSummary) Desc() *Desc {
... ...
@@ -479,8 +505,9 @@ func (s *noObjectivesSummary) Write(out *dto.Metric) error {
479 479
 	}
480 480
 
481 481
 	sum := &dto.Summary{
482
-		SampleCount: proto.Uint64(count),
483
-		SampleSum:   proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
482
+		SampleCount:      proto.Uint64(count),
483
+		SampleSum:        proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
484
+		CreatedTimestamp: s.createdTs,
484 485
 	}
485 486
 
486 487
 	out.Summary = sum
... ...
@@ -530,20 +557,28 @@ type SummaryVec struct {
530 530
 // it is handled by the Prometheus server internally, “quantile” is an illegal
531 531
 // label name. NewSummaryVec will panic if this label name is used.
532 532
 func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
533
-	for _, ln := range labelNames {
533
+	return V2.NewSummaryVec(SummaryVecOpts{
534
+		SummaryOpts:    opts,
535
+		VariableLabels: UnconstrainedLabels(labelNames),
536
+	})
537
+}
538
+
539
+// NewSummaryVec creates a new SummaryVec based on the provided SummaryVecOpts.
540
+func (v2) NewSummaryVec(opts SummaryVecOpts) *SummaryVec {
541
+	for _, ln := range opts.VariableLabels.labelNames() {
534 542
 		if ln == quantileLabel {
535 543
 			panic(errQuantileLabelNotAllowed)
536 544
 		}
537 545
 	}
538
-	desc := NewDesc(
546
+	desc := V2.NewDesc(
539 547
 		BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
540 548
 		opts.Help,
541
-		labelNames,
549
+		opts.VariableLabels,
542 550
 		opts.ConstLabels,
543 551
 	)
544 552
 	return &SummaryVec{
545 553
 		MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
546
-			return newSummary(desc, opts, lvs...)
554
+			return newSummary(desc, opts.SummaryOpts, lvs...)
547 555
 		}),
548 556
 	}
549 557
 }
... ...
@@ -662,6 +697,7 @@ type constSummary struct {
662 662
 	sum        float64
663 663
 	quantiles  map[float64]float64
664 664
 	labelPairs []*dto.LabelPair
665
+	createdTs  *timestamppb.Timestamp
665 666
 }
666 667
 
667 668
 func (s *constSummary) Desc() *Desc {
... ...
@@ -669,7 +705,9 @@ func (s *constSummary) Desc() *Desc {
669 669
 }
670 670
 
671 671
 func (s *constSummary) Write(out *dto.Metric) error {
672
-	sum := &dto.Summary{}
672
+	sum := &dto.Summary{
673
+		CreatedTimestamp: s.createdTs,
674
+	}
673 675
 	qs := make([]*dto.Quantile, 0, len(s.quantiles))
674 676
 
675 677
 	sum.SampleCount = proto.Uint64(s.count)
... ...
@@ -718,7 +756,7 @@ func NewConstSummary(
718 718
 	if desc.err != nil {
719 719
 		return nil, desc.err
720 720
 	}
721
-	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
721
+	if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
722 722
 		return nil, err
723 723
 	}
724 724
 	return &constSummary{
... ...
@@ -23,7 +23,9 @@ type Timer struct {
23 23
 }
24 24
 
25 25
 // NewTimer creates a new Timer. The provided Observer is used to observe a
26
-// duration in seconds. Timer is usually used to time a function call in the
26
+// duration in seconds. If the Observer implements ExemplarObserver, passing exemplar
27
+// later on will be also supported.
28
+// Timer is usually used to time a function call in the
27 29
 // following way:
28 30
 //
29 31
 //	func TimeMe() {
... ...
@@ -31,6 +33,14 @@ type Timer struct {
31 31
 //	    defer timer.ObserveDuration()
32 32
 //	    // Do actual work.
33 33
 //	}
34
+//
35
+// or
36
+//
37
+//	func TimeMeWithExemplar() {
38
+//		    timer := NewTimer(myHistogram)
39
+//		    defer timer.ObserveDurationWithExemplar(exemplar)
40
+//		    // Do actual work.
41
+//		}
34 42
 func NewTimer(o Observer) *Timer {
35 43
 	return &Timer{
36 44
 		begin:    time.Now(),
... ...
@@ -53,3 +63,19 @@ func (t *Timer) ObserveDuration() time.Duration {
53 53
 	}
54 54
 	return d
55 55
 }
56
+
57
+// ObserveDurationWithExemplar is like ObserveDuration, but it will also
58
+// observe exemplar with the duration unless exemplar is nil or provided Observer can't
59
+// be casted to ExemplarObserver.
60
+func (t *Timer) ObserveDurationWithExemplar(exemplar Labels) time.Duration {
61
+	d := time.Since(t.begin)
62
+	eo, ok := t.observer.(ExemplarObserver)
63
+	if ok && exemplar != nil {
64
+		eo.ObserveWithExemplar(d.Seconds(), exemplar)
65
+		return d
66
+	}
67
+	if t.observer != nil {
68
+		t.observer.Observe(d.Seconds())
69
+	}
70
+	return d
71
+}
... ...
@@ -14,18 +14,17 @@
14 14
 package prometheus
15 15
 
16 16
 import (
17
+	"errors"
17 18
 	"fmt"
18 19
 	"sort"
19 20
 	"time"
20 21
 	"unicode/utf8"
21 22
 
22
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
23
-	"github.com/golang/protobuf/proto"
24
-	"google.golang.org/protobuf/types/known/timestamppb"
25
-
26 23
 	"github.com/prometheus/client_golang/prometheus/internal"
27 24
 
28 25
 	dto "github.com/prometheus/client_model/go"
26
+	"google.golang.org/protobuf/proto"
27
+	"google.golang.org/protobuf/types/known/timestamppb"
29 28
 )
30 29
 
31 30
 // ValueType is an enumeration of metric types that represent a simple value.
... ...
@@ -93,7 +92,7 @@ func (v *valueFunc) Desc() *Desc {
93 93
 }
94 94
 
95 95
 func (v *valueFunc) Write(out *dto.Metric) error {
96
-	return populateMetric(v.valType, v.function(), v.labelPairs, nil, out)
96
+	return populateMetric(v.valType, v.function(), v.labelPairs, nil, out, nil)
97 97
 }
98 98
 
99 99
 // NewConstMetric returns a metric with one fixed value that cannot be
... ...
@@ -107,12 +106,12 @@ func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues
107 107
 	if desc.err != nil {
108 108
 		return nil, desc.err
109 109
 	}
110
-	if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
110
+	if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
111 111
 		return nil, err
112 112
 	}
113 113
 
114 114
 	metric := &dto.Metric{}
115
-	if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric); err != nil {
115
+	if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric, nil); err != nil {
116 116
 		return nil, err
117 117
 	}
118 118
 
... ...
@@ -132,6 +131,43 @@ func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelVal
132 132
 	return m
133 133
 }
134 134
 
135
+// NewConstMetricWithCreatedTimestamp does the same thing as NewConstMetric, but generates Counters
136
+// with created timestamp set and returns an error for other metric types.
137
+func NewConstMetricWithCreatedTimestamp(desc *Desc, valueType ValueType, value float64, ct time.Time, labelValues ...string) (Metric, error) {
138
+	if desc.err != nil {
139
+		return nil, desc.err
140
+	}
141
+	if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
142
+		return nil, err
143
+	}
144
+	switch valueType {
145
+	case CounterValue:
146
+		break
147
+	default:
148
+		return nil, errors.New("created timestamps are only supported for counters")
149
+	}
150
+
151
+	metric := &dto.Metric{}
152
+	if err := populateMetric(valueType, value, MakeLabelPairs(desc, labelValues), nil, metric, timestamppb.New(ct)); err != nil {
153
+		return nil, err
154
+	}
155
+
156
+	return &constMetric{
157
+		desc:   desc,
158
+		metric: metric,
159
+	}, nil
160
+}
161
+
162
+// MustNewConstMetricWithCreatedTimestamp is a version of NewConstMetricWithCreatedTimestamp that panics where
163
+// NewConstMetricWithCreatedTimestamp would have returned an error.
164
+func MustNewConstMetricWithCreatedTimestamp(desc *Desc, valueType ValueType, value float64, ct time.Time, labelValues ...string) Metric {
165
+	m, err := NewConstMetricWithCreatedTimestamp(desc, valueType, value, ct, labelValues...)
166
+	if err != nil {
167
+		panic(err)
168
+	}
169
+	return m
170
+}
171
+
135 172
 type constMetric struct {
136 173
 	desc   *Desc
137 174
 	metric *dto.Metric
... ...
@@ -155,11 +191,12 @@ func populateMetric(
155 155
 	labelPairs []*dto.LabelPair,
156 156
 	e *dto.Exemplar,
157 157
 	m *dto.Metric,
158
+	ct *timestamppb.Timestamp,
158 159
 ) error {
159 160
 	m.Label = labelPairs
160 161
 	switch t {
161 162
 	case CounterValue:
162
-		m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e}
163
+		m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e, CreatedTimestamp: ct}
163 164
 	case GaugeValue:
164 165
 		m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
165 166
 	case UntypedValue:
... ...
@@ -178,19 +215,19 @@ func populateMetric(
178 178
 // This function is only needed for custom Metric implementations. See MetricVec
179 179
 // example.
180 180
 func MakeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
181
-	totalLen := len(desc.variableLabels) + len(desc.constLabelPairs)
181
+	totalLen := len(desc.variableLabels.names) + len(desc.constLabelPairs)
182 182
 	if totalLen == 0 {
183 183
 		// Super fast path.
184 184
 		return nil
185 185
 	}
186
-	if len(desc.variableLabels) == 0 {
186
+	if len(desc.variableLabels.names) == 0 {
187 187
 		// Moderately fast path.
188 188
 		return desc.constLabelPairs
189 189
 	}
190 190
 	labelPairs := make([]*dto.LabelPair, 0, totalLen)
191
-	for i, n := range desc.variableLabels {
191
+	for i, l := range desc.variableLabels.names {
192 192
 		labelPairs = append(labelPairs, &dto.LabelPair{
193
-			Name:  proto.String(n),
193
+			Name:  proto.String(l),
194 194
 			Value: proto.String(labelValues[i]),
195 195
 		})
196 196
 	}
... ...
@@ -72,6 +72,8 @@ func NewMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
72 72
 // with a performance overhead (for creating and processing the Labels map).
73 73
 // See also the CounterVec example.
74 74
 func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
75
+	lvs = constrainLabelValues(m.desc, lvs, m.curry)
76
+
75 77
 	h, err := m.hashLabelValues(lvs)
76 78
 	if err != nil {
77 79
 		return false
... ...
@@ -91,6 +93,9 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
91 91
 // This method is used for the same purpose as DeleteLabelValues(...string). See
92 92
 // there for pros and cons of the two methods.
93 93
 func (m *MetricVec) Delete(labels Labels) bool {
94
+	labels, closer := constrainLabels(m.desc, labels)
95
+	defer closer()
96
+
94 97
 	h, err := m.hashLabels(labels)
95 98
 	if err != nil {
96 99
 		return false
... ...
@@ -106,6 +111,9 @@ func (m *MetricVec) Delete(labels Labels) bool {
106 106
 // Note that curried labels will never be matched if deleting from the curried vector.
107 107
 // To match curried labels with DeletePartialMatch, it must be called on the base vector.
108 108
 func (m *MetricVec) DeletePartialMatch(labels Labels) int {
109
+	labels, closer := constrainLabels(m.desc, labels)
110
+	defer closer()
111
+
109 112
 	return m.metricMap.deleteByLabels(labels, m.curry)
110 113
 }
111 114
 
... ...
@@ -144,11 +152,11 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
144 144
 		oldCurry = m.curry
145 145
 		iCurry   int
146 146
 	)
147
-	for i, label := range m.desc.variableLabels {
148
-		val, ok := labels[label]
147
+	for i, labelName := range m.desc.variableLabels.names {
148
+		val, ok := labels[labelName]
149 149
 		if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
150 150
 			if ok {
151
-				return nil, fmt.Errorf("label name %q is already curried", label)
151
+				return nil, fmt.Errorf("label name %q is already curried", labelName)
152 152
 			}
153 153
 			newCurry = append(newCurry, oldCurry[iCurry])
154 154
 			iCurry++
... ...
@@ -156,7 +164,10 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
156 156
 			if !ok {
157 157
 				continue // Label stays uncurried.
158 158
 			}
159
-			newCurry = append(newCurry, curriedLabelValue{i, val})
159
+			newCurry = append(newCurry, curriedLabelValue{
160
+				i,
161
+				m.desc.variableLabels.constrain(labelName, val),
162
+			})
160 163
 		}
161 164
 	}
162 165
 	if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
... ...
@@ -199,6 +210,7 @@ func (m *MetricVec) CurryWith(labels Labels) (*MetricVec, error) {
199 199
 // a wrapper around MetricVec, implementing a vector for a specific Metric
200 200
 // implementation, for example GaugeVec.
201 201
 func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
202
+	lvs = constrainLabelValues(m.desc, lvs, m.curry)
202 203
 	h, err := m.hashLabelValues(lvs)
203 204
 	if err != nil {
204 205
 		return nil, err
... ...
@@ -224,6 +236,9 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
224 224
 // around MetricVec, implementing a vector for a specific Metric implementation,
225 225
 // for example GaugeVec.
226 226
 func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
227
+	labels, closer := constrainLabels(m.desc, labels)
228
+	defer closer()
229
+
227 230
 	h, err := m.hashLabels(labels)
228 231
 	if err != nil {
229 232
 		return nil, err
... ...
@@ -233,7 +248,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
233 233
 }
234 234
 
235 235
 func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
236
-	if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
236
+	if err := validateLabelValues(vals, len(m.desc.variableLabels.names)-len(m.curry)); err != nil {
237 237
 		return 0, err
238 238
 	}
239 239
 
... ...
@@ -242,7 +257,7 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
242 242
 		curry         = m.curry
243 243
 		iVals, iCurry int
244 244
 	)
245
-	for i := 0; i < len(m.desc.variableLabels); i++ {
245
+	for i := 0; i < len(m.desc.variableLabels.names); i++ {
246 246
 		if iCurry < len(curry) && curry[iCurry].index == i {
247 247
 			h = m.hashAdd(h, curry[iCurry].value)
248 248
 			iCurry++
... ...
@@ -256,7 +271,7 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
256 256
 }
257 257
 
258 258
 func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
259
-	if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
259
+	if err := validateValuesInLabels(labels, len(m.desc.variableLabels.names)-len(m.curry)); err != nil {
260 260
 		return 0, err
261 261
 	}
262 262
 
... ...
@@ -265,17 +280,17 @@ func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
265 265
 		curry  = m.curry
266 266
 		iCurry int
267 267
 	)
268
-	for i, label := range m.desc.variableLabels {
269
-		val, ok := labels[label]
268
+	for i, labelName := range m.desc.variableLabels.names {
269
+		val, ok := labels[labelName]
270 270
 		if iCurry < len(curry) && curry[iCurry].index == i {
271 271
 			if ok {
272
-				return 0, fmt.Errorf("label name %q is already curried", label)
272
+				return 0, fmt.Errorf("label name %q is already curried", labelName)
273 273
 			}
274 274
 			h = m.hashAdd(h, curry[iCurry].value)
275 275
 			iCurry++
276 276
 		} else {
277 277
 			if !ok {
278
-				return 0, fmt.Errorf("label name %q missing in label map", label)
278
+				return 0, fmt.Errorf("label name %q missing in label map", labelName)
279 279
 			}
280 280
 			h = m.hashAdd(h, val)
281 281
 		}
... ...
@@ -453,7 +468,7 @@ func valueMatchesVariableOrCurriedValue(targetValue string, index int, values []
453 453
 func matchPartialLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
454 454
 	for l, v := range labels {
455 455
 		// Check if the target label exists in our metrics and get the index.
456
-		varLabelIndex, validLabel := indexOf(l, desc.variableLabels)
456
+		varLabelIndex, validLabel := indexOf(l, desc.variableLabels.names)
457 457
 		if validLabel {
458 458
 			// Check the value of that label against the target value.
459 459
 			// We don't consider curried values in partial matches.
... ...
@@ -597,7 +612,7 @@ func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabe
597 597
 		return false
598 598
 	}
599 599
 	iCurry := 0
600
-	for i, k := range desc.variableLabels {
600
+	for i, k := range desc.variableLabels.names {
601 601
 		if iCurry < len(curry) && curry[iCurry].index == i {
602 602
 			if values[i] != curry[iCurry].value {
603 603
 				return false
... ...
@@ -615,7 +630,7 @@ func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabe
615 615
 func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string {
616 616
 	labelValues := make([]string, len(labels)+len(curry))
617 617
 	iCurry := 0
618
-	for i, k := range desc.variableLabels {
618
+	for i, k := range desc.variableLabels.names {
619 619
 		if iCurry < len(curry) && curry[iCurry].index == i {
620 620
 			labelValues[i] = curry[iCurry].value
621 621
 			iCurry++
... ...
@@ -640,3 +655,55 @@ func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
640 640
 	}
641 641
 	return labelValues
642 642
 }
643
+
644
+var labelsPool = &sync.Pool{
645
+	New: func() interface{} {
646
+		return make(Labels)
647
+	},
648
+}
649
+
650
+func constrainLabels(desc *Desc, labels Labels) (Labels, func()) {
651
+	if len(desc.variableLabels.labelConstraints) == 0 {
652
+		// Fast path when there's no constraints
653
+		return labels, func() {}
654
+	}
655
+
656
+	constrainedLabels := labelsPool.Get().(Labels)
657
+	for l, v := range labels {
658
+		constrainedLabels[l] = desc.variableLabels.constrain(l, v)
659
+	}
660
+
661
+	return constrainedLabels, func() {
662
+		for k := range constrainedLabels {
663
+			delete(constrainedLabels, k)
664
+		}
665
+		labelsPool.Put(constrainedLabels)
666
+	}
667
+}
668
+
669
+func constrainLabelValues(desc *Desc, lvs []string, curry []curriedLabelValue) []string {
670
+	if len(desc.variableLabels.labelConstraints) == 0 {
671
+		// Fast path when there's no constraints
672
+		return lvs
673
+	}
674
+
675
+	constrainedValues := make([]string, len(lvs))
676
+	var iCurry, iLVs int
677
+	for i := 0; i < len(lvs)+len(curry); i++ {
678
+		if iCurry < len(curry) && curry[iCurry].index == i {
679
+			iCurry++
680
+			continue
681
+		}
682
+
683
+		if i < len(desc.variableLabels.names) {
684
+			constrainedValues[iLVs] = desc.variableLabels.constrain(
685
+				desc.variableLabels.names[i],
686
+				lvs[iLVs],
687
+			)
688
+		} else {
689
+			constrainedValues[iLVs] = lvs[iLVs]
690
+		}
691
+		iLVs++
692
+	}
693
+	return constrainedValues
694
+}
643 695
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// Copyright 2022 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package prometheus
14
+
15
+type v2 struct{}
16
+
17
+// V2 is a struct that can be referenced to access experimental API that might
18
+// be present in v2 of client golang someday. It offers extended functionality
19
+// of v1 with slightly changed API. It is acceptable to use some pieces from v1
20
+// and e.g `prometheus.NewGauge` and some from v2 e.g. `prometheus.V2.NewDesc`
21
+// in the same codebase.
22
+var V2 = v2{}
... ...
@@ -17,12 +17,10 @@ import (
17 17
 	"fmt"
18 18
 	"sort"
19 19
 
20
-	//nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
21
-	"github.com/golang/protobuf/proto"
20
+	"github.com/prometheus/client_golang/prometheus/internal"
22 21
 
23 22
 	dto "github.com/prometheus/client_model/go"
24
-
25
-	"github.com/prometheus/client_golang/prometheus/internal"
23
+	"google.golang.org/protobuf/proto"
26 24
 )
27 25
 
28 26
 // WrapRegistererWith returns a Registerer wrapping the provided
... ...
@@ -206,7 +204,7 @@ func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
206 206
 		constLabels[ln] = lv
207 207
 	}
208 208
 	// NewDesc will do remaining validations.
209
-	newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
209
+	newDesc := V2.NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
210 210
 	// Propagate errors if there was any. This will override any errer
211 211
 	// created by NewDesc above, i.e. earlier errors get precedence.
212 212
 	if desc.err != nil {
... ...
@@ -1,25 +1,38 @@
1
+// Copyright 2013 Prometheus Team
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 // Code generated by protoc-gen-go. DO NOT EDIT.
15
+// versions:
16
+// 	protoc-gen-go v1.30.0
17
+// 	protoc        v3.20.3
2 18
 // source: io/prometheus/client/metrics.proto
3 19
 
4 20
 package io_prometheus_client
5 21
 
6 22
 import (
7
-	fmt "fmt"
8
-	proto "github.com/golang/protobuf/proto"
9
-	timestamp "github.com/golang/protobuf/ptypes/timestamp"
10
-	math "math"
23
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
24
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
25
+	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
26
+	reflect "reflect"
27
+	sync "sync"
11 28
 )
12 29
 
13
-// Reference imports to suppress errors if they are not otherwise used.
14
-var _ = proto.Marshal
15
-var _ = fmt.Errorf
16
-var _ = math.Inf
17
-
18
-// This is a compile-time assertion to ensure that this generated file
19
-// is compatible with the proto package it is being compiled against.
20
-// A compilation error at this line likely means your copy of the
21
-// proto package needs to be updated.
22
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
30
+const (
31
+	// Verify that this generated code is sufficiently up-to-date.
32
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
33
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
34
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
35
+)
23 36
 
24 37
 type MetricType int32
25 38
 
... ...
@@ -38,23 +51,25 @@ const (
38 38
 	MetricType_GAUGE_HISTOGRAM MetricType = 5
39 39
 )
40 40
 
41
-var MetricType_name = map[int32]string{
42
-	0: "COUNTER",
43
-	1: "GAUGE",
44
-	2: "SUMMARY",
45
-	3: "UNTYPED",
46
-	4: "HISTOGRAM",
47
-	5: "GAUGE_HISTOGRAM",
48
-}
49
-
50
-var MetricType_value = map[string]int32{
51
-	"COUNTER":         0,
52
-	"GAUGE":           1,
53
-	"SUMMARY":         2,
54
-	"UNTYPED":         3,
55
-	"HISTOGRAM":       4,
56
-	"GAUGE_HISTOGRAM": 5,
57
-}
41
+// Enum value maps for MetricType.
42
+var (
43
+	MetricType_name = map[int32]string{
44
+		0: "COUNTER",
45
+		1: "GAUGE",
46
+		2: "SUMMARY",
47
+		3: "UNTYPED",
48
+		4: "HISTOGRAM",
49
+		5: "GAUGE_HISTOGRAM",
50
+	}
51
+	MetricType_value = map[string]int32{
52
+		"COUNTER":         0,
53
+		"GAUGE":           1,
54
+		"SUMMARY":         2,
55
+		"UNTYPED":         3,
56
+		"HISTOGRAM":       4,
57
+		"GAUGE_HISTOGRAM": 5,
58
+	}
59
+)
58 60
 
59 61
 func (x MetricType) Enum() *MetricType {
60 62
 	p := new(MetricType)
... ...
@@ -63,449 +78,546 @@ func (x MetricType) Enum() *MetricType {
63 63
 }
64 64
 
65 65
 func (x MetricType) String() string {
66
-	return proto.EnumName(MetricType_name, int32(x))
66
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
67 67
 }
68 68
 
69
-func (x *MetricType) UnmarshalJSON(data []byte) error {
70
-	value, err := proto.UnmarshalJSONEnum(MetricType_value, data, "MetricType")
69
+func (MetricType) Descriptor() protoreflect.EnumDescriptor {
70
+	return file_io_prometheus_client_metrics_proto_enumTypes[0].Descriptor()
71
+}
72
+
73
+func (MetricType) Type() protoreflect.EnumType {
74
+	return &file_io_prometheus_client_metrics_proto_enumTypes[0]
75
+}
76
+
77
+func (x MetricType) Number() protoreflect.EnumNumber {
78
+	return protoreflect.EnumNumber(x)
79
+}
80
+
81
+// Deprecated: Do not use.
82
+func (x *MetricType) UnmarshalJSON(b []byte) error {
83
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
71 84
 	if err != nil {
72 85
 		return err
73 86
 	}
74
-	*x = MetricType(value)
87
+	*x = MetricType(num)
75 88
 	return nil
76 89
 }
77 90
 
91
+// Deprecated: Use MetricType.Descriptor instead.
78 92
 func (MetricType) EnumDescriptor() ([]byte, []int) {
79
-	return fileDescriptor_d1e5ddb18987a258, []int{0}
93
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{0}
80 94
 }
81 95
 
82 96
 type LabelPair struct {
83
-	Name                 *string  `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
84
-	Value                *string  `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
85
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
86
-	XXX_unrecognized     []byte   `json:"-"`
87
-	XXX_sizecache        int32    `json:"-"`
88
-}
97
+	state         protoimpl.MessageState
98
+	sizeCache     protoimpl.SizeCache
99
+	unknownFields protoimpl.UnknownFields
89 100
 
90
-func (m *LabelPair) Reset()         { *m = LabelPair{} }
91
-func (m *LabelPair) String() string { return proto.CompactTextString(m) }
92
-func (*LabelPair) ProtoMessage()    {}
93
-func (*LabelPair) Descriptor() ([]byte, []int) {
94
-	return fileDescriptor_d1e5ddb18987a258, []int{0}
101
+	Name  *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
102
+	Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
95 103
 }
96 104
 
97
-func (m *LabelPair) XXX_Unmarshal(b []byte) error {
98
-	return xxx_messageInfo_LabelPair.Unmarshal(m, b)
99
-}
100
-func (m *LabelPair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
101
-	return xxx_messageInfo_LabelPair.Marshal(b, m, deterministic)
102
-}
103
-func (m *LabelPair) XXX_Merge(src proto.Message) {
104
-	xxx_messageInfo_LabelPair.Merge(m, src)
105
+func (x *LabelPair) Reset() {
106
+	*x = LabelPair{}
107
+	if protoimpl.UnsafeEnabled {
108
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[0]
109
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
110
+		ms.StoreMessageInfo(mi)
111
+	}
105 112
 }
106
-func (m *LabelPair) XXX_Size() int {
107
-	return xxx_messageInfo_LabelPair.Size(m)
113
+
114
+func (x *LabelPair) String() string {
115
+	return protoimpl.X.MessageStringOf(x)
108 116
 }
109
-func (m *LabelPair) XXX_DiscardUnknown() {
110
-	xxx_messageInfo_LabelPair.DiscardUnknown(m)
117
+
118
+func (*LabelPair) ProtoMessage() {}
119
+
120
+func (x *LabelPair) ProtoReflect() protoreflect.Message {
121
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[0]
122
+	if protoimpl.UnsafeEnabled && x != nil {
123
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
124
+		if ms.LoadMessageInfo() == nil {
125
+			ms.StoreMessageInfo(mi)
126
+		}
127
+		return ms
128
+	}
129
+	return mi.MessageOf(x)
111 130
 }
112 131
 
113
-var xxx_messageInfo_LabelPair proto.InternalMessageInfo
132
+// Deprecated: Use LabelPair.ProtoReflect.Descriptor instead.
133
+func (*LabelPair) Descriptor() ([]byte, []int) {
134
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{0}
135
+}
114 136
 
115
-func (m *LabelPair) GetName() string {
116
-	if m != nil && m.Name != nil {
117
-		return *m.Name
137
+func (x *LabelPair) GetName() string {
138
+	if x != nil && x.Name != nil {
139
+		return *x.Name
118 140
 	}
119 141
 	return ""
120 142
 }
121 143
 
122
-func (m *LabelPair) GetValue() string {
123
-	if m != nil && m.Value != nil {
124
-		return *m.Value
144
+func (x *LabelPair) GetValue() string {
145
+	if x != nil && x.Value != nil {
146
+		return *x.Value
125 147
 	}
126 148
 	return ""
127 149
 }
128 150
 
129 151
 type Gauge struct {
130
-	Value                *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
131
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
132
-	XXX_unrecognized     []byte   `json:"-"`
133
-	XXX_sizecache        int32    `json:"-"`
134
-}
152
+	state         protoimpl.MessageState
153
+	sizeCache     protoimpl.SizeCache
154
+	unknownFields protoimpl.UnknownFields
135 155
 
136
-func (m *Gauge) Reset()         { *m = Gauge{} }
137
-func (m *Gauge) String() string { return proto.CompactTextString(m) }
138
-func (*Gauge) ProtoMessage()    {}
139
-func (*Gauge) Descriptor() ([]byte, []int) {
140
-	return fileDescriptor_d1e5ddb18987a258, []int{1}
156
+	Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
141 157
 }
142 158
 
143
-func (m *Gauge) XXX_Unmarshal(b []byte) error {
144
-	return xxx_messageInfo_Gauge.Unmarshal(m, b)
145
-}
146
-func (m *Gauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
147
-	return xxx_messageInfo_Gauge.Marshal(b, m, deterministic)
148
-}
149
-func (m *Gauge) XXX_Merge(src proto.Message) {
150
-	xxx_messageInfo_Gauge.Merge(m, src)
159
+func (x *Gauge) Reset() {
160
+	*x = Gauge{}
161
+	if protoimpl.UnsafeEnabled {
162
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[1]
163
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
164
+		ms.StoreMessageInfo(mi)
165
+	}
151 166
 }
152
-func (m *Gauge) XXX_Size() int {
153
-	return xxx_messageInfo_Gauge.Size(m)
167
+
168
+func (x *Gauge) String() string {
169
+	return protoimpl.X.MessageStringOf(x)
154 170
 }
155
-func (m *Gauge) XXX_DiscardUnknown() {
156
-	xxx_messageInfo_Gauge.DiscardUnknown(m)
171
+
172
+func (*Gauge) ProtoMessage() {}
173
+
174
+func (x *Gauge) ProtoReflect() protoreflect.Message {
175
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[1]
176
+	if protoimpl.UnsafeEnabled && x != nil {
177
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
178
+		if ms.LoadMessageInfo() == nil {
179
+			ms.StoreMessageInfo(mi)
180
+		}
181
+		return ms
182
+	}
183
+	return mi.MessageOf(x)
157 184
 }
158 185
 
159
-var xxx_messageInfo_Gauge proto.InternalMessageInfo
186
+// Deprecated: Use Gauge.ProtoReflect.Descriptor instead.
187
+func (*Gauge) Descriptor() ([]byte, []int) {
188
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{1}
189
+}
160 190
 
161
-func (m *Gauge) GetValue() float64 {
162
-	if m != nil && m.Value != nil {
163
-		return *m.Value
191
+func (x *Gauge) GetValue() float64 {
192
+	if x != nil && x.Value != nil {
193
+		return *x.Value
164 194
 	}
165 195
 	return 0
166 196
 }
167 197
 
168 198
 type Counter struct {
169
-	Value                *float64  `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
170
-	Exemplar             *Exemplar `protobuf:"bytes,2,opt,name=exemplar" json:"exemplar,omitempty"`
171
-	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
172
-	XXX_unrecognized     []byte    `json:"-"`
173
-	XXX_sizecache        int32     `json:"-"`
174
-}
199
+	state         protoimpl.MessageState
200
+	sizeCache     protoimpl.SizeCache
201
+	unknownFields protoimpl.UnknownFields
175 202
 
176
-func (m *Counter) Reset()         { *m = Counter{} }
177
-func (m *Counter) String() string { return proto.CompactTextString(m) }
178
-func (*Counter) ProtoMessage()    {}
179
-func (*Counter) Descriptor() ([]byte, []int) {
180
-	return fileDescriptor_d1e5ddb18987a258, []int{2}
203
+	Value            *float64               `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
204
+	Exemplar         *Exemplar              `protobuf:"bytes,2,opt,name=exemplar" json:"exemplar,omitempty"`
205
+	CreatedTimestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=created_timestamp,json=createdTimestamp" json:"created_timestamp,omitempty"`
181 206
 }
182 207
 
183
-func (m *Counter) XXX_Unmarshal(b []byte) error {
184
-	return xxx_messageInfo_Counter.Unmarshal(m, b)
185
-}
186
-func (m *Counter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
187
-	return xxx_messageInfo_Counter.Marshal(b, m, deterministic)
188
-}
189
-func (m *Counter) XXX_Merge(src proto.Message) {
190
-	xxx_messageInfo_Counter.Merge(m, src)
208
+func (x *Counter) Reset() {
209
+	*x = Counter{}
210
+	if protoimpl.UnsafeEnabled {
211
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[2]
212
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
213
+		ms.StoreMessageInfo(mi)
214
+	}
191 215
 }
192
-func (m *Counter) XXX_Size() int {
193
-	return xxx_messageInfo_Counter.Size(m)
216
+
217
+func (x *Counter) String() string {
218
+	return protoimpl.X.MessageStringOf(x)
194 219
 }
195
-func (m *Counter) XXX_DiscardUnknown() {
196
-	xxx_messageInfo_Counter.DiscardUnknown(m)
220
+
221
+func (*Counter) ProtoMessage() {}
222
+
223
+func (x *Counter) ProtoReflect() protoreflect.Message {
224
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[2]
225
+	if protoimpl.UnsafeEnabled && x != nil {
226
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
227
+		if ms.LoadMessageInfo() == nil {
228
+			ms.StoreMessageInfo(mi)
229
+		}
230
+		return ms
231
+	}
232
+	return mi.MessageOf(x)
197 233
 }
198 234
 
199
-var xxx_messageInfo_Counter proto.InternalMessageInfo
235
+// Deprecated: Use Counter.ProtoReflect.Descriptor instead.
236
+func (*Counter) Descriptor() ([]byte, []int) {
237
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{2}
238
+}
200 239
 
201
-func (m *Counter) GetValue() float64 {
202
-	if m != nil && m.Value != nil {
203
-		return *m.Value
240
+func (x *Counter) GetValue() float64 {
241
+	if x != nil && x.Value != nil {
242
+		return *x.Value
204 243
 	}
205 244
 	return 0
206 245
 }
207 246
 
208
-func (m *Counter) GetExemplar() *Exemplar {
209
-	if m != nil {
210
-		return m.Exemplar
247
+func (x *Counter) GetExemplar() *Exemplar {
248
+	if x != nil {
249
+		return x.Exemplar
211 250
 	}
212 251
 	return nil
213 252
 }
214 253
 
215
-type Quantile struct {
216
-	Quantile             *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"`
217
-	Value                *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
218
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
219
-	XXX_unrecognized     []byte   `json:"-"`
220
-	XXX_sizecache        int32    `json:"-"`
254
+func (x *Counter) GetCreatedTimestamp() *timestamppb.Timestamp {
255
+	if x != nil {
256
+		return x.CreatedTimestamp
257
+	}
258
+	return nil
221 259
 }
222 260
 
223
-func (m *Quantile) Reset()         { *m = Quantile{} }
224
-func (m *Quantile) String() string { return proto.CompactTextString(m) }
225
-func (*Quantile) ProtoMessage()    {}
226
-func (*Quantile) Descriptor() ([]byte, []int) {
227
-	return fileDescriptor_d1e5ddb18987a258, []int{3}
228
-}
261
+type Quantile struct {
262
+	state         protoimpl.MessageState
263
+	sizeCache     protoimpl.SizeCache
264
+	unknownFields protoimpl.UnknownFields
229 265
 
230
-func (m *Quantile) XXX_Unmarshal(b []byte) error {
231
-	return xxx_messageInfo_Quantile.Unmarshal(m, b)
232
-}
233
-func (m *Quantile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
234
-	return xxx_messageInfo_Quantile.Marshal(b, m, deterministic)
266
+	Quantile *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"`
267
+	Value    *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
235 268
 }
236
-func (m *Quantile) XXX_Merge(src proto.Message) {
237
-	xxx_messageInfo_Quantile.Merge(m, src)
269
+
270
+func (x *Quantile) Reset() {
271
+	*x = Quantile{}
272
+	if protoimpl.UnsafeEnabled {
273
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[3]
274
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
275
+		ms.StoreMessageInfo(mi)
276
+	}
238 277
 }
239
-func (m *Quantile) XXX_Size() int {
240
-	return xxx_messageInfo_Quantile.Size(m)
278
+
279
+func (x *Quantile) String() string {
280
+	return protoimpl.X.MessageStringOf(x)
241 281
 }
242
-func (m *Quantile) XXX_DiscardUnknown() {
243
-	xxx_messageInfo_Quantile.DiscardUnknown(m)
282
+
283
+func (*Quantile) ProtoMessage() {}
284
+
285
+func (x *Quantile) ProtoReflect() protoreflect.Message {
286
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[3]
287
+	if protoimpl.UnsafeEnabled && x != nil {
288
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
289
+		if ms.LoadMessageInfo() == nil {
290
+			ms.StoreMessageInfo(mi)
291
+		}
292
+		return ms
293
+	}
294
+	return mi.MessageOf(x)
244 295
 }
245 296
 
246
-var xxx_messageInfo_Quantile proto.InternalMessageInfo
297
+// Deprecated: Use Quantile.ProtoReflect.Descriptor instead.
298
+func (*Quantile) Descriptor() ([]byte, []int) {
299
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{3}
300
+}
247 301
 
248
-func (m *Quantile) GetQuantile() float64 {
249
-	if m != nil && m.Quantile != nil {
250
-		return *m.Quantile
302
+func (x *Quantile) GetQuantile() float64 {
303
+	if x != nil && x.Quantile != nil {
304
+		return *x.Quantile
251 305
 	}
252 306
 	return 0
253 307
 }
254 308
 
255
-func (m *Quantile) GetValue() float64 {
256
-	if m != nil && m.Value != nil {
257
-		return *m.Value
309
+func (x *Quantile) GetValue() float64 {
310
+	if x != nil && x.Value != nil {
311
+		return *x.Value
258 312
 	}
259 313
 	return 0
260 314
 }
261 315
 
262 316
 type Summary struct {
263
-	SampleCount          *uint64     `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
264
-	SampleSum            *float64    `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
265
-	Quantile             []*Quantile `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"`
266
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
267
-	XXX_unrecognized     []byte      `json:"-"`
268
-	XXX_sizecache        int32       `json:"-"`
317
+	state         protoimpl.MessageState
318
+	sizeCache     protoimpl.SizeCache
319
+	unknownFields protoimpl.UnknownFields
320
+
321
+	SampleCount      *uint64                `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
322
+	SampleSum        *float64               `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
323
+	Quantile         []*Quantile            `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"`
324
+	CreatedTimestamp *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_timestamp,json=createdTimestamp" json:"created_timestamp,omitempty"`
325
+}
326
+
327
+func (x *Summary) Reset() {
328
+	*x = Summary{}
329
+	if protoimpl.UnsafeEnabled {
330
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[4]
331
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
332
+		ms.StoreMessageInfo(mi)
333
+	}
269 334
 }
270 335
 
271
-func (m *Summary) Reset()         { *m = Summary{} }
272
-func (m *Summary) String() string { return proto.CompactTextString(m) }
273
-func (*Summary) ProtoMessage()    {}
274
-func (*Summary) Descriptor() ([]byte, []int) {
275
-	return fileDescriptor_d1e5ddb18987a258, []int{4}
336
+func (x *Summary) String() string {
337
+	return protoimpl.X.MessageStringOf(x)
276 338
 }
277 339
 
278
-func (m *Summary) XXX_Unmarshal(b []byte) error {
279
-	return xxx_messageInfo_Summary.Unmarshal(m, b)
280
-}
281
-func (m *Summary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
282
-	return xxx_messageInfo_Summary.Marshal(b, m, deterministic)
283
-}
284
-func (m *Summary) XXX_Merge(src proto.Message) {
285
-	xxx_messageInfo_Summary.Merge(m, src)
286
-}
287
-func (m *Summary) XXX_Size() int {
288
-	return xxx_messageInfo_Summary.Size(m)
289
-}
290
-func (m *Summary) XXX_DiscardUnknown() {
291
-	xxx_messageInfo_Summary.DiscardUnknown(m)
340
+func (*Summary) ProtoMessage() {}
341
+
342
+func (x *Summary) ProtoReflect() protoreflect.Message {
343
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[4]
344
+	if protoimpl.UnsafeEnabled && x != nil {
345
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
346
+		if ms.LoadMessageInfo() == nil {
347
+			ms.StoreMessageInfo(mi)
348
+		}
349
+		return ms
350
+	}
351
+	return mi.MessageOf(x)
292 352
 }
293 353
 
294
-var xxx_messageInfo_Summary proto.InternalMessageInfo
354
+// Deprecated: Use Summary.ProtoReflect.Descriptor instead.
355
+func (*Summary) Descriptor() ([]byte, []int) {
356
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{4}
357
+}
295 358
 
296
-func (m *Summary) GetSampleCount() uint64 {
297
-	if m != nil && m.SampleCount != nil {
298
-		return *m.SampleCount
359
+func (x *Summary) GetSampleCount() uint64 {
360
+	if x != nil && x.SampleCount != nil {
361
+		return *x.SampleCount
299 362
 	}
300 363
 	return 0
301 364
 }
302 365
 
303
-func (m *Summary) GetSampleSum() float64 {
304
-	if m != nil && m.SampleSum != nil {
305
-		return *m.SampleSum
366
+func (x *Summary) GetSampleSum() float64 {
367
+	if x != nil && x.SampleSum != nil {
368
+		return *x.SampleSum
306 369
 	}
307 370
 	return 0
308 371
 }
309 372
 
310
-func (m *Summary) GetQuantile() []*Quantile {
311
-	if m != nil {
312
-		return m.Quantile
373
+func (x *Summary) GetQuantile() []*Quantile {
374
+	if x != nil {
375
+		return x.Quantile
313 376
 	}
314 377
 	return nil
315 378
 }
316 379
 
317
-type Untyped struct {
318
-	Value                *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
319
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
320
-	XXX_unrecognized     []byte   `json:"-"`
321
-	XXX_sizecache        int32    `json:"-"`
380
+func (x *Summary) GetCreatedTimestamp() *timestamppb.Timestamp {
381
+	if x != nil {
382
+		return x.CreatedTimestamp
383
+	}
384
+	return nil
322 385
 }
323 386
 
324
-func (m *Untyped) Reset()         { *m = Untyped{} }
325
-func (m *Untyped) String() string { return proto.CompactTextString(m) }
326
-func (*Untyped) ProtoMessage()    {}
327
-func (*Untyped) Descriptor() ([]byte, []int) {
328
-	return fileDescriptor_d1e5ddb18987a258, []int{5}
329
-}
387
+type Untyped struct {
388
+	state         protoimpl.MessageState
389
+	sizeCache     protoimpl.SizeCache
390
+	unknownFields protoimpl.UnknownFields
330 391
 
331
-func (m *Untyped) XXX_Unmarshal(b []byte) error {
332
-	return xxx_messageInfo_Untyped.Unmarshal(m, b)
333
-}
334
-func (m *Untyped) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
335
-	return xxx_messageInfo_Untyped.Marshal(b, m, deterministic)
392
+	Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
336 393
 }
337
-func (m *Untyped) XXX_Merge(src proto.Message) {
338
-	xxx_messageInfo_Untyped.Merge(m, src)
394
+
395
+func (x *Untyped) Reset() {
396
+	*x = Untyped{}
397
+	if protoimpl.UnsafeEnabled {
398
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[5]
399
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
400
+		ms.StoreMessageInfo(mi)
401
+	}
339 402
 }
340
-func (m *Untyped) XXX_Size() int {
341
-	return xxx_messageInfo_Untyped.Size(m)
403
+
404
+func (x *Untyped) String() string {
405
+	return protoimpl.X.MessageStringOf(x)
342 406
 }
343
-func (m *Untyped) XXX_DiscardUnknown() {
344
-	xxx_messageInfo_Untyped.DiscardUnknown(m)
407
+
408
+func (*Untyped) ProtoMessage() {}
409
+
410
+func (x *Untyped) ProtoReflect() protoreflect.Message {
411
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[5]
412
+	if protoimpl.UnsafeEnabled && x != nil {
413
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
414
+		if ms.LoadMessageInfo() == nil {
415
+			ms.StoreMessageInfo(mi)
416
+		}
417
+		return ms
418
+	}
419
+	return mi.MessageOf(x)
345 420
 }
346 421
 
347
-var xxx_messageInfo_Untyped proto.InternalMessageInfo
422
+// Deprecated: Use Untyped.ProtoReflect.Descriptor instead.
423
+func (*Untyped) Descriptor() ([]byte, []int) {
424
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{5}
425
+}
348 426
 
349
-func (m *Untyped) GetValue() float64 {
350
-	if m != nil && m.Value != nil {
351
-		return *m.Value
427
+func (x *Untyped) GetValue() float64 {
428
+	if x != nil && x.Value != nil {
429
+		return *x.Value
352 430
 	}
353 431
 	return 0
354 432
 }
355 433
 
356 434
 type Histogram struct {
435
+	state         protoimpl.MessageState
436
+	sizeCache     protoimpl.SizeCache
437
+	unknownFields protoimpl.UnknownFields
438
+
357 439
 	SampleCount      *uint64  `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
358
-	SampleCountFloat *float64 `protobuf:"fixed64,4,opt,name=sample_count_float,json=sampleCountFloat" json:"sample_count_float,omitempty"`
440
+	SampleCountFloat *float64 `protobuf:"fixed64,4,opt,name=sample_count_float,json=sampleCountFloat" json:"sample_count_float,omitempty"` // Overrides sample_count if > 0.
359 441
 	SampleSum        *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
360 442
 	// Buckets for the conventional histogram.
361
-	Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"`
443
+	Bucket           []*Bucket              `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"` // Ordered in increasing order of upper_bound, +Inf bucket is optional.
444
+	CreatedTimestamp *timestamppb.Timestamp `protobuf:"bytes,15,opt,name=created_timestamp,json=createdTimestamp" json:"created_timestamp,omitempty"`
362 445
 	// schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8.
363 446
 	// They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and
364 447
 	// then each power of two is divided into 2^n logarithmic buckets.
365 448
 	// Or in other words, each bucket boundary is the previous boundary times 2^(2^-n).
366 449
 	// In the future, more bucket schemas may be added using numbers < -4 or > 8.
367 450
 	Schema         *int32   `protobuf:"zigzag32,5,opt,name=schema" json:"schema,omitempty"`
368
-	ZeroThreshold  *float64 `protobuf:"fixed64,6,opt,name=zero_threshold,json=zeroThreshold" json:"zero_threshold,omitempty"`
369
-	ZeroCount      *uint64  `protobuf:"varint,7,opt,name=zero_count,json=zeroCount" json:"zero_count,omitempty"`
370
-	ZeroCountFloat *float64 `protobuf:"fixed64,8,opt,name=zero_count_float,json=zeroCountFloat" json:"zero_count_float,omitempty"`
451
+	ZeroThreshold  *float64 `protobuf:"fixed64,6,opt,name=zero_threshold,json=zeroThreshold" json:"zero_threshold,omitempty"`      // Breadth of the zero bucket.
452
+	ZeroCount      *uint64  `protobuf:"varint,7,opt,name=zero_count,json=zeroCount" json:"zero_count,omitempty"`                   // Count in zero bucket.
453
+	ZeroCountFloat *float64 `protobuf:"fixed64,8,opt,name=zero_count_float,json=zeroCountFloat" json:"zero_count_float,omitempty"` // Overrides sb_zero_count if > 0.
371 454
 	// Negative buckets for the native histogram.
372 455
 	NegativeSpan []*BucketSpan `protobuf:"bytes,9,rep,name=negative_span,json=negativeSpan" json:"negative_span,omitempty"`
373 456
 	// Use either "negative_delta" or "negative_count", the former for
374 457
 	// regular histograms with integer counts, the latter for float
375 458
 	// histograms.
376
-	NegativeDelta []int64   `protobuf:"zigzag64,10,rep,name=negative_delta,json=negativeDelta" json:"negative_delta,omitempty"`
377
-	NegativeCount []float64 `protobuf:"fixed64,11,rep,name=negative_count,json=negativeCount" json:"negative_count,omitempty"`
459
+	NegativeDelta []int64   `protobuf:"zigzag64,10,rep,name=negative_delta,json=negativeDelta" json:"negative_delta,omitempty"` // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
460
+	NegativeCount []float64 `protobuf:"fixed64,11,rep,name=negative_count,json=negativeCount" json:"negative_count,omitempty"`  // Absolute count of each bucket.
378 461
 	// Positive buckets for the native histogram.
462
+	// Use a no-op span (offset 0, length 0) for a native histogram without any
463
+	// observations yet and with a zero_threshold of 0. Otherwise, it would be
464
+	// indistinguishable from a classic histogram.
379 465
 	PositiveSpan []*BucketSpan `protobuf:"bytes,12,rep,name=positive_span,json=positiveSpan" json:"positive_span,omitempty"`
380 466
 	// Use either "positive_delta" or "positive_count", the former for
381 467
 	// regular histograms with integer counts, the latter for float
382 468
 	// histograms.
383
-	PositiveDelta        []int64   `protobuf:"zigzag64,13,rep,name=positive_delta,json=positiveDelta" json:"positive_delta,omitempty"`
384
-	PositiveCount        []float64 `protobuf:"fixed64,14,rep,name=positive_count,json=positiveCount" json:"positive_count,omitempty"`
385
-	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
386
-	XXX_unrecognized     []byte    `json:"-"`
387
-	XXX_sizecache        int32     `json:"-"`
469
+	PositiveDelta []int64   `protobuf:"zigzag64,13,rep,name=positive_delta,json=positiveDelta" json:"positive_delta,omitempty"` // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
470
+	PositiveCount []float64 `protobuf:"fixed64,14,rep,name=positive_count,json=positiveCount" json:"positive_count,omitempty"`  // Absolute count of each bucket.
388 471
 }
389 472
 
390
-func (m *Histogram) Reset()         { *m = Histogram{} }
391
-func (m *Histogram) String() string { return proto.CompactTextString(m) }
392
-func (*Histogram) ProtoMessage()    {}
393
-func (*Histogram) Descriptor() ([]byte, []int) {
394
-	return fileDescriptor_d1e5ddb18987a258, []int{6}
473
+func (x *Histogram) Reset() {
474
+	*x = Histogram{}
475
+	if protoimpl.UnsafeEnabled {
476
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[6]
477
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
478
+		ms.StoreMessageInfo(mi)
479
+	}
395 480
 }
396 481
 
397
-func (m *Histogram) XXX_Unmarshal(b []byte) error {
398
-	return xxx_messageInfo_Histogram.Unmarshal(m, b)
399
-}
400
-func (m *Histogram) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
401
-	return xxx_messageInfo_Histogram.Marshal(b, m, deterministic)
402
-}
403
-func (m *Histogram) XXX_Merge(src proto.Message) {
404
-	xxx_messageInfo_Histogram.Merge(m, src)
482
+func (x *Histogram) String() string {
483
+	return protoimpl.X.MessageStringOf(x)
405 484
 }
406
-func (m *Histogram) XXX_Size() int {
407
-	return xxx_messageInfo_Histogram.Size(m)
408
-}
409
-func (m *Histogram) XXX_DiscardUnknown() {
410
-	xxx_messageInfo_Histogram.DiscardUnknown(m)
485
+
486
+func (*Histogram) ProtoMessage() {}
487
+
488
+func (x *Histogram) ProtoReflect() protoreflect.Message {
489
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[6]
490
+	if protoimpl.UnsafeEnabled && x != nil {
491
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
492
+		if ms.LoadMessageInfo() == nil {
493
+			ms.StoreMessageInfo(mi)
494
+		}
495
+		return ms
496
+	}
497
+	return mi.MessageOf(x)
411 498
 }
412 499
 
413
-var xxx_messageInfo_Histogram proto.InternalMessageInfo
500
+// Deprecated: Use Histogram.ProtoReflect.Descriptor instead.
501
+func (*Histogram) Descriptor() ([]byte, []int) {
502
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{6}
503
+}
414 504
 
415
-func (m *Histogram) GetSampleCount() uint64 {
416
-	if m != nil && m.SampleCount != nil {
417
-		return *m.SampleCount
505
+func (x *Histogram) GetSampleCount() uint64 {
506
+	if x != nil && x.SampleCount != nil {
507
+		return *x.SampleCount
418 508
 	}
419 509
 	return 0
420 510
 }
421 511
 
422
-func (m *Histogram) GetSampleCountFloat() float64 {
423
-	if m != nil && m.SampleCountFloat != nil {
424
-		return *m.SampleCountFloat
512
+func (x *Histogram) GetSampleCountFloat() float64 {
513
+	if x != nil && x.SampleCountFloat != nil {
514
+		return *x.SampleCountFloat
425 515
 	}
426 516
 	return 0
427 517
 }
428 518
 
429
-func (m *Histogram) GetSampleSum() float64 {
430
-	if m != nil && m.SampleSum != nil {
431
-		return *m.SampleSum
519
+func (x *Histogram) GetSampleSum() float64 {
520
+	if x != nil && x.SampleSum != nil {
521
+		return *x.SampleSum
432 522
 	}
433 523
 	return 0
434 524
 }
435 525
 
436
-func (m *Histogram) GetBucket() []*Bucket {
437
-	if m != nil {
438
-		return m.Bucket
526
+func (x *Histogram) GetBucket() []*Bucket {
527
+	if x != nil {
528
+		return x.Bucket
529
+	}
530
+	return nil
531
+}
532
+
533
+func (x *Histogram) GetCreatedTimestamp() *timestamppb.Timestamp {
534
+	if x != nil {
535
+		return x.CreatedTimestamp
439 536
 	}
440 537
 	return nil
441 538
 }
442 539
 
443
-func (m *Histogram) GetSchema() int32 {
444
-	if m != nil && m.Schema != nil {
445
-		return *m.Schema
540
+func (x *Histogram) GetSchema() int32 {
541
+	if x != nil && x.Schema != nil {
542
+		return *x.Schema
446 543
 	}
447 544
 	return 0
448 545
 }
449 546
 
450
-func (m *Histogram) GetZeroThreshold() float64 {
451
-	if m != nil && m.ZeroThreshold != nil {
452
-		return *m.ZeroThreshold
547
+func (x *Histogram) GetZeroThreshold() float64 {
548
+	if x != nil && x.ZeroThreshold != nil {
549
+		return *x.ZeroThreshold
453 550
 	}
454 551
 	return 0
455 552
 }
456 553
 
457
-func (m *Histogram) GetZeroCount() uint64 {
458
-	if m != nil && m.ZeroCount != nil {
459
-		return *m.ZeroCount
554
+func (x *Histogram) GetZeroCount() uint64 {
555
+	if x != nil && x.ZeroCount != nil {
556
+		return *x.ZeroCount
460 557
 	}
461 558
 	return 0
462 559
 }
463 560
 
464
-func (m *Histogram) GetZeroCountFloat() float64 {
465
-	if m != nil && m.ZeroCountFloat != nil {
466
-		return *m.ZeroCountFloat
561
+func (x *Histogram) GetZeroCountFloat() float64 {
562
+	if x != nil && x.ZeroCountFloat != nil {
563
+		return *x.ZeroCountFloat
467 564
 	}
468 565
 	return 0
469 566
 }
470 567
 
471
-func (m *Histogram) GetNegativeSpan() []*BucketSpan {
472
-	if m != nil {
473
-		return m.NegativeSpan
568
+func (x *Histogram) GetNegativeSpan() []*BucketSpan {
569
+	if x != nil {
570
+		return x.NegativeSpan
474 571
 	}
475 572
 	return nil
476 573
 }
477 574
 
478
-func (m *Histogram) GetNegativeDelta() []int64 {
479
-	if m != nil {
480
-		return m.NegativeDelta
575
+func (x *Histogram) GetNegativeDelta() []int64 {
576
+	if x != nil {
577
+		return x.NegativeDelta
481 578
 	}
482 579
 	return nil
483 580
 }
484 581
 
485
-func (m *Histogram) GetNegativeCount() []float64 {
486
-	if m != nil {
487
-		return m.NegativeCount
582
+func (x *Histogram) GetNegativeCount() []float64 {
583
+	if x != nil {
584
+		return x.NegativeCount
488 585
 	}
489 586
 	return nil
490 587
 }
491 588
 
492
-func (m *Histogram) GetPositiveSpan() []*BucketSpan {
493
-	if m != nil {
494
-		return m.PositiveSpan
589
+func (x *Histogram) GetPositiveSpan() []*BucketSpan {
590
+	if x != nil {
591
+		return x.PositiveSpan
495 592
 	}
496 593
 	return nil
497 594
 }
498 595
 
499
-func (m *Histogram) GetPositiveDelta() []int64 {
500
-	if m != nil {
501
-		return m.PositiveDelta
596
+func (x *Histogram) GetPositiveDelta() []int64 {
597
+	if x != nil {
598
+		return x.PositiveDelta
502 599
 	}
503 600
 	return nil
504 601
 }
505 602
 
506
-func (m *Histogram) GetPositiveCount() []float64 {
507
-	if m != nil {
508
-		return m.PositiveCount
603
+func (x *Histogram) GetPositiveCount() []float64 {
604
+	if x != nil {
605
+		return x.PositiveCount
509 606
 	}
510 607
 	return nil
511 608
 }
... ...
@@ -513,64 +625,72 @@ func (m *Histogram) GetPositiveCount() []float64 {
513 513
 // A Bucket of a conventional histogram, each of which is treated as
514 514
 // an individual counter-like time series by Prometheus.
515 515
 type Bucket struct {
516
-	CumulativeCount      *uint64   `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount" json:"cumulative_count,omitempty"`
517
-	CumulativeCountFloat *float64  `protobuf:"fixed64,4,opt,name=cumulative_count_float,json=cumulativeCountFloat" json:"cumulative_count_float,omitempty"`
518
-	UpperBound           *float64  `protobuf:"fixed64,2,opt,name=upper_bound,json=upperBound" json:"upper_bound,omitempty"`
516
+	state         protoimpl.MessageState
517
+	sizeCache     protoimpl.SizeCache
518
+	unknownFields protoimpl.UnknownFields
519
+
520
+	CumulativeCount      *uint64   `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount" json:"cumulative_count,omitempty"`                   // Cumulative in increasing order.
521
+	CumulativeCountFloat *float64  `protobuf:"fixed64,4,opt,name=cumulative_count_float,json=cumulativeCountFloat" json:"cumulative_count_float,omitempty"` // Overrides cumulative_count if > 0.
522
+	UpperBound           *float64  `protobuf:"fixed64,2,opt,name=upper_bound,json=upperBound" json:"upper_bound,omitempty"`                                 // Inclusive.
519 523
 	Exemplar             *Exemplar `protobuf:"bytes,3,opt,name=exemplar" json:"exemplar,omitempty"`
520
-	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
521
-	XXX_unrecognized     []byte    `json:"-"`
522
-	XXX_sizecache        int32     `json:"-"`
523 524
 }
524 525
 
525
-func (m *Bucket) Reset()         { *m = Bucket{} }
526
-func (m *Bucket) String() string { return proto.CompactTextString(m) }
527
-func (*Bucket) ProtoMessage()    {}
528
-func (*Bucket) Descriptor() ([]byte, []int) {
529
-	return fileDescriptor_d1e5ddb18987a258, []int{7}
526
+func (x *Bucket) Reset() {
527
+	*x = Bucket{}
528
+	if protoimpl.UnsafeEnabled {
529
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[7]
530
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
531
+		ms.StoreMessageInfo(mi)
532
+	}
530 533
 }
531 534
 
532
-func (m *Bucket) XXX_Unmarshal(b []byte) error {
533
-	return xxx_messageInfo_Bucket.Unmarshal(m, b)
534
-}
535
-func (m *Bucket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
536
-	return xxx_messageInfo_Bucket.Marshal(b, m, deterministic)
537
-}
538
-func (m *Bucket) XXX_Merge(src proto.Message) {
539
-	xxx_messageInfo_Bucket.Merge(m, src)
535
+func (x *Bucket) String() string {
536
+	return protoimpl.X.MessageStringOf(x)
540 537
 }
541
-func (m *Bucket) XXX_Size() int {
542
-	return xxx_messageInfo_Bucket.Size(m)
543
-}
544
-func (m *Bucket) XXX_DiscardUnknown() {
545
-	xxx_messageInfo_Bucket.DiscardUnknown(m)
538
+
539
+func (*Bucket) ProtoMessage() {}
540
+
541
+func (x *Bucket) ProtoReflect() protoreflect.Message {
542
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[7]
543
+	if protoimpl.UnsafeEnabled && x != nil {
544
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
545
+		if ms.LoadMessageInfo() == nil {
546
+			ms.StoreMessageInfo(mi)
547
+		}
548
+		return ms
549
+	}
550
+	return mi.MessageOf(x)
546 551
 }
547 552
 
548
-var xxx_messageInfo_Bucket proto.InternalMessageInfo
553
+// Deprecated: Use Bucket.ProtoReflect.Descriptor instead.
554
+func (*Bucket) Descriptor() ([]byte, []int) {
555
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{7}
556
+}
549 557
 
550
-func (m *Bucket) GetCumulativeCount() uint64 {
551
-	if m != nil && m.CumulativeCount != nil {
552
-		return *m.CumulativeCount
558
+func (x *Bucket) GetCumulativeCount() uint64 {
559
+	if x != nil && x.CumulativeCount != nil {
560
+		return *x.CumulativeCount
553 561
 	}
554 562
 	return 0
555 563
 }
556 564
 
557
-func (m *Bucket) GetCumulativeCountFloat() float64 {
558
-	if m != nil && m.CumulativeCountFloat != nil {
559
-		return *m.CumulativeCountFloat
565
+func (x *Bucket) GetCumulativeCountFloat() float64 {
566
+	if x != nil && x.CumulativeCountFloat != nil {
567
+		return *x.CumulativeCountFloat
560 568
 	}
561 569
 	return 0
562 570
 }
563 571
 
564
-func (m *Bucket) GetUpperBound() float64 {
565
-	if m != nil && m.UpperBound != nil {
566
-		return *m.UpperBound
572
+func (x *Bucket) GetUpperBound() float64 {
573
+	if x != nil && x.UpperBound != nil {
574
+		return *x.UpperBound
567 575
 	}
568 576
 	return 0
569 577
 }
570 578
 
571
-func (m *Bucket) GetExemplar() *Exemplar {
572
-	if m != nil {
573
-		return m.Exemplar
579
+func (x *Bucket) GetExemplar() *Exemplar {
580
+	if x != nil {
581
+		return x.Exemplar
574 582
 	}
575 583
 	return nil
576 584
 }
... ...
@@ -582,333 +702,675 @@ func (m *Bucket) GetExemplar() *Exemplar {
582 582
 // structured here (with all the buckets in a single array separate
583 583
 // from the Spans).
584 584
 type BucketSpan struct {
585
-	Offset               *int32   `protobuf:"zigzag32,1,opt,name=offset" json:"offset,omitempty"`
586
-	Length               *uint32  `protobuf:"varint,2,opt,name=length" json:"length,omitempty"`
587
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
588
-	XXX_unrecognized     []byte   `json:"-"`
589
-	XXX_sizecache        int32    `json:"-"`
590
-}
585
+	state         protoimpl.MessageState
586
+	sizeCache     protoimpl.SizeCache
587
+	unknownFields protoimpl.UnknownFields
591 588
 
592
-func (m *BucketSpan) Reset()         { *m = BucketSpan{} }
593
-func (m *BucketSpan) String() string { return proto.CompactTextString(m) }
594
-func (*BucketSpan) ProtoMessage()    {}
595
-func (*BucketSpan) Descriptor() ([]byte, []int) {
596
-	return fileDescriptor_d1e5ddb18987a258, []int{8}
589
+	Offset *int32  `protobuf:"zigzag32,1,opt,name=offset" json:"offset,omitempty"` // Gap to previous span, or starting point for 1st span (which can be negative).
590
+	Length *uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"`   // Length of consecutive buckets.
597 591
 }
598 592
 
599
-func (m *BucketSpan) XXX_Unmarshal(b []byte) error {
600
-	return xxx_messageInfo_BucketSpan.Unmarshal(m, b)
601
-}
602
-func (m *BucketSpan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
603
-	return xxx_messageInfo_BucketSpan.Marshal(b, m, deterministic)
604
-}
605
-func (m *BucketSpan) XXX_Merge(src proto.Message) {
606
-	xxx_messageInfo_BucketSpan.Merge(m, src)
593
+func (x *BucketSpan) Reset() {
594
+	*x = BucketSpan{}
595
+	if protoimpl.UnsafeEnabled {
596
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[8]
597
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
598
+		ms.StoreMessageInfo(mi)
599
+	}
607 600
 }
608
-func (m *BucketSpan) XXX_Size() int {
609
-	return xxx_messageInfo_BucketSpan.Size(m)
601
+
602
+func (x *BucketSpan) String() string {
603
+	return protoimpl.X.MessageStringOf(x)
610 604
 }
611
-func (m *BucketSpan) XXX_DiscardUnknown() {
612
-	xxx_messageInfo_BucketSpan.DiscardUnknown(m)
605
+
606
+func (*BucketSpan) ProtoMessage() {}
607
+
608
+func (x *BucketSpan) ProtoReflect() protoreflect.Message {
609
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[8]
610
+	if protoimpl.UnsafeEnabled && x != nil {
611
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
612
+		if ms.LoadMessageInfo() == nil {
613
+			ms.StoreMessageInfo(mi)
614
+		}
615
+		return ms
616
+	}
617
+	return mi.MessageOf(x)
613 618
 }
614 619
 
615
-var xxx_messageInfo_BucketSpan proto.InternalMessageInfo
620
+// Deprecated: Use BucketSpan.ProtoReflect.Descriptor instead.
621
+func (*BucketSpan) Descriptor() ([]byte, []int) {
622
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{8}
623
+}
616 624
 
617
-func (m *BucketSpan) GetOffset() int32 {
618
-	if m != nil && m.Offset != nil {
619
-		return *m.Offset
625
+func (x *BucketSpan) GetOffset() int32 {
626
+	if x != nil && x.Offset != nil {
627
+		return *x.Offset
620 628
 	}
621 629
 	return 0
622 630
 }
623 631
 
624
-func (m *BucketSpan) GetLength() uint32 {
625
-	if m != nil && m.Length != nil {
626
-		return *m.Length
632
+func (x *BucketSpan) GetLength() uint32 {
633
+	if x != nil && x.Length != nil {
634
+		return *x.Length
627 635
 	}
628 636
 	return 0
629 637
 }
630 638
 
631 639
 type Exemplar struct {
632
-	Label                []*LabelPair         `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
633
-	Value                *float64             `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
634
-	Timestamp            *timestamp.Timestamp `protobuf:"bytes,3,opt,name=timestamp" json:"timestamp,omitempty"`
635
-	XXX_NoUnkeyedLiteral struct{}             `json:"-"`
636
-	XXX_unrecognized     []byte               `json:"-"`
637
-	XXX_sizecache        int32                `json:"-"`
638
-}
640
+	state         protoimpl.MessageState
641
+	sizeCache     protoimpl.SizeCache
642
+	unknownFields protoimpl.UnknownFields
639 643
 
640
-func (m *Exemplar) Reset()         { *m = Exemplar{} }
641
-func (m *Exemplar) String() string { return proto.CompactTextString(m) }
642
-func (*Exemplar) ProtoMessage()    {}
643
-func (*Exemplar) Descriptor() ([]byte, []int) {
644
-	return fileDescriptor_d1e5ddb18987a258, []int{9}
644
+	Label     []*LabelPair           `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
645
+	Value     *float64               `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
646
+	Timestamp *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=timestamp" json:"timestamp,omitempty"` // OpenMetrics-style.
645 647
 }
646 648
 
647
-func (m *Exemplar) XXX_Unmarshal(b []byte) error {
648
-	return xxx_messageInfo_Exemplar.Unmarshal(m, b)
649
-}
650
-func (m *Exemplar) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
651
-	return xxx_messageInfo_Exemplar.Marshal(b, m, deterministic)
652
-}
653
-func (m *Exemplar) XXX_Merge(src proto.Message) {
654
-	xxx_messageInfo_Exemplar.Merge(m, src)
649
+func (x *Exemplar) Reset() {
650
+	*x = Exemplar{}
651
+	if protoimpl.UnsafeEnabled {
652
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[9]
653
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
654
+		ms.StoreMessageInfo(mi)
655
+	}
655 656
 }
656
-func (m *Exemplar) XXX_Size() int {
657
-	return xxx_messageInfo_Exemplar.Size(m)
657
+
658
+func (x *Exemplar) String() string {
659
+	return protoimpl.X.MessageStringOf(x)
658 660
 }
659
-func (m *Exemplar) XXX_DiscardUnknown() {
660
-	xxx_messageInfo_Exemplar.DiscardUnknown(m)
661
+
662
+func (*Exemplar) ProtoMessage() {}
663
+
664
+func (x *Exemplar) ProtoReflect() protoreflect.Message {
665
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[9]
666
+	if protoimpl.UnsafeEnabled && x != nil {
667
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
668
+		if ms.LoadMessageInfo() == nil {
669
+			ms.StoreMessageInfo(mi)
670
+		}
671
+		return ms
672
+	}
673
+	return mi.MessageOf(x)
661 674
 }
662 675
 
663
-var xxx_messageInfo_Exemplar proto.InternalMessageInfo
676
+// Deprecated: Use Exemplar.ProtoReflect.Descriptor instead.
677
+func (*Exemplar) Descriptor() ([]byte, []int) {
678
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{9}
679
+}
664 680
 
665
-func (m *Exemplar) GetLabel() []*LabelPair {
666
-	if m != nil {
667
-		return m.Label
681
+func (x *Exemplar) GetLabel() []*LabelPair {
682
+	if x != nil {
683
+		return x.Label
668 684
 	}
669 685
 	return nil
670 686
 }
671 687
 
672
-func (m *Exemplar) GetValue() float64 {
673
-	if m != nil && m.Value != nil {
674
-		return *m.Value
688
+func (x *Exemplar) GetValue() float64 {
689
+	if x != nil && x.Value != nil {
690
+		return *x.Value
675 691
 	}
676 692
 	return 0
677 693
 }
678 694
 
679
-func (m *Exemplar) GetTimestamp() *timestamp.Timestamp {
680
-	if m != nil {
681
-		return m.Timestamp
695
+func (x *Exemplar) GetTimestamp() *timestamppb.Timestamp {
696
+	if x != nil {
697
+		return x.Timestamp
682 698
 	}
683 699
 	return nil
684 700
 }
685 701
 
686 702
 type Metric struct {
687
-	Label                []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
688
-	Gauge                *Gauge       `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"`
689
-	Counter              *Counter     `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"`
690
-	Summary              *Summary     `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
691
-	Untyped              *Untyped     `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"`
692
-	Histogram            *Histogram   `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"`
693
-	TimestampMs          *int64       `protobuf:"varint,6,opt,name=timestamp_ms,json=timestampMs" json:"timestamp_ms,omitempty"`
694
-	XXX_NoUnkeyedLiteral struct{}     `json:"-"`
695
-	XXX_unrecognized     []byte       `json:"-"`
696
-	XXX_sizecache        int32        `json:"-"`
697
-}
698
-
699
-func (m *Metric) Reset()         { *m = Metric{} }
700
-func (m *Metric) String() string { return proto.CompactTextString(m) }
701
-func (*Metric) ProtoMessage()    {}
702
-func (*Metric) Descriptor() ([]byte, []int) {
703
-	return fileDescriptor_d1e5ddb18987a258, []int{10}
703
+	state         protoimpl.MessageState
704
+	sizeCache     protoimpl.SizeCache
705
+	unknownFields protoimpl.UnknownFields
706
+
707
+	Label       []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
708
+	Gauge       *Gauge       `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"`
709
+	Counter     *Counter     `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"`
710
+	Summary     *Summary     `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
711
+	Untyped     *Untyped     `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"`
712
+	Histogram   *Histogram   `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"`
713
+	TimestampMs *int64       `protobuf:"varint,6,opt,name=timestamp_ms,json=timestampMs" json:"timestamp_ms,omitempty"`
714
+}
715
+
716
+func (x *Metric) Reset() {
717
+	*x = Metric{}
718
+	if protoimpl.UnsafeEnabled {
719
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[10]
720
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
721
+		ms.StoreMessageInfo(mi)
722
+	}
704 723
 }
705 724
 
706
-func (m *Metric) XXX_Unmarshal(b []byte) error {
707
-	return xxx_messageInfo_Metric.Unmarshal(m, b)
708
-}
709
-func (m *Metric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
710
-	return xxx_messageInfo_Metric.Marshal(b, m, deterministic)
711
-}
712
-func (m *Metric) XXX_Merge(src proto.Message) {
713
-	xxx_messageInfo_Metric.Merge(m, src)
725
+func (x *Metric) String() string {
726
+	return protoimpl.X.MessageStringOf(x)
714 727
 }
715
-func (m *Metric) XXX_Size() int {
716
-	return xxx_messageInfo_Metric.Size(m)
717
-}
718
-func (m *Metric) XXX_DiscardUnknown() {
719
-	xxx_messageInfo_Metric.DiscardUnknown(m)
728
+
729
+func (*Metric) ProtoMessage() {}
730
+
731
+func (x *Metric) ProtoReflect() protoreflect.Message {
732
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[10]
733
+	if protoimpl.UnsafeEnabled && x != nil {
734
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
735
+		if ms.LoadMessageInfo() == nil {
736
+			ms.StoreMessageInfo(mi)
737
+		}
738
+		return ms
739
+	}
740
+	return mi.MessageOf(x)
720 741
 }
721 742
 
722
-var xxx_messageInfo_Metric proto.InternalMessageInfo
743
+// Deprecated: Use Metric.ProtoReflect.Descriptor instead.
744
+func (*Metric) Descriptor() ([]byte, []int) {
745
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{10}
746
+}
723 747
 
724
-func (m *Metric) GetLabel() []*LabelPair {
725
-	if m != nil {
726
-		return m.Label
748
+func (x *Metric) GetLabel() []*LabelPair {
749
+	if x != nil {
750
+		return x.Label
727 751
 	}
728 752
 	return nil
729 753
 }
730 754
 
731
-func (m *Metric) GetGauge() *Gauge {
732
-	if m != nil {
733
-		return m.Gauge
755
+func (x *Metric) GetGauge() *Gauge {
756
+	if x != nil {
757
+		return x.Gauge
734 758
 	}
735 759
 	return nil
736 760
 }
737 761
 
738
-func (m *Metric) GetCounter() *Counter {
739
-	if m != nil {
740
-		return m.Counter
762
+func (x *Metric) GetCounter() *Counter {
763
+	if x != nil {
764
+		return x.Counter
741 765
 	}
742 766
 	return nil
743 767
 }
744 768
 
745
-func (m *Metric) GetSummary() *Summary {
746
-	if m != nil {
747
-		return m.Summary
769
+func (x *Metric) GetSummary() *Summary {
770
+	if x != nil {
771
+		return x.Summary
748 772
 	}
749 773
 	return nil
750 774
 }
751 775
 
752
-func (m *Metric) GetUntyped() *Untyped {
753
-	if m != nil {
754
-		return m.Untyped
776
+func (x *Metric) GetUntyped() *Untyped {
777
+	if x != nil {
778
+		return x.Untyped
755 779
 	}
756 780
 	return nil
757 781
 }
758 782
 
759
-func (m *Metric) GetHistogram() *Histogram {
760
-	if m != nil {
761
-		return m.Histogram
783
+func (x *Metric) GetHistogram() *Histogram {
784
+	if x != nil {
785
+		return x.Histogram
762 786
 	}
763 787
 	return nil
764 788
 }
765 789
 
766
-func (m *Metric) GetTimestampMs() int64 {
767
-	if m != nil && m.TimestampMs != nil {
768
-		return *m.TimestampMs
790
+func (x *Metric) GetTimestampMs() int64 {
791
+	if x != nil && x.TimestampMs != nil {
792
+		return *x.TimestampMs
769 793
 	}
770 794
 	return 0
771 795
 }
772 796
 
773 797
 type MetricFamily struct {
774
-	Name                 *string     `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
775
-	Help                 *string     `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
776
-	Type                 *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
777
-	Metric               []*Metric   `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
778
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
779
-	XXX_unrecognized     []byte      `json:"-"`
780
-	XXX_sizecache        int32       `json:"-"`
781
-}
782
-
783
-func (m *MetricFamily) Reset()         { *m = MetricFamily{} }
784
-func (m *MetricFamily) String() string { return proto.CompactTextString(m) }
785
-func (*MetricFamily) ProtoMessage()    {}
786
-func (*MetricFamily) Descriptor() ([]byte, []int) {
787
-	return fileDescriptor_d1e5ddb18987a258, []int{11}
798
+	state         protoimpl.MessageState
799
+	sizeCache     protoimpl.SizeCache
800
+	unknownFields protoimpl.UnknownFields
801
+
802
+	Name   *string     `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
803
+	Help   *string     `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
804
+	Type   *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
805
+	Metric []*Metric   `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
806
+}
807
+
808
+func (x *MetricFamily) Reset() {
809
+	*x = MetricFamily{}
810
+	if protoimpl.UnsafeEnabled {
811
+		mi := &file_io_prometheus_client_metrics_proto_msgTypes[11]
812
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
813
+		ms.StoreMessageInfo(mi)
814
+	}
788 815
 }
789 816
 
790
-func (m *MetricFamily) XXX_Unmarshal(b []byte) error {
791
-	return xxx_messageInfo_MetricFamily.Unmarshal(m, b)
792
-}
793
-func (m *MetricFamily) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
794
-	return xxx_messageInfo_MetricFamily.Marshal(b, m, deterministic)
795
-}
796
-func (m *MetricFamily) XXX_Merge(src proto.Message) {
797
-	xxx_messageInfo_MetricFamily.Merge(m, src)
817
+func (x *MetricFamily) String() string {
818
+	return protoimpl.X.MessageStringOf(x)
798 819
 }
799
-func (m *MetricFamily) XXX_Size() int {
800
-	return xxx_messageInfo_MetricFamily.Size(m)
801
-}
802
-func (m *MetricFamily) XXX_DiscardUnknown() {
803
-	xxx_messageInfo_MetricFamily.DiscardUnknown(m)
820
+
821
+func (*MetricFamily) ProtoMessage() {}
822
+
823
+func (x *MetricFamily) ProtoReflect() protoreflect.Message {
824
+	mi := &file_io_prometheus_client_metrics_proto_msgTypes[11]
825
+	if protoimpl.UnsafeEnabled && x != nil {
826
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
827
+		if ms.LoadMessageInfo() == nil {
828
+			ms.StoreMessageInfo(mi)
829
+		}
830
+		return ms
831
+	}
832
+	return mi.MessageOf(x)
804 833
 }
805 834
 
806
-var xxx_messageInfo_MetricFamily proto.InternalMessageInfo
835
+// Deprecated: Use MetricFamily.ProtoReflect.Descriptor instead.
836
+func (*MetricFamily) Descriptor() ([]byte, []int) {
837
+	return file_io_prometheus_client_metrics_proto_rawDescGZIP(), []int{11}
838
+}
807 839
 
808
-func (m *MetricFamily) GetName() string {
809
-	if m != nil && m.Name != nil {
810
-		return *m.Name
840
+func (x *MetricFamily) GetName() string {
841
+	if x != nil && x.Name != nil {
842
+		return *x.Name
811 843
 	}
812 844
 	return ""
813 845
 }
814 846
 
815
-func (m *MetricFamily) GetHelp() string {
816
-	if m != nil && m.Help != nil {
817
-		return *m.Help
847
+func (x *MetricFamily) GetHelp() string {
848
+	if x != nil && x.Help != nil {
849
+		return *x.Help
818 850
 	}
819 851
 	return ""
820 852
 }
821 853
 
822
-func (m *MetricFamily) GetType() MetricType {
823
-	if m != nil && m.Type != nil {
824
-		return *m.Type
854
+func (x *MetricFamily) GetType() MetricType {
855
+	if x != nil && x.Type != nil {
856
+		return *x.Type
825 857
 	}
826 858
 	return MetricType_COUNTER
827 859
 }
828 860
 
829
-func (m *MetricFamily) GetMetric() []*Metric {
830
-	if m != nil {
831
-		return m.Metric
861
+func (x *MetricFamily) GetMetric() []*Metric {
862
+	if x != nil {
863
+		return x.Metric
832 864
 	}
833 865
 	return nil
834 866
 }
835 867
 
836
-func init() {
837
-	proto.RegisterEnum("io.prometheus.client.MetricType", MetricType_name, MetricType_value)
838
-	proto.RegisterType((*LabelPair)(nil), "io.prometheus.client.LabelPair")
839
-	proto.RegisterType((*Gauge)(nil), "io.prometheus.client.Gauge")
840
-	proto.RegisterType((*Counter)(nil), "io.prometheus.client.Counter")
841
-	proto.RegisterType((*Quantile)(nil), "io.prometheus.client.Quantile")
842
-	proto.RegisterType((*Summary)(nil), "io.prometheus.client.Summary")
843
-	proto.RegisterType((*Untyped)(nil), "io.prometheus.client.Untyped")
844
-	proto.RegisterType((*Histogram)(nil), "io.prometheus.client.Histogram")
845
-	proto.RegisterType((*Bucket)(nil), "io.prometheus.client.Bucket")
846
-	proto.RegisterType((*BucketSpan)(nil), "io.prometheus.client.BucketSpan")
847
-	proto.RegisterType((*Exemplar)(nil), "io.prometheus.client.Exemplar")
848
-	proto.RegisterType((*Metric)(nil), "io.prometheus.client.Metric")
849
-	proto.RegisterType((*MetricFamily)(nil), "io.prometheus.client.MetricFamily")
850
-}
851
-
852
-func init() {
853
-	proto.RegisterFile("io/prometheus/client/metrics.proto", fileDescriptor_d1e5ddb18987a258)
854
-}
855
-
856
-var fileDescriptor_d1e5ddb18987a258 = []byte{
857
-	// 896 bytes of a gzipped FileDescriptorProto
858
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x8e, 0xdb, 0x44,
859
-	0x18, 0xc5, 0x9b, 0x5f, 0x7f, 0xd9, 0x6c, 0xd3, 0x61, 0x55, 0x59, 0x0b, 0xcb, 0x06, 0x4b, 0x48,
860
-	0x0b, 0x42, 0x8e, 0x40, 0x5b, 0x81, 0x0a, 0x5c, 0xec, 0xb6, 0xe9, 0x16, 0x89, 0xb4, 0x65, 0x92,
861
-	0x5c, 0x14, 0x2e, 0xac, 0x49, 0x32, 0xeb, 0x58, 0x78, 0x3c, 0xc6, 0x1e, 0x57, 0x2c, 0x2f, 0xc0,
862
-	0x35, 0xaf, 0xc0, 0xc3, 0xf0, 0x22, 0x3c, 0x08, 0x68, 0xfe, 0xec, 0xdd, 0xe2, 0x94, 0xd2, 0x3b,
863
-	0x7f, 0x67, 0xce, 0xf7, 0xcd, 0x39, 0xe3, 0xc9, 0x71, 0xc0, 0x8f, 0xf9, 0x24, 0xcb, 0x39, 0xa3,
864
-	0x62, 0x4b, 0xcb, 0x62, 0xb2, 0x4e, 0x62, 0x9a, 0x8a, 0x09, 0xa3, 0x22, 0x8f, 0xd7, 0x45, 0x90,
865
-	0xe5, 0x5c, 0x70, 0x74, 0x18, 0xf3, 0xa0, 0xe6, 0x04, 0x9a, 0x73, 0x74, 0x12, 0x71, 0x1e, 0x25,
866
-	0x74, 0xa2, 0x38, 0xab, 0xf2, 0x6a, 0x22, 0x62, 0x46, 0x0b, 0x41, 0x58, 0xa6, 0xdb, 0xfc, 0xfb,
867
-	0xe0, 0x7e, 0x47, 0x56, 0x34, 0x79, 0x4e, 0xe2, 0x1c, 0x21, 0x68, 0xa7, 0x84, 0x51, 0xcf, 0x19,
868
-	0x3b, 0xa7, 0x2e, 0x56, 0xcf, 0xe8, 0x10, 0x3a, 0x2f, 0x49, 0x52, 0x52, 0x6f, 0x4f, 0x81, 0xba,
869
-	0xf0, 0x8f, 0xa1, 0x73, 0x49, 0xca, 0xe8, 0xc6, 0xb2, 0xec, 0x71, 0xec, 0xf2, 0x8f, 0xd0, 0x7b,
870
-	0xc8, 0xcb, 0x54, 0xd0, 0xbc, 0x99, 0x80, 0x1e, 0x40, 0x9f, 0xfe, 0x42, 0x59, 0x96, 0x90, 0x5c,
871
-	0x0d, 0x1e, 0x7c, 0xfe, 0x41, 0xd0, 0x64, 0x20, 0x98, 0x1a, 0x16, 0xae, 0xf8, 0xfe, 0xd7, 0xd0,
872
-	0xff, 0xbe, 0x24, 0xa9, 0x88, 0x13, 0x8a, 0x8e, 0xa0, 0xff, 0xb3, 0x79, 0x36, 0x1b, 0x54, 0xf5,
873
-	0x6d, 0xe5, 0x95, 0xb4, 0xdf, 0x1c, 0xe8, 0xcd, 0x4b, 0xc6, 0x48, 0x7e, 0x8d, 0x3e, 0x84, 0xfd,
874
-	0x82, 0xb0, 0x2c, 0xa1, 0xe1, 0x5a, 0xaa, 0x55, 0x13, 0xda, 0x78, 0xa0, 0x31, 0x65, 0x00, 0x1d,
875
-	0x03, 0x18, 0x4a, 0x51, 0x32, 0x33, 0xc9, 0xd5, 0xc8, 0xbc, 0x64, 0xd2, 0x47, 0xb5, 0x7f, 0x6b,
876
-	0xdc, 0xda, 0xed, 0xc3, 0x2a, 0xae, 0xf5, 0xf9, 0x27, 0xd0, 0x5b, 0xa6, 0xe2, 0x3a, 0xa3, 0x9b,
877
-	0x1d, 0xa7, 0xf8, 0x57, 0x1b, 0xdc, 0x27, 0x71, 0x21, 0x78, 0x94, 0x13, 0xf6, 0x26, 0x62, 0x3f,
878
-	0x05, 0x74, 0x93, 0x12, 0x5e, 0x25, 0x9c, 0x08, 0xaf, 0xad, 0x66, 0x8e, 0x6e, 0x10, 0x1f, 0x4b,
879
-	0xfc, 0xbf, 0xac, 0x9d, 0x41, 0x77, 0x55, 0xae, 0x7f, 0xa2, 0xc2, 0x18, 0x7b, 0xbf, 0xd9, 0xd8,
880
-	0x85, 0xe2, 0x60, 0xc3, 0x45, 0xf7, 0xa0, 0x5b, 0xac, 0xb7, 0x94, 0x11, 0xaf, 0x33, 0x76, 0x4e,
881
-	0xef, 0x62, 0x53, 0xa1, 0x8f, 0xe0, 0xe0, 0x57, 0x9a, 0xf3, 0x50, 0x6c, 0x73, 0x5a, 0x6c, 0x79,
882
-	0xb2, 0xf1, 0xba, 0x6a, 0xc3, 0xa1, 0x44, 0x17, 0x16, 0x94, 0x9a, 0x14, 0x4d, 0x5b, 0xec, 0x29,
883
-	0x8b, 0xae, 0x44, 0xb4, 0xc1, 0x53, 0x18, 0xd5, 0xcb, 0xc6, 0x5e, 0x5f, 0xcd, 0x39, 0xa8, 0x48,
884
-	0xda, 0xdc, 0x14, 0x86, 0x29, 0x8d, 0x88, 0x88, 0x5f, 0xd2, 0xb0, 0xc8, 0x48, 0xea, 0xb9, 0xca,
885
-	0xc4, 0xf8, 0x75, 0x26, 0xe6, 0x19, 0x49, 0xf1, 0xbe, 0x6d, 0x93, 0x95, 0x94, 0x5d, 0x8d, 0xd9,
886
-	0xd0, 0x44, 0x10, 0x0f, 0xc6, 0xad, 0x53, 0x84, 0xab, 0xe1, 0x8f, 0x24, 0x78, 0x8b, 0xa6, 0xa5,
887
-	0x0f, 0xc6, 0x2d, 0xe9, 0xce, 0xa2, 0x5a, 0xfe, 0x14, 0x86, 0x19, 0x2f, 0xe2, 0x5a, 0xd4, 0xfe,
888
-	0x9b, 0x8a, 0xb2, 0x6d, 0x56, 0x54, 0x35, 0x46, 0x8b, 0x1a, 0x6a, 0x51, 0x16, 0xad, 0x44, 0x55,
889
-	0x34, 0x2d, 0xea, 0x40, 0x8b, 0xb2, 0xa8, 0x12, 0xe5, 0xff, 0xe9, 0x40, 0x57, 0x6f, 0x85, 0x3e,
890
-	0x86, 0xd1, 0xba, 0x64, 0x65, 0x72, 0xd3, 0x88, 0xbe, 0x66, 0x77, 0x6a, 0x5c, 0x5b, 0x39, 0x83,
891
-	0x7b, 0xaf, 0x52, 0x6f, 0x5d, 0xb7, 0xc3, 0x57, 0x1a, 0xf4, 0x5b, 0x39, 0x81, 0x41, 0x99, 0x65,
892
-	0x34, 0x0f, 0x57, 0xbc, 0x4c, 0x37, 0xe6, 0xce, 0x81, 0x82, 0x2e, 0x24, 0x72, 0x2b, 0x17, 0x5a,
893
-	0xff, 0x3b, 0x17, 0xa0, 0x3e, 0x32, 0x79, 0x11, 0xf9, 0xd5, 0x55, 0x41, 0xb5, 0x83, 0xbb, 0xd8,
894
-	0x54, 0x12, 0x4f, 0x68, 0x1a, 0x89, 0xad, 0xda, 0x7d, 0x88, 0x4d, 0xe5, 0xff, 0xee, 0x40, 0xdf,
895
-	0x0e, 0x45, 0xf7, 0xa1, 0x93, 0xc8, 0x54, 0xf4, 0x1c, 0xf5, 0x82, 0x4e, 0x9a, 0x35, 0x54, 0xc1,
896
-	0x89, 0x35, 0xbb, 0x39, 0x71, 0xd0, 0x97, 0xe0, 0x56, 0xa9, 0x6b, 0x4c, 0x1d, 0x05, 0x3a, 0x97,
897
-	0x03, 0x9b, 0xcb, 0xc1, 0xc2, 0x32, 0x70, 0x4d, 0xf6, 0xff, 0xde, 0x83, 0xee, 0x4c, 0xa5, 0xfc,
898
-	0xdb, 0x2a, 0xfa, 0x0c, 0x3a, 0x91, 0xcc, 0x69, 0x13, 0xb2, 0xef, 0x35, 0xb7, 0xa9, 0x28, 0xc7,
899
-	0x9a, 0x89, 0xbe, 0x80, 0xde, 0x5a, 0x67, 0xb7, 0x11, 0x7b, 0xdc, 0xdc, 0x64, 0x02, 0x1e, 0x5b,
900
-	0xb6, 0x6c, 0x2c, 0x74, 0xb0, 0xaa, 0x3b, 0xb0, 0xb3, 0xd1, 0xa4, 0x2f, 0xb6, 0x6c, 0xd9, 0x58,
901
-	0xea, 0x20, 0x54, 0xa1, 0xb1, 0xb3, 0xd1, 0xa4, 0x25, 0xb6, 0x6c, 0xf4, 0x0d, 0xb8, 0x5b, 0x9b,
902
-	0x8f, 0x2a, 0x2c, 0x76, 0x1e, 0x4c, 0x15, 0xa3, 0xb8, 0xee, 0x90, 0x89, 0x5a, 0x9d, 0x75, 0xc8,
903
-	0x0a, 0x95, 0x48, 0x2d, 0x3c, 0xa8, 0xb0, 0x59, 0xe1, 0xff, 0xe1, 0xc0, 0xbe, 0x7e, 0x03, 0x8f,
904
-	0x09, 0x8b, 0x93, 0xeb, 0xc6, 0x4f, 0x24, 0x82, 0xf6, 0x96, 0x26, 0x99, 0xf9, 0x42, 0xaa, 0x67,
905
-	0x74, 0x06, 0x6d, 0xa9, 0x51, 0x1d, 0xe1, 0xc1, 0xae, 0x5f, 0xb8, 0x9e, 0xbc, 0xb8, 0xce, 0x28,
906
-	0x56, 0x6c, 0x99, 0xb9, 0xfa, 0xab, 0xee, 0xb5, 0x5f, 0x97, 0xb9, 0xba, 0x0f, 0x1b, 0xee, 0x27,
907
-	0x2b, 0x80, 0x7a, 0x12, 0x1a, 0x40, 0xef, 0xe1, 0xb3, 0xe5, 0xd3, 0xc5, 0x14, 0x8f, 0xde, 0x41,
908
-	0x2e, 0x74, 0x2e, 0xcf, 0x97, 0x97, 0xd3, 0x91, 0x23, 0xf1, 0xf9, 0x72, 0x36, 0x3b, 0xc7, 0x2f,
909
-	0x46, 0x7b, 0xb2, 0x58, 0x3e, 0x5d, 0xbc, 0x78, 0x3e, 0x7d, 0x34, 0x6a, 0xa1, 0x21, 0xb8, 0x4f,
910
-	0xbe, 0x9d, 0x2f, 0x9e, 0x5d, 0xe2, 0xf3, 0xd9, 0xa8, 0x8d, 0xde, 0x85, 0x3b, 0xaa, 0x27, 0xac,
911
-	0xc1, 0xce, 0x05, 0x86, 0xc6, 0x3f, 0x18, 0x3f, 0x3c, 0x88, 0x62, 0xb1, 0x2d, 0x57, 0xc1, 0x9a,
912
-	0xb3, 0x7f, 0xff, 0x45, 0x09, 0x19, 0xdf, 0xd0, 0x64, 0x12, 0xf1, 0xaf, 0x62, 0x1e, 0xd6, 0xab,
913
-	0xa1, 0x5e, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0x16, 0x77, 0x81, 0x98, 0xd7, 0x08, 0x00, 0x00,
868
+var File_io_prometheus_client_metrics_proto protoreflect.FileDescriptor
869
+
870
+var file_io_prometheus_client_metrics_proto_rawDesc = []byte{
871
+	0x0a, 0x22, 0x69, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2f,
872
+	0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70,
873
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68,
874
+	0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
875
+	0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
876
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x09, 0x4c,
877
+	0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
878
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05,
879
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c,
880
+	0x75, 0x65, 0x22, 0x1d, 0x0a, 0x05, 0x47, 0x61, 0x75, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76,
881
+	0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
882
+	0x65, 0x22, 0xa4, 0x01, 0x0a, 0x07, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x14, 0x0a,
883
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61,
884
+	0x6c, 0x75, 0x65, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x18,
885
+	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65,
886
+	0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x78, 0x65,
887
+	0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x12,
888
+	0x47, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73,
889
+	0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
890
+	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
891
+	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x54,
892
+	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x3c, 0x0a, 0x08, 0x51, 0x75, 0x61, 0x6e,
893
+	0x74, 0x69, 0x6c, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x6c, 0x65,
894
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x6c, 0x65,
895
+	0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
896
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x07, 0x53, 0x75, 0x6d, 0x6d, 0x61,
897
+	0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75,
898
+	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65,
899
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f,
900
+	0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x73, 0x61, 0x6d, 0x70, 0x6c,
901
+	0x65, 0x53, 0x75, 0x6d, 0x12, 0x3a, 0x0a, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x6c, 0x65,
902
+	0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d,
903
+	0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x51, 0x75,
904
+	0x61, 0x6e, 0x74, 0x69, 0x6c, 0x65, 0x52, 0x08, 0x71, 0x75, 0x61, 0x6e, 0x74, 0x69, 0x6c, 0x65,
905
+	0x12, 0x47, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65,
906
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
907
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
908
+	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
909
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x1f, 0x0a, 0x07, 0x55, 0x6e, 0x74,
910
+	0x79, 0x70, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
911
+	0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xac, 0x05, 0x0a, 0x09, 0x48,
912
+	0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x61, 0x6d, 0x70,
913
+	0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b,
914
+	0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73,
915
+	0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61,
916
+	0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x43,
917
+	0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x61, 0x6d,
918
+	0x70, 0x6c, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x73,
919
+	0x61, 0x6d, 0x70, 0x6c, 0x65, 0x53, 0x75, 0x6d, 0x12, 0x34, 0x0a, 0x06, 0x62, 0x75, 0x63, 0x6b,
920
+	0x65, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72,
921
+	0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e,
922
+	0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x47,
923
+	0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
924
+	0x61, 0x6d, 0x70, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
925
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
926
+	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x54, 0x69,
927
+	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d,
928
+	0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12,
929
+	0x25, 0x0a, 0x0e, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c,
930
+	0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0d, 0x7a, 0x65, 0x72, 0x6f, 0x54, 0x68, 0x72,
931
+	0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63,
932
+	0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x7a, 0x65, 0x72, 0x6f,
933
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x63, 0x6f,
934
+	0x75, 0x6e, 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x01, 0x52,
935
+	0x0e, 0x7a, 0x65, 0x72, 0x6f, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12,
936
+	0x45, 0x0a, 0x0d, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x73, 0x70, 0x61, 0x6e,
937
+	0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d,
938
+	0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x42, 0x75,
939
+	0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x0c, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69,
940
+	0x76, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69,
941
+	0x76, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x12, 0x52, 0x0d,
942
+	0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x25, 0x0a,
943
+	0x0e, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18,
944
+	0x0b, 0x20, 0x03, 0x28, 0x01, 0x52, 0x0d, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43,
945
+	0x6f, 0x75, 0x6e, 0x74, 0x12, 0x45, 0x0a, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65,
946
+	0x5f, 0x73, 0x70, 0x61, 0x6e, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x69, 0x6f,
947
+	0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65,
948
+	0x6e, 0x74, 0x2e, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e, 0x52, 0x0c, 0x70,
949
+	0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x53, 0x70, 0x61, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x70,
950
+	0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x0d, 0x20,
951
+	0x03, 0x28, 0x12, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x44, 0x65, 0x6c,
952
+	0x74, 0x61, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63,
953
+	0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x01, 0x52, 0x0d, 0x70, 0x6f, 0x73, 0x69,
954
+	0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x42, 0x75,
955
+	0x63, 0x6b, 0x65, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69,
956
+	0x76, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f,
957
+	0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12,
958
+	0x34, 0x0a, 0x16, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f,
959
+	0x75, 0x6e, 0x74, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52,
960
+	0x14, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
961
+	0x46, 0x6c, 0x6f, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x70, 0x70, 0x65, 0x72, 0x5f, 0x62,
962
+	0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x75, 0x70, 0x70, 0x65,
963
+	0x72, 0x42, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c,
964
+	0x61, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72,
965
+	0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e,
966
+	0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x52, 0x08, 0x65, 0x78, 0x65, 0x6d, 0x70, 0x6c,
967
+	0x61, 0x72, 0x22, 0x3c, 0x0a, 0x0a, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x70, 0x61, 0x6e,
968
+	0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x11,
969
+	0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x65, 0x6e, 0x67,
970
+	0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68,
971
+	0x22, 0x91, 0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x72, 0x12, 0x35, 0x0a,
972
+	0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69,
973
+	0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69,
974
+	0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x6c,
975
+	0x61, 0x62, 0x65, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20,
976
+	0x01, 0x28, 0x01, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69,
977
+	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
978
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
979
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73,
980
+	0x74, 0x61, 0x6d, 0x70, 0x22, 0xff, 0x02, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12,
981
+	0x35, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
982
+	0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63,
983
+	0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x50, 0x61, 0x69, 0x72, 0x52,
984
+	0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x31, 0x0a, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x18,
985
+	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65,
986
+	0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x47, 0x61, 0x75,
987
+	0x67, 0x65, 0x52, 0x05, 0x67, 0x61, 0x75, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x75,
988
+	0x6e, 0x74, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e,
989
+	0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e,
990
+	0x74, 0x2e, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74,
991
+	0x65, 0x72, 0x12, 0x37, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20,
992
+	0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68,
993
+	0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61,
994
+	0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x07, 0x75,
995
+	0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x69,
996
+	0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69,
997
+	0x65, 0x6e, 0x74, 0x2e, 0x55, 0x6e, 0x74, 0x79, 0x70, 0x65, 0x64, 0x52, 0x07, 0x75, 0x6e, 0x74,
998
+	0x79, 0x70, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61,
999
+	0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f,
1000
+	0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x48,
1001
+	0x69, 0x73, 0x74, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x52, 0x09, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x67,
1002
+	0x72, 0x61, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
1003
+	0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73,
1004
+	0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x22, 0xa2, 0x01, 0x0a, 0x0c, 0x4d, 0x65, 0x74, 0x72, 0x69,
1005
+	0x63, 0x46, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
1006
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68,
1007
+	0x65, 0x6c, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x12,
1008
+	0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e,
1009
+	0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c,
1010
+	0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x52,
1011
+	0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x34, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18,
1012
+	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65,
1013
+	0x74, 0x68, 0x65, 0x75, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74,
1014
+	0x72, 0x69, 0x63, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x2a, 0x62, 0x0a, 0x0a, 0x4d,
1015
+	0x65, 0x74, 0x72, 0x69, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x55,
1016
+	0x4e, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x47, 0x41, 0x55, 0x47, 0x45, 0x10,
1017
+	0x01, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x4d, 0x4d, 0x41, 0x52, 0x59, 0x10, 0x02, 0x12, 0x0b,
1018
+	0x0a, 0x07, 0x55, 0x4e, 0x54, 0x59, 0x50, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x48,
1019
+	0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x47, 0x41,
1020
+	0x55, 0x47, 0x45, 0x5f, 0x48, 0x49, 0x53, 0x54, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x10, 0x05, 0x42,
1021
+	0x52, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73,
1022
+	0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
1023
+	0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x2f, 0x63,
1024
+	0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x67, 0x6f, 0x3b, 0x69,
1025
+	0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x65, 0x74, 0x68, 0x65, 0x75, 0x73, 0x5f, 0x63, 0x6c, 0x69,
1026
+	0x65, 0x6e, 0x74,
1027
+}
1028
+
1029
+var (
1030
+	file_io_prometheus_client_metrics_proto_rawDescOnce sync.Once
1031
+	file_io_prometheus_client_metrics_proto_rawDescData = file_io_prometheus_client_metrics_proto_rawDesc
1032
+)
1033
+
1034
+func file_io_prometheus_client_metrics_proto_rawDescGZIP() []byte {
1035
+	file_io_prometheus_client_metrics_proto_rawDescOnce.Do(func() {
1036
+		file_io_prometheus_client_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_io_prometheus_client_metrics_proto_rawDescData)
1037
+	})
1038
+	return file_io_prometheus_client_metrics_proto_rawDescData
1039
+}
1040
+
1041
+var file_io_prometheus_client_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
1042
+var file_io_prometheus_client_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
1043
+var file_io_prometheus_client_metrics_proto_goTypes = []interface{}{
1044
+	(MetricType)(0),               // 0: io.prometheus.client.MetricType
1045
+	(*LabelPair)(nil),             // 1: io.prometheus.client.LabelPair
1046
+	(*Gauge)(nil),                 // 2: io.prometheus.client.Gauge
1047
+	(*Counter)(nil),               // 3: io.prometheus.client.Counter
1048
+	(*Quantile)(nil),              // 4: io.prometheus.client.Quantile
1049
+	(*Summary)(nil),               // 5: io.prometheus.client.Summary
1050
+	(*Untyped)(nil),               // 6: io.prometheus.client.Untyped
1051
+	(*Histogram)(nil),             // 7: io.prometheus.client.Histogram
1052
+	(*Bucket)(nil),                // 8: io.prometheus.client.Bucket
1053
+	(*BucketSpan)(nil),            // 9: io.prometheus.client.BucketSpan
1054
+	(*Exemplar)(nil),              // 10: io.prometheus.client.Exemplar
1055
+	(*Metric)(nil),                // 11: io.prometheus.client.Metric
1056
+	(*MetricFamily)(nil),          // 12: io.prometheus.client.MetricFamily
1057
+	(*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp
1058
+}
1059
+var file_io_prometheus_client_metrics_proto_depIdxs = []int32{
1060
+	10, // 0: io.prometheus.client.Counter.exemplar:type_name -> io.prometheus.client.Exemplar
1061
+	13, // 1: io.prometheus.client.Counter.created_timestamp:type_name -> google.protobuf.Timestamp
1062
+	4,  // 2: io.prometheus.client.Summary.quantile:type_name -> io.prometheus.client.Quantile
1063
+	13, // 3: io.prometheus.client.Summary.created_timestamp:type_name -> google.protobuf.Timestamp
1064
+	8,  // 4: io.prometheus.client.Histogram.bucket:type_name -> io.prometheus.client.Bucket
1065
+	13, // 5: io.prometheus.client.Histogram.created_timestamp:type_name -> google.protobuf.Timestamp
1066
+	9,  // 6: io.prometheus.client.Histogram.negative_span:type_name -> io.prometheus.client.BucketSpan
1067
+	9,  // 7: io.prometheus.client.Histogram.positive_span:type_name -> io.prometheus.client.BucketSpan
1068
+	10, // 8: io.prometheus.client.Bucket.exemplar:type_name -> io.prometheus.client.Exemplar
1069
+	1,  // 9: io.prometheus.client.Exemplar.label:type_name -> io.prometheus.client.LabelPair
1070
+	13, // 10: io.prometheus.client.Exemplar.timestamp:type_name -> google.protobuf.Timestamp
1071
+	1,  // 11: io.prometheus.client.Metric.label:type_name -> io.prometheus.client.LabelPair
1072
+	2,  // 12: io.prometheus.client.Metric.gauge:type_name -> io.prometheus.client.Gauge
1073
+	3,  // 13: io.prometheus.client.Metric.counter:type_name -> io.prometheus.client.Counter
1074
+	5,  // 14: io.prometheus.client.Metric.summary:type_name -> io.prometheus.client.Summary
1075
+	6,  // 15: io.prometheus.client.Metric.untyped:type_name -> io.prometheus.client.Untyped
1076
+	7,  // 16: io.prometheus.client.Metric.histogram:type_name -> io.prometheus.client.Histogram
1077
+	0,  // 17: io.prometheus.client.MetricFamily.type:type_name -> io.prometheus.client.MetricType
1078
+	11, // 18: io.prometheus.client.MetricFamily.metric:type_name -> io.prometheus.client.Metric
1079
+	19, // [19:19] is the sub-list for method output_type
1080
+	19, // [19:19] is the sub-list for method input_type
1081
+	19, // [19:19] is the sub-list for extension type_name
1082
+	19, // [19:19] is the sub-list for extension extendee
1083
+	0,  // [0:19] is the sub-list for field type_name
1084
+}
1085
+
1086
+func init() { file_io_prometheus_client_metrics_proto_init() }
1087
+func file_io_prometheus_client_metrics_proto_init() {
1088
+	if File_io_prometheus_client_metrics_proto != nil {
1089
+		return
1090
+	}
1091
+	if !protoimpl.UnsafeEnabled {
1092
+		file_io_prometheus_client_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
1093
+			switch v := v.(*LabelPair); i {
1094
+			case 0:
1095
+				return &v.state
1096
+			case 1:
1097
+				return &v.sizeCache
1098
+			case 2:
1099
+				return &v.unknownFields
1100
+			default:
1101
+				return nil
1102
+			}
1103
+		}
1104
+		file_io_prometheus_client_metrics_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
1105
+			switch v := v.(*Gauge); i {
1106
+			case 0:
1107
+				return &v.state
1108
+			case 1:
1109
+				return &v.sizeCache
1110
+			case 2:
1111
+				return &v.unknownFields
1112
+			default:
1113
+				return nil
1114
+			}
1115
+		}
1116
+		file_io_prometheus_client_metrics_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
1117
+			switch v := v.(*Counter); i {
1118
+			case 0:
1119
+				return &v.state
1120
+			case 1:
1121
+				return &v.sizeCache
1122
+			case 2:
1123
+				return &v.unknownFields
1124
+			default:
1125
+				return nil
1126
+			}
1127
+		}
1128
+		file_io_prometheus_client_metrics_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
1129
+			switch v := v.(*Quantile); i {
1130
+			case 0:
1131
+				return &v.state
1132
+			case 1:
1133
+				return &v.sizeCache
1134
+			case 2:
1135
+				return &v.unknownFields
1136
+			default:
1137
+				return nil
1138
+			}
1139
+		}
1140
+		file_io_prometheus_client_metrics_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
1141
+			switch v := v.(*Summary); i {
1142
+			case 0:
1143
+				return &v.state
1144
+			case 1:
1145
+				return &v.sizeCache
1146
+			case 2:
1147
+				return &v.unknownFields
1148
+			default:
1149
+				return nil
1150
+			}
1151
+		}
1152
+		file_io_prometheus_client_metrics_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
1153
+			switch v := v.(*Untyped); i {
1154
+			case 0:
1155
+				return &v.state
1156
+			case 1:
1157
+				return &v.sizeCache
1158
+			case 2:
1159
+				return &v.unknownFields
1160
+			default:
1161
+				return nil
1162
+			}
1163
+		}
1164
+		file_io_prometheus_client_metrics_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
1165
+			switch v := v.(*Histogram); i {
1166
+			case 0:
1167
+				return &v.state
1168
+			case 1:
1169
+				return &v.sizeCache
1170
+			case 2:
1171
+				return &v.unknownFields
1172
+			default:
1173
+				return nil
1174
+			}
1175
+		}
1176
+		file_io_prometheus_client_metrics_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
1177
+			switch v := v.(*Bucket); i {
1178
+			case 0:
1179
+				return &v.state
1180
+			case 1:
1181
+				return &v.sizeCache
1182
+			case 2:
1183
+				return &v.unknownFields
1184
+			default:
1185
+				return nil
1186
+			}
1187
+		}
1188
+		file_io_prometheus_client_metrics_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
1189
+			switch v := v.(*BucketSpan); i {
1190
+			case 0:
1191
+				return &v.state
1192
+			case 1:
1193
+				return &v.sizeCache
1194
+			case 2:
1195
+				return &v.unknownFields
1196
+			default:
1197
+				return nil
1198
+			}
1199
+		}
1200
+		file_io_prometheus_client_metrics_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
1201
+			switch v := v.(*Exemplar); i {
1202
+			case 0:
1203
+				return &v.state
1204
+			case 1:
1205
+				return &v.sizeCache
1206
+			case 2:
1207
+				return &v.unknownFields
1208
+			default:
1209
+				return nil
1210
+			}
1211
+		}
1212
+		file_io_prometheus_client_metrics_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
1213
+			switch v := v.(*Metric); i {
1214
+			case 0:
1215
+				return &v.state
1216
+			case 1:
1217
+				return &v.sizeCache
1218
+			case 2:
1219
+				return &v.unknownFields
1220
+			default:
1221
+				return nil
1222
+			}
1223
+		}
1224
+		file_io_prometheus_client_metrics_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
1225
+			switch v := v.(*MetricFamily); i {
1226
+			case 0:
1227
+				return &v.state
1228
+			case 1:
1229
+				return &v.sizeCache
1230
+			case 2:
1231
+				return &v.unknownFields
1232
+			default:
1233
+				return nil
1234
+			}
1235
+		}
1236
+	}
1237
+	type x struct{}
1238
+	out := protoimpl.TypeBuilder{
1239
+		File: protoimpl.DescBuilder{
1240
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
1241
+			RawDescriptor: file_io_prometheus_client_metrics_proto_rawDesc,
1242
+			NumEnums:      1,
1243
+			NumMessages:   12,
1244
+			NumExtensions: 0,
1245
+			NumServices:   0,
1246
+		},
1247
+		GoTypes:           file_io_prometheus_client_metrics_proto_goTypes,
1248
+		DependencyIndexes: file_io_prometheus_client_metrics_proto_depIdxs,
1249
+		EnumInfos:         file_io_prometheus_client_metrics_proto_enumTypes,
1250
+		MessageInfos:      file_io_prometheus_client_metrics_proto_msgTypes,
1251
+	}.Build()
1252
+	File_io_prometheus_client_metrics_proto = out.File
1253
+	file_io_prometheus_client_metrics_proto_rawDesc = nil
1254
+	file_io_prometheus_client_metrics_proto_goTypes = nil
1255
+	file_io_prometheus_client_metrics_proto_depIdxs = nil
914 1256
 }
... ...
@@ -132,7 +132,10 @@ func (d *textDecoder) Decode(v *dto.MetricFamily) error {
132 132
 	}
133 133
 	// Pick off one MetricFamily per Decode until there's nothing left.
134 134
 	for key, fam := range d.fams {
135
-		*v = *fam
135
+		v.Name = fam.Name
136
+		v.Help = fam.Help
137
+		v.Type = fam.Type
138
+		v.Metric = fam.Metric
136 139
 		delete(d.fams, key)
137 140
 		return nil
138 141
 	}
... ...
@@ -18,9 +18,9 @@ import (
18 18
 	"io"
19 19
 	"net/http"
20 20
 
21
-	"github.com/golang/protobuf/proto" //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
22 21
 	"github.com/matttproud/golang_protobuf_extensions/pbutil"
23 22
 	"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
23
+	"google.golang.org/protobuf/encoding/prototext"
24 24
 
25 25
 	dto "github.com/prometheus/client_model/go"
26 26
 )
... ...
@@ -99,8 +99,11 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
99 99
 		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
100 100
 			return FmtText
101 101
 		}
102
-		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion || ver == "") {
103
-			return FmtOpenMetrics
102
+		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
103
+			if ver == OpenMetricsVersion_1_0_0 {
104
+				return FmtOpenMetrics_1_0_0
105
+			}
106
+			return FmtOpenMetrics_0_0_1
104 107
 		}
105 108
 	}
106 109
 	return FmtText
... ...
@@ -133,7 +136,7 @@ func NewEncoder(w io.Writer, format Format) Encoder {
133 133
 	case FmtProtoText:
134 134
 		return encoderCloser{
135 135
 			encode: func(v *dto.MetricFamily) error {
136
-				_, err := fmt.Fprintln(w, proto.MarshalTextString(v))
136
+				_, err := fmt.Fprintln(w, prototext.Format(v))
137 137
 				return err
138 138
 			},
139 139
 			close: func() error { return nil },
... ...
@@ -146,7 +149,7 @@ func NewEncoder(w io.Writer, format Format) Encoder {
146 146
 			},
147 147
 			close: func() error { return nil },
148 148
 		}
149
-	case FmtOpenMetrics:
149
+	case FmtOpenMetrics_0_0_1, FmtOpenMetrics_1_0_0:
150 150
 		return encoderCloser{
151 151
 			encode: func(v *dto.MetricFamily) error {
152 152
 				_, err := MetricFamilyToOpenMetrics(w, v)
... ...
@@ -19,20 +19,22 @@ type Format string
19 19
 
20 20
 // Constants to assemble the Content-Type values for the different wire protocols.
21 21
 const (
22
-	TextVersion        = "0.0.4"
23
-	ProtoType          = `application/vnd.google.protobuf`
24
-	ProtoProtocol      = `io.prometheus.client.MetricFamily`
25
-	ProtoFmt           = ProtoType + "; proto=" + ProtoProtocol + ";"
26
-	OpenMetricsType    = `application/openmetrics-text`
27
-	OpenMetricsVersion = "0.0.1"
22
+	TextVersion              = "0.0.4"
23
+	ProtoType                = `application/vnd.google.protobuf`
24
+	ProtoProtocol            = `io.prometheus.client.MetricFamily`
25
+	ProtoFmt                 = ProtoType + "; proto=" + ProtoProtocol + ";"
26
+	OpenMetricsType          = `application/openmetrics-text`
27
+	OpenMetricsVersion_0_0_1 = "0.0.1"
28
+	OpenMetricsVersion_1_0_0 = "1.0.0"
28 29
 
29 30
 	// The Content-Type values for the different wire protocols.
30
-	FmtUnknown      Format = `<unknown>`
31
-	FmtText         Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
32
-	FmtProtoDelim   Format = ProtoFmt + ` encoding=delimited`
33
-	FmtProtoText    Format = ProtoFmt + ` encoding=text`
34
-	FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
35
-	FmtOpenMetrics  Format = OpenMetricsType + `; version=` + OpenMetricsVersion + `; charset=utf-8`
31
+	FmtUnknown           Format = `<unknown>`
32
+	FmtText              Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
33
+	FmtProtoDelim        Format = ProtoFmt + ` encoding=delimited`
34
+	FmtProtoText         Format = ProtoFmt + ` encoding=text`
35
+	FmtProtoCompact      Format = ProtoFmt + ` encoding=compact-text`
36
+	FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
37
+	FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
36 38
 )
37 39
 
38 40
 const (
... ...
@@ -24,8 +24,8 @@ import (
24 24
 
25 25
 	dto "github.com/prometheus/client_model/go"
26 26
 
27
-	"github.com/golang/protobuf/proto" //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
28 27
 	"github.com/prometheus/common/model"
28
+	"google.golang.org/protobuf/proto"
29 29
 )
30 30
 
31 31
 // A stateFn is a function that represents a state in a state machine. By
... ...
@@ -2,6 +2,7 @@
2 2
 linters:
3 3
   enable:
4 4
   - godot
5
+  - misspell
5 6
   - revive
6 7
 
7 8
 linter-settings:
... ...
@@ -10,3 +11,5 @@ linter-settings:
10 10
     exclude:
11 11
     # Ignore "See: URL"
12 12
     - 'See:'
13
+  misspell:
14
+    locale: US
... ...
@@ -49,19 +49,19 @@ endif
49 49
 GOTEST := $(GO) test
50 50
 GOTEST_DIR :=
51 51
 ifneq ($(CIRCLE_JOB),)
52
-ifneq ($(shell which gotestsum),)
52
+ifneq ($(shell command -v gotestsum > /dev/null),)
53 53
 	GOTEST_DIR := test-results
54 54
 	GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml --
55 55
 endif
56 56
 endif
57 57
 
58
-PROMU_VERSION ?= 0.14.0
58
+PROMU_VERSION ?= 0.15.0
59 59
 PROMU_URL     := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
60 60
 
61 61
 SKIP_GOLANGCI_LINT :=
62 62
 GOLANGCI_LINT :=
63 63
 GOLANGCI_LINT_OPTS ?=
64
-GOLANGCI_LINT_VERSION ?= v1.49.0
64
+GOLANGCI_LINT_VERSION ?= v1.53.3
65 65
 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64.
66 66
 # windows isn't included here because of the path separator being different.
67 67
 ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin))
... ...
@@ -91,6 +91,8 @@ BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS))
91 91
 PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS))
92 92
 TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS))
93 93
 
94
+SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG))
95
+
94 96
 ifeq ($(GOHOSTARCH),amd64)
95 97
         ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows))
96 98
                 # Only supported on amd64
... ...
@@ -176,7 +178,7 @@ endif
176 176
 .PHONY: common-yamllint
177 177
 common-yamllint:
178 178
 	@echo ">> running yamllint on all YAML files in the repository"
179
-ifeq (, $(shell which yamllint))
179
+ifeq (, $(shell command -v yamllint > /dev/null))
180 180
 	@echo "yamllint not installed so skipping"
181 181
 else
182 182
 	yamllint .
... ...
@@ -205,7 +207,7 @@ common-tarball: promu
205 205
 .PHONY: common-docker $(BUILD_DOCKER_ARCHS)
206 206
 common-docker: $(BUILD_DOCKER_ARCHS)
207 207
 $(BUILD_DOCKER_ARCHS): common-docker-%:
208
-	docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" \
208
+	docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \
209 209
 		-f $(DOCKERFILE_PATH) \
210 210
 		--build-arg ARCH="$*" \
211 211
 		--build-arg OS="linux" \
... ...
@@ -214,19 +216,19 @@ $(BUILD_DOCKER_ARCHS): common-docker-%:
214 214
 .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS)
215 215
 common-docker-publish: $(PUBLISH_DOCKER_ARCHS)
216 216
 $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%:
217
-	docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)"
217
+	docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)"
218 218
 
219 219
 DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION)))
220 220
 .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS)
221 221
 common-docker-tag-latest: $(TAG_DOCKER_ARCHS)
222 222
 $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%:
223
-	docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
224
-	docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
223
+	docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest"
224
+	docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)"
225 225
 
226 226
 .PHONY: common-docker-manifest
227 227
 common-docker-manifest:
228
-	DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(DOCKER_IMAGE_TAG))
229
-	DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)"
228
+	DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG))
229
+	DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"
230 230
 
231 231
 .PHONY: promu
232 232
 promu: $(PROMU)
... ...
@@ -51,11 +51,11 @@ ensure the `fixtures` directory is up to date by removing the existing directory
51 51
 extracting the ttar file using `make fixtures/.unpacked` or just `make test`.
52 52
 
53 53
 ```bash
54
-rm -rf fixtures
54
+rm -rf testdata/fixtures
55 55
 make test
56 56
 ```
57 57
 
58 58
 Next, make the required changes to the extracted files in the `fixtures` directory.  When
59 59
 the changes are complete, run `make update_fixtures` to create a new `fixtures.ttar` file
60 60
 based on the updated `fixtures` directory.  And finally, verify the changes using
61
-`git diff fixtures.ttar`.
61
+`git diff testdata/fixtures.ttar`.
... ...
@@ -55,7 +55,7 @@ type ARPEntry struct {
55 55
 func (fs FS) GatherARPEntries() ([]ARPEntry, error) {
56 56
 	data, err := os.ReadFile(fs.proc.Path("net/arp"))
57 57
 	if err != nil {
58
-		return nil, fmt.Errorf("error reading arp %q: %w", fs.proc.Path("net/arp"), err)
58
+		return nil, fmt.Errorf("%s: error reading arp %s: %w", ErrFileRead, fs.proc.Path("net/arp"), err)
59 59
 	}
60 60
 
61 61
 	return parseARPEntries(data)
... ...
@@ -78,11 +78,11 @@ func parseARPEntries(data []byte) ([]ARPEntry, error) {
78 78
 		} else if width == expectedDataWidth {
79 79
 			entry, err := parseARPEntry(columns)
80 80
 			if err != nil {
81
-				return []ARPEntry{}, fmt.Errorf("failed to parse ARP entry: %w", err)
81
+				return []ARPEntry{}, fmt.Errorf("%s: Failed to parse ARP entry: %v: %w", ErrFileParse, entry, err)
82 82
 			}
83 83
 			entries = append(entries, entry)
84 84
 		} else {
85
-			return []ARPEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedDataWidth)
85
+			return []ARPEntry{}, fmt.Errorf("%s: %d columns found, but expected %d: %w", ErrFileParse, width, expectedDataWidth, err)
86 86
 		}
87 87
 
88 88
 	}
... ...
@@ -55,7 +55,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
55 55
 		parts := strings.Fields(line)
56 56
 
57 57
 		if len(parts) < 4 {
58
-			return nil, fmt.Errorf("invalid number of fields when parsing buddyinfo")
58
+			return nil, fmt.Errorf("%w: Invalid number of fields, found: %v", ErrFileParse, parts)
59 59
 		}
60 60
 
61 61
 		node := strings.TrimRight(parts[1], ",")
... ...
@@ -66,7 +66,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
66 66
 			bucketCount = arraySize
67 67
 		} else {
68 68
 			if bucketCount != arraySize {
69
-				return nil, fmt.Errorf("mismatch in number of buddyinfo buckets, previous count %d, new count %d", bucketCount, arraySize)
69
+				return nil, fmt.Errorf("%w: mismatch in number of buddyinfo buckets, previous count %d, new count %d", ErrFileParse, bucketCount, arraySize)
70 70
 			}
71 71
 		}
72 72
 
... ...
@@ -74,7 +74,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
74 74
 		for i := 0; i < arraySize; i++ {
75 75
 			sizes[i], err = strconv.ParseFloat(parts[i+4], 64)
76 76
 			if err != nil {
77
-				return nil, fmt.Errorf("invalid value in buddyinfo: %w", err)
77
+				return nil, fmt.Errorf("%s: Invalid valid in buddyinfo: %f: %w", ErrFileParse, sizes[i], err)
78 78
 			}
79 79
 		}
80 80
 
... ...
@@ -79,7 +79,7 @@ func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
79 79
 	// find the first "processor" line
80 80
 	firstLine := firstNonEmptyLine(scanner)
81 81
 	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
82
-		return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
82
+		return nil, fmt.Errorf("%w: Cannot parse  line: %q", ErrFileParse, firstLine)
83 83
 	}
84 84
 	field := strings.SplitN(firstLine, ": ", 2)
85 85
 	v, err := strconv.ParseUint(field[1], 0, 32)
... ...
@@ -192,9 +192,10 @@ func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
192 192
 	scanner := bufio.NewScanner(bytes.NewReader(info))
193 193
 
194 194
 	firstLine := firstNonEmptyLine(scanner)
195
-	match, _ := regexp.MatchString("^[Pp]rocessor", firstLine)
195
+	match, err := regexp.MatchString("^[Pp]rocessor", firstLine)
196 196
 	if !match || !strings.Contains(firstLine, ":") {
197
-		return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
197
+		return nil, fmt.Errorf("%s: Cannot parse line: %q: %w", ErrFileParse, firstLine, err)
198
+
198 199
 	}
199 200
 	field := strings.SplitN(firstLine, ": ", 2)
200 201
 	cpuinfo := []CPUInfo{}
... ...
@@ -258,7 +259,7 @@ func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
258 258
 
259 259
 	firstLine := firstNonEmptyLine(scanner)
260 260
 	if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
261
-		return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
261
+		return nil, fmt.Errorf("%w: Cannot parse line: %q", ErrFileParse, firstLine)
262 262
 	}
263 263
 	field := strings.SplitN(firstLine, ": ", 2)
264 264
 	cpuinfo := []CPUInfo{}
... ...
@@ -283,7 +284,7 @@ func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
283 283
 		if strings.HasPrefix(line, "processor") {
284 284
 			match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
285 285
 			if len(match) < 2 {
286
-				return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
286
+				return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
287 287
 			}
288 288
 			cpu := commonCPUInfo
289 289
 			v, err := strconv.ParseUint(match[1], 0, 32)
... ...
@@ -343,7 +344,7 @@ func parseCPUInfoMips(info []byte) ([]CPUInfo, error) {
343 343
 	// find the first "processor" line
344 344
 	firstLine := firstNonEmptyLine(scanner)
345 345
 	if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
346
-		return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
346
+		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
347 347
 	}
348 348
 	field := strings.SplitN(firstLine, ": ", 2)
349 349
 	cpuinfo := []CPUInfo{}
... ...
@@ -421,7 +422,7 @@ func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
421 421
 
422 422
 	firstLine := firstNonEmptyLine(scanner)
423 423
 	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
424
-		return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
424
+		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
425 425
 	}
426 426
 	field := strings.SplitN(firstLine, ": ", 2)
427 427
 	v, err := strconv.ParseUint(field[1], 0, 32)
... ...
@@ -466,7 +467,7 @@ func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) {
466 466
 
467 467
 	firstLine := firstNonEmptyLine(scanner)
468 468
 	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
469
-		return nil, fmt.Errorf("invalid cpuinfo file: %q", firstLine)
469
+		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
470 470
 	}
471 471
 	field := strings.SplitN(firstLine, ": ", 2)
472 472
 	v, err := strconv.ParseUint(field[1], 0, 32)
... ...
@@ -55,12 +55,13 @@ func (fs FS) Crypto() ([]Crypto, error) {
55 55
 	path := fs.proc.Path("crypto")
56 56
 	b, err := util.ReadFileNoStat(path)
57 57
 	if err != nil {
58
-		return nil, fmt.Errorf("error reading crypto %q: %w", path, err)
58
+		return nil, fmt.Errorf("%s: Cannot read file %v: %w", ErrFileRead, b, err)
59
+
59 60
 	}
60 61
 
61 62
 	crypto, err := parseCrypto(bytes.NewReader(b))
62 63
 	if err != nil {
63
-		return nil, fmt.Errorf("error parsing crypto %q: %w", path, err)
64
+		return nil, fmt.Errorf("%s: Cannot parse %v: %w", ErrFileParse, crypto, err)
64 65
 	}
65 66
 
66 67
 	return crypto, nil
... ...
@@ -83,7 +84,7 @@ func parseCrypto(r io.Reader) ([]Crypto, error) {
83 83
 
84 84
 		kv := strings.Split(text, ":")
85 85
 		if len(kv) != 2 {
86
-			return nil, fmt.Errorf("malformed crypto line: %q", text)
86
+			return nil, fmt.Errorf("%w: Cannot parae line: %q", ErrFileParse, text)
87 87
 		}
88 88
 
89 89
 		k := strings.TrimSpace(kv[0])
... ...
@@ -20,7 +20,8 @@ import (
20 20
 // FS represents the pseudo-filesystem sys, which provides an interface to
21 21
 // kernel data structures.
22 22
 type FS struct {
23
-	proc fs.FS
23
+	proc   fs.FS
24
+	isReal bool
24 25
 }
25 26
 
26 27
 // DefaultMountPoint is the common mount point of the proc filesystem.
... ...
@@ -39,5 +40,11 @@ func NewFS(mountPoint string) (FS, error) {
39 39
 	if err != nil {
40 40
 		return FS{}, err
41 41
 	}
42
-	return FS{fs}, nil
42
+
43
+	isReal, err := isRealProc(mountPoint)
44
+	if err != nil {
45
+		return FS{}, err
46
+	}
47
+
48
+	return FS{fs, isReal}, nil
43 49
 }
44 50
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+//go:build netbsd || openbsd || solaris || windows || nostatfs
14
+// +build netbsd openbsd solaris windows nostatfs
15
+
16
+package procfs
17
+
18
+// isRealProc returns true on architectures that don't have a Type argument
19
+// in their Statfs_t struct
20
+func isRealProc(mountPoint string) (bool, error) {
21
+	return true, nil
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+//go:build !netbsd && !openbsd && !solaris && !windows && !nostatfs
14
+// +build !netbsd,!openbsd,!solaris,!windows,!nostatfs
15
+
16
+package procfs
17
+
18
+import (
19
+	"syscall"
20
+)
21
+
22
+// isRealProc determines whether supplied mountpoint is really a proc filesystem.
23
+func isRealProc(mountPoint string) (bool, error) {
24
+	stat := syscall.Statfs_t{}
25
+	err := syscall.Statfs(mountPoint, &stat)
26
+	if err != nil {
27
+		return false, err
28
+	}
29
+
30
+	// 0x9fa0 is PROC_SUPER_MAGIC: https://elixir.bootlin.com/linux/v6.1/source/include/uapi/linux/magic.h#L87
31
+	return stat.Type == 0x9fa0, nil
32
+}
... ...
@@ -236,7 +236,7 @@ func (fs FS) Fscacheinfo() (Fscacheinfo, error) {
236 236
 
237 237
 	m, err := parseFscacheinfo(bytes.NewReader(b))
238 238
 	if err != nil {
239
-		return Fscacheinfo{}, fmt.Errorf("failed to parse Fscacheinfo: %w", err)
239
+		return Fscacheinfo{}, fmt.Errorf("%s: Cannot parse %v: %w", ErrFileParse, m, err)
240 240
 	}
241 241
 
242 242
 	return *m, nil
... ...
@@ -245,7 +245,7 @@ func (fs FS) Fscacheinfo() (Fscacheinfo, error) {
245 245
 func setFSCacheFields(fields []string, setFields ...*uint64) error {
246 246
 	var err error
247 247
 	if len(fields) < len(setFields) {
248
-		return fmt.Errorf("Insufficient number of fields, expected %v, got %v", len(setFields), len(fields))
248
+		return fmt.Errorf("%s: Expected %d, but got %d: %w", ErrFileParse, len(setFields), len(fields), err)
249 249
 	}
250 250
 
251 251
 	for i := range setFields {
... ...
@@ -263,7 +263,7 @@ func parseFscacheinfo(r io.Reader) (*Fscacheinfo, error) {
263 263
 	for s.Scan() {
264 264
 		fields := strings.Fields(s.Text())
265 265
 		if len(fields) < 2 {
266
-			return nil, fmt.Errorf("malformed Fscacheinfo line: %q", s.Text())
266
+			return nil, fmt.Errorf("%w: malformed Fscacheinfo line: %q", ErrFileParse, s.Text())
267 267
 		}
268 268
 
269 269
 		switch fields[0] {
... ...
@@ -64,6 +64,21 @@ func ParsePInt64s(ss []string) ([]*int64, error) {
64 64
 	return us, nil
65 65
 }
66 66
 
67
+// Parses a uint64 from given hex in string.
68
+func ParseHexUint64s(ss []string) ([]*uint64, error) {
69
+	us := make([]*uint64, 0, len(ss))
70
+	for _, s := range ss {
71
+		u, err := strconv.ParseUint(s, 16, 64)
72
+		if err != nil {
73
+			return nil, err
74
+		}
75
+
76
+		us = append(us, &u)
77
+	}
78
+
79
+	return us, nil
80
+}
81
+
67 82
 // ReadUintFromFile reads a file and attempts to parse a uint64 from it.
68 83
 func ReadUintFromFile(path string) (uint64, error) {
69 84
 	data, err := os.ReadFile(path)
... ...
@@ -221,15 +221,16 @@ func parseIPPort(s string) (net.IP, uint16, error) {
221 221
 	case 46:
222 222
 		ip = net.ParseIP(s[1:40])
223 223
 		if ip == nil {
224
-			return nil, 0, fmt.Errorf("invalid IPv6 address: %s", s[1:40])
224
+			return nil, 0, fmt.Errorf("%s: Invalid IPv6 addr %s: %w", ErrFileParse, s[1:40], err)
225 225
 		}
226 226
 	default:
227
-		return nil, 0, fmt.Errorf("unexpected IP:Port: %s", s)
227
+		return nil, 0, fmt.Errorf("%s: Unexpected IP:Port %s: %w", ErrFileParse, s, err)
228 228
 	}
229 229
 
230 230
 	portString := s[len(s)-4:]
231 231
 	if len(portString) != 4 {
232
-		return nil, 0, fmt.Errorf("unexpected port string format: %s", portString)
232
+		return nil, 0,
233
+			fmt.Errorf("%s: Unexpected port string format %s: %w", ErrFileParse, portString, err)
233 234
 	}
234 235
 	port, err := strconv.ParseUint(portString, 16, 16)
235 236
 	if err != nil {
... ...
@@ -44,14 +44,14 @@ func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
44 44
 	loads := make([]float64, 3)
45 45
 	parts := strings.Fields(string(loadavgBytes))
46 46
 	if len(parts) < 3 {
47
-		return nil, fmt.Errorf("malformed loadavg line: too few fields in loadavg string: %q", string(loadavgBytes))
47
+		return nil, fmt.Errorf("%w: Malformed line %q", ErrFileParse, string(loadavgBytes))
48 48
 	}
49 49
 
50 50
 	var err error
51 51
 	for i, load := range parts[0:3] {
52 52
 		loads[i], err = strconv.ParseFloat(load, 64)
53 53
 		if err != nil {
54
-			return nil, fmt.Errorf("could not parse load %q: %w", load, err)
54
+			return nil, fmt.Errorf("%s: Cannot parse load: %f: %w", ErrFileParse, loads[i], err)
55 55
 		}
56 56
 	}
57 57
 	return &LoadAvg{
... ...
@@ -70,7 +70,7 @@ func (fs FS) MDStat() ([]MDStat, error) {
70 70
 	}
71 71
 	mdstat, err := parseMDStat(data)
72 72
 	if err != nil {
73
-		return nil, fmt.Errorf("error parsing mdstat %q: %w", fs.proc.Path("mdstat"), err)
73
+		return nil, fmt.Errorf("%s: Cannot parse %v: %w", ErrFileParse, fs.proc.Path("mdstat"), err)
74 74
 	}
75 75
 	return mdstat, nil
76 76
 }
... ...
@@ -90,13 +90,13 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
90 90
 
91 91
 		deviceFields := strings.Fields(line)
92 92
 		if len(deviceFields) < 3 {
93
-			return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line)
93
+			return nil, fmt.Errorf("%s: Expected 3+ lines, got %q", ErrFileParse, line)
94 94
 		}
95 95
 		mdName := deviceFields[0] // mdx
96 96
 		state := deviceFields[2]  // active or inactive
97 97
 
98 98
 		if len(lines) <= i+3 {
99
-			return nil, fmt.Errorf("error parsing %q: too few lines for md device", mdName)
99
+			return nil, fmt.Errorf("%w: Too few lines for md device: %q", ErrFileParse, mdName)
100 100
 		}
101 101
 
102 102
 		// Failed disks have the suffix (F) & Spare disks have the suffix (S).
... ...
@@ -105,7 +105,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
105 105
 		active, total, down, size, err := evalStatusLine(lines[i], lines[i+1])
106 106
 
107 107
 		if err != nil {
108
-			return nil, fmt.Errorf("error parsing md device lines: %w", err)
108
+			return nil, fmt.Errorf("%s: Cannot parse md device lines: %v: %w", ErrFileParse, active, err)
109 109
 		}
110 110
 
111 111
 		syncLineIdx := i + 2
... ...
@@ -140,7 +140,7 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
140 140
 			} else {
141 141
 				syncedBlocks, pct, finish, speed, err = evalRecoveryLine(lines[syncLineIdx])
142 142
 				if err != nil {
143
-					return nil, fmt.Errorf("error parsing sync line in md device %q: %w", mdName, err)
143
+					return nil, fmt.Errorf("%s: Cannot parse sync line in md device: %q: %w", ErrFileParse, mdName, err)
144 144
 				}
145 145
 			}
146 146
 		}
... ...
@@ -168,13 +168,13 @@ func parseMDStat(mdStatData []byte) ([]MDStat, error) {
168 168
 func evalStatusLine(deviceLine, statusLine string) (active, total, down, size int64, err error) {
169 169
 	statusFields := strings.Fields(statusLine)
170 170
 	if len(statusFields) < 1 {
171
-		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q", statusLine)
171
+		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
172 172
 	}
173 173
 
174 174
 	sizeStr := statusFields[0]
175 175
 	size, err = strconv.ParseInt(sizeStr, 10, 64)
176 176
 	if err != nil {
177
-		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
177
+		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
178 178
 	}
179 179
 
180 180
 	if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
... ...
@@ -189,17 +189,17 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, down, size in
189 189
 
190 190
 	matches := statusLineRE.FindStringSubmatch(statusLine)
191 191
 	if len(matches) != 5 {
192
-		return 0, 0, 0, 0, fmt.Errorf("couldn't find all the substring matches: %s", statusLine)
192
+		return 0, 0, 0, 0, fmt.Errorf("%s: Could not fild all substring matches %s: %w", ErrFileParse, statusLine, err)
193 193
 	}
194 194
 
195 195
 	total, err = strconv.ParseInt(matches[2], 10, 64)
196 196
 	if err != nil {
197
-		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
197
+		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected statusline %q: %w", ErrFileParse, statusLine, err)
198 198
 	}
199 199
 
200 200
 	active, err = strconv.ParseInt(matches[3], 10, 64)
201 201
 	if err != nil {
202
-		return 0, 0, 0, 0, fmt.Errorf("unexpected statusLine %q: %w", statusLine, err)
202
+		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected active %d: %w", ErrFileParse, active, err)
203 203
 	}
204 204
 	down = int64(strings.Count(matches[4], "_"))
205 205
 
... ...
@@ -209,42 +209,42 @@ func evalStatusLine(deviceLine, statusLine string) (active, total, down, size in
209 209
 func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, pct float64, finish float64, speed float64, err error) {
210 210
 	matches := recoveryLineBlocksRE.FindStringSubmatch(recoveryLine)
211 211
 	if len(matches) != 2 {
212
-		return 0, 0, 0, 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine)
212
+		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected recoveryLine %s: %w", ErrFileParse, recoveryLine, err)
213 213
 	}
214 214
 
215 215
 	syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
216 216
 	if err != nil {
217
-		return 0, 0, 0, 0, fmt.Errorf("error parsing int from recoveryLine %q: %w", recoveryLine, err)
217
+		return 0, 0, 0, 0, fmt.Errorf("%s: Unexpected parsing of recoveryLine %q: %w", ErrFileParse, recoveryLine, err)
218 218
 	}
219 219
 
220 220
 	// Get percentage complete
221 221
 	matches = recoveryLinePctRE.FindStringSubmatch(recoveryLine)
222 222
 	if len(matches) != 2 {
223
-		return syncedBlocks, 0, 0, 0, fmt.Errorf("unexpected recoveryLine matching percentage: %s", recoveryLine)
223
+		return syncedBlocks, 0, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching percentage %s", ErrFileParse, recoveryLine)
224 224
 	}
225 225
 	pct, err = strconv.ParseFloat(strings.TrimSpace(matches[1]), 64)
226 226
 	if err != nil {
227
-		return syncedBlocks, 0, 0, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
227
+		return syncedBlocks, 0, 0, 0, fmt.Errorf("%w: Error parsing float from recoveryLine %q", ErrFileParse, recoveryLine)
228 228
 	}
229 229
 
230 230
 	// Get time expected left to complete
231 231
 	matches = recoveryLineFinishRE.FindStringSubmatch(recoveryLine)
232 232
 	if len(matches) != 2 {
233
-		return syncedBlocks, pct, 0, 0, fmt.Errorf("unexpected recoveryLine matching est. finish time: %s", recoveryLine)
233
+		return syncedBlocks, pct, 0, 0, fmt.Errorf("%w: Unexpected recoveryLine matching est. finish time: %s", ErrFileParse, recoveryLine)
234 234
 	}
235 235
 	finish, err = strconv.ParseFloat(matches[1], 64)
236 236
 	if err != nil {
237
-		return syncedBlocks, pct, 0, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
237
+		return syncedBlocks, pct, 0, 0, fmt.Errorf("%w: Unable to parse float from recoveryLine: %q", ErrFileParse, recoveryLine)
238 238
 	}
239 239
 
240 240
 	// Get recovery speed
241 241
 	matches = recoveryLineSpeedRE.FindStringSubmatch(recoveryLine)
242 242
 	if len(matches) != 2 {
243
-		return syncedBlocks, pct, finish, 0, fmt.Errorf("unexpected recoveryLine matching speed: %s", recoveryLine)
243
+		return syncedBlocks, pct, finish, 0, fmt.Errorf("%w: Unexpected recoveryLine value: %s", ErrFileParse, recoveryLine)
244 244
 	}
245 245
 	speed, err = strconv.ParseFloat(matches[1], 64)
246 246
 	if err != nil {
247
-		return syncedBlocks, pct, finish, 0, fmt.Errorf("error parsing float from recoveryLine %q: %w", recoveryLine, err)
247
+		return syncedBlocks, pct, finish, 0, fmt.Errorf("%s: Error parsing float from recoveryLine: %q: %w", ErrFileParse, recoveryLine, err)
248 248
 	}
249 249
 
250 250
 	return syncedBlocks, pct, finish, speed, nil
... ...
@@ -152,7 +152,7 @@ func (fs FS) Meminfo() (Meminfo, error) {
152 152
 
153 153
 	m, err := parseMemInfo(bytes.NewReader(b))
154 154
 	if err != nil {
155
-		return Meminfo{}, fmt.Errorf("failed to parse meminfo: %w", err)
155
+		return Meminfo{}, fmt.Errorf("%s: %w", ErrFileParse, err)
156 156
 	}
157 157
 
158 158
 	return *m, nil
... ...
@@ -165,7 +165,7 @@ func parseMemInfo(r io.Reader) (*Meminfo, error) {
165 165
 		// Each line has at least a name and value; we ignore the unit.
166 166
 		fields := strings.Fields(s.Text())
167 167
 		if len(fields) < 2 {
168
-			return nil, fmt.Errorf("malformed meminfo line: %q", s.Text())
168
+			return nil, fmt.Errorf("%w: Malformed line %q", ErrFileParse, s.Text())
169 169
 		}
170 170
 
171 171
 		v, err := strconv.ParseUint(fields[1], 0, 64)
... ...
@@ -78,11 +78,11 @@ func parseMountInfoString(mountString string) (*MountInfo, error) {
78 78
 	mountInfo := strings.Split(mountString, " ")
79 79
 	mountInfoLength := len(mountInfo)
80 80
 	if mountInfoLength < 10 {
81
-		return nil, fmt.Errorf("couldn't find enough fields in mount string: %s", mountString)
81
+		return nil, fmt.Errorf("%w: Too few fields in mount string: %s", ErrFileParse, mountString)
82 82
 	}
83 83
 
84 84
 	if mountInfo[mountInfoLength-4] != "-" {
85
-		return nil, fmt.Errorf("couldn't find separator in expected field: %s", mountInfo[mountInfoLength-4])
85
+		return nil, fmt.Errorf("%w: couldn't find separator in expected field: %s", ErrFileParse, mountInfo[mountInfoLength-4])
86 86
 	}
87 87
 
88 88
 	mount := &MountInfo{
... ...
@@ -98,18 +98,18 @@ func parseMountInfoString(mountString string) (*MountInfo, error) {
98 98
 
99 99
 	mount.MountID, err = strconv.Atoi(mountInfo[0])
100 100
 	if err != nil {
101
-		return nil, fmt.Errorf("failed to parse mount ID")
101
+		return nil, fmt.Errorf("%w: mount ID: %q", ErrFileParse, mount.MountID)
102 102
 	}
103 103
 	mount.ParentID, err = strconv.Atoi(mountInfo[1])
104 104
 	if err != nil {
105
-		return nil, fmt.Errorf("failed to parse parent ID")
105
+		return nil, fmt.Errorf("%w: parent ID: %q", ErrFileParse, mount.ParentID)
106 106
 	}
107 107
 	// Has optional fields, which is a space separated list of values.
108 108
 	// Example: shared:2 master:7
109 109
 	if mountInfo[6] != "" {
110 110
 		mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6 : mountInfoLength-4])
111 111
 		if err != nil {
112
-			return nil, err
112
+			return nil, fmt.Errorf("%s: %w", ErrFileParse, err)
113 113
 		}
114 114
 	}
115 115
 	return mount, nil
... ...
@@ -186,6 +186,8 @@ type NFSOperationStats struct {
186 186
 	CumulativeTotalResponseMilliseconds uint64
187 187
 	// Duration from when a request was enqueued to when it was completely handled.
188 188
 	CumulativeTotalRequestMilliseconds uint64
189
+	// The average time from the point the client sends RPC requests until it receives the response.
190
+	AverageRTTMilliseconds float64
189 191
 	// The count of operations that complete with tk_status < 0.  These statuses usually indicate error conditions.
190 192
 	Errors uint64
191 193
 }
... ...
@@ -264,7 +266,7 @@ func parseMountStats(r io.Reader) ([]*Mount, error) {
264 264
 		if len(ss) > deviceEntryLen {
265 265
 			// Only NFSv3 and v4 are supported for parsing statistics
266 266
 			if m.Type != nfs3Type && m.Type != nfs4Type {
267
-				return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)
267
+				return nil, fmt.Errorf("%w: Cannot parse MountStats for %q", ErrFileParse, m.Type)
268 268
 			}
269 269
 
270 270
 			statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
... ...
@@ -288,7 +290,7 @@ func parseMountStats(r io.Reader) ([]*Mount, error) {
288 288
 //	device [device] mounted on [mount] with fstype [type]
289 289
 func parseMount(ss []string) (*Mount, error) {
290 290
 	if len(ss) < deviceEntryLen {
291
-		return nil, fmt.Errorf("invalid device entry: %v", ss)
291
+		return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
292 292
 	}
293 293
 
294 294
 	// Check for specific words appearing at specific indices to ensure
... ...
@@ -306,7 +308,7 @@ func parseMount(ss []string) (*Mount, error) {
306 306
 
307 307
 	for _, f := range format {
308 308
 		if ss[f.i] != f.s {
309
-			return nil, fmt.Errorf("invalid device entry: %v", ss)
309
+			return nil, fmt.Errorf("%w: Invalid device %q", ErrFileParse, ss)
310 310
 		}
311 311
 	}
312 312
 
... ...
@@ -343,7 +345,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
343 343
 		switch ss[0] {
344 344
 		case fieldOpts:
345 345
 			if len(ss) < 2 {
346
-				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
346
+				return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
347 347
 			}
348 348
 			if stats.Opts == nil {
349 349
 				stats.Opts = map[string]string{}
... ...
@@ -358,7 +360,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
358 358
 			}
359 359
 		case fieldAge:
360 360
 			if len(ss) < 2 {
361
-				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
361
+				return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
362 362
 			}
363 363
 			// Age integer is in seconds
364 364
 			d, err := time.ParseDuration(ss[1] + "s")
... ...
@@ -369,7 +371,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
369 369
 			stats.Age = d
370 370
 		case fieldBytes:
371 371
 			if len(ss) < 2 {
372
-				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
372
+				return nil, fmt.Errorf("%w: Incomplete information for NFS stats: %v", ErrFileParse, ss)
373 373
 			}
374 374
 			bstats, err := parseNFSBytesStats(ss[1:])
375 375
 			if err != nil {
... ...
@@ -379,7 +381,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
379 379
 			stats.Bytes = *bstats
380 380
 		case fieldEvents:
381 381
 			if len(ss) < 2 {
382
-				return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
382
+				return nil, fmt.Errorf("%w: Incomplete information for NFS events: %v", ErrFileParse, ss)
383 383
 			}
384 384
 			estats, err := parseNFSEventsStats(ss[1:])
385 385
 			if err != nil {
... ...
@@ -389,7 +391,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
389 389
 			stats.Events = *estats
390 390
 		case fieldTransport:
391 391
 			if len(ss) < 3 {
392
-				return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
392
+				return nil, fmt.Errorf("%w: Incomplete information for NFS transport stats: %v", ErrFileParse, ss)
393 393
 			}
394 394
 
395 395
 			tstats, err := parseNFSTransportStats(ss[1:], statVersion)
... ...
@@ -428,7 +430,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
428 428
 // integer fields.
429 429
 func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
430 430
 	if len(ss) != fieldBytesLen {
431
-		return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)
431
+		return nil, fmt.Errorf("%w: Invalid NFS bytes stats: %v", ErrFileParse, ss)
432 432
 	}
433 433
 
434 434
 	ns := make([]uint64, 0, fieldBytesLen)
... ...
@@ -457,7 +459,7 @@ func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
457 457
 // integer fields.
458 458
 func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
459 459
 	if len(ss) != fieldEventsLen {
460
-		return nil, fmt.Errorf("invalid NFS events stats: %v", ss)
460
+		return nil, fmt.Errorf("%w: invalid NFS events stats: %v", ErrFileParse, ss)
461 461
 	}
462 462
 
463 463
 	ns := make([]uint64, 0, fieldEventsLen)
... ...
@@ -521,7 +523,7 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
521 521
 		}
522 522
 
523 523
 		if len(ss) < minFields {
524
-			return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
524
+			return nil, fmt.Errorf("%w: invalid NFS per-operations stats: %v", ErrFileParse, ss)
525 525
 		}
526 526
 
527 527
 		// Skip string operation name for integers
... ...
@@ -534,7 +536,6 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
534 534
 
535 535
 			ns = append(ns, n)
536 536
 		}
537
-
538 537
 		opStats := NFSOperationStats{
539 538
 			Operation:                           strings.TrimSuffix(ss[0], ":"),
540 539
 			Requests:                            ns[0],
... ...
@@ -546,6 +547,9 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
546 546
 			CumulativeTotalResponseMilliseconds: ns[6],
547 547
 			CumulativeTotalRequestMilliseconds:  ns[7],
548 548
 		}
549
+		if ns[0] != 0 {
550
+			opStats.AverageRTTMilliseconds = float64(ns[6]) / float64(ns[0])
551
+		}
549 552
 
550 553
 		if len(ns) > 8 {
551 554
 			opStats.Errors = ns[8]
... ...
@@ -572,10 +576,10 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
572 572
 		} else if protocol == "udp" {
573 573
 			expectedLength = fieldTransport10UDPLen
574 574
 		} else {
575
-			return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.0 statement: %v", protocol, ss)
575
+			return nil, fmt.Errorf("%w: Invalid NFS protocol \"%s\" in stats 1.0 statement: %v", ErrFileParse, protocol, ss)
576 576
 		}
577 577
 		if len(ss) != expectedLength {
578
-			return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
578
+			return nil, fmt.Errorf("%w: Invalid NFS transport stats 1.0 statement: %v", ErrFileParse, ss)
579 579
 		}
580 580
 	case statVersion11:
581 581
 		var expectedLength int
... ...
@@ -584,13 +588,13 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
584 584
 		} else if protocol == "udp" {
585 585
 			expectedLength = fieldTransport11UDPLen
586 586
 		} else {
587
-			return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.1 statement: %v", protocol, ss)
587
+			return nil, fmt.Errorf("%w: invalid NFS protocol \"%s\" in stats 1.1 statement: %v", ErrFileParse, protocol, ss)
588 588
 		}
589 589
 		if len(ss) != expectedLength {
590
-			return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
590
+			return nil, fmt.Errorf("%w: invalid NFS transport stats 1.1 statement: %v", ErrFileParse, ss)
591 591
 		}
592 592
 	default:
593
-		return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion)
593
+		return nil, fmt.Errorf("%s: Unrecognized NFS transport stats version: %q", ErrFileParse, statVersion)
594 594
 	}
595 595
 
596 596
 	// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"bytes"
19 19
 	"fmt"
20 20
 	"io"
21
-	"strconv"
22 21
 	"strings"
23 22
 
24 23
 	"github.com/prometheus/procfs/internal/util"
... ...
@@ -28,9 +27,13 @@ import (
28 28
 // and contains netfilter conntrack statistics at one CPU core.
29 29
 type ConntrackStatEntry struct {
30 30
 	Entries       uint64
31
+	Searched      uint64
31 32
 	Found         uint64
33
+	New           uint64
32 34
 	Invalid       uint64
33 35
 	Ignore        uint64
36
+	Delete        uint64
37
+	DeleteList    uint64
34 38
 	Insert        uint64
35 39
 	InsertFailed  uint64
36 40
 	Drop          uint64
... ...
@@ -55,7 +58,7 @@ func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
55 55
 
56 56
 	stat, err := parseConntrackStat(bytes.NewReader(b))
57 57
 	if err != nil {
58
-		return nil, fmt.Errorf("failed to read conntrack stats from %q: %w", path, err)
58
+		return nil, fmt.Errorf("%s: Cannot read file: %v: %w", ErrFileRead, path, err)
59 59
 	}
60 60
 
61 61
 	return stat, nil
... ...
@@ -81,73 +84,35 @@ func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
81 81
 
82 82
 // Parses a ConntrackStatEntry from given array of fields.
83 83
 func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
84
-	if len(fields) != 17 {
85
-		return nil, fmt.Errorf("invalid conntrackstat entry, missing fields")
86
-	}
87
-	entry := &ConntrackStatEntry{}
88
-
89
-	entries, err := parseConntrackStatField(fields[0])
90
-	if err != nil {
91
-		return nil, err
92
-	}
93
-	entry.Entries = entries
94
-
95
-	found, err := parseConntrackStatField(fields[2])
96
-	if err != nil {
97
-		return nil, err
98
-	}
99
-	entry.Found = found
100
-
101
-	invalid, err := parseConntrackStatField(fields[4])
102
-	if err != nil {
103
-		return nil, err
104
-	}
105
-	entry.Invalid = invalid
106
-
107
-	ignore, err := parseConntrackStatField(fields[5])
108
-	if err != nil {
109
-		return nil, err
110
-	}
111
-	entry.Ignore = ignore
112
-
113
-	insert, err := parseConntrackStatField(fields[8])
84
+	entries, err := util.ParseHexUint64s(fields)
114 85
 	if err != nil {
115
-		return nil, err
86
+		return nil, fmt.Errorf("%s: Cannot parse entry: %d: %w", ErrFileParse, entries, err)
116 87
 	}
117
-	entry.Insert = insert
118
-
119
-	insertFailed, err := parseConntrackStatField(fields[9])
120
-	if err != nil {
121
-		return nil, err
88
+	numEntries := len(entries)
89
+	if numEntries < 16 || numEntries > 17 {
90
+		return nil,
91
+			fmt.Errorf("%w: invalid conntrackstat entry, invalid number of fields: %d", ErrFileParse, numEntries)
122 92
 	}
123
-	entry.InsertFailed = insertFailed
124 93
 
125
-	drop, err := parseConntrackStatField(fields[10])
126
-	if err != nil {
127
-		return nil, err
94
+	stats := &ConntrackStatEntry{
95
+		Entries:      *entries[0],
96
+		Searched:     *entries[1],
97
+		Found:        *entries[2],
98
+		New:          *entries[3],
99
+		Invalid:      *entries[4],
100
+		Ignore:       *entries[5],
101
+		Delete:       *entries[6],
102
+		DeleteList:   *entries[7],
103
+		Insert:       *entries[8],
104
+		InsertFailed: *entries[9],
105
+		Drop:         *entries[10],
106
+		EarlyDrop:    *entries[11],
128 107
 	}
129
-	entry.Drop = drop
130 108
 
131
-	earlyDrop, err := parseConntrackStatField(fields[11])
132
-	if err != nil {
133
-		return nil, err
109
+	// Ignore missing search_restart on Linux < 2.6.35.
110
+	if numEntries == 17 {
111
+		stats.SearchRestart = *entries[16]
134 112
 	}
135
-	entry.EarlyDrop = earlyDrop
136 113
 
137
-	searchRestart, err := parseConntrackStatField(fields[16])
138
-	if err != nil {
139
-		return nil, err
140
-	}
141
-	entry.SearchRestart = searchRestart
142
-
143
-	return entry, nil
144
-}
145
-
146
-// Parses a uint64 from given hex in string.
147
-func parseConntrackStatField(field string) (uint64, error) {
148
-	val, err := strconv.ParseUint(field, 16, 64)
149
-	if err != nil {
150
-		return 0, fmt.Errorf("couldn't parse %q field: %w", field, err)
151
-	}
152
-	return val, err
114
+	return stats, nil
153 115
 }
... ...
@@ -130,7 +130,7 @@ func parseIP(hexIP string) (net.IP, error) {
130 130
 	var byteIP []byte
131 131
 	byteIP, err := hex.DecodeString(hexIP)
132 132
 	if err != nil {
133
-		return nil, fmt.Errorf("cannot parse address field in socket line %q", hexIP)
133
+		return nil, fmt.Errorf("%s: Cannot parse socket field in %q: %w", ErrFileParse, hexIP, err)
134 134
 	}
135 135
 	switch len(byteIP) {
136 136
 	case 4:
... ...
@@ -144,7 +144,7 @@ func parseIP(hexIP string) (net.IP, error) {
144 144
 		}
145 145
 		return i, nil
146 146
 	default:
147
-		return nil, fmt.Errorf("Unable to parse IP %s", hexIP)
147
+		return nil, fmt.Errorf("%s: Unable to parse IP %s: %w", ErrFileParse, hexIP, nil)
148 148
 	}
149 149
 }
150 150
 
... ...
@@ -153,7 +153,8 @@ func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) {
153 153
 	line := &netIPSocketLine{}
154 154
 	if len(fields) < 10 {
155 155
 		return nil, fmt.Errorf(
156
-			"cannot parse net socket line as it has less then 10 columns %q",
156
+			"%w: Less than 10 columns found %q",
157
+			ErrFileParse,
157 158
 			strings.Join(fields, " "),
158 159
 		)
159 160
 	}
... ...
@@ -162,64 +163,65 @@ func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) {
162 162
 	// sl
163 163
 	s := strings.Split(fields[0], ":")
164 164
 	if len(s) != 2 {
165
-		return nil, fmt.Errorf("cannot parse sl field in socket line %q", fields[0])
165
+		return nil, fmt.Errorf("%w: Unable to parse sl field in line %q", ErrFileParse, fields[0])
166 166
 	}
167 167
 
168 168
 	if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
169
-		return nil, fmt.Errorf("cannot parse sl value in socket line: %w", err)
169
+		return nil, fmt.Errorf("%s: Unable to parse sl field in %q: %w", ErrFileParse, line.Sl, err)
170 170
 	}
171 171
 	// local_address
172 172
 	l := strings.Split(fields[1], ":")
173 173
 	if len(l) != 2 {
174
-		return nil, fmt.Errorf("cannot parse local_address field in socket line %q", fields[1])
174
+		return nil, fmt.Errorf("%w: Unable to parse local_address field in %q", ErrFileParse, fields[1])
175 175
 	}
176 176
 	if line.LocalAddr, err = parseIP(l[0]); err != nil {
177 177
 		return nil, err
178 178
 	}
179 179
 	if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
180
-		return nil, fmt.Errorf("cannot parse local_address port value in socket line: %w", err)
180
+		return nil, fmt.Errorf("%s: Unable to parse local_address port value line %q: %w", ErrFileParse, line.LocalPort, err)
181 181
 	}
182 182
 
183 183
 	// remote_address
184 184
 	r := strings.Split(fields[2], ":")
185 185
 	if len(r) != 2 {
186
-		return nil, fmt.Errorf("cannot parse rem_address field in socket line %q", fields[1])
186
+		return nil, fmt.Errorf("%w: Unable to parse rem_address field in %q", ErrFileParse, fields[1])
187 187
 	}
188 188
 	if line.RemAddr, err = parseIP(r[0]); err != nil {
189 189
 		return nil, err
190 190
 	}
191 191
 	if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
192
-		return nil, fmt.Errorf("cannot parse rem_address port value in socket line: %w", err)
192
+		return nil, fmt.Errorf("%s: Cannot parse rem_address port value in %q: %w", ErrFileParse, line.RemPort, err)
193 193
 	}
194 194
 
195 195
 	// st
196 196
 	if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
197
-		return nil, fmt.Errorf("cannot parse st value in socket line: %w", err)
197
+		return nil, fmt.Errorf("%s: Cannot parse st value in %q: %w", ErrFileParse, line.St, err)
198 198
 	}
199 199
 
200 200
 	// tx_queue and rx_queue
201 201
 	q := strings.Split(fields[4], ":")
202 202
 	if len(q) != 2 {
203 203
 		return nil, fmt.Errorf(
204
-			"cannot parse tx/rx queues in socket line as it has a missing colon %q",
204
+			"%w: Missing colon for tx/rx queues in socket line %q",
205
+			ErrFileParse,
205 206
 			fields[4],
206 207
 		)
207 208
 	}
208 209
 	if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
209
-		return nil, fmt.Errorf("cannot parse tx_queue value in socket line: %w", err)
210
+		return nil, fmt.Errorf("%s: Cannot parse tx_queue value in %q: %w", ErrFileParse, line.TxQueue, err)
210 211
 	}
211 212
 	if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
212
-		return nil, fmt.Errorf("cannot parse rx_queue value in socket line: %w", err)
213
+		return nil, fmt.Errorf("%s: Cannot parse trx_queue value in %q: %w", ErrFileParse, line.RxQueue, err)
213 214
 	}
214 215
 
215 216
 	// uid
216 217
 	if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
217
-		return nil, fmt.Errorf("cannot parse uid value in socket line: %w", err)
218
+		return nil, fmt.Errorf("%s: Cannot parse UID value in %q: %w", ErrFileParse, line.UID, err)
218 219
 	}
219 220
 
220 221
 	// inode
221 222
 	if line.Inode, err = strconv.ParseUint(fields[9], 0, 64); err != nil {
222
-		return nil, fmt.Errorf("cannot parse inode value in socket line: %w", err)
223
+		return nil, fmt.Errorf("%s: Cannot parse inode value in %q: %w", ErrFileParse, line.Inode, err)
223 224
 	}
224 225
 
225 226
 	return line, nil
... ...
@@ -131,7 +131,7 @@ func (ps NetProtocolStats) parseLine(rawLine string) (*NetProtocolStatLine, erro
131 131
 	} else if fields[6] == disabled {
132 132
 		line.Slab = false
133 133
 	} else {
134
-		return nil, fmt.Errorf("unable to parse capability for protocol: %s", line.Name)
134
+		return nil, fmt.Errorf("%w: capability for protocol: %s", ErrFileParse, line.Name)
135 135
 	}
136 136
 	line.ModuleName = fields[7]
137 137
 
... ...
@@ -173,7 +173,7 @@ func (pc *NetProtocolCapabilities) parseCapabilities(capabilities []string) erro
173 173
 		} else if capabilities[i] == "n" {
174 174
 			*capabilityFields[i] = false
175 175
 		} else {
176
-			return fmt.Errorf("unable to parse capability block for protocol: position %d", i)
176
+			return fmt.Errorf("%w: capability block for protocol: position %d", ErrFileParse, i)
177 177
 		}
178 178
 	}
179 179
 	return nil
180 180
new file mode 100644
... ...
@@ -0,0 +1,143 @@
0
+// Copyright 2023 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+import (
16
+	"bufio"
17
+	"bytes"
18
+	"fmt"
19
+	"io"
20
+	"strconv"
21
+	"strings"
22
+
23
+	"github.com/prometheus/procfs/internal/util"
24
+)
25
+
26
+const (
27
+	blackholeRepresentation string = "*"
28
+	blackholeIfaceName      string = "blackhole"
29
+	routeLineColumns        int    = 11
30
+)
31
+
32
+// A NetRouteLine represents one line from net/route.
33
+type NetRouteLine struct {
34
+	Iface       string
35
+	Destination uint32
36
+	Gateway     uint32
37
+	Flags       uint32
38
+	RefCnt      uint32
39
+	Use         uint32
40
+	Metric      uint32
41
+	Mask        uint32
42
+	MTU         uint32
43
+	Window      uint32
44
+	IRTT        uint32
45
+}
46
+
47
+func (fs FS) NetRoute() ([]NetRouteLine, error) {
48
+	return readNetRoute(fs.proc.Path("net", "route"))
49
+}
50
+
51
+func readNetRoute(path string) ([]NetRouteLine, error) {
52
+	b, err := util.ReadFileNoStat(path)
53
+	if err != nil {
54
+		return nil, err
55
+	}
56
+
57
+	routelines, err := parseNetRoute(bytes.NewReader(b))
58
+	if err != nil {
59
+		return nil, fmt.Errorf("failed to read net route from %s: %w", path, err)
60
+	}
61
+	return routelines, nil
62
+}
63
+
64
+func parseNetRoute(r io.Reader) ([]NetRouteLine, error) {
65
+	var routelines []NetRouteLine
66
+
67
+	scanner := bufio.NewScanner(r)
68
+	scanner.Scan()
69
+	for scanner.Scan() {
70
+		fields := strings.Fields(scanner.Text())
71
+		routeline, err := parseNetRouteLine(fields)
72
+		if err != nil {
73
+			return nil, err
74
+		}
75
+		routelines = append(routelines, *routeline)
76
+	}
77
+	return routelines, nil
78
+}
79
+
80
+func parseNetRouteLine(fields []string) (*NetRouteLine, error) {
81
+	if len(fields) != routeLineColumns {
82
+		return nil, fmt.Errorf("invalid routeline, num of digits: %d", len(fields))
83
+	}
84
+	iface := fields[0]
85
+	if iface == blackholeRepresentation {
86
+		iface = blackholeIfaceName
87
+	}
88
+	destination, err := strconv.ParseUint(fields[1], 16, 32)
89
+	if err != nil {
90
+		return nil, err
91
+	}
92
+	gateway, err := strconv.ParseUint(fields[2], 16, 32)
93
+	if err != nil {
94
+		return nil, err
95
+	}
96
+	flags, err := strconv.ParseUint(fields[3], 10, 32)
97
+	if err != nil {
98
+		return nil, err
99
+	}
100
+	refcnt, err := strconv.ParseUint(fields[4], 10, 32)
101
+	if err != nil {
102
+		return nil, err
103
+	}
104
+	use, err := strconv.ParseUint(fields[5], 10, 32)
105
+	if err != nil {
106
+		return nil, err
107
+	}
108
+	metric, err := strconv.ParseUint(fields[6], 10, 32)
109
+	if err != nil {
110
+		return nil, err
111
+	}
112
+	mask, err := strconv.ParseUint(fields[7], 16, 32)
113
+	if err != nil {
114
+		return nil, err
115
+	}
116
+	mtu, err := strconv.ParseUint(fields[8], 10, 32)
117
+	if err != nil {
118
+		return nil, err
119
+	}
120
+	window, err := strconv.ParseUint(fields[9], 10, 32)
121
+	if err != nil {
122
+		return nil, err
123
+	}
124
+	irtt, err := strconv.ParseUint(fields[10], 10, 32)
125
+	if err != nil {
126
+		return nil, err
127
+	}
128
+	routeline := &NetRouteLine{
129
+		Iface:       iface,
130
+		Destination: uint32(destination),
131
+		Gateway:     uint32(gateway),
132
+		Flags:       uint32(flags),
133
+		RefCnt:      uint32(refcnt),
134
+		Use:         uint32(use),
135
+		Metric:      uint32(metric),
136
+		Mask:        uint32(mask),
137
+		MTU:         uint32(mtu),
138
+		Window:      uint32(window),
139
+		IRTT:        uint32(irtt),
140
+	}
141
+	return routeline, nil
142
+}
... ...
@@ -16,7 +16,6 @@ package procfs
16 16
 import (
17 17
 	"bufio"
18 18
 	"bytes"
19
-	"errors"
20 19
 	"fmt"
21 20
 	"io"
22 21
 	"strings"
... ...
@@ -70,7 +69,7 @@ func readSockstat(name string) (*NetSockstat, error) {
70 70
 
71 71
 	stat, err := parseSockstat(bytes.NewReader(b))
72 72
 	if err != nil {
73
-		return nil, fmt.Errorf("failed to read sockstats from %q: %w", name, err)
73
+		return nil, fmt.Errorf("%s: sockstats from %q: %w", ErrFileRead, name, err)
74 74
 	}
75 75
 
76 76
 	return stat, nil
... ...
@@ -84,13 +83,13 @@ func parseSockstat(r io.Reader) (*NetSockstat, error) {
84 84
 		// Expect a minimum of a protocol and one key/value pair.
85 85
 		fields := strings.Split(s.Text(), " ")
86 86
 		if len(fields) < 3 {
87
-			return nil, fmt.Errorf("malformed sockstat line: %q", s.Text())
87
+			return nil, fmt.Errorf("%w: Malformed sockstat line: %q", ErrFileParse, s.Text())
88 88
 		}
89 89
 
90 90
 		// The remaining fields are key/value pairs.
91 91
 		kvs, err := parseSockstatKVs(fields[1:])
92 92
 		if err != nil {
93
-			return nil, fmt.Errorf("error parsing sockstat key/value pairs from %q: %w", s.Text(), err)
93
+			return nil, fmt.Errorf("%s: sockstat key/value pairs from %q: %w", ErrFileParse, s.Text(), err)
94 94
 		}
95 95
 
96 96
 		// The first field is the protocol. We must trim its colon suffix.
... ...
@@ -119,7 +118,7 @@ func parseSockstat(r io.Reader) (*NetSockstat, error) {
119 119
 // parseSockstatKVs parses a string slice into a map of key/value pairs.
120 120
 func parseSockstatKVs(kvs []string) (map[string]int, error) {
121 121
 	if len(kvs)%2 != 0 {
122
-		return nil, errors.New("odd number of fields in key/value pairs")
122
+		return nil, fmt.Errorf("%w:: Odd number of fields in key/value pairs %q", ErrFileParse, kvs)
123 123
 	}
124 124
 
125 125
 	// Iterate two values at a time to gather key/value pairs.
... ...
@@ -64,7 +64,7 @@ func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
64 64
 
65 65
 	entries, err := parseSoftnet(bytes.NewReader(b))
66 66
 	if err != nil {
67
-		return nil, fmt.Errorf("failed to parse /proc/net/softnet_stat: %w", err)
67
+		return nil, fmt.Errorf("%s: /proc/net/softnet_stat: %w", ErrFileParse, err)
68 68
 	}
69 69
 
70 70
 	return entries, nil
... ...
@@ -76,13 +76,14 @@ func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
76 76
 	s := bufio.NewScanner(r)
77 77
 
78 78
 	var stats []SoftnetStat
79
+	cpuIndex := 0
79 80
 	for s.Scan() {
80 81
 		columns := strings.Fields(s.Text())
81 82
 		width := len(columns)
82 83
 		softnetStat := SoftnetStat{}
83 84
 
84 85
 		if width < minColumns {
85
-			return nil, fmt.Errorf("%d columns were detected, but at least %d were expected", width, minColumns)
86
+			return nil, fmt.Errorf("%w: detected %d columns, but expected at least %d", ErrFileParse, width, minColumns)
86 87
 		}
87 88
 
88 89
 		// Linux 2.6.23 https://elixir.bootlin.com/linux/v2.6.23/source/net/core/dev.c#L2347
... ...
@@ -127,9 +128,13 @@ func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
127 127
 
128 128
 			softnetStat.SoftnetBacklogLen = us[0]
129 129
 			softnetStat.Index = us[1]
130
+		} else {
131
+			// For older kernels, create the Index based on the scan line number.
132
+			softnetStat.Index = uint32(cpuIndex)
130 133
 		}
131 134
 		softnetStat.Width = width
132 135
 		stats = append(stats, softnetStat)
136
+		cpuIndex++
133 137
 	}
134 138
 
135 139
 	return stats, nil
... ...
@@ -108,14 +108,14 @@ func parseNetUNIX(r io.Reader) (*NetUNIX, error) {
108 108
 		line := s.Text()
109 109
 		item, err := nu.parseLine(line, hasInode, minFields)
110 110
 		if err != nil {
111
-			return nil, fmt.Errorf("failed to parse /proc/net/unix data %q: %w", line, err)
111
+			return nil, fmt.Errorf("%s: /proc/net/unix encountered data %q: %w", ErrFileParse, line, err)
112 112
 		}
113 113
 
114 114
 		nu.Rows = append(nu.Rows, item)
115 115
 	}
116 116
 
117 117
 	if err := s.Err(); err != nil {
118
-		return nil, fmt.Errorf("failed to scan /proc/net/unix data: %w", err)
118
+		return nil, fmt.Errorf("%s: /proc/net/unix encountered data: %w", ErrFileParse, err)
119 119
 	}
120 120
 
121 121
 	return &nu, nil
... ...
@@ -126,7 +126,7 @@ func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine,
126 126
 
127 127
 	l := len(fields)
128 128
 	if l < min {
129
-		return nil, fmt.Errorf("expected at least %d fields but got %d", min, l)
129
+		return nil, fmt.Errorf("%w: expected at least %d fields but got %d", ErrFileParse, min, l)
130 130
 	}
131 131
 
132 132
 	// Field offsets are as follows:
... ...
@@ -136,29 +136,29 @@ func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine,
136 136
 
137 137
 	users, err := u.parseUsers(fields[1])
138 138
 	if err != nil {
139
-		return nil, fmt.Errorf("failed to parse ref count %q: %w", fields[1], err)
139
+		return nil, fmt.Errorf("%s: ref count %q: %w", ErrFileParse, fields[1], err)
140 140
 	}
141 141
 
142 142
 	flags, err := u.parseFlags(fields[3])
143 143
 	if err != nil {
144
-		return nil, fmt.Errorf("failed to parse flags %q: %w", fields[3], err)
144
+		return nil, fmt.Errorf("%s: Unable to parse flags %q: %w", ErrFileParse, fields[3], err)
145 145
 	}
146 146
 
147 147
 	typ, err := u.parseType(fields[4])
148 148
 	if err != nil {
149
-		return nil, fmt.Errorf("failed to parse type %q: %w", fields[4], err)
149
+		return nil, fmt.Errorf("%s: Failed to parse type %q: %w", ErrFileParse, fields[4], err)
150 150
 	}
151 151
 
152 152
 	state, err := u.parseState(fields[5])
153 153
 	if err != nil {
154
-		return nil, fmt.Errorf("failed to parse state %q: %w", fields[5], err)
154
+		return nil, fmt.Errorf("%s: Failed to parse state %q: %w", ErrFileParse, fields[5], err)
155 155
 	}
156 156
 
157 157
 	var inode uint64
158 158
 	if hasInode {
159 159
 		inode, err = u.parseInode(fields[6])
160 160
 		if err != nil {
161
-			return nil, fmt.Errorf("failed to parse inode %q: %w", fields[6], err)
161
+			return nil, fmt.Errorf("%s failed to parse inode %q: %w", ErrFileParse, fields[6], err)
162 162
 		}
163 163
 	}
164 164
 
165 165
new file mode 100644
... ...
@@ -0,0 +1,182 @@
0
+// Copyright 2023 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+import (
16
+	"bufio"
17
+	"bytes"
18
+	"fmt"
19
+	"io"
20
+	"strconv"
21
+	"strings"
22
+
23
+	"github.com/prometheus/procfs/internal/util"
24
+)
25
+
26
+// Wireless models the content of /proc/net/wireless.
27
+type Wireless struct {
28
+	Name string
29
+
30
+	// Status is the current 4-digit hex value status of the interface.
31
+	Status uint64
32
+
33
+	// QualityLink is the link quality.
34
+	QualityLink int
35
+
36
+	// QualityLevel is the signal gain (dBm).
37
+	QualityLevel int
38
+
39
+	// QualityNoise is the signal noise baseline (dBm).
40
+	QualityNoise int
41
+
42
+	// DiscardedNwid is the number of discarded packets with wrong nwid/essid.
43
+	DiscardedNwid int
44
+
45
+	// DiscardedCrypt is the number of discarded packets with wrong code/decode (WEP).
46
+	DiscardedCrypt int
47
+
48
+	// DiscardedFrag is the number of discarded packets that can't perform MAC reassembly.
49
+	DiscardedFrag int
50
+
51
+	// DiscardedRetry is the number of discarded packets that reached max MAC retries.
52
+	DiscardedRetry int
53
+
54
+	// DiscardedMisc is the number of discarded packets for other reasons.
55
+	DiscardedMisc int
56
+
57
+	// MissedBeacon is the number of missed beacons/superframe.
58
+	MissedBeacon int
59
+}
60
+
61
+// Wireless returns kernel wireless statistics.
62
+func (fs FS) Wireless() ([]*Wireless, error) {
63
+	b, err := util.ReadFileNoStat(fs.proc.Path("net/wireless"))
64
+	if err != nil {
65
+		return nil, err
66
+	}
67
+
68
+	m, err := parseWireless(bytes.NewReader(b))
69
+	if err != nil {
70
+		return nil, fmt.Errorf("%s: wireless: %w", ErrFileParse, err)
71
+	}
72
+
73
+	return m, nil
74
+}
75
+
76
+// parseWireless parses the contents of /proc/net/wireless.
77
+/*
78
+Inter-| sta-|   Quality        |   Discarded packets               | Missed | WE
79
+face | tus | link level noise |  nwid  crypt   frag  retry   misc | beacon | 22
80
+ eth1: 0000    5.  -256.  -10.       0      1      0     3      0        0
81
+ eth2: 0000    5.  -256.  -20.       0      2      0     4      0        0
82
+*/
83
+func parseWireless(r io.Reader) ([]*Wireless, error) {
84
+	var (
85
+		interfaces []*Wireless
86
+		scanner    = bufio.NewScanner(r)
87
+	)
88
+
89
+	for n := 0; scanner.Scan(); n++ {
90
+		// Skip the 2 header lines.
91
+		if n < 2 {
92
+			continue
93
+		}
94
+
95
+		line := scanner.Text()
96
+
97
+		parts := strings.Split(line, ":")
98
+		if len(parts) != 2 {
99
+			return nil, fmt.Errorf("%w: expected 2 parts after splitting line by ':', got %d for line %q", ErrFileParse, len(parts), line)
100
+		}
101
+
102
+		name := strings.TrimSpace(parts[0])
103
+		stats := strings.Fields(parts[1])
104
+
105
+		if len(stats) < 10 {
106
+			return nil, fmt.Errorf("%w: invalid number of fields in line %d, expected 10+, got %d: %q", ErrFileParse, n, len(stats), line)
107
+		}
108
+
109
+		status, err := strconv.ParseUint(stats[0], 16, 16)
110
+		if err != nil {
111
+			return nil, fmt.Errorf("%w: invalid status in line %d: %q", ErrFileParse, n, line)
112
+		}
113
+
114
+		qlink, err := strconv.Atoi(strings.TrimSuffix(stats[1], "."))
115
+		if err != nil {
116
+			return nil, fmt.Errorf("%s: parse Quality:link as integer %q: %w", ErrFileParse, qlink, err)
117
+		}
118
+
119
+		qlevel, err := strconv.Atoi(strings.TrimSuffix(stats[2], "."))
120
+		if err != nil {
121
+			return nil, fmt.Errorf("%s: Quality:level as integer %q: %w", ErrFileParse, qlevel, err)
122
+		}
123
+
124
+		qnoise, err := strconv.Atoi(strings.TrimSuffix(stats[3], "."))
125
+		if err != nil {
126
+			return nil, fmt.Errorf("%s: Quality:noise as integer %q: %w", ErrFileParse, qnoise, err)
127
+		}
128
+
129
+		dnwid, err := strconv.Atoi(stats[4])
130
+		if err != nil {
131
+			return nil, fmt.Errorf("%s: Discarded:nwid as integer %q: %w", ErrFileParse, dnwid, err)
132
+		}
133
+
134
+		dcrypt, err := strconv.Atoi(stats[5])
135
+		if err != nil {
136
+			return nil, fmt.Errorf("%s: Discarded:crypt as integer %q: %w", ErrFileParse, dcrypt, err)
137
+		}
138
+
139
+		dfrag, err := strconv.Atoi(stats[6])
140
+		if err != nil {
141
+			return nil, fmt.Errorf("%s: Discarded:frag as integer %q: %w", ErrFileParse, dfrag, err)
142
+		}
143
+
144
+		dretry, err := strconv.Atoi(stats[7])
145
+		if err != nil {
146
+			return nil, fmt.Errorf("%s: Discarded:retry as integer %q: %w", ErrFileParse, dretry, err)
147
+		}
148
+
149
+		dmisc, err := strconv.Atoi(stats[8])
150
+		if err != nil {
151
+			return nil, fmt.Errorf("%s: Discarded:misc as integer %q: %w", ErrFileParse, dmisc, err)
152
+		}
153
+
154
+		mbeacon, err := strconv.Atoi(stats[9])
155
+		if err != nil {
156
+			return nil, fmt.Errorf("%s: Missed:beacon as integer %q: %w", ErrFileParse, mbeacon, err)
157
+		}
158
+
159
+		w := &Wireless{
160
+			Name:           name,
161
+			Status:         status,
162
+			QualityLink:    qlink,
163
+			QualityLevel:   qlevel,
164
+			QualityNoise:   qnoise,
165
+			DiscardedNwid:  dnwid,
166
+			DiscardedCrypt: dcrypt,
167
+			DiscardedFrag:  dfrag,
168
+			DiscardedRetry: dretry,
169
+			DiscardedMisc:  dmisc,
170
+			MissedBeacon:   mbeacon,
171
+		}
172
+
173
+		interfaces = append(interfaces, w)
174
+	}
175
+
176
+	if err := scanner.Err(); err != nil {
177
+		return nil, fmt.Errorf("%s: Failed to scan /proc/net/wireless: %w", ErrFileRead, err)
178
+	}
179
+
180
+	return interfaces, nil
181
+}
... ...
@@ -115,7 +115,7 @@ func (fs FS) NewXfrmStat() (XfrmStat, error) {
115 115
 		fields := strings.Fields(s.Text())
116 116
 
117 117
 		if len(fields) != 2 {
118
-			return XfrmStat{}, fmt.Errorf("couldn't parse %q line %q", file.Name(), s.Text())
118
+			return XfrmStat{}, fmt.Errorf("%w: %q line %q", ErrFileParse, file.Name(), s.Text())
119 119
 		}
120 120
 
121 121
 		name := fields[0]
... ...
@@ -15,7 +15,6 @@ package procfs
15 15
 
16 16
 import (
17 17
 	"bufio"
18
-	"io"
19 18
 	"os"
20 19
 	"path/filepath"
21 20
 	"strconv"
... ...
@@ -38,12 +37,7 @@ func (fs FS) NetStat() ([]NetStat, error) {
38 38
 	var netStatsTotal []NetStat
39 39
 
40 40
 	for _, filePath := range statFiles {
41
-		file, err := os.Open(filePath)
42
-		if err != nil {
43
-			return nil, err
44
-		}
45
-
46
-		procNetstat, err := parseNetstat(file)
41
+		procNetstat, err := parseNetstat(filePath)
47 42
 		if err != nil {
48 43
 			return nil, err
49 44
 		}
... ...
@@ -56,14 +50,17 @@ func (fs FS) NetStat() ([]NetStat, error) {
56 56
 
57 57
 // parseNetstat parses the metrics from `/proc/net/stat/` file
58 58
 // and returns a NetStat structure.
59
-func parseNetstat(r io.Reader) (NetStat, error) {
60
-	var (
61
-		scanner = bufio.NewScanner(r)
62
-		netStat = NetStat{
63
-			Stats: make(map[string][]uint64),
64
-		}
65
-	)
59
+func parseNetstat(filePath string) (NetStat, error) {
60
+	netStat := NetStat{
61
+		Stats: make(map[string][]uint64),
62
+	}
63
+	file, err := os.Open(filePath)
64
+	if err != nil {
65
+		return netStat, err
66
+	}
67
+	defer file.Close()
66 68
 
69
+	scanner := bufio.NewScanner(file)
67 70
 	scanner.Scan()
68 71
 
69 72
 	// First string is always a header for stats
... ...
@@ -15,13 +15,13 @@ package procfs
15 15
 
16 16
 import (
17 17
 	"bytes"
18
+	"errors"
18 19
 	"fmt"
19 20
 	"io"
20 21
 	"os"
21 22
 	"strconv"
22 23
 	"strings"
23 24
 
24
-	"github.com/prometheus/procfs/internal/fs"
25 25
 	"github.com/prometheus/procfs/internal/util"
26 26
 )
27 27
 
... ...
@@ -30,12 +30,18 @@ type Proc struct {
30 30
 	// The process ID.
31 31
 	PID int
32 32
 
33
-	fs fs.FS
33
+	fs FS
34 34
 }
35 35
 
36 36
 // Procs represents a list of Proc structs.
37 37
 type Procs []Proc
38 38
 
39
+var (
40
+	ErrFileParse  = errors.New("Error Parsing File")
41
+	ErrFileRead   = errors.New("Error Reading File")
42
+	ErrMountPoint = errors.New("Error Accessing Mount point")
43
+)
44
+
39 45
 func (p Procs) Len() int           { return len(p) }
40 46
 func (p Procs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
41 47
 func (p Procs) Less(i, j int) bool { return p[i].PID < p[j].PID }
... ...
@@ -43,7 +49,7 @@ func (p Procs) Less(i, j int) bool { return p[i].PID < p[j].PID }
43 43
 // Self returns a process for the current process read via /proc/self.
44 44
 func Self() (Proc, error) {
45 45
 	fs, err := NewFS(DefaultMountPoint)
46
-	if err != nil {
46
+	if err != nil || errors.Unwrap(err) == ErrMountPoint {
47 47
 		return Proc{}, err
48 48
 	}
49 49
 	return fs.Self()
... ...
@@ -92,7 +98,7 @@ func (fs FS) Proc(pid int) (Proc, error) {
92 92
 	if _, err := os.Stat(fs.proc.Path(strconv.Itoa(pid))); err != nil {
93 93
 		return Proc{}, err
94 94
 	}
95
-	return Proc{PID: pid, fs: fs.proc}, nil
95
+	return Proc{PID: pid, fs: fs}, nil
96 96
 }
97 97
 
98 98
 // AllProcs returns a list of all currently available processes.
... ...
@@ -105,7 +111,7 @@ func (fs FS) AllProcs() (Procs, error) {
105 105
 
106 106
 	names, err := d.Readdirnames(-1)
107 107
 	if err != nil {
108
-		return Procs{}, fmt.Errorf("could not read %q: %w", d.Name(), err)
108
+		return Procs{}, fmt.Errorf("%s: Cannot read file: %v: %w", ErrFileRead, names, err)
109 109
 	}
110 110
 
111 111
 	p := Procs{}
... ...
@@ -114,7 +120,7 @@ func (fs FS) AllProcs() (Procs, error) {
114 114
 		if err != nil {
115 115
 			continue
116 116
 		}
117
-		p = append(p, Proc{PID: int(pid), fs: fs.proc})
117
+		p = append(p, Proc{PID: int(pid), fs: fs})
118 118
 	}
119 119
 
120 120
 	return p, nil
... ...
@@ -206,7 +212,7 @@ func (p Proc) FileDescriptors() ([]uintptr, error) {
206 206
 	for i, n := range names {
207 207
 		fd, err := strconv.ParseInt(n, 10, 32)
208 208
 		if err != nil {
209
-			return nil, fmt.Errorf("could not parse fd %q: %w", n, err)
209
+			return nil, fmt.Errorf("%s: Cannot parse line: %v: %w", ErrFileParse, i, err)
210 210
 		}
211 211
 		fds[i] = uintptr(fd)
212 212
 	}
... ...
@@ -237,6 +243,19 @@ func (p Proc) FileDescriptorTargets() ([]string, error) {
237 237
 // FileDescriptorsLen returns the number of currently open file descriptors of
238 238
 // a process.
239 239
 func (p Proc) FileDescriptorsLen() (int, error) {
240
+	// Use fast path if available (Linux v6.2): https://github.com/torvalds/linux/commit/f1f1f2569901
241
+	if p.fs.isReal {
242
+		stat, err := os.Stat(p.path("fd"))
243
+		if err != nil {
244
+			return 0, err
245
+		}
246
+
247
+		size := stat.Size()
248
+		if size > 0 {
249
+			return int(size), nil
250
+		}
251
+	}
252
+
240 253
 	fds, err := p.fileDescriptors()
241 254
 	if err != nil {
242 255
 		return 0, err
... ...
@@ -278,14 +297,14 @@ func (p Proc) fileDescriptors() ([]string, error) {
278 278
 
279 279
 	names, err := d.Readdirnames(-1)
280 280
 	if err != nil {
281
-		return nil, fmt.Errorf("could not read %q: %w", d.Name(), err)
281
+		return nil, fmt.Errorf("%s: Cannot read file: %v: %w", ErrFileRead, names, err)
282 282
 	}
283 283
 
284 284
 	return names, nil
285 285
 }
286 286
 
287 287
 func (p Proc) path(pa ...string) string {
288
-	return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...)
288
+	return p.fs.proc.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...)
289 289
 }
290 290
 
291 291
 // FileDescriptorsInfo retrieves information about all file descriptors of
... ...
@@ -51,7 +51,7 @@ func parseCgroupString(cgroupStr string) (*Cgroup, error) {
51 51
 
52 52
 	fields := strings.SplitN(cgroupStr, ":", 3)
53 53
 	if len(fields) < 3 {
54
-		return nil, fmt.Errorf("at least 3 fields required, found %d fields in cgroup string: %s", len(fields), cgroupStr)
54
+		return nil, fmt.Errorf("%w: 3+ fields required, found %d fields in cgroup string: %s", ErrFileParse, len(fields), cgroupStr)
55 55
 	}
56 56
 
57 57
 	cgroup := &Cgroup{
... ...
@@ -60,7 +60,7 @@ func parseCgroupString(cgroupStr string) (*Cgroup, error) {
60 60
 	}
61 61
 	cgroup.HierarchyID, err = strconv.Atoi(fields[0])
62 62
 	if err != nil {
63
-		return nil, fmt.Errorf("failed to parse hierarchy ID")
63
+		return nil, fmt.Errorf("%w: hierarchy ID: %q", ErrFileParse, cgroup.HierarchyID)
64 64
 	}
65 65
 	if fields[1] != "" {
66 66
 		ssNames := strings.Split(fields[1], ",")
... ...
@@ -46,7 +46,7 @@ func parseCgroupSummaryString(CgroupSummaryStr string) (*CgroupSummary, error) {
46 46
 	fields := strings.Fields(CgroupSummaryStr)
47 47
 	// require at least 4 fields
48 48
 	if len(fields) < 4 {
49
-		return nil, fmt.Errorf("at least 4 fields required, found %d fields in cgroup info string: %s", len(fields), CgroupSummaryStr)
49
+		return nil, fmt.Errorf("%w: 4+ fields required, found %d fields in cgroup info string: %s", ErrFileParse, len(fields), CgroupSummaryStr)
50 50
 	}
51 51
 
52 52
 	CgroupSummary := &CgroupSummary{
... ...
@@ -54,15 +54,15 @@ func parseCgroupSummaryString(CgroupSummaryStr string) (*CgroupSummary, error) {
54 54
 	}
55 55
 	CgroupSummary.Hierarchy, err = strconv.Atoi(fields[1])
56 56
 	if err != nil {
57
-		return nil, fmt.Errorf("failed to parse hierarchy ID")
57
+		return nil, fmt.Errorf("%w: Unable to parse hierarchy ID from %q", ErrFileParse, fields[1])
58 58
 	}
59 59
 	CgroupSummary.Cgroups, err = strconv.Atoi(fields[2])
60 60
 	if err != nil {
61
-		return nil, fmt.Errorf("failed to parse Cgroup Num")
61
+		return nil, fmt.Errorf("%w: Unable to parse Cgroup Num from %q", ErrFileParse, fields[2])
62 62
 	}
63 63
 	CgroupSummary.Enabled, err = strconv.Atoi(fields[3])
64 64
 	if err != nil {
65
-		return nil, fmt.Errorf("failed to parse Enabled")
65
+		return nil, fmt.Errorf("%w: Unable to parse Enabled from %q", ErrFileParse, fields[3])
66 66
 	}
67 67
 	return CgroupSummary, nil
68 68
 }
... ...
@@ -111,7 +111,7 @@ func parseInotifyInfo(line string) (*InotifyInfo, error) {
111 111
 		}
112 112
 		return i, nil
113 113
 	}
114
-	return nil, fmt.Errorf("invalid inode entry: %q", line)
114
+	return nil, fmt.Errorf("%w: invalid inode entry: %q", ErrFileParse, line)
115 115
 }
116 116
 
117 117
 // ProcFDInfos represents a list of ProcFDInfo structs.
... ...
@@ -66,7 +66,7 @@ func parseInterrupts(r io.Reader) (Interrupts, error) {
66 66
 			continue
67 67
 		}
68 68
 		if len(parts) < 2 {
69
-			return nil, fmt.Errorf("not enough fields in interrupts (expected at least 2 fields but got %d): %s", len(parts), parts)
69
+			return nil, fmt.Errorf("%w: Not enough fields in interrupts (expected 2+ fields but got %d): %s", ErrFileParse, len(parts), parts)
70 70
 		}
71 71
 		intName := parts[0][:len(parts[0])-1] // remove trailing :
72 72
 
... ...
@@ -103,7 +103,7 @@ func (p Proc) Limits() (ProcLimits, error) {
103 103
 		//fields := limitsMatch.Split(s.Text(), limitsFields)
104 104
 		fields := limitsMatch.FindStringSubmatch(s.Text())
105 105
 		if len(fields) != limitsFields {
106
-			return ProcLimits{}, fmt.Errorf("couldn't parse %q line %q", f.Name(), s.Text())
106
+			return ProcLimits{}, fmt.Errorf("%w: couldn't parse %q line %q", ErrFileParse, f.Name(), s.Text())
107 107
 		}
108 108
 
109 109
 		switch fields[1] {
... ...
@@ -154,7 +154,7 @@ func parseUint(s string) (uint64, error) {
154 154
 	}
155 155
 	i, err := strconv.ParseUint(s, 10, 64)
156 156
 	if err != nil {
157
-		return 0, fmt.Errorf("couldn't parse value %q: %w", s, err)
157
+		return 0, fmt.Errorf("%s: couldn't parse value %q: %w", ErrFileParse, s, err)
158 158
 	}
159 159
 	return i, nil
160 160
 }
... ...
@@ -65,7 +65,7 @@ type ProcMap struct {
65 65
 func parseDevice(s string) (uint64, error) {
66 66
 	toks := strings.Split(s, ":")
67 67
 	if len(toks) < 2 {
68
-		return 0, fmt.Errorf("unexpected number of fields")
68
+		return 0, fmt.Errorf("%w: unexpected number of fields, expected: 2, got: %q", ErrFileParse, len(toks))
69 69
 	}
70 70
 
71 71
 	major, err := strconv.ParseUint(toks[0], 16, 0)
... ...
@@ -95,7 +95,7 @@ func parseAddress(s string) (uintptr, error) {
95 95
 func parseAddresses(s string) (uintptr, uintptr, error) {
96 96
 	toks := strings.Split(s, "-")
97 97
 	if len(toks) < 2 {
98
-		return 0, 0, fmt.Errorf("invalid address")
98
+		return 0, 0, fmt.Errorf("%w: invalid address", ErrFileParse)
99 99
 	}
100 100
 
101 101
 	saddr, err := parseAddress(toks[0])
... ...
@@ -114,7 +114,7 @@ func parseAddresses(s string) (uintptr, uintptr, error) {
114 114
 // parsePermissions parses a token and returns any that are set.
115 115
 func parsePermissions(s string) (*ProcMapPermissions, error) {
116 116
 	if len(s) < 4 {
117
-		return nil, fmt.Errorf("invalid permissions token")
117
+		return nil, fmt.Errorf("%w: invalid permissions token", ErrFileParse)
118 118
 	}
119 119
 
120 120
 	perms := ProcMapPermissions{}
... ...
@@ -141,7 +141,7 @@ func parsePermissions(s string) (*ProcMapPermissions, error) {
141 141
 func parseProcMap(text string) (*ProcMap, error) {
142 142
 	fields := strings.Fields(text)
143 143
 	if len(fields) < 5 {
144
-		return nil, fmt.Errorf("truncated procmap entry")
144
+		return nil, fmt.Errorf("%w: truncated procmap entry", ErrFileParse)
145 145
 	}
146 146
 
147 147
 	saddr, eaddr, err := parseAddresses(fields[0])
... ...
@@ -195,8 +195,8 @@ func parseProcNetstat(r io.Reader, fileName string) (ProcNetstat, error) {
195 195
 		// Remove trailing :.
196 196
 		protocol := strings.TrimSuffix(nameParts[0], ":")
197 197
 		if len(nameParts) != len(valueParts) {
198
-			return procNetstat, fmt.Errorf("mismatch field count mismatch in %s: %s",
199
-				fileName, protocol)
198
+			return procNetstat, fmt.Errorf("%w: mismatch field count mismatch in %s: %s",
199
+				ErrFileParse, fileName, protocol)
200 200
 		}
201 201
 		for i := 1; i < len(nameParts); i++ {
202 202
 			value, err := strconv.ParseFloat(valueParts[i], 64)
... ...
@@ -40,7 +40,7 @@ func (p Proc) Namespaces() (Namespaces, error) {
40 40
 
41 41
 	names, err := d.Readdirnames(-1)
42 42
 	if err != nil {
43
-		return nil, fmt.Errorf("failed to read contents of ns dir: %w", err)
43
+		return nil, fmt.Errorf("%s: failed to read contents of ns dir: %w", ErrFileRead, err)
44 44
 	}
45 45
 
46 46
 	ns := make(Namespaces, len(names))
... ...
@@ -52,13 +52,13 @@ func (p Proc) Namespaces() (Namespaces, error) {
52 52
 
53 53
 		fields := strings.SplitN(target, ":", 2)
54 54
 		if len(fields) != 2 {
55
-			return nil, fmt.Errorf("failed to parse namespace type and inode from %q", target)
55
+			return nil, fmt.Errorf("%w: namespace type and inode from %q", ErrFileParse, target)
56 56
 		}
57 57
 
58 58
 		typ := fields[0]
59 59
 		inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32)
60 60
 		if err != nil {
61
-			return nil, fmt.Errorf("failed to parse inode from %q: %w", fields[1], err)
61
+			return nil, fmt.Errorf("%s: inode from %q: %w", ErrFileParse, fields[1], err)
62 62
 		}
63 63
 
64 64
 		ns[name] = Namespace{typ, uint32(inode)}
... ...
@@ -61,14 +61,14 @@ type PSIStats struct {
61 61
 func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) {
62 62
 	data, err := util.ReadFileNoStat(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
63 63
 	if err != nil {
64
-		return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %q: %w", resource, err)
64
+		return PSIStats{}, fmt.Errorf("%s: psi_stats: unavailable for %q: %w", ErrFileRead, resource, err)
65 65
 	}
66 66
 
67
-	return parsePSIStats(resource, bytes.NewReader(data))
67
+	return parsePSIStats(bytes.NewReader(data))
68 68
 }
69 69
 
70 70
 // parsePSIStats parses the specified file for pressure stall information.
71
-func parsePSIStats(resource string, r io.Reader) (PSIStats, error) {
71
+func parsePSIStats(r io.Reader) (PSIStats, error) {
72 72
 	psiStats := PSIStats{}
73 73
 
74 74
 	scanner := bufio.NewScanner(r)
... ...
@@ -135,12 +135,12 @@ func (s *ProcSMapsRollup) parseLine(line string) error {
135 135
 	}
136 136
 	vBytes := vKBytes * 1024
137 137
 
138
-	s.addValue(k, v, vKBytes, vBytes)
138
+	s.addValue(k, vBytes)
139 139
 
140 140
 	return nil
141 141
 }
142 142
 
143
-func (s *ProcSMapsRollup) addValue(k string, vString string, vUint uint64, vUintBytes uint64) {
143
+func (s *ProcSMapsRollup) addValue(k string, vUintBytes uint64) {
144 144
 	switch k {
145 145
 	case "Rss":
146 146
 		s.Rss += vUintBytes
... ...
@@ -159,8 +159,8 @@ func parseSnmp(r io.Reader, fileName string) (ProcSnmp, error) {
159 159
 		// Remove trailing :.
160 160
 		protocol := strings.TrimSuffix(nameParts[0], ":")
161 161
 		if len(nameParts) != len(valueParts) {
162
-			return procSnmp, fmt.Errorf("mismatch field count mismatch in %s: %s",
163
-				fileName, protocol)
162
+			return procSnmp, fmt.Errorf("%w: mismatch field count mismatch in %s: %s",
163
+				ErrFileParse, fileName, protocol)
164 164
 		}
165 165
 		for i := 1; i < len(nameParts); i++ {
166 166
 			value, err := strconv.ParseFloat(valueParts[i], 64)
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"fmt"
19 19
 	"os"
20 20
 
21
-	"github.com/prometheus/procfs/internal/fs"
22 21
 	"github.com/prometheus/procfs/internal/util"
23 22
 )
24 23
 
... ...
@@ -112,7 +111,7 @@ type ProcStat struct {
112 112
 	// Aggregated block I/O delays, measured in clock ticks (centiseconds).
113 113
 	DelayAcctBlkIOTicks uint64
114 114
 
115
-	proc fs.FS
115
+	proc FS
116 116
 }
117 117
 
118 118
 // NewStat returns the current status information of the process.
... ...
@@ -139,7 +138,7 @@ func (p Proc) Stat() (ProcStat, error) {
139 139
 	)
140 140
 
141 141
 	if l < 0 || r < 0 {
142
-		return ProcStat{}, fmt.Errorf("unexpected format, couldn't extract comm %q", data)
142
+		return ProcStat{}, fmt.Errorf("%w: unexpected format, couldn't extract comm %q", ErrFileParse, data)
143 143
 	}
144 144
 
145 145
 	s.Comm = string(data[l+1 : r])
... ...
@@ -210,8 +209,7 @@ func (s ProcStat) ResidentMemory() int {
210 210
 
211 211
 // StartTime returns the unix timestamp of the process in seconds.
212 212
 func (s ProcStat) StartTime() (float64, error) {
213
-	fs := FS{proc: s.proc}
214
-	stat, err := fs.Stat()
213
+	stat, err := s.proc.Stat()
215 214
 	if err != nil {
216 215
 		return 0, err
217 216
 	}
... ...
@@ -15,6 +15,7 @@ package procfs
15 15
 
16 16
 import (
17 17
 	"bytes"
18
+	"sort"
18 19
 	"strconv"
19 20
 	"strings"
20 21
 
... ...
@@ -76,6 +77,9 @@ type ProcStatus struct {
76 76
 	UIDs [4]string
77 77
 	// GIDs of the process (Real, effective, saved set, and filesystem GIDs)
78 78
 	GIDs [4]string
79
+
80
+	// CpusAllowedList: List of cpu cores processes are allowed to run on.
81
+	CpusAllowedList []uint64
79 82
 }
80 83
 
81 84
 // NewStatus returns the current status information of the process.
... ...
@@ -161,10 +165,38 @@ func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintByt
161 161
 		s.VoluntaryCtxtSwitches = vUint
162 162
 	case "nonvoluntary_ctxt_switches":
163 163
 		s.NonVoluntaryCtxtSwitches = vUint
164
+	case "Cpus_allowed_list":
165
+		s.CpusAllowedList = calcCpusAllowedList(vString)
164 166
 	}
167
+
165 168
 }
166 169
 
167 170
 // TotalCtxtSwitches returns the total context switch.
168 171
 func (s ProcStatus) TotalCtxtSwitches() uint64 {
169 172
 	return s.VoluntaryCtxtSwitches + s.NonVoluntaryCtxtSwitches
170 173
 }
174
+
175
+func calcCpusAllowedList(cpuString string) []uint64 {
176
+	s := strings.Split(cpuString, ",")
177
+
178
+	var g []uint64
179
+
180
+	for _, cpu := range s {
181
+		// parse cpu ranges, example: 1-3=[1,2,3]
182
+		if l := strings.Split(strings.TrimSpace(cpu), "-"); len(l) > 1 {
183
+			startCPU, _ := strconv.ParseUint(l[0], 10, 64)
184
+			endCPU, _ := strconv.ParseUint(l[1], 10, 64)
185
+
186
+			for i := startCPU; i <= endCPU; i++ {
187
+				g = append(g, i)
188
+			}
189
+		} else if len(l) == 1 {
190
+			cpu, _ := strconv.ParseUint(l[0], 10, 64)
191
+			g = append(g, cpu)
192
+		}
193
+
194
+	}
195
+
196
+	sort.Slice(g, func(i, j int) bool { return g[i] < g[j] })
197
+	return g
198
+}
... ...
@@ -44,7 +44,7 @@ func (fs FS) SysctlInts(sysctl string) ([]int, error) {
44 44
 		vp := util.NewValueParser(f)
45 45
 		values[i] = vp.Int()
46 46
 		if err := vp.Err(); err != nil {
47
-			return nil, fmt.Errorf("field %d in sysctl %s is not a valid int: %w", i, sysctl, err)
47
+			return nil, fmt.Errorf("%s: field %d in sysctl %s is not a valid int: %w", ErrFileParse, i, sysctl, err)
48 48
 		}
49 49
 	}
50 50
 	return values, nil
... ...
@@ -68,7 +68,7 @@ func parseV21SlabEntry(line string) (*Slab, error) {
68 68
 	l := slabSpace.ReplaceAllString(line, " ")
69 69
 	s := strings.Split(l, " ")
70 70
 	if len(s) != 16 {
71
-		return nil, fmt.Errorf("unable to parse: %q", line)
71
+		return nil, fmt.Errorf("%w: unable to parse: %q", ErrFileParse, line)
72 72
 	}
73 73
 	var err error
74 74
 	i := &Slab{Name: s[0]}
... ...
@@ -57,7 +57,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
57 57
 	)
58 58
 
59 59
 	if !scanner.Scan() {
60
-		return Softirqs{}, fmt.Errorf("softirqs empty")
60
+		return Softirqs{}, fmt.Errorf("%w: softirqs empty", ErrFileRead)
61 61
 	}
62 62
 
63 63
 	for scanner.Scan() {
... ...
@@ -74,7 +74,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
74 74
 			softirqs.Hi = make([]uint64, len(perCPU))
75 75
 			for i, count := range perCPU {
76 76
 				if softirqs.Hi[i], err = strconv.ParseUint(count, 10, 64); err != nil {
77
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (HI%d): %w", count, i, err)
77
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (HI%d): %w", ErrFileParse, count, i, err)
78 78
 				}
79 79
 			}
80 80
 		case parts[0] == "TIMER:":
... ...
@@ -82,7 +82,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
82 82
 			softirqs.Timer = make([]uint64, len(perCPU))
83 83
 			for i, count := range perCPU {
84 84
 				if softirqs.Timer[i], err = strconv.ParseUint(count, 10, 64); err != nil {
85
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (TIMER%d): %w", count, i, err)
85
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (TIMER%d): %w", ErrFileParse, count, i, err)
86 86
 				}
87 87
 			}
88 88
 		case parts[0] == "NET_TX:":
... ...
@@ -90,7 +90,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
90 90
 			softirqs.NetTx = make([]uint64, len(perCPU))
91 91
 			for i, count := range perCPU {
92 92
 				if softirqs.NetTx[i], err = strconv.ParseUint(count, 10, 64); err != nil {
93
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (NET_TX%d): %w", count, i, err)
93
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (NET_TX%d): %w", ErrFileParse, count, i, err)
94 94
 				}
95 95
 			}
96 96
 		case parts[0] == "NET_RX:":
... ...
@@ -98,7 +98,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
98 98
 			softirqs.NetRx = make([]uint64, len(perCPU))
99 99
 			for i, count := range perCPU {
100 100
 				if softirqs.NetRx[i], err = strconv.ParseUint(count, 10, 64); err != nil {
101
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (NET_RX%d): %w", count, i, err)
101
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (NET_RX%d): %w", ErrFileParse, count, i, err)
102 102
 				}
103 103
 			}
104 104
 		case parts[0] == "BLOCK:":
... ...
@@ -106,7 +106,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
106 106
 			softirqs.Block = make([]uint64, len(perCPU))
107 107
 			for i, count := range perCPU {
108 108
 				if softirqs.Block[i], err = strconv.ParseUint(count, 10, 64); err != nil {
109
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (BLOCK%d): %w", count, i, err)
109
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (BLOCK%d): %w", ErrFileParse, count, i, err)
110 110
 				}
111 111
 			}
112 112
 		case parts[0] == "IRQ_POLL:":
... ...
@@ -114,7 +114,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
114 114
 			softirqs.IRQPoll = make([]uint64, len(perCPU))
115 115
 			for i, count := range perCPU {
116 116
 				if softirqs.IRQPoll[i], err = strconv.ParseUint(count, 10, 64); err != nil {
117
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (IRQ_POLL%d): %w", count, i, err)
117
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (IRQ_POLL%d): %w", ErrFileParse, count, i, err)
118 118
 				}
119 119
 			}
120 120
 		case parts[0] == "TASKLET:":
... ...
@@ -122,7 +122,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
122 122
 			softirqs.Tasklet = make([]uint64, len(perCPU))
123 123
 			for i, count := range perCPU {
124 124
 				if softirqs.Tasklet[i], err = strconv.ParseUint(count, 10, 64); err != nil {
125
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (TASKLET%d): %w", count, i, err)
125
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (TASKLET%d): %w", ErrFileParse, count, i, err)
126 126
 				}
127 127
 			}
128 128
 		case parts[0] == "SCHED:":
... ...
@@ -130,7 +130,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
130 130
 			softirqs.Sched = make([]uint64, len(perCPU))
131 131
 			for i, count := range perCPU {
132 132
 				if softirqs.Sched[i], err = strconv.ParseUint(count, 10, 64); err != nil {
133
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (SCHED%d): %w", count, i, err)
133
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (SCHED%d): %w", ErrFileParse, count, i, err)
134 134
 				}
135 135
 			}
136 136
 		case parts[0] == "HRTIMER:":
... ...
@@ -138,7 +138,7 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
138 138
 			softirqs.HRTimer = make([]uint64, len(perCPU))
139 139
 			for i, count := range perCPU {
140 140
 				if softirqs.HRTimer[i], err = strconv.ParseUint(count, 10, 64); err != nil {
141
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (HRTIMER%d): %w", count, i, err)
141
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (HRTIMER%d): %w", ErrFileParse, count, i, err)
142 142
 				}
143 143
 			}
144 144
 		case parts[0] == "RCU:":
... ...
@@ -146,14 +146,14 @@ func parseSoftirqs(r io.Reader) (Softirqs, error) {
146 146
 			softirqs.RCU = make([]uint64, len(perCPU))
147 147
 			for i, count := range perCPU {
148 148
 				if softirqs.RCU[i], err = strconv.ParseUint(count, 10, 64); err != nil {
149
-					return Softirqs{}, fmt.Errorf("couldn't parse %q (RCU%d): %w", count, i, err)
149
+					return Softirqs{}, fmt.Errorf("%s: couldn't parse %q (RCU%d): %w", ErrFileParse, count, i, err)
150 150
 				}
151 151
 			}
152 152
 		}
153 153
 	}
154 154
 
155 155
 	if err := scanner.Err(); err != nil {
156
-		return Softirqs{}, fmt.Errorf("couldn't parse softirqs: %w", err)
156
+		return Softirqs{}, fmt.Errorf("%s: couldn't parse softirqs: %w", ErrFileParse, err)
157 157
 	}
158 158
 
159 159
 	return softirqs, scanner.Err()
... ...
@@ -93,10 +93,10 @@ func parseCPUStat(line string) (CPUStat, int64, error) {
93 93
 		&cpuStat.Guest, &cpuStat.GuestNice)
94 94
 
95 95
 	if err != nil && err != io.EOF {
96
-		return CPUStat{}, -1, fmt.Errorf("couldn't parse %q (cpu): %w", line, err)
96
+		return CPUStat{}, -1, fmt.Errorf("%s: couldn't parse %q (cpu): %w", ErrFileParse, line, err)
97 97
 	}
98 98
 	if count == 0 {
99
-		return CPUStat{}, -1, fmt.Errorf("couldn't parse %q (cpu): 0 elements parsed", line)
99
+		return CPUStat{}, -1, fmt.Errorf("%w: couldn't parse %q (cpu): 0 elements parsed", ErrFileParse, line)
100 100
 	}
101 101
 
102 102
 	cpuStat.User /= userHZ
... ...
@@ -116,7 +116,7 @@ func parseCPUStat(line string) (CPUStat, int64, error) {
116 116
 
117 117
 	cpuID, err := strconv.ParseInt(cpu[3:], 10, 64)
118 118
 	if err != nil {
119
-		return CPUStat{}, -1, fmt.Errorf("couldn't parse %q (cpu/cpuid): %w", line, err)
119
+		return CPUStat{}, -1, fmt.Errorf("%s: couldn't parse %q (cpu/cpuid): %w", ErrFileParse, line, err)
120 120
 	}
121 121
 
122 122
 	return cpuStat, cpuID, nil
... ...
@@ -136,7 +136,7 @@ func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) {
136 136
 		&softIRQStat.Hrtimer, &softIRQStat.Rcu)
137 137
 
138 138
 	if err != nil {
139
-		return SoftIRQStat{}, 0, fmt.Errorf("couldn't parse %q (softirq): %w", line, err)
139
+		return SoftIRQStat{}, 0, fmt.Errorf("%s: couldn't parse %q (softirq): %w", ErrFileParse, line, err)
140 140
 	}
141 141
 
142 142
 	return softIRQStat, total, nil
... ...
@@ -187,6 +187,10 @@ func parseStat(r io.Reader, fileName string) (Stat, error) {
187 187
 		err error
188 188
 	)
189 189
 
190
+	// Increase default scanner buffer to handle very long `intr` lines.
191
+	buf := make([]byte, 0, 8*1024)
192
+	scanner.Buffer(buf, 1024*1024)
193
+
190 194
 	for scanner.Scan() {
191 195
 		line := scanner.Text()
192 196
 		parts := strings.Fields(scanner.Text())
... ...
@@ -197,34 +201,34 @@ func parseStat(r io.Reader, fileName string) (Stat, error) {
197 197
 		switch {
198 198
 		case parts[0] == "btime":
199 199
 			if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
200
-				return Stat{}, fmt.Errorf("couldn't parse %q (btime): %w", parts[1], err)
200
+				return Stat{}, fmt.Errorf("%s: couldn't parse %q (btime): %w", ErrFileParse, parts[1], err)
201 201
 			}
202 202
 		case parts[0] == "intr":
203 203
 			if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
204
-				return Stat{}, fmt.Errorf("couldn't parse %q (intr): %w", parts[1], err)
204
+				return Stat{}, fmt.Errorf("%s: couldn't parse %q (intr): %w", ErrFileParse, parts[1], err)
205 205
 			}
206 206
 			numberedIRQs := parts[2:]
207 207
 			stat.IRQ = make([]uint64, len(numberedIRQs))
208 208
 			for i, count := range numberedIRQs {
209 209
 				if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil {
210
-					return Stat{}, fmt.Errorf("couldn't parse %q (intr%d): %w", count, i, err)
210
+					return Stat{}, fmt.Errorf("%s: couldn't parse %q (intr%d): %w", ErrFileParse, count, i, err)
211 211
 				}
212 212
 			}
213 213
 		case parts[0] == "ctxt":
214 214
 			if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
215
-				return Stat{}, fmt.Errorf("couldn't parse %q (ctxt): %w", parts[1], err)
215
+				return Stat{}, fmt.Errorf("%s: couldn't parse %q (ctxt): %w", ErrFileParse, parts[1], err)
216 216
 			}
217 217
 		case parts[0] == "processes":
218 218
 			if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
219
-				return Stat{}, fmt.Errorf("couldn't parse %q (processes): %w", parts[1], err)
219
+				return Stat{}, fmt.Errorf("%s: couldn't parse %q (processes): %w", ErrFileParse, parts[1], err)
220 220
 			}
221 221
 		case parts[0] == "procs_running":
222 222
 			if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
223
-				return Stat{}, fmt.Errorf("couldn't parse %q (procs_running): %w", parts[1], err)
223
+				return Stat{}, fmt.Errorf("%s: couldn't parse %q (procs_running): %w", ErrFileParse, parts[1], err)
224 224
 			}
225 225
 		case parts[0] == "procs_blocked":
226 226
 			if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
227
-				return Stat{}, fmt.Errorf("couldn't parse %q (procs_blocked): %w", parts[1], err)
227
+				return Stat{}, fmt.Errorf("%s: couldn't parse %q (procs_blocked): %w", ErrFileParse, parts[1], err)
228 228
 			}
229 229
 		case parts[0] == "softirq":
230 230
 			softIRQStats, total, err := parseSoftIRQStat(line)
... ...
@@ -247,7 +251,7 @@ func parseStat(r io.Reader, fileName string) (Stat, error) {
247 247
 	}
248 248
 
249 249
 	if err := scanner.Err(); err != nil {
250
-		return Stat{}, fmt.Errorf("couldn't parse %q: %w", fileName, err)
250
+		return Stat{}, fmt.Errorf("%s: couldn't parse %q: %w", ErrFileParse, fileName, err)
251 251
 	}
252 252
 
253 253
 	return stat, nil
... ...
@@ -64,7 +64,7 @@ func parseSwapString(swapString string) (*Swap, error) {
64 64
 	swapFields := strings.Fields(swapString)
65 65
 	swapLength := len(swapFields)
66 66
 	if swapLength < 5 {
67
-		return nil, fmt.Errorf("too few fields in swap string: %s", swapString)
67
+		return nil, fmt.Errorf("%w: too few fields in swap string: %s", ErrFileParse, swapString)
68 68
 	}
69 69
 
70 70
 	swap := &Swap{
... ...
@@ -74,15 +74,15 @@ func parseSwapString(swapString string) (*Swap, error) {
74 74
 
75 75
 	swap.Size, err = strconv.Atoi(swapFields[2])
76 76
 	if err != nil {
77
-		return nil, fmt.Errorf("invalid swap size: %s", swapFields[2])
77
+		return nil, fmt.Errorf("%s: invalid swap size: %s: %w", ErrFileParse, swapFields[2], err)
78 78
 	}
79 79
 	swap.Used, err = strconv.Atoi(swapFields[3])
80 80
 	if err != nil {
81
-		return nil, fmt.Errorf("invalid swap used: %s", swapFields[3])
81
+		return nil, fmt.Errorf("%s: invalid swap used: %s: %w", ErrFileParse, swapFields[3], err)
82 82
 	}
83 83
 	swap.Priority, err = strconv.Atoi(swapFields[4])
84 84
 	if err != nil {
85
-		return nil, fmt.Errorf("invalid swap priority: %s", swapFields[4])
85
+		return nil, fmt.Errorf("%s: invalid swap priority: %s: %w", ErrFileParse, swapFields[4], err)
86 86
 	}
87 87
 
88 88
 	return swap, nil
... ...
@@ -45,7 +45,7 @@ func (fs FS) AllThreads(pid int) (Procs, error) {
45 45
 
46 46
 	names, err := d.Readdirnames(-1)
47 47
 	if err != nil {
48
-		return Procs{}, fmt.Errorf("could not read %q: %w", d.Name(), err)
48
+		return Procs{}, fmt.Errorf("%s: could not read %q: %w", ErrFileRead, d.Name(), err)
49 49
 	}
50 50
 
51 51
 	t := Procs{}
... ...
@@ -54,7 +54,8 @@ func (fs FS) AllThreads(pid int) (Procs, error) {
54 54
 		if err != nil {
55 55
 			continue
56 56
 		}
57
-		t = append(t, Proc{PID: int(tid), fs: fsi.FS(taskPath)})
57
+
58
+		t = append(t, Proc{PID: int(tid), fs: FS{fsi.FS(taskPath), fs.isReal}})
58 59
 	}
59 60
 
60 61
 	return t, nil
... ...
@@ -66,13 +67,13 @@ func (fs FS) Thread(pid, tid int) (Proc, error) {
66 66
 	if _, err := os.Stat(taskPath); err != nil {
67 67
 		return Proc{}, err
68 68
 	}
69
-	return Proc{PID: tid, fs: fsi.FS(taskPath)}, nil
69
+	return Proc{PID: tid, fs: FS{fsi.FS(taskPath), fs.isReal}}, nil
70 70
 }
71 71
 
72 72
 // Thread returns a process for a given TID of Proc.
73 73
 func (proc Proc) Thread(tid int) (Proc, error) {
74
-	tfs := fsi.FS(proc.path("task"))
75
-	if _, err := os.Stat(tfs.Path(strconv.Itoa(tid))); err != nil {
74
+	tfs := FS{fsi.FS(proc.path("task")), proc.fs.isReal}
75
+	if _, err := os.Stat(tfs.proc.Path(strconv.Itoa(tid))); err != nil {
76 76
 		return Proc{}, err
77 77
 	}
78 78
 	return Proc{PID: tid, fs: tfs}, nil
... ...
@@ -86,7 +86,7 @@ func (fs FS) VM() (*VM, error) {
86 86
 		return nil, err
87 87
 	}
88 88
 	if !file.Mode().IsDir() {
89
-		return nil, fmt.Errorf("%s is not a directory", path)
89
+		return nil, fmt.Errorf("%w: %s is not a directory", ErrFileRead, path)
90 90
 	}
91 91
 
92 92
 	files, err := os.ReadDir(path)
... ...
@@ -75,11 +75,11 @@ var nodeZoneRE = regexp.MustCompile(`(\d+), zone\s+(\w+)`)
75 75
 func (fs FS) Zoneinfo() ([]Zoneinfo, error) {
76 76
 	data, err := os.ReadFile(fs.proc.Path("zoneinfo"))
77 77
 	if err != nil {
78
-		return nil, fmt.Errorf("error reading zoneinfo %q: %w", fs.proc.Path("zoneinfo"), err)
78
+		return nil, fmt.Errorf("%s: error reading zoneinfo %q: %w", ErrFileRead, fs.proc.Path("zoneinfo"), err)
79 79
 	}
80 80
 	zoneinfo, err := parseZoneinfo(data)
81 81
 	if err != nil {
82
-		return nil, fmt.Errorf("error parsing zoneinfo %q: %w", fs.proc.Path("zoneinfo"), err)
82
+		return nil, fmt.Errorf("%s: error parsing zoneinfo %q: %w", ErrFileParse, fs.proc.Path("zoneinfo"), err)
83 83
 	}
84 84
 	return zoneinfo, nil
85 85
 }
... ...
@@ -984,21 +984,21 @@ github.com/philhofer/fwd
984 984
 # github.com/pkg/errors v0.9.1
985 985
 ## explicit
986 986
 github.com/pkg/errors
987
-# github.com/prometheus/client_golang v1.14.0
988
-## explicit; go 1.17
987
+# github.com/prometheus/client_golang v1.17.0
988
+## explicit; go 1.19
989 989
 github.com/prometheus/client_golang/prometheus
990 990
 github.com/prometheus/client_golang/prometheus/internal
991 991
 github.com/prometheus/client_golang/prometheus/promhttp
992
-# github.com/prometheus/client_model v0.3.0
993
-## explicit; go 1.9
992
+# github.com/prometheus/client_model v0.5.0
993
+## explicit; go 1.19
994 994
 github.com/prometheus/client_model/go
995
-# github.com/prometheus/common v0.42.0
995
+# github.com/prometheus/common v0.44.0
996 996
 ## explicit; go 1.18
997 997
 github.com/prometheus/common/expfmt
998 998
 github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg
999 999
 github.com/prometheus/common/model
1000
-# github.com/prometheus/procfs v0.9.0
1001
-## explicit; go 1.18
1000
+# github.com/prometheus/procfs v0.11.1
1001
+## explicit; go 1.19
1002 1002
 github.com/prometheus/procfs
1003 1003
 github.com/prometheus/procfs/internal/fs
1004 1004
 github.com/prometheus/procfs/internal/util