full diff: https://github.com/prometheus/client_golang/compare/v1.20.5...v1.22.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -88,7 +88,7 @@ require ( |
| 88 | 88 |
github.com/opencontainers/selinux v1.12.0 |
| 89 | 89 |
github.com/pelletier/go-toml v1.9.5 |
| 90 | 90 |
github.com/pkg/errors v0.9.1 |
| 91 |
- github.com/prometheus/client_golang v1.20.5 |
|
| 91 |
+ github.com/prometheus/client_golang v1.22.0 |
|
| 92 | 92 |
github.com/rootless-containers/rootlesskit/v2 v2.3.4 |
| 93 | 93 |
github.com/sirupsen/logrus v1.9.3 |
| 94 | 94 |
github.com/spf13/cobra v1.9.1 |
| ... | ... |
@@ -198,7 +198,7 @@ require ( |
| 198 | 198 |
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect |
| 199 | 199 |
github.com/pmezard/go-difflib v1.0.0 // indirect |
| 200 | 200 |
github.com/prometheus/client_model v0.6.1 // indirect |
| 201 |
- github.com/prometheus/common v0.55.0 // indirect |
|
| 201 |
+ github.com/prometheus/common v0.62.0 // indirect |
|
| 202 | 202 |
github.com/prometheus/procfs v0.15.1 // indirect |
| 203 | 203 |
github.com/sasha-s/go-deadlock v0.3.5 // indirect |
| 204 | 204 |
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect |
| ... | ... |
@@ -485,8 +485,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP |
| 485 | 485 |
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= |
| 486 | 486 |
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= |
| 487 | 487 |
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= |
| 488 |
-github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= |
|
| 489 |
-github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= |
|
| 488 |
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= |
|
| 489 |
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= |
|
| 490 | 490 |
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= |
| 491 | 491 |
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
| 492 | 492 |
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |
| ... | ... |
@@ -496,8 +496,8 @@ github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQy |
| 496 | 496 |
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= |
| 497 | 497 |
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= |
| 498 | 498 |
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= |
| 499 |
-github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= |
|
| 500 |
-github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= |
|
| 499 |
+github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= |
|
| 500 |
+github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= |
|
| 501 | 501 |
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= |
| 502 | 502 |
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= |
| 503 | 503 |
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= |
| 504 | 504 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,30 @@ |
| 0 |
+// Copyright 2025 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 |
+// CollectorFunc is a convenient way to implement a Prometheus Collector |
|
| 16 |
+// without interface boilerplate. |
|
| 17 |
+// This implementation is based on DescribeByCollect method. |
|
| 18 |
+// familiarize yourself to it before using. |
|
| 19 |
+type CollectorFunc func(chan<- Metric) |
|
| 20 |
+ |
|
| 21 |
+// Collect calls the defined CollectorFunc function with the provided Metrics channel |
|
| 22 |
+func (f CollectorFunc) Collect(ch chan<- Metric) {
|
|
| 23 |
+ f(ch) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// Describe sends the descriptor information using DescribeByCollect |
|
| 27 |
+func (f CollectorFunc) Describe(ch chan<- *Desc) {
|
|
| 28 |
+ DescribeByCollect(f, ch) |
|
| 29 |
+} |
| ... | ... |
@@ -189,12 +189,15 @@ func (d *Desc) String() string {
|
| 189 | 189 |
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
|
| 190 | 190 |
) |
| 191 | 191 |
} |
| 192 |
- vlStrings := make([]string, 0, len(d.variableLabels.names)) |
|
| 193 |
- for _, vl := range d.variableLabels.names {
|
|
| 194 |
- if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil {
|
|
| 195 |
- vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl))
|
|
| 196 |
- } else {
|
|
| 197 |
- vlStrings = append(vlStrings, vl) |
|
| 192 |
+ vlStrings := []string{}
|
|
| 193 |
+ if d.variableLabels != nil {
|
|
| 194 |
+ vlStrings = make([]string, 0, len(d.variableLabels.names)) |
|
| 195 |
+ for _, vl := range d.variableLabels.names {
|
|
| 196 |
+ if fn, ok := d.variableLabels.labelConstraints[vl]; ok && fn != nil {
|
|
| 197 |
+ vlStrings = append(vlStrings, fmt.Sprintf("c(%s)", vl))
|
|
| 198 |
+ } else {
|
|
| 199 |
+ vlStrings = append(vlStrings, vl) |
|
| 200 |
+ } |
|
| 198 | 201 |
} |
| 199 | 202 |
} |
| 200 | 203 |
return fmt.Sprintf( |
| ... | ... |
@@ -288,7 +288,7 @@ func NewGoCollector(opts ...func(o *internal.GoCollectorOptions)) Collector {
|
| 288 | 288 |
} |
| 289 | 289 |
|
| 290 | 290 |
func attachOriginalName(desc, origName string) string {
|
| 291 |
- return fmt.Sprintf("%s Sourced from %s", desc, origName)
|
|
| 291 |
+ return fmt.Sprintf("%s Sourced from %s.", desc, origName)
|
|
| 292 | 292 |
} |
| 293 | 293 |
|
| 294 | 294 |
// Describe returns all descriptions of the collector. |
| ... | ... |
@@ -14,6 +14,7 @@ |
| 14 | 14 |
package prometheus |
| 15 | 15 |
|
| 16 | 16 |
import ( |
| 17 |
+ "errors" |
|
| 17 | 18 |
"fmt" |
| 18 | 19 |
"math" |
| 19 | 20 |
"runtime" |
| ... | ... |
@@ -28,6 +29,11 @@ import ( |
| 28 | 28 |
"google.golang.org/protobuf/types/known/timestamppb" |
| 29 | 29 |
) |
| 30 | 30 |
|
| 31 |
+const ( |
|
| 32 |
+ nativeHistogramSchemaMaximum = 8 |
|
| 33 |
+ nativeHistogramSchemaMinimum = -4 |
|
| 34 |
+) |
|
| 35 |
+ |
|
| 31 | 36 |
// nativeHistogramBounds for the frac of observed values. Only relevant for |
| 32 | 37 |
// schema > 0. The position in the slice is the schema. (0 is never used, just |
| 33 | 38 |
// here for convenience of using the schema directly as the index.) |
| ... | ... |
@@ -330,11 +336,11 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
|
| 330 | 330 |
// used for the Buckets field of HistogramOpts. |
| 331 | 331 |
// |
| 332 | 332 |
// The function panics if 'count' is 0 or negative, if 'min' is 0 or negative. |
| 333 |
-func ExponentialBucketsRange(min, max float64, count int) []float64 {
|
|
| 333 |
+func ExponentialBucketsRange(minBucket, maxBucket float64, count int) []float64 {
|
|
| 334 | 334 |
if count < 1 {
|
| 335 | 335 |
panic("ExponentialBucketsRange count needs a positive count")
|
| 336 | 336 |
} |
| 337 |
- if min <= 0 {
|
|
| 337 |
+ if minBucket <= 0 {
|
|
| 338 | 338 |
panic("ExponentialBucketsRange min needs to be greater than 0")
|
| 339 | 339 |
} |
| 340 | 340 |
|
| ... | ... |
@@ -342,12 +348,12 @@ func ExponentialBucketsRange(min, max float64, count int) []float64 {
|
| 342 | 342 |
// max = min*growthFactor^(bucketCount-1) |
| 343 | 343 |
|
| 344 | 344 |
// We know max/min and highest bucket. Solve for growthFactor. |
| 345 |
- growthFactor := math.Pow(max/min, 1.0/float64(count-1)) |
|
| 345 |
+ growthFactor := math.Pow(maxBucket/minBucket, 1.0/float64(count-1)) |
|
| 346 | 346 |
|
| 347 | 347 |
// Now that we know growthFactor, solve for each bucket. |
| 348 | 348 |
buckets := make([]float64, count) |
| 349 | 349 |
for i := 1; i <= count; i++ {
|
| 350 |
- buckets[i-1] = min * math.Pow(growthFactor, float64(i-1)) |
|
| 350 |
+ buckets[i-1] = minBucket * math.Pow(growthFactor, float64(i-1)) |
|
| 351 | 351 |
} |
| 352 | 352 |
return buckets |
| 353 | 353 |
} |
| ... | ... |
@@ -858,15 +864,35 @@ func (h *histogram) Write(out *dto.Metric) error {
|
| 858 | 858 |
// findBucket returns the index of the bucket for the provided value, or |
| 859 | 859 |
// len(h.upperBounds) for the +Inf bucket. |
| 860 | 860 |
func (h *histogram) findBucket(v float64) int {
|
| 861 |
- // TODO(beorn7): For small numbers of buckets (<30), a linear search is |
|
| 862 |
- // slightly faster than the binary search. If we really care, we could |
|
| 863 |
- // switch from one search strategy to the other depending on the number |
|
| 864 |
- // of buckets. |
|
| 865 |
- // |
|
| 866 |
- // Microbenchmarks (BenchmarkHistogramNoLabels): |
|
| 867 |
- // 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op |
|
| 868 |
- // 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op |
|
| 869 |
- // 300 buckets: 154 ns/op linear - binary 61.6 ns/op |
|
| 861 |
+ n := len(h.upperBounds) |
|
| 862 |
+ if n == 0 {
|
|
| 863 |
+ return 0 |
|
| 864 |
+ } |
|
| 865 |
+ |
|
| 866 |
+ // Early exit: if v is less than or equal to the first upper bound, return 0 |
|
| 867 |
+ if v <= h.upperBounds[0] {
|
|
| 868 |
+ return 0 |
|
| 869 |
+ } |
|
| 870 |
+ |
|
| 871 |
+ // Early exit: if v is greater than the last upper bound, return len(h.upperBounds) |
|
| 872 |
+ if v > h.upperBounds[n-1] {
|
|
| 873 |
+ return n |
|
| 874 |
+ } |
|
| 875 |
+ |
|
| 876 |
+ // For small arrays, use simple linear search |
|
| 877 |
+ // "magic number" 35 is result of tests on couple different (AWS and baremetal) servers |
|
| 878 |
+ // see more details here: https://github.com/prometheus/client_golang/pull/1662 |
|
| 879 |
+ if n < 35 {
|
|
| 880 |
+ for i, bound := range h.upperBounds {
|
|
| 881 |
+ if v <= bound {
|
|
| 882 |
+ return i |
|
| 883 |
+ } |
|
| 884 |
+ } |
|
| 885 |
+ // If v is greater than all upper bounds, return len(h.upperBounds) |
|
| 886 |
+ return n |
|
| 887 |
+ } |
|
| 888 |
+ |
|
| 889 |
+ // For larger arrays, use stdlib's binary search |
|
| 870 | 890 |
return sort.SearchFloat64s(h.upperBounds, v) |
| 871 | 891 |
} |
| 872 | 892 |
|
| ... | ... |
@@ -1440,9 +1466,9 @@ func pickSchema(bucketFactor float64) int32 {
|
| 1440 | 1440 |
floor := math.Floor(math.Log2(math.Log2(bucketFactor))) |
| 1441 | 1441 |
switch {
|
| 1442 | 1442 |
case floor <= -8: |
| 1443 |
- return 8 |
|
| 1443 |
+ return nativeHistogramSchemaMaximum |
|
| 1444 | 1444 |
case floor >= 4: |
| 1445 |
- return -4 |
|
| 1445 |
+ return nativeHistogramSchemaMinimum |
|
| 1446 | 1446 |
default: |
| 1447 | 1447 |
return -int32(floor) |
| 1448 | 1448 |
} |
| ... | ... |
@@ -1835,3 +1861,196 @@ func (n *nativeExemplars) addExemplar(e *dto.Exemplar) {
|
| 1835 | 1835 |
n.exemplars = append(n.exemplars[:nIdx], append([]*dto.Exemplar{e}, append(n.exemplars[nIdx:rIdx], n.exemplars[rIdx+1:]...)...)...)
|
| 1836 | 1836 |
} |
| 1837 | 1837 |
} |
| 1838 |
+ |
|
| 1839 |
+type constNativeHistogram struct {
|
|
| 1840 |
+ desc *Desc |
|
| 1841 |
+ dto.Histogram |
|
| 1842 |
+ labelPairs []*dto.LabelPair |
|
| 1843 |
+} |
|
| 1844 |
+ |
|
| 1845 |
+func validateCount(sum float64, count uint64, negativeBuckets, positiveBuckets map[int]int64, zeroBucket uint64) error {
|
|
| 1846 |
+ var bucketPopulationSum int64 |
|
| 1847 |
+ for _, v := range positiveBuckets {
|
|
| 1848 |
+ bucketPopulationSum += v |
|
| 1849 |
+ } |
|
| 1850 |
+ for _, v := range negativeBuckets {
|
|
| 1851 |
+ bucketPopulationSum += v |
|
| 1852 |
+ } |
|
| 1853 |
+ bucketPopulationSum += int64(zeroBucket) |
|
| 1854 |
+ |
|
| 1855 |
+ // If the sum of observations is NaN, the number of observations must be greater or equal to the sum of all bucket counts. |
|
| 1856 |
+ // Otherwise, the number of observations must be equal to the sum of all bucket counts . |
|
| 1857 |
+ |
|
| 1858 |
+ if math.IsNaN(sum) && bucketPopulationSum > int64(count) || |
|
| 1859 |
+ !math.IsNaN(sum) && bucketPopulationSum != int64(count) {
|
|
| 1860 |
+ return errors.New("the sum of all bucket populations exceeds the count of observations")
|
|
| 1861 |
+ } |
|
| 1862 |
+ return nil |
|
| 1863 |
+} |
|
| 1864 |
+ |
|
| 1865 |
+// NewConstNativeHistogram returns a metric representing a Prometheus native histogram with |
|
| 1866 |
+// fixed values for the count, sum, and positive/negative/zero bucket counts. As those parameters |
|
| 1867 |
+// cannot be changed, the returned value does not implement the Histogram |
|
| 1868 |
+// interface (but only the Metric interface). Users of this package will not |
|
| 1869 |
+// have much use for it in regular operations. However, when implementing custom |
|
| 1870 |
+// OpenTelemetry Collectors, it is useful as a throw-away metric that is generated on the fly |
|
| 1871 |
+// to send it to Prometheus in the Collect method. |
|
| 1872 |
+// |
|
| 1873 |
+// zeroBucket counts all (positive and negative) |
|
| 1874 |
+// observations in the zero bucket (with an absolute value less or equal |
|
| 1875 |
+// the current threshold). |
|
| 1876 |
+// positiveBuckets and negativeBuckets are separate maps for negative and positive |
|
| 1877 |
+// observations. The map's value is an int64, counting observations in |
|
| 1878 |
+// that bucket. The map's key is the |
|
| 1879 |
+// index of the bucket according to the used |
|
| 1880 |
+// Schema. Index 0 is for an upper bound of 1 in positive buckets and for a lower bound of -1 in negative buckets. |
|
| 1881 |
+// NewConstNativeHistogram returns an error if |
|
| 1882 |
+// - the length of labelValues is not consistent with the variable labels in Desc or if Desc is invalid. |
|
| 1883 |
+// - the schema passed is not between 8 and -4 |
|
| 1884 |
+// - the sum of counts in all buckets including the zero bucket does not equal the count if sum is not NaN (or exceeds the count if sum is NaN) |
|
| 1885 |
+// |
|
| 1886 |
+// See https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#exponential-histograms for more details about the conversion from OTel to Prometheus. |
|
| 1887 |
+func NewConstNativeHistogram( |
|
| 1888 |
+ desc *Desc, |
|
| 1889 |
+ count uint64, |
|
| 1890 |
+ sum float64, |
|
| 1891 |
+ positiveBuckets, negativeBuckets map[int]int64, |
|
| 1892 |
+ zeroBucket uint64, |
|
| 1893 |
+ schema int32, |
|
| 1894 |
+ zeroThreshold float64, |
|
| 1895 |
+ createdTimestamp time.Time, |
|
| 1896 |
+ labelValues ...string, |
|
| 1897 |
+) (Metric, error) {
|
|
| 1898 |
+ if desc.err != nil {
|
|
| 1899 |
+ return nil, desc.err |
|
| 1900 |
+ } |
|
| 1901 |
+ if err := validateLabelValues(labelValues, len(desc.variableLabels.names)); err != nil {
|
|
| 1902 |
+ return nil, err |
|
| 1903 |
+ } |
|
| 1904 |
+ if schema > nativeHistogramSchemaMaximum || schema < nativeHistogramSchemaMinimum {
|
|
| 1905 |
+ return nil, errors.New("invalid native histogram schema")
|
|
| 1906 |
+ } |
|
| 1907 |
+ if err := validateCount(sum, count, negativeBuckets, positiveBuckets, zeroBucket); err != nil {
|
|
| 1908 |
+ return nil, err |
|
| 1909 |
+ } |
|
| 1910 |
+ |
|
| 1911 |
+ NegativeSpan, NegativeDelta := makeBucketsFromMap(negativeBuckets) |
|
| 1912 |
+ PositiveSpan, PositiveDelta := makeBucketsFromMap(positiveBuckets) |
|
| 1913 |
+ ret := &constNativeHistogram{
|
|
| 1914 |
+ desc: desc, |
|
| 1915 |
+ Histogram: dto.Histogram{
|
|
| 1916 |
+ CreatedTimestamp: timestamppb.New(createdTimestamp), |
|
| 1917 |
+ Schema: &schema, |
|
| 1918 |
+ ZeroThreshold: &zeroThreshold, |
|
| 1919 |
+ SampleCount: &count, |
|
| 1920 |
+ SampleSum: &sum, |
|
| 1921 |
+ |
|
| 1922 |
+ NegativeSpan: NegativeSpan, |
|
| 1923 |
+ NegativeDelta: NegativeDelta, |
|
| 1924 |
+ |
|
| 1925 |
+ PositiveSpan: PositiveSpan, |
|
| 1926 |
+ PositiveDelta: PositiveDelta, |
|
| 1927 |
+ |
|
| 1928 |
+ ZeroCount: proto.Uint64(zeroBucket), |
|
| 1929 |
+ }, |
|
| 1930 |
+ labelPairs: MakeLabelPairs(desc, labelValues), |
|
| 1931 |
+ } |
|
| 1932 |
+ if *ret.ZeroThreshold == 0 && *ret.ZeroCount == 0 && len(ret.PositiveSpan) == 0 && len(ret.NegativeSpan) == 0 {
|
|
| 1933 |
+ ret.PositiveSpan = []*dto.BucketSpan{{
|
|
| 1934 |
+ Offset: proto.Int32(0), |
|
| 1935 |
+ Length: proto.Uint32(0), |
|
| 1936 |
+ }} |
|
| 1937 |
+ } |
|
| 1938 |
+ return ret, nil |
|
| 1939 |
+} |
|
| 1940 |
+ |
|
| 1941 |
+// MustNewConstNativeHistogram is a version of NewConstNativeHistogram that panics where |
|
| 1942 |
+// NewConstNativeHistogram would have returned an error. |
|
| 1943 |
+func MustNewConstNativeHistogram( |
|
| 1944 |
+ desc *Desc, |
|
| 1945 |
+ count uint64, |
|
| 1946 |
+ sum float64, |
|
| 1947 |
+ positiveBuckets, negativeBuckets map[int]int64, |
|
| 1948 |
+ zeroBucket uint64, |
|
| 1949 |
+ nativeHistogramSchema int32, |
|
| 1950 |
+ nativeHistogramZeroThreshold float64, |
|
| 1951 |
+ createdTimestamp time.Time, |
|
| 1952 |
+ labelValues ...string, |
|
| 1953 |
+) Metric {
|
|
| 1954 |
+ nativehistogram, err := NewConstNativeHistogram(desc, |
|
| 1955 |
+ count, |
|
| 1956 |
+ sum, |
|
| 1957 |
+ positiveBuckets, |
|
| 1958 |
+ negativeBuckets, |
|
| 1959 |
+ zeroBucket, |
|
| 1960 |
+ nativeHistogramSchema, |
|
| 1961 |
+ nativeHistogramZeroThreshold, |
|
| 1962 |
+ createdTimestamp, |
|
| 1963 |
+ labelValues...) |
|
| 1964 |
+ if err != nil {
|
|
| 1965 |
+ panic(err) |
|
| 1966 |
+ } |
|
| 1967 |
+ return nativehistogram |
|
| 1968 |
+} |
|
| 1969 |
+ |
|
| 1970 |
+func (h *constNativeHistogram) Desc() *Desc {
|
|
| 1971 |
+ return h.desc |
|
| 1972 |
+} |
|
| 1973 |
+ |
|
| 1974 |
+func (h *constNativeHistogram) Write(out *dto.Metric) error {
|
|
| 1975 |
+ out.Histogram = &h.Histogram |
|
| 1976 |
+ out.Label = h.labelPairs |
|
| 1977 |
+ return nil |
|
| 1978 |
+} |
|
| 1979 |
+ |
|
| 1980 |
+func makeBucketsFromMap(buckets map[int]int64) ([]*dto.BucketSpan, []int64) {
|
|
| 1981 |
+ if len(buckets) == 0 {
|
|
| 1982 |
+ return nil, nil |
|
| 1983 |
+ } |
|
| 1984 |
+ var ii []int |
|
| 1985 |
+ for k := range buckets {
|
|
| 1986 |
+ ii = append(ii, k) |
|
| 1987 |
+ } |
|
| 1988 |
+ sort.Ints(ii) |
|
| 1989 |
+ |
|
| 1990 |
+ var ( |
|
| 1991 |
+ spans []*dto.BucketSpan |
|
| 1992 |
+ deltas []int64 |
|
| 1993 |
+ prevCount int64 |
|
| 1994 |
+ nextI int |
|
| 1995 |
+ ) |
|
| 1996 |
+ |
|
| 1997 |
+ appendDelta := func(count int64) {
|
|
| 1998 |
+ *spans[len(spans)-1].Length++ |
|
| 1999 |
+ deltas = append(deltas, count-prevCount) |
|
| 2000 |
+ prevCount = count |
|
| 2001 |
+ } |
|
| 2002 |
+ |
|
| 2003 |
+ for n, i := range ii {
|
|
| 2004 |
+ count := buckets[i] |
|
| 2005 |
+ // Multiple spans with only small gaps in between are probably |
|
| 2006 |
+ // encoded more efficiently as one larger span with a few empty |
|
| 2007 |
+ // buckets. Needs some research to find the sweet spot. For now, |
|
| 2008 |
+ // we assume that gaps of one or two buckets should not create |
|
| 2009 |
+ // a new span. |
|
| 2010 |
+ iDelta := int32(i - nextI) |
|
| 2011 |
+ if n == 0 || iDelta > 2 {
|
|
| 2012 |
+ // We have to create a new span, either because we are |
|
| 2013 |
+ // at the very beginning, or because we have found a gap |
|
| 2014 |
+ // of more than two buckets. |
|
| 2015 |
+ spans = append(spans, &dto.BucketSpan{
|
|
| 2016 |
+ Offset: proto.Int32(iDelta), |
|
| 2017 |
+ Length: proto.Uint32(0), |
|
| 2018 |
+ }) |
|
| 2019 |
+ } else {
|
|
| 2020 |
+ // We have found a small gap (or no gap at all). |
|
| 2021 |
+ // Insert empty buckets as needed. |
|
| 2022 |
+ for j := int32(0); j < iDelta; j++ {
|
|
| 2023 |
+ appendDelta(0) |
|
| 2024 |
+ } |
|
| 2025 |
+ } |
|
| 2026 |
+ appendDelta(count) |
|
| 2027 |
+ nextI = i + 1 |
|
| 2028 |
+ } |
|
| 2029 |
+ return spans, deltas |
|
| 2030 |
+} |
| ... | ... |
@@ -22,17 +22,18 @@ import ( |
| 22 | 22 |
"bytes" |
| 23 | 23 |
"fmt" |
| 24 | 24 |
"io" |
| 25 |
+ "strconv" |
|
| 25 | 26 |
"strings" |
| 26 | 27 |
) |
| 27 | 28 |
|
| 28 |
-func min(a, b int) int {
|
|
| 29 |
+func minInt(a, b int) int {
|
|
| 29 | 30 |
if a < b {
|
| 30 | 31 |
return a |
| 31 | 32 |
} |
| 32 | 33 |
return b |
| 33 | 34 |
} |
| 34 | 35 |
|
| 35 |
-func max(a, b int) int {
|
|
| 36 |
+func maxInt(a, b int) int {
|
|
| 36 | 37 |
if a > b {
|
| 37 | 38 |
return a |
| 38 | 39 |
} |
| ... | ... |
@@ -427,12 +428,12 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
| 427 | 427 |
if codes[0].Tag == 'e' {
|
| 428 | 428 |
c := codes[0] |
| 429 | 429 |
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 |
| 430 |
- codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
|
| 430 |
+ codes[0] = OpCode{c.Tag, maxInt(i1, i2-n), i2, maxInt(j1, j2-n), j2}
|
|
| 431 | 431 |
} |
| 432 | 432 |
if codes[len(codes)-1].Tag == 'e' {
|
| 433 | 433 |
c := codes[len(codes)-1] |
| 434 | 434 |
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2 |
| 435 |
- codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
|
| 435 |
+ codes[len(codes)-1] = OpCode{c.Tag, i1, minInt(i2, i1+n), j1, minInt(j2, j1+n)}
|
|
| 436 | 436 |
} |
| 437 | 437 |
nn := n + n |
| 438 | 438 |
groups := [][]OpCode{}
|
| ... | ... |
@@ -443,12 +444,12 @@ func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
| 443 | 443 |
// there is a large range with no changes. |
| 444 | 444 |
if c.Tag == 'e' && i2-i1 > nn {
|
| 445 | 445 |
group = append(group, OpCode{
|
| 446 |
- c.Tag, i1, min(i2, i1+n), |
|
| 447 |
- j1, min(j2, j1+n), |
|
| 446 |
+ c.Tag, i1, minInt(i2, i1+n), |
|
| 447 |
+ j1, minInt(j2, j1+n), |
|
| 448 | 448 |
}) |
| 449 | 449 |
groups = append(groups, group) |
| 450 | 450 |
group = []OpCode{}
|
| 451 |
- i1, j1 = max(i1, i2-n), max(j1, j2-n) |
|
| 451 |
+ i1, j1 = maxInt(i1, i2-n), maxInt(j1, j2-n) |
|
| 452 | 452 |
} |
| 453 | 453 |
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
| 454 | 454 |
} |
| ... | ... |
@@ -515,7 +516,7 @@ func (m *SequenceMatcher) QuickRatio() float64 {
|
| 515 | 515 |
// is faster to compute than either .Ratio() or .QuickRatio(). |
| 516 | 516 |
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
| 517 | 517 |
la, lb := len(m.a), len(m.b) |
| 518 |
- return calculateRatio(min(la, lb), la+lb) |
|
| 518 |
+ return calculateRatio(minInt(la, lb), la+lb) |
|
| 519 | 519 |
} |
| 520 | 520 |
|
| 521 | 521 |
// Convert range to the "ed" format |
| ... | ... |
@@ -524,7 +525,7 @@ func formatRangeUnified(start, stop int) string {
|
| 524 | 524 |
beginning := start + 1 // lines start numbering with one |
| 525 | 525 |
length := stop - start |
| 526 | 526 |
if length == 1 {
|
| 527 |
- return fmt.Sprintf("%d", beginning)
|
|
| 527 |
+ return strconv.Itoa(beginning) |
|
| 528 | 528 |
} |
| 529 | 529 |
if length == 0 {
|
| 530 | 530 |
beginning-- // empty ranges begin at line just before the range |
| ... | ... |
@@ -66,7 +66,8 @@ func RuntimeMetricsToProm(d *metrics.Description) (string, string, string, bool) |
| 66 | 66 |
name += "_total" |
| 67 | 67 |
} |
| 68 | 68 |
|
| 69 |
- valid := model.IsValidMetricName(model.LabelValue(namespace + "_" + subsystem + "_" + name)) |
|
| 69 |
+ // Our current conversion moves to legacy naming, so use legacy validation. |
|
| 70 |
+ valid := model.IsValidLegacyMetricName(namespace + "_" + subsystem + "_" + name) |
|
| 70 | 71 |
switch d.Kind {
|
| 71 | 72 |
case metrics.KindUint64: |
| 72 | 73 |
case metrics.KindFloat64: |
| ... | ... |
@@ -108,15 +108,23 @@ func BuildFQName(namespace, subsystem, name string) string {
|
| 108 | 108 |
if name == "" {
|
| 109 | 109 |
return "" |
| 110 | 110 |
} |
| 111 |
- switch {
|
|
| 112 |
- case namespace != "" && subsystem != "": |
|
| 113 |
- return strings.Join([]string{namespace, subsystem, name}, "_")
|
|
| 114 |
- case namespace != "": |
|
| 115 |
- return strings.Join([]string{namespace, name}, "_")
|
|
| 116 |
- case subsystem != "": |
|
| 117 |
- return strings.Join([]string{subsystem, name}, "_")
|
|
| 111 |
+ |
|
| 112 |
+ sb := strings.Builder{}
|
|
| 113 |
+ sb.Grow(len(namespace) + len(subsystem) + len(name) + 2) |
|
| 114 |
+ |
|
| 115 |
+ if namespace != "" {
|
|
| 116 |
+ sb.WriteString(namespace) |
|
| 117 |
+ sb.WriteString("_")
|
|
| 118 | 118 |
} |
| 119 |
- return name |
|
| 119 |
+ |
|
| 120 |
+ if subsystem != "" {
|
|
| 121 |
+ sb.WriteString(subsystem) |
|
| 122 |
+ sb.WriteString("_")
|
|
| 123 |
+ } |
|
| 124 |
+ |
|
| 125 |
+ sb.WriteString(name) |
|
| 126 |
+ |
|
| 127 |
+ return sb.String() |
|
| 120 | 128 |
} |
| 121 | 129 |
|
| 122 | 130 |
type invalidMetric struct {
|
| ... | ... |
@@ -23,6 +23,7 @@ import ( |
| 23 | 23 |
|
| 24 | 24 |
type processCollector struct {
|
| 25 | 25 |
collectFn func(chan<- Metric) |
| 26 |
+ describeFn func(chan<- *Desc) |
|
| 26 | 27 |
pidFn func() (int, error) |
| 27 | 28 |
reportErrors bool |
| 28 | 29 |
cpuTotal *Desc |
| ... | ... |
@@ -122,26 +123,23 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
|
| 122 | 122 |
// Set up process metric collection if supported by the runtime. |
| 123 | 123 |
if canCollectProcess() {
|
| 124 | 124 |
c.collectFn = c.processCollect |
| 125 |
+ c.describeFn = c.describe |
|
| 125 | 126 |
} else {
|
| 126 |
- c.collectFn = func(ch chan<- Metric) {
|
|
| 127 |
- c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
|
|
| 128 |
- } |
|
| 127 |
+ c.collectFn = c.errorCollectFn |
|
| 128 |
+ c.describeFn = c.errorDescribeFn |
|
| 129 | 129 |
} |
| 130 | 130 |
|
| 131 | 131 |
return c |
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 |
-// Describe returns all descriptions of the collector. |
|
| 135 |
-func (c *processCollector) Describe(ch chan<- *Desc) {
|
|
| 136 |
- ch <- c.cpuTotal |
|
| 137 |
- ch <- c.openFDs |
|
| 138 |
- ch <- c.maxFDs |
|
| 139 |
- ch <- c.vsize |
|
| 140 |
- ch <- c.maxVsize |
|
| 141 |
- ch <- c.rss |
|
| 142 |
- ch <- c.startTime |
|
| 143 |
- ch <- c.inBytes |
|
| 144 |
- ch <- c.outBytes |
|
| 134 |
+func (c *processCollector) errorCollectFn(ch chan<- Metric) {
|
|
| 135 |
+ c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
|
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+func (c *processCollector) errorDescribeFn(ch chan<- *Desc) {
|
|
| 139 |
+ if c.reportErrors {
|
|
| 140 |
+ ch <- NewInvalidDesc(errors.New("process metrics not supported on this platform"))
|
|
| 141 |
+ } |
|
| 145 | 142 |
} |
| 146 | 143 |
|
| 147 | 144 |
// Collect returns the current state of all metrics of the collector. |
| ... | ... |
@@ -149,6 +147,11 @@ func (c *processCollector) Collect(ch chan<- Metric) {
|
| 149 | 149 |
c.collectFn(ch) |
| 150 | 150 |
} |
| 151 | 151 |
|
| 152 |
+// Describe returns all descriptions of the collector. |
|
| 153 |
+func (c *processCollector) Describe(ch chan<- *Desc) {
|
|
| 154 |
+ c.describeFn(ch) |
|
| 155 |
+} |
|
| 156 |
+ |
|
| 152 | 157 |
func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
|
| 153 | 158 |
if !c.reportErrors {
|
| 154 | 159 |
return |
| 155 | 160 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,130 @@ |
| 0 |
+// Copyright 2024 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 darwin && !ios |
|
| 14 |
+ |
|
| 15 |
+package prometheus |
|
| 16 |
+ |
|
| 17 |
+import ( |
|
| 18 |
+ "errors" |
|
| 19 |
+ "fmt" |
|
| 20 |
+ "os" |
|
| 21 |
+ "syscall" |
|
| 22 |
+ "time" |
|
| 23 |
+ |
|
| 24 |
+ "golang.org/x/sys/unix" |
|
| 25 |
+) |
|
| 26 |
+ |
|
| 27 |
+// notImplementedErr is returned by stub functions that replace cgo functions, when cgo |
|
| 28 |
+// isn't available. |
|
| 29 |
+var notImplementedErr = errors.New("not implemented")
|
|
| 30 |
+ |
|
| 31 |
+type memoryInfo struct {
|
|
| 32 |
+ vsize uint64 // Virtual memory size in bytes |
|
| 33 |
+ rss uint64 // Resident memory size in bytes |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func canCollectProcess() bool {
|
|
| 37 |
+ return true |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func getSoftLimit(which int) (uint64, error) {
|
|
| 41 |
+ rlimit := syscall.Rlimit{}
|
|
| 42 |
+ |
|
| 43 |
+ if err := syscall.Getrlimit(which, &rlimit); err != nil {
|
|
| 44 |
+ return 0, err |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ return rlimit.Cur, nil |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+func getOpenFileCount() (float64, error) {
|
|
| 51 |
+ // Alternately, the undocumented proc_pidinfo(PROC_PIDLISTFDS) can be used to |
|
| 52 |
+ // return a list of open fds, but that requires a way to call C APIs. The |
|
| 53 |
+ // benefits, however, include fewer system calls and not failing when at the |
|
| 54 |
+ // open file soft limit. |
|
| 55 |
+ |
|
| 56 |
+ if dir, err := os.Open("/dev/fd"); err != nil {
|
|
| 57 |
+ return 0.0, err |
|
| 58 |
+ } else {
|
|
| 59 |
+ defer dir.Close() |
|
| 60 |
+ |
|
| 61 |
+ // Avoid ReadDir(), as it calls stat(2) on each descriptor. Not only is |
|
| 62 |
+ // that info not used, but KQUEUE descriptors fail stat(2), which causes |
|
| 63 |
+ // the whole method to fail. |
|
| 64 |
+ if names, err := dir.Readdirnames(0); err != nil {
|
|
| 65 |
+ return 0.0, err |
|
| 66 |
+ } else {
|
|
| 67 |
+ // Subtract 1 to ignore the open /dev/fd descriptor above. |
|
| 68 |
+ return float64(len(names) - 1), nil |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+} |
|
| 72 |
+ |
|
| 73 |
+func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
| 74 |
+ if procs, err := unix.SysctlKinfoProcSlice("kern.proc.pid", os.Getpid()); err == nil {
|
|
| 75 |
+ if len(procs) == 1 {
|
|
| 76 |
+ startTime := float64(procs[0].Proc.P_starttime.Nano() / 1e9) |
|
| 77 |
+ ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) |
|
| 78 |
+ } else {
|
|
| 79 |
+ err = fmt.Errorf("sysctl() returned %d proc structs (expected 1)", len(procs))
|
|
| 80 |
+ c.reportError(ch, c.startTime, err) |
|
| 81 |
+ } |
|
| 82 |
+ } else {
|
|
| 83 |
+ c.reportError(ch, c.startTime, err) |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ // The proc structure returned by kern.proc.pid above has an Rusage member, |
|
| 87 |
+ // but it is not filled in, so it needs to be fetched by getrusage(2). For |
|
| 88 |
+ // that call, the UTime, STime, and Maxrss members are filled out, but not |
|
| 89 |
+ // Ixrss, Idrss, or Isrss for the memory usage. Memory stats will require |
|
| 90 |
+ // access to the C API to call task_info(TASK_BASIC_INFO). |
|
| 91 |
+ rusage := unix.Rusage{}
|
|
| 92 |
+ |
|
| 93 |
+ if err := unix.Getrusage(syscall.RUSAGE_SELF, &rusage); err == nil {
|
|
| 94 |
+ cpuTime := time.Duration(rusage.Stime.Nano() + rusage.Utime.Nano()).Seconds() |
|
| 95 |
+ ch <- MustNewConstMetric(c.cpuTotal, CounterValue, cpuTime) |
|
| 96 |
+ } else {
|
|
| 97 |
+ c.reportError(ch, c.cpuTotal, err) |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ if memInfo, err := getMemory(); err == nil {
|
|
| 101 |
+ ch <- MustNewConstMetric(c.rss, GaugeValue, float64(memInfo.rss)) |
|
| 102 |
+ ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(memInfo.vsize)) |
|
| 103 |
+ } else if !errors.Is(err, notImplementedErr) {
|
|
| 104 |
+ // Don't report an error when support is not compiled in. |
|
| 105 |
+ c.reportError(ch, c.rss, err) |
|
| 106 |
+ c.reportError(ch, c.vsize, err) |
|
| 107 |
+ } |
|
| 108 |
+ |
|
| 109 |
+ if fds, err := getOpenFileCount(); err == nil {
|
|
| 110 |
+ ch <- MustNewConstMetric(c.openFDs, GaugeValue, fds) |
|
| 111 |
+ } else {
|
|
| 112 |
+ c.reportError(ch, c.openFDs, err) |
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ if openFiles, err := getSoftLimit(syscall.RLIMIT_NOFILE); err == nil {
|
|
| 116 |
+ ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(openFiles)) |
|
| 117 |
+ } else {
|
|
| 118 |
+ c.reportError(ch, c.maxFDs, err) |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ if addressSpace, err := getSoftLimit(syscall.RLIMIT_AS); err == nil {
|
|
| 122 |
+ ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(addressSpace)) |
|
| 123 |
+ } else {
|
|
| 124 |
+ c.reportError(ch, c.maxVsize, err) |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ // TODO: socket(PF_SYSTEM) to fetch "com.apple.network.statistics" might |
|
| 128 |
+ // be able to get the per-process network send/receive counts. |
|
| 129 |
+} |
| 0 | 130 |
deleted file mode 100644 |
| ... | ... |
@@ -1,26 +0,0 @@ |
| 1 |
-// Copyright 2019 The Prometheus Authors |
|
| 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 |
- |
|
| 14 |
-//go:build js |
|
| 15 |
-// +build js |
|
| 16 |
- |
|
| 17 |
-package prometheus |
|
| 18 |
- |
|
| 19 |
-func canCollectProcess() bool {
|
|
| 20 |
- return false |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
| 24 |
- // noop on this platform |
|
| 25 |
- return |
|
| 26 |
-} |
| 27 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,84 @@ |
| 0 |
+// Copyright 2024 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 darwin && !ios && cgo |
|
| 14 |
+ |
|
| 15 |
+#include <mach/mach_init.h> |
|
| 16 |
+#include <mach/task.h> |
|
| 17 |
+#include <mach/mach_vm.h> |
|
| 18 |
+ |
|
| 19 |
+// The compiler warns that mach/shared_memory_server.h is deprecated, and to use |
|
| 20 |
+// mach/shared_region.h instead. But that doesn't define |
|
| 21 |
+// SHARED_DATA_REGION_SIZE or SHARED_TEXT_REGION_SIZE, so redefine them here and |
|
| 22 |
+// avoid a warning message when running tests. |
|
| 23 |
+#define GLOBAL_SHARED_TEXT_SEGMENT 0x90000000U |
|
| 24 |
+#define SHARED_DATA_REGION_SIZE 0x10000000 |
|
| 25 |
+#define SHARED_TEXT_REGION_SIZE 0x10000000 |
|
| 26 |
+ |
|
| 27 |
+ |
|
| 28 |
+int get_memory_info(unsigned long long *rss, unsigned long long *vsize) |
|
| 29 |
+{
|
|
| 30 |
+ // This is lightly adapted from how ps(1) obtains its memory info. |
|
| 31 |
+ // https://github.com/apple-oss-distributions/adv_cmds/blob/8744084ea0ff41ca4bb96b0f9c22407d0e48e9b7/ps/tasks.c#L109 |
|
| 32 |
+ |
|
| 33 |
+ kern_return_t error; |
|
| 34 |
+ task_t task = MACH_PORT_NULL; |
|
| 35 |
+ mach_task_basic_info_data_t info; |
|
| 36 |
+ mach_msg_type_number_t info_count = MACH_TASK_BASIC_INFO_COUNT; |
|
| 37 |
+ |
|
| 38 |
+ error = task_info( |
|
| 39 |
+ mach_task_self(), |
|
| 40 |
+ MACH_TASK_BASIC_INFO, |
|
| 41 |
+ (task_info_t) &info, |
|
| 42 |
+ &info_count ); |
|
| 43 |
+ |
|
| 44 |
+ if( error != KERN_SUCCESS ) |
|
| 45 |
+ {
|
|
| 46 |
+ return error; |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ *rss = info.resident_size; |
|
| 50 |
+ *vsize = info.virtual_size; |
|
| 51 |
+ |
|
| 52 |
+ {
|
|
| 53 |
+ vm_region_basic_info_data_64_t b_info; |
|
| 54 |
+ mach_vm_address_t address = GLOBAL_SHARED_TEXT_SEGMENT; |
|
| 55 |
+ mach_vm_size_t size; |
|
| 56 |
+ mach_port_t object_name; |
|
| 57 |
+ |
|
| 58 |
+ /* |
|
| 59 |
+ * try to determine if this task has the split libraries |
|
| 60 |
+ * mapped in... if so, adjust its virtual size down by |
|
| 61 |
+ * the 2 segments that are used for split libraries |
|
| 62 |
+ */ |
|
| 63 |
+ info_count = VM_REGION_BASIC_INFO_COUNT_64; |
|
| 64 |
+ |
|
| 65 |
+ error = mach_vm_region( |
|
| 66 |
+ mach_task_self(), |
|
| 67 |
+ &address, |
|
| 68 |
+ &size, |
|
| 69 |
+ VM_REGION_BASIC_INFO_64, |
|
| 70 |
+ (vm_region_info_t) &b_info, |
|
| 71 |
+ &info_count, |
|
| 72 |
+ &object_name); |
|
| 73 |
+ |
|
| 74 |
+ if (error == KERN_SUCCESS) {
|
|
| 75 |
+ if (b_info.reserved && size == (SHARED_TEXT_REGION_SIZE) && |
|
| 76 |
+ *vsize > (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE)) {
|
|
| 77 |
+ *vsize -= (SHARED_TEXT_REGION_SIZE + SHARED_DATA_REGION_SIZE); |
|
| 78 |
+ } |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ return 0; |
|
| 83 |
+} |
| 0 | 84 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,51 @@ |
| 0 |
+// Copyright 2024 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 darwin && !ios && cgo |
|
| 14 |
+ |
|
| 15 |
+package prometheus |
|
| 16 |
+ |
|
| 17 |
+/* |
|
| 18 |
+int get_memory_info(unsigned long long *rss, unsigned long long *vs); |
|
| 19 |
+*/ |
|
| 20 |
+import "C" |
|
| 21 |
+import "fmt" |
|
| 22 |
+ |
|
| 23 |
+func getMemory() (*memoryInfo, error) {
|
|
| 24 |
+ var rss, vsize C.ulonglong |
|
| 25 |
+ |
|
| 26 |
+ if err := C.get_memory_info(&rss, &vsize); err != 0 {
|
|
| 27 |
+ return nil, fmt.Errorf("task_info() failed with 0x%x", int(err))
|
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ return &memoryInfo{vsize: uint64(vsize), rss: uint64(rss)}, nil
|
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// describe returns all descriptions of the collector for Darwin. |
|
| 34 |
+// Ensure that this list of descriptors is kept in sync with the metrics collected |
|
| 35 |
+// in the processCollect method. Any changes to the metrics in processCollect |
|
| 36 |
+// (such as adding or removing metrics) should be reflected in this list of descriptors. |
|
| 37 |
+func (c *processCollector) describe(ch chan<- *Desc) {
|
|
| 38 |
+ ch <- c.cpuTotal |
|
| 39 |
+ ch <- c.openFDs |
|
| 40 |
+ ch <- c.maxFDs |
|
| 41 |
+ ch <- c.maxVsize |
|
| 42 |
+ ch <- c.startTime |
|
| 43 |
+ ch <- c.rss |
|
| 44 |
+ ch <- c.vsize |
|
| 45 |
+ |
|
| 46 |
+ /* the process could be collected but not implemented yet |
|
| 47 |
+ ch <- c.inBytes |
|
| 48 |
+ ch <- c.outBytes |
|
| 49 |
+ */ |
|
| 50 |
+} |
| 0 | 51 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,39 @@ |
| 0 |
+// Copyright 2024 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 darwin && !ios && !cgo |
|
| 14 |
+ |
|
| 15 |
+package prometheus |
|
| 16 |
+ |
|
| 17 |
+func getMemory() (*memoryInfo, error) {
|
|
| 18 |
+ return nil, notImplementedErr |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// describe returns all descriptions of the collector for Darwin. |
|
| 22 |
+// Ensure that this list of descriptors is kept in sync with the metrics collected |
|
| 23 |
+// in the processCollect method. Any changes to the metrics in processCollect |
|
| 24 |
+// (such as adding or removing metrics) should be reflected in this list of descriptors. |
|
| 25 |
+func (c *processCollector) describe(ch chan<- *Desc) {
|
|
| 26 |
+ ch <- c.cpuTotal |
|
| 27 |
+ ch <- c.openFDs |
|
| 28 |
+ ch <- c.maxFDs |
|
| 29 |
+ ch <- c.maxVsize |
|
| 30 |
+ ch <- c.startTime |
|
| 31 |
+ |
|
| 32 |
+ /* the process could be collected but not implemented yet |
|
| 33 |
+ ch <- c.rss |
|
| 34 |
+ ch <- c.vsize |
|
| 35 |
+ ch <- c.inBytes |
|
| 36 |
+ ch <- c.outBytes |
|
| 37 |
+ */ |
|
| 38 |
+} |
| 0 | 39 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,33 @@ |
| 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 |
+//go:build wasip1 || js || ios |
|
| 14 |
+// +build wasip1 js ios |
|
| 15 |
+ |
|
| 16 |
+package prometheus |
|
| 17 |
+ |
|
| 18 |
+func canCollectProcess() bool {
|
|
| 19 |
+ return false |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
| 23 |
+ c.errorCollectFn(ch) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// describe returns all descriptions of the collector for wasip1 and js. |
|
| 27 |
+// Ensure that this list of descriptors is kept in sync with the metrics collected |
|
| 28 |
+// in the processCollect method. Any changes to the metrics in processCollect |
|
| 29 |
+// (such as adding or removing metrics) should be reflected in this list of descriptors. |
|
| 30 |
+func (c *processCollector) describe(ch chan<- *Desc) {
|
|
| 31 |
+ c.errorDescribeFn(ch) |
|
| 32 |
+} |
| 0 | 33 |
deleted file mode 100644 |
| ... | ... |
@@ -1,80 +0,0 @@ |
| 1 |
-// Copyright 2019 The Prometheus Authors |
|
| 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 |
- |
|
| 14 |
-//go:build !windows && !js && !wasip1 |
|
| 15 |
-// +build !windows,!js,!wasip1 |
|
| 16 |
- |
|
| 17 |
-package prometheus |
|
| 18 |
- |
|
| 19 |
-import ( |
|
| 20 |
- "github.com/prometheus/procfs" |
|
| 21 |
-) |
|
| 22 |
- |
|
| 23 |
-func canCollectProcess() bool {
|
|
| 24 |
- _, err := procfs.NewDefaultFS() |
|
| 25 |
- return err == nil |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
| 29 |
- pid, err := c.pidFn() |
|
| 30 |
- if err != nil {
|
|
| 31 |
- c.reportError(ch, nil, err) |
|
| 32 |
- return |
|
| 33 |
- } |
|
| 34 |
- |
|
| 35 |
- p, err := procfs.NewProc(pid) |
|
| 36 |
- if err != nil {
|
|
| 37 |
- c.reportError(ch, nil, err) |
|
| 38 |
- return |
|
| 39 |
- } |
|
| 40 |
- |
|
| 41 |
- if stat, err := p.Stat(); err == nil {
|
|
| 42 |
- ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime()) |
|
| 43 |
- ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory())) |
|
| 44 |
- ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory())) |
|
| 45 |
- if startTime, err := stat.StartTime(); err == nil {
|
|
| 46 |
- ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) |
|
| 47 |
- } else {
|
|
| 48 |
- c.reportError(ch, c.startTime, err) |
|
| 49 |
- } |
|
| 50 |
- } else {
|
|
| 51 |
- c.reportError(ch, nil, err) |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- if fds, err := p.FileDescriptorsLen(); err == nil {
|
|
| 55 |
- ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds)) |
|
| 56 |
- } else {
|
|
| 57 |
- c.reportError(ch, c.openFDs, err) |
|
| 58 |
- } |
|
| 59 |
- |
|
| 60 |
- if limits, err := p.Limits(); err == nil {
|
|
| 61 |
- ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles)) |
|
| 62 |
- ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace)) |
|
| 63 |
- } else {
|
|
| 64 |
- c.reportError(ch, nil, err) |
|
| 65 |
- } |
|
| 66 |
- |
|
| 67 |
- if netstat, err := p.Netstat(); err == nil {
|
|
| 68 |
- var inOctets, outOctets float64 |
|
| 69 |
- if netstat.IpExt.InOctets != nil {
|
|
| 70 |
- inOctets = *netstat.IpExt.InOctets |
|
| 71 |
- } |
|
| 72 |
- if netstat.IpExt.OutOctets != nil {
|
|
| 73 |
- outOctets = *netstat.IpExt.OutOctets |
|
| 74 |
- } |
|
| 75 |
- ch <- MustNewConstMetric(c.inBytes, CounterValue, inOctets) |
|
| 76 |
- ch <- MustNewConstMetric(c.outBytes, CounterValue, outOctets) |
|
| 77 |
- } else {
|
|
| 78 |
- c.reportError(ch, nil, err) |
|
| 79 |
- } |
|
| 80 |
-} |
| 81 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,96 @@ |
| 0 |
+// Copyright 2019 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 !windows && !js && !wasip1 && !darwin |
|
| 14 |
+// +build !windows,!js,!wasip1,!darwin |
|
| 15 |
+ |
|
| 16 |
+package prometheus |
|
| 17 |
+ |
|
| 18 |
+import ( |
|
| 19 |
+ "github.com/prometheus/procfs" |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 22 |
+func canCollectProcess() bool {
|
|
| 23 |
+ _, err := procfs.NewDefaultFS() |
|
| 24 |
+ return err == nil |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
| 28 |
+ pid, err := c.pidFn() |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ c.reportError(ch, nil, err) |
|
| 31 |
+ return |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ p, err := procfs.NewProc(pid) |
|
| 35 |
+ if err != nil {
|
|
| 36 |
+ c.reportError(ch, nil, err) |
|
| 37 |
+ return |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ if stat, err := p.Stat(); err == nil {
|
|
| 41 |
+ ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime()) |
|
| 42 |
+ ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory())) |
|
| 43 |
+ ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory())) |
|
| 44 |
+ if startTime, err := stat.StartTime(); err == nil {
|
|
| 45 |
+ ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime) |
|
| 46 |
+ } else {
|
|
| 47 |
+ c.reportError(ch, c.startTime, err) |
|
| 48 |
+ } |
|
| 49 |
+ } else {
|
|
| 50 |
+ c.reportError(ch, nil, err) |
|
| 51 |
+ } |
|
| 52 |
+ |
|
| 53 |
+ if fds, err := p.FileDescriptorsLen(); err == nil {
|
|
| 54 |
+ ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds)) |
|
| 55 |
+ } else {
|
|
| 56 |
+ c.reportError(ch, c.openFDs, err) |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ if limits, err := p.Limits(); err == nil {
|
|
| 60 |
+ ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles)) |
|
| 61 |
+ ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace)) |
|
| 62 |
+ } else {
|
|
| 63 |
+ c.reportError(ch, nil, err) |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ if netstat, err := p.Netstat(); err == nil {
|
|
| 67 |
+ var inOctets, outOctets float64 |
|
| 68 |
+ if netstat.IpExt.InOctets != nil {
|
|
| 69 |
+ inOctets = *netstat.IpExt.InOctets |
|
| 70 |
+ } |
|
| 71 |
+ if netstat.IpExt.OutOctets != nil {
|
|
| 72 |
+ outOctets = *netstat.IpExt.OutOctets |
|
| 73 |
+ } |
|
| 74 |
+ ch <- MustNewConstMetric(c.inBytes, CounterValue, inOctets) |
|
| 75 |
+ ch <- MustNewConstMetric(c.outBytes, CounterValue, outOctets) |
|
| 76 |
+ } else {
|
|
| 77 |
+ c.reportError(ch, nil, err) |
|
| 78 |
+ } |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+// describe returns all descriptions of the collector for others than windows, js, wasip1 and darwin. |
|
| 82 |
+// Ensure that this list of descriptors is kept in sync with the metrics collected |
|
| 83 |
+// in the processCollect method. Any changes to the metrics in processCollect |
|
| 84 |
+// (such as adding or removing metrics) should be reflected in this list of descriptors. |
|
| 85 |
+func (c *processCollector) describe(ch chan<- *Desc) {
|
|
| 86 |
+ ch <- c.cpuTotal |
|
| 87 |
+ ch <- c.openFDs |
|
| 88 |
+ ch <- c.maxFDs |
|
| 89 |
+ ch <- c.vsize |
|
| 90 |
+ ch <- c.maxVsize |
|
| 91 |
+ ch <- c.rss |
|
| 92 |
+ ch <- c.startTime |
|
| 93 |
+ ch <- c.inBytes |
|
| 94 |
+ ch <- c.outBytes |
|
| 95 |
+} |
| 0 | 96 |
deleted file mode 100644 |
| ... | ... |
@@ -1,26 +0,0 @@ |
| 1 |
-// Copyright 2023 The Prometheus Authors |
|
| 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 |
- |
|
| 14 |
-//go:build wasip1 |
|
| 15 |
-// +build wasip1 |
|
| 16 |
- |
|
| 17 |
-package prometheus |
|
| 18 |
- |
|
| 19 |
-func canCollectProcess() bool {
|
|
| 20 |
- return false |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func (*processCollector) processCollect(chan<- Metric) {
|
|
| 24 |
- // noop on this platform |
|
| 25 |
- return |
|
| 26 |
-} |
| ... | ... |
@@ -79,14 +79,10 @@ func getProcessHandleCount(handle windows.Handle) (uint32, error) {
|
| 79 | 79 |
} |
| 80 | 80 |
|
| 81 | 81 |
func (c *processCollector) processCollect(ch chan<- Metric) {
|
| 82 |
- h, err := windows.GetCurrentProcess() |
|
| 83 |
- if err != nil {
|
|
| 84 |
- c.reportError(ch, nil, err) |
|
| 85 |
- return |
|
| 86 |
- } |
|
| 82 |
+ h := windows.CurrentProcess() |
|
| 87 | 83 |
|
| 88 | 84 |
var startTime, exitTime, kernelTime, userTime windows.Filetime |
| 89 |
- err = windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) |
|
| 85 |
+ err := windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime) |
|
| 90 | 86 |
if err != nil {
|
| 91 | 87 |
c.reportError(ch, nil, err) |
| 92 | 88 |
return |
| ... | ... |
@@ -111,6 +107,19 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
|
| 111 | 111 |
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(16*1024*1024)) // Windows has a hard-coded max limit, not per-process. |
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 |
+// describe returns all descriptions of the collector for windows. |
|
| 115 |
+// Ensure that this list of descriptors is kept in sync with the metrics collected |
|
| 116 |
+// in the processCollect method. Any changes to the metrics in processCollect |
|
| 117 |
+// (such as adding or removing metrics) should be reflected in this list of descriptors. |
|
| 118 |
+func (c *processCollector) describe(ch chan<- *Desc) {
|
|
| 119 |
+ ch <- c.cpuTotal |
|
| 120 |
+ ch <- c.openFDs |
|
| 121 |
+ ch <- c.maxFDs |
|
| 122 |
+ ch <- c.vsize |
|
| 123 |
+ ch <- c.rss |
|
| 124 |
+ ch <- c.startTime |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 114 | 127 |
func fileTimeToSeconds(ft windows.Filetime) float64 {
|
| 115 | 128 |
return float64(uint64(ft.HighDateTime)<<32+uint64(ft.LowDateTime)) / 1e7 |
| 116 | 129 |
} |
| ... | ... |
@@ -41,11 +41,11 @@ import ( |
| 41 | 41 |
"sync" |
| 42 | 42 |
"time" |
| 43 | 43 |
|
| 44 |
- "github.com/klauspost/compress/zstd" |
|
| 45 | 44 |
"github.com/prometheus/common/expfmt" |
| 46 | 45 |
|
| 47 | 46 |
"github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil" |
| 48 | 47 |
"github.com/prometheus/client_golang/prometheus" |
| 48 |
+ "github.com/prometheus/client_golang/prometheus/promhttp/internal" |
|
| 49 | 49 |
) |
| 50 | 50 |
|
| 51 | 51 |
const ( |
| ... | ... |
@@ -65,7 +65,13 @@ const ( |
| 65 | 65 |
Zstd Compression = "zstd" |
| 66 | 66 |
) |
| 67 | 67 |
|
| 68 |
-var defaultCompressionFormats = []Compression{Identity, Gzip, Zstd}
|
|
| 68 |
+func defaultCompressionFormats() []Compression {
|
|
| 69 |
+ if internal.NewZstdWriter != nil {
|
|
| 70 |
+ return []Compression{Identity, Gzip, Zstd}
|
|
| 71 |
+ } else {
|
|
| 72 |
+ return []Compression{Identity, Gzip}
|
|
| 73 |
+ } |
|
| 74 |
+} |
|
| 69 | 75 |
|
| 70 | 76 |
var gzipPool = sync.Pool{
|
| 71 | 77 |
New: func() interface{} {
|
| ... | ... |
@@ -138,7 +144,7 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO |
| 138 | 138 |
// Select compression formats to offer based on default or user choice. |
| 139 | 139 |
var compressions []string |
| 140 | 140 |
if !opts.DisableCompression {
|
| 141 |
- offers := defaultCompressionFormats |
|
| 141 |
+ offers := defaultCompressionFormats() |
|
| 142 | 142 |
if len(opts.OfferedCompressions) > 0 {
|
| 143 | 143 |
offers = opts.OfferedCompressions |
| 144 | 144 |
} |
| ... | ... |
@@ -207,7 +213,13 @@ func HandlerForTransactional(reg prometheus.TransactionalGatherer, opts HandlerO |
| 207 | 207 |
if encodingHeader != string(Identity) {
|
| 208 | 208 |
rsp.Header().Set(contentEncodingHeader, encodingHeader) |
| 209 | 209 |
} |
| 210 |
- enc := expfmt.NewEncoder(w, contentType) |
|
| 210 |
+ |
|
| 211 |
+ var enc expfmt.Encoder |
|
| 212 |
+ if opts.EnableOpenMetricsTextCreatedSamples {
|
|
| 213 |
+ enc = expfmt.NewEncoder(w, contentType, expfmt.WithCreatedLines()) |
|
| 214 |
+ } else {
|
|
| 215 |
+ enc = expfmt.NewEncoder(w, contentType) |
|
| 216 |
+ } |
|
| 211 | 217 |
|
| 212 | 218 |
// handleError handles the error according to opts.ErrorHandling |
| 213 | 219 |
// and returns true if we have to abort after the handling. |
| ... | ... |
@@ -408,6 +420,21 @@ type HandlerOpts struct {
|
| 408 | 408 |
// (which changes the identity of the resulting series on the Prometheus |
| 409 | 409 |
// server). |
| 410 | 410 |
EnableOpenMetrics bool |
| 411 |
+ // EnableOpenMetricsTextCreatedSamples specifies if this handler should add, extra, synthetic |
|
| 412 |
+ // Created Timestamps for counters, histograms and summaries, which for the current |
|
| 413 |
+ // version of OpenMetrics are defined as extra series with the same name and "_created" |
|
| 414 |
+ // suffix. See also the OpenMetrics specification for more details |
|
| 415 |
+ // https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1 |
|
| 416 |
+ // |
|
| 417 |
+ // Created timestamps are used to improve the accuracy of reset detection, |
|
| 418 |
+ // but the way it's designed in OpenMetrics 1.0 it also dramatically increases cardinality |
|
| 419 |
+ // if the scraper does not handle those metrics correctly (converting to created timestamp |
|
| 420 |
+ // instead of leaving those series as-is). New OpenMetrics versions might improve |
|
| 421 |
+ // this situation. |
|
| 422 |
+ // |
|
| 423 |
+ // Prometheus introduced the feature flag 'created-timestamp-zero-ingestion' |
|
| 424 |
+ // in version 2.50.0 to handle this situation. |
|
| 425 |
+ EnableOpenMetricsTextCreatedSamples bool |
|
| 411 | 426 |
// ProcessStartTime allows setting process start timevalue that will be exposed |
| 412 | 427 |
// with "Process-Start-Time-Unix" response header along with the metrics |
| 413 | 428 |
// payload. This allow callers to have efficient transformations to cumulative |
| ... | ... |
@@ -445,14 +472,12 @@ func negotiateEncodingWriter(r *http.Request, rw io.Writer, compressions []strin |
| 445 | 445 |
|
| 446 | 446 |
switch selected {
|
| 447 | 447 |
case "zstd": |
| 448 |
- // TODO(mrueg): Replace klauspost/compress with stdlib implementation once https://github.com/golang/go/issues/62513 is implemented. |
|
| 449 |
- z, err := zstd.NewWriter(rw, zstd.WithEncoderLevel(zstd.SpeedFastest)) |
|
| 450 |
- if err != nil {
|
|
| 451 |
- return nil, "", func() {}, err
|
|
| 448 |
+ if internal.NewZstdWriter == nil {
|
|
| 449 |
+ // The content encoding was not implemented yet. |
|
| 450 |
+ return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats())
|
|
| 452 | 451 |
} |
| 453 |
- |
|
| 454 |
- z.Reset(rw) |
|
| 455 |
- return z, selected, func() { _ = z.Close() }, nil
|
|
| 452 |
+ writer, closeWriter, err := internal.NewZstdWriter(rw) |
|
| 453 |
+ return writer, selected, closeWriter, err |
|
| 456 | 454 |
case "gzip": |
| 457 | 455 |
gz := gzipPool.Get().(*gzip.Writer) |
| 458 | 456 |
gz.Reset(rw) |
| ... | ... |
@@ -462,6 +487,6 @@ func negotiateEncodingWriter(r *http.Request, rw io.Writer, compressions []strin |
| 462 | 462 |
return rw, selected, func() {}, nil
|
| 463 | 463 |
default: |
| 464 | 464 |
// The content encoding was not implemented yet. |
| 465 |
- return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats)
|
|
| 465 |
+ return nil, "", func() {}, fmt.Errorf("content compression format not recognized: %s. Valid formats are: %s", selected, defaultCompressionFormats())
|
|
| 466 | 466 |
} |
| 467 | 467 |
} |
| 468 | 468 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,21 @@ |
| 0 |
+// Copyright 2025 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 internal |
|
| 14 |
+ |
|
| 15 |
+import ( |
|
| 16 |
+ "io" |
|
| 17 |
+) |
|
| 18 |
+ |
|
| 19 |
+// NewZstdWriter enables zstd write support if non-nil. |
|
| 20 |
+var NewZstdWriter func(rw io.Writer) (_ io.Writer, closeWriter func(), _ error) |
| ... | ... |
@@ -243,6 +243,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
| 243 | 243 |
|
| 244 | 244 |
s := &summary{
|
| 245 | 245 |
desc: desc, |
| 246 |
+ now: opts.now, |
|
| 246 | 247 |
|
| 247 | 248 |
objectives: opts.Objectives, |
| 248 | 249 |
sortedObjectives: make([]float64, 0, len(opts.Objectives)), |
| ... | ... |
@@ -280,6 +281,8 @@ type summary struct {
|
| 280 | 280 |
|
| 281 | 281 |
desc *Desc |
| 282 | 282 |
|
| 283 |
+ now func() time.Time |
|
| 284 |
+ |
|
| 283 | 285 |
objectives map[float64]float64 |
| 284 | 286 |
sortedObjectives []float64 |
| 285 | 287 |
|
| ... | ... |
@@ -307,7 +310,7 @@ func (s *summary) Observe(v float64) {
|
| 307 | 307 |
s.bufMtx.Lock() |
| 308 | 308 |
defer s.bufMtx.Unlock() |
| 309 | 309 |
|
| 310 |
- now := time.Now() |
|
| 310 |
+ now := s.now() |
|
| 311 | 311 |
if now.After(s.hotBufExpTime) {
|
| 312 | 312 |
s.asyncFlush(now) |
| 313 | 313 |
} |
| ... | ... |
@@ -326,7 +329,7 @@ func (s *summary) Write(out *dto.Metric) error {
|
| 326 | 326 |
s.bufMtx.Lock() |
| 327 | 327 |
s.mtx.Lock() |
| 328 | 328 |
// Swap bufs even if hotBuf is empty to set new hotBufExpTime. |
| 329 |
- s.swapBufs(time.Now()) |
|
| 329 |
+ s.swapBufs(s.now()) |
|
| 330 | 330 |
s.bufMtx.Unlock() |
| 331 | 331 |
|
| 332 | 332 |
s.flushColdBuf() |
| ... | ... |
@@ -45,7 +45,7 @@ func ResponseFormat(h http.Header) Format {
|
| 45 | 45 |
|
| 46 | 46 |
mediatype, params, err := mime.ParseMediaType(ct) |
| 47 | 47 |
if err != nil {
|
| 48 |
- return fmtUnknown |
|
| 48 |
+ return FmtUnknown |
|
| 49 | 49 |
} |
| 50 | 50 |
|
| 51 | 51 |
const textType = "text/plain" |
| ... | ... |
@@ -53,21 +53,21 @@ func ResponseFormat(h http.Header) Format {
|
| 53 | 53 |
switch mediatype {
|
| 54 | 54 |
case ProtoType: |
| 55 | 55 |
if p, ok := params["proto"]; ok && p != ProtoProtocol {
|
| 56 |
- return fmtUnknown |
|
| 56 |
+ return FmtUnknown |
|
| 57 | 57 |
} |
| 58 | 58 |
if e, ok := params["encoding"]; ok && e != "delimited" {
|
| 59 |
- return fmtUnknown |
|
| 59 |
+ return FmtUnknown |
|
| 60 | 60 |
} |
| 61 |
- return fmtProtoDelim |
|
| 61 |
+ return FmtProtoDelim |
|
| 62 | 62 |
|
| 63 | 63 |
case textType: |
| 64 | 64 |
if v, ok := params["version"]; ok && v != TextVersion {
|
| 65 |
- return fmtUnknown |
|
| 65 |
+ return FmtUnknown |
|
| 66 | 66 |
} |
| 67 |
- return fmtText |
|
| 67 |
+ return FmtText |
|
| 68 | 68 |
} |
| 69 | 69 |
|
| 70 |
- return fmtUnknown |
|
| 70 |
+ return FmtUnknown |
|
| 71 | 71 |
} |
| 72 | 72 |
|
| 73 | 73 |
// NewDecoder returns a new decoder based on the given input format. |
| ... | ... |
@@ -68,7 +68,7 @@ func Negotiate(h http.Header) Format {
|
| 68 | 68 |
if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
|
| 69 | 69 |
switch Format(escapeParam) {
|
| 70 | 70 |
case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues: |
| 71 |
- escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam))
|
|
| 71 |
+ escapingScheme = Format("; escaping=" + escapeParam)
|
|
| 72 | 72 |
default: |
| 73 | 73 |
// If the escaping parameter is unknown, ignore it. |
| 74 | 74 |
} |
| ... | ... |
@@ -77,18 +77,18 @@ func Negotiate(h http.Header) Format {
|
| 77 | 77 |
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
|
| 78 | 78 |
switch ac.Params["encoding"] {
|
| 79 | 79 |
case "delimited": |
| 80 |
- return fmtProtoDelim + escapingScheme |
|
| 80 |
+ return FmtProtoDelim + escapingScheme |
|
| 81 | 81 |
case "text": |
| 82 |
- return fmtProtoText + escapingScheme |
|
| 82 |
+ return FmtProtoText + escapingScheme |
|
| 83 | 83 |
case "compact-text": |
| 84 |
- return fmtProtoCompact + escapingScheme |
|
| 84 |
+ return FmtProtoCompact + escapingScheme |
|
| 85 | 85 |
} |
| 86 | 86 |
} |
| 87 | 87 |
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
|
| 88 |
- return fmtText + escapingScheme |
|
| 88 |
+ return FmtText + escapingScheme |
|
| 89 | 89 |
} |
| 90 | 90 |
} |
| 91 |
- return fmtText + escapingScheme |
|
| 91 |
+ return FmtText + escapingScheme |
|
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 | 94 |
// NegotiateIncludingOpenMetrics works like Negotiate but includes |
| ... | ... |
@@ -101,7 +101,7 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
|
| 101 | 101 |
if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
|
| 102 | 102 |
switch Format(escapeParam) {
|
| 103 | 103 |
case model.AllowUTF8, model.EscapeUnderscores, model.EscapeDots, model.EscapeValues: |
| 104 |
- escapingScheme = Format(fmt.Sprintf("; escaping=%s", escapeParam))
|
|
| 104 |
+ escapingScheme = Format("; escaping=" + escapeParam)
|
|
| 105 | 105 |
default: |
| 106 | 106 |
// If the escaping parameter is unknown, ignore it. |
| 107 | 107 |
} |
| ... | ... |
@@ -110,26 +110,26 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
|
| 110 | 110 |
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
|
| 111 | 111 |
switch ac.Params["encoding"] {
|
| 112 | 112 |
case "delimited": |
| 113 |
- return fmtProtoDelim + escapingScheme |
|
| 113 |
+ return FmtProtoDelim + escapingScheme |
|
| 114 | 114 |
case "text": |
| 115 |
- return fmtProtoText + escapingScheme |
|
| 115 |
+ return FmtProtoText + escapingScheme |
|
| 116 | 116 |
case "compact-text": |
| 117 |
- return fmtProtoCompact + escapingScheme |
|
| 117 |
+ return FmtProtoCompact + escapingScheme |
|
| 118 | 118 |
} |
| 119 | 119 |
} |
| 120 | 120 |
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
|
| 121 |
- return fmtText + escapingScheme |
|
| 121 |
+ return FmtText + escapingScheme |
|
| 122 | 122 |
} |
| 123 | 123 |
if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
|
| 124 | 124 |
switch ver {
|
| 125 | 125 |
case OpenMetricsVersion_1_0_0: |
| 126 |
- return fmtOpenMetrics_1_0_0 + escapingScheme |
|
| 126 |
+ return FmtOpenMetrics_1_0_0 + escapingScheme |
|
| 127 | 127 |
default: |
| 128 |
- return fmtOpenMetrics_0_0_1 + escapingScheme |
|
| 128 |
+ return FmtOpenMetrics_0_0_1 + escapingScheme |
|
| 129 | 129 |
} |
| 130 | 130 |
} |
| 131 | 131 |
} |
| 132 |
- return fmtText + escapingScheme |
|
| 132 |
+ return FmtText + escapingScheme |
|
| 133 | 133 |
} |
| 134 | 134 |
|
| 135 | 135 |
// NewEncoder returns a new encoder based on content type negotiation. All |
| ... | ... |
@@ -15,7 +15,7 @@ |
| 15 | 15 |
package expfmt |
| 16 | 16 |
|
| 17 | 17 |
import ( |
| 18 |
- "fmt" |
|
| 18 |
+ "errors" |
|
| 19 | 19 |
"strings" |
| 20 | 20 |
|
| 21 | 21 |
"github.com/prometheus/common/model" |
| ... | ... |
@@ -32,24 +32,31 @@ type Format string |
| 32 | 32 |
// it on the wire, new content-type strings will have to be agreed upon and |
| 33 | 33 |
// added here. |
| 34 | 34 |
const ( |
| 35 |
- TextVersion = "0.0.4" |
|
| 36 |
- ProtoType = `application/vnd.google.protobuf` |
|
| 37 |
- ProtoProtocol = `io.prometheus.client.MetricFamily` |
|
| 38 |
- protoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" |
|
| 35 |
+ TextVersion = "0.0.4" |
|
| 36 |
+ ProtoType = `application/vnd.google.protobuf` |
|
| 37 |
+ ProtoProtocol = `io.prometheus.client.MetricFamily` |
|
| 38 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. |
|
| 39 |
+ ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" |
|
| 39 | 40 |
OpenMetricsType = `application/openmetrics-text` |
| 40 | 41 |
OpenMetricsVersion_0_0_1 = "0.0.1" |
| 41 | 42 |
OpenMetricsVersion_1_0_0 = "1.0.0" |
| 42 | 43 |
|
| 43 |
- // The Content-Type values for the different wire protocols. Note that these |
|
| 44 |
- // values are now unexported. If code was relying on comparisons to these |
|
| 45 |
- // constants, instead use FormatType(). |
|
| 46 |
- fmtUnknown Format = `<unknown>` |
|
| 47 |
- fmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` |
|
| 48 |
- fmtProtoDelim Format = protoFmt + ` encoding=delimited` |
|
| 49 |
- fmtProtoText Format = protoFmt + ` encoding=text` |
|
| 50 |
- fmtProtoCompact Format = protoFmt + ` encoding=compact-text` |
|
| 51 |
- fmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` |
|
| 52 |
- fmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` |
|
| 44 |
+ // The Content-Type values for the different wire protocols. Do not do direct |
|
| 45 |
+ // comparisons to these constants, instead use the comparison functions. |
|
| 46 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeUnknown) instead. |
|
| 47 |
+ FmtUnknown Format = `<unknown>` |
|
| 48 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeTextPlain) instead. |
|
| 49 |
+ FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8` |
|
| 50 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoDelim) instead. |
|
| 51 |
+ FmtProtoDelim Format = ProtoFmt + ` encoding=delimited` |
|
| 52 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoText) instead. |
|
| 53 |
+ FmtProtoText Format = ProtoFmt + ` encoding=text` |
|
| 54 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. |
|
| 55 |
+ FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text` |
|
| 56 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. |
|
| 57 |
+ FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` |
|
| 58 |
+ // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. |
|
| 59 |
+ FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` |
|
| 53 | 60 |
) |
| 54 | 61 |
|
| 55 | 62 |
const ( |
| ... | ... |
@@ -79,17 +86,17 @@ const ( |
| 79 | 79 |
func NewFormat(t FormatType) Format {
|
| 80 | 80 |
switch t {
|
| 81 | 81 |
case TypeProtoCompact: |
| 82 |
- return fmtProtoCompact |
|
| 82 |
+ return FmtProtoCompact |
|
| 83 | 83 |
case TypeProtoDelim: |
| 84 |
- return fmtProtoDelim |
|
| 84 |
+ return FmtProtoDelim |
|
| 85 | 85 |
case TypeProtoText: |
| 86 |
- return fmtProtoText |
|
| 86 |
+ return FmtProtoText |
|
| 87 | 87 |
case TypeTextPlain: |
| 88 |
- return fmtText |
|
| 88 |
+ return FmtText |
|
| 89 | 89 |
case TypeOpenMetrics: |
| 90 |
- return fmtOpenMetrics_1_0_0 |
|
| 90 |
+ return FmtOpenMetrics_1_0_0 |
|
| 91 | 91 |
default: |
| 92 |
- return fmtUnknown |
|
| 92 |
+ return FmtUnknown |
|
| 93 | 93 |
} |
| 94 | 94 |
} |
| 95 | 95 |
|
| ... | ... |
@@ -97,12 +104,35 @@ func NewFormat(t FormatType) Format {
|
| 97 | 97 |
// specified version number. |
| 98 | 98 |
func NewOpenMetricsFormat(version string) (Format, error) {
|
| 99 | 99 |
if version == OpenMetricsVersion_0_0_1 {
|
| 100 |
- return fmtOpenMetrics_0_0_1, nil |
|
| 100 |
+ return FmtOpenMetrics_0_0_1, nil |
|
| 101 | 101 |
} |
| 102 | 102 |
if version == OpenMetricsVersion_1_0_0 {
|
| 103 |
- return fmtOpenMetrics_1_0_0, nil |
|
| 103 |
+ return FmtOpenMetrics_1_0_0, nil |
|
| 104 | 104 |
} |
| 105 |
- return fmtUnknown, fmt.Errorf("unknown open metrics version string")
|
|
| 105 |
+ return FmtUnknown, errors.New("unknown open metrics version string")
|
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+// WithEscapingScheme returns a copy of Format with the specified escaping |
|
| 109 |
+// scheme appended to the end. If an escaping scheme already exists it is |
|
| 110 |
+// removed. |
|
| 111 |
+func (f Format) WithEscapingScheme(s model.EscapingScheme) Format {
|
|
| 112 |
+ var terms []string |
|
| 113 |
+ for _, p := range strings.Split(string(f), ";") {
|
|
| 114 |
+ toks := strings.Split(p, "=") |
|
| 115 |
+ if len(toks) != 2 {
|
|
| 116 |
+ trimmed := strings.TrimSpace(p) |
|
| 117 |
+ if len(trimmed) > 0 {
|
|
| 118 |
+ terms = append(terms, trimmed) |
|
| 119 |
+ } |
|
| 120 |
+ continue |
|
| 121 |
+ } |
|
| 122 |
+ key := strings.TrimSpace(toks[0]) |
|
| 123 |
+ if key != model.EscapingKey {
|
|
| 124 |
+ terms = append(terms, strings.TrimSpace(p)) |
|
| 125 |
+ } |
|
| 126 |
+ } |
|
| 127 |
+ terms = append(terms, model.EscapingKey+"="+s.String()) |
|
| 128 |
+ return Format(strings.Join(terms, "; ")) |
|
| 106 | 129 |
} |
| 107 | 130 |
|
| 108 | 131 |
// FormatType deduces an overall FormatType for the given format. |
| ... | ... |
@@ -38,7 +38,7 @@ type EncoderOption func(*encoderOption) |
| 38 | 38 |
|
| 39 | 39 |
// WithCreatedLines is an EncoderOption that configures the OpenMetrics encoder |
| 40 | 40 |
// to include _created lines (See |
| 41 |
-// https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#counter-1). |
|
| 41 |
+// https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#counter-1). |
|
| 42 | 42 |
// Created timestamps can improve the accuracy of series reset detection, but |
| 43 | 43 |
// come with a bandwidth cost. |
| 44 | 44 |
// |
| ... | ... |
@@ -102,7 +102,7 @@ func WithUnit() EncoderOption {
|
| 102 | 102 |
// |
| 103 | 103 |
// - According to the OM specs, the `# UNIT` line is optional, but if populated, |
| 104 | 104 |
// the unit has to be present in the metric name as its suffix: |
| 105 |
-// (see https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#unit). |
|
| 105 |
+// (see https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#unit). |
|
| 106 | 106 |
// However, in order to accommodate any potential scenario where such a change in the |
| 107 | 107 |
// metric name is not desirable, the users are here given the choice of either explicitly |
| 108 | 108 |
// opt in, in case they wish for the unit to be included in the output AND in the metric name |
| ... | ... |
@@ -152,8 +152,8 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E |
| 152 | 152 |
if metricType == dto.MetricType_COUNTER && strings.HasSuffix(compliantName, "_total") {
|
| 153 | 153 |
compliantName = name[:len(name)-6] |
| 154 | 154 |
} |
| 155 |
- if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, fmt.Sprintf("_%s", *in.Unit)) {
|
|
| 156 |
- compliantName = compliantName + fmt.Sprintf("_%s", *in.Unit)
|
|
| 155 |
+ if toOM.withUnit && in.Unit != nil && !strings.HasSuffix(compliantName, "_"+*in.Unit) {
|
|
| 156 |
+ compliantName = compliantName + "_" + *in.Unit |
|
| 157 | 157 |
} |
| 158 | 158 |
|
| 159 | 159 |
// Comments, first HELP, then TYPE. |
| ... | ... |
@@ -477,7 +477,7 @@ func writeOpenMetricsNameAndLabelPairs( |
| 477 | 477 |
if name != "" {
|
| 478 | 478 |
// If the name does not pass the legacy validity check, we must put the |
| 479 | 479 |
// metric name inside the braces, quoted. |
| 480 |
- if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
|
|
| 480 |
+ if !model.IsValidLegacyMetricName(name) {
|
|
| 481 | 481 |
metricInsideBraces = true |
| 482 | 482 |
err := w.WriteByte(separator) |
| 483 | 483 |
written++ |
| ... | ... |
@@ -354,7 +354,7 @@ func writeNameAndLabelPairs( |
| 354 | 354 |
if name != "" {
|
| 355 | 355 |
// If the name does not pass the legacy validity check, we must put the |
| 356 | 356 |
// metric name inside the braces. |
| 357 |
- if !model.IsValidLegacyMetricName(model.LabelValue(name)) {
|
|
| 357 |
+ if !model.IsValidLegacyMetricName(name) {
|
|
| 358 | 358 |
metricInsideBraces = true |
| 359 | 359 |
err := w.WriteByte(separator) |
| 360 | 360 |
written++ |
| ... | ... |
@@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) {
|
| 498 | 498 |
// writeName writes a string as-is if it complies with the legacy naming |
| 499 | 499 |
// scheme, or escapes it in double quotes if not. |
| 500 | 500 |
func writeName(w enhancedWriter, name string) (int, error) {
|
| 501 |
- if model.IsValidLegacyMetricName(model.LabelValue(name)) {
|
|
| 501 |
+ if model.IsValidLegacyMetricName(name) {
|
|
| 502 | 502 |
return w.WriteString(name) |
| 503 | 503 |
} |
| 504 | 504 |
var written int |
| ... | ... |
@@ -22,9 +22,9 @@ import ( |
| 22 | 22 |
"math" |
| 23 | 23 |
"strconv" |
| 24 | 24 |
"strings" |
| 25 |
+ "unicode/utf8" |
|
| 25 | 26 |
|
| 26 | 27 |
dto "github.com/prometheus/client_model/go" |
| 27 |
- |
|
| 28 | 28 |
"google.golang.org/protobuf/proto" |
| 29 | 29 |
|
| 30 | 30 |
"github.com/prometheus/common/model" |
| ... | ... |
@@ -60,6 +60,7 @@ type TextParser struct {
|
| 60 | 60 |
currentMF *dto.MetricFamily |
| 61 | 61 |
currentMetric *dto.Metric |
| 62 | 62 |
currentLabelPair *dto.LabelPair |
| 63 |
+ currentLabelPairs []*dto.LabelPair // Temporarily stores label pairs while parsing a metric line. |
|
| 63 | 64 |
|
| 64 | 65 |
// The remaining member variables are only used for summaries/histograms. |
| 65 | 66 |
currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le' |
| ... | ... |
@@ -74,6 +75,9 @@ type TextParser struct {
|
| 74 | 74 |
// count and sum of that summary/histogram. |
| 75 | 75 |
currentIsSummaryCount, currentIsSummarySum bool |
| 76 | 76 |
currentIsHistogramCount, currentIsHistogramSum bool |
| 77 |
+ // These indicate if the metric name from the current line being parsed is inside |
|
| 78 |
+ // braces and if that metric name was found respectively. |
|
| 79 |
+ currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool |
|
| 77 | 80 |
} |
| 78 | 81 |
|
| 79 | 82 |
// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange |
| ... | ... |
@@ -137,12 +141,15 @@ func (p *TextParser) reset(in io.Reader) {
|
| 137 | 137 |
} |
| 138 | 138 |
p.currentQuantile = math.NaN() |
| 139 | 139 |
p.currentBucket = math.NaN() |
| 140 |
+ p.currentMF = nil |
|
| 140 | 141 |
} |
| 141 | 142 |
|
| 142 | 143 |
// startOfLine represents the state where the next byte read from p.buf is the |
| 143 | 144 |
// start of a line (or whitespace leading up to it). |
| 144 | 145 |
func (p *TextParser) startOfLine() stateFn {
|
| 145 | 146 |
p.lineCount++ |
| 147 |
+ p.currentMetricIsInsideBraces = false |
|
| 148 |
+ p.currentMetricInsideBracesIsPresent = false |
|
| 146 | 149 |
if p.skipBlankTab(); p.err != nil {
|
| 147 | 150 |
// This is the only place that we expect to see io.EOF, |
| 148 | 151 |
// which is not an error but the signal that we are done. |
| ... | ... |
@@ -158,6 +165,9 @@ func (p *TextParser) startOfLine() stateFn {
|
| 158 | 158 |
return p.startComment |
| 159 | 159 |
case '\n': |
| 160 | 160 |
return p.startOfLine // Empty line, start the next one. |
| 161 |
+ case '{':
|
|
| 162 |
+ p.currentMetricIsInsideBraces = true |
|
| 163 |
+ return p.readingLabels |
|
| 161 | 164 |
} |
| 162 | 165 |
return p.readingMetricName |
| 163 | 166 |
} |
| ... | ... |
@@ -275,6 +285,8 @@ func (p *TextParser) startLabelName() stateFn {
|
| 275 | 275 |
return nil // Unexpected end of input. |
| 276 | 276 |
} |
| 277 | 277 |
if p.currentByte == '}' {
|
| 278 |
+ p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) |
|
| 279 |
+ p.currentLabelPairs = nil |
|
| 278 | 280 |
if p.skipBlankTab(); p.err != nil {
|
| 279 | 281 |
return nil // Unexpected end of input. |
| 280 | 282 |
} |
| ... | ... |
@@ -287,6 +299,45 @@ func (p *TextParser) startLabelName() stateFn {
|
| 287 | 287 |
p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName()))
|
| 288 | 288 |
return nil |
| 289 | 289 |
} |
| 290 |
+ if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
|
|
| 291 |
+ return nil // Unexpected end of input. |
|
| 292 |
+ } |
|
| 293 |
+ if p.currentByte != '=' {
|
|
| 294 |
+ if p.currentMetricIsInsideBraces {
|
|
| 295 |
+ if p.currentMetricInsideBracesIsPresent {
|
|
| 296 |
+ p.parseError(fmt.Sprintf("multiple metric names for metric %q", p.currentMF.GetName()))
|
|
| 297 |
+ return nil |
|
| 298 |
+ } |
|
| 299 |
+ switch p.currentByte {
|
|
| 300 |
+ case ',': |
|
| 301 |
+ p.setOrCreateCurrentMF() |
|
| 302 |
+ if p.currentMF.Type == nil {
|
|
| 303 |
+ p.currentMF.Type = dto.MetricType_UNTYPED.Enum() |
|
| 304 |
+ } |
|
| 305 |
+ p.currentMetric = &dto.Metric{}
|
|
| 306 |
+ p.currentMetricInsideBracesIsPresent = true |
|
| 307 |
+ return p.startLabelName |
|
| 308 |
+ case '}': |
|
| 309 |
+ p.setOrCreateCurrentMF() |
|
| 310 |
+ if p.currentMF.Type == nil {
|
|
| 311 |
+ p.currentMF.Type = dto.MetricType_UNTYPED.Enum() |
|
| 312 |
+ } |
|
| 313 |
+ p.currentMetric = &dto.Metric{}
|
|
| 314 |
+ p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) |
|
| 315 |
+ p.currentLabelPairs = nil |
|
| 316 |
+ if p.skipBlankTab(); p.err != nil {
|
|
| 317 |
+ return nil // Unexpected end of input. |
|
| 318 |
+ } |
|
| 319 |
+ return p.readingValue |
|
| 320 |
+ default: |
|
| 321 |
+ p.parseError(fmt.Sprintf("unexpected end of metric name %q", p.currentByte))
|
|
| 322 |
+ return nil |
|
| 323 |
+ } |
|
| 324 |
+ } |
|
| 325 |
+ p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
|
|
| 326 |
+ p.currentLabelPairs = nil |
|
| 327 |
+ return nil |
|
| 328 |
+ } |
|
| 290 | 329 |
p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
|
| 291 | 330 |
if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
|
| 292 | 331 |
p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
|
| ... | ... |
@@ -296,23 +347,17 @@ func (p *TextParser) startLabelName() stateFn {
|
| 296 | 296 |
// labels to 'real' labels. |
| 297 | 297 |
if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) && |
| 298 | 298 |
!(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) {
|
| 299 |
- p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair) |
|
| 300 |
- } |
|
| 301 |
- if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
|
|
| 302 |
- return nil // Unexpected end of input. |
|
| 303 |
- } |
|
| 304 |
- if p.currentByte != '=' {
|
|
| 305 |
- p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
|
|
| 306 |
- return nil |
|
| 299 |
+ p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair) |
|
| 307 | 300 |
} |
| 308 | 301 |
// Check for duplicate label names. |
| 309 | 302 |
labels := make(map[string]struct{})
|
| 310 |
- for _, l := range p.currentMetric.Label {
|
|
| 303 |
+ for _, l := range p.currentLabelPairs {
|
|
| 311 | 304 |
lName := l.GetName() |
| 312 | 305 |
if _, exists := labels[lName]; !exists {
|
| 313 | 306 |
labels[lName] = struct{}{}
|
| 314 | 307 |
} else {
|
| 315 | 308 |
p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName()))
|
| 309 |
+ p.currentLabelPairs = nil |
|
| 316 | 310 |
return nil |
| 317 | 311 |
} |
| 318 | 312 |
} |
| ... | ... |
@@ -345,6 +390,7 @@ func (p *TextParser) startLabelValue() stateFn {
|
| 345 | 345 |
if p.currentQuantile, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
|
| 346 | 346 |
// Create a more helpful error message. |
| 347 | 347 |
p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
|
| 348 |
+ p.currentLabelPairs = nil |
|
| 348 | 349 |
return nil |
| 349 | 350 |
} |
| 350 | 351 |
} else {
|
| ... | ... |
@@ -371,12 +417,19 @@ func (p *TextParser) startLabelValue() stateFn {
|
| 371 | 371 |
return p.startLabelName |
| 372 | 372 |
|
| 373 | 373 |
case '}': |
| 374 |
+ if p.currentMF == nil {
|
|
| 375 |
+ p.parseError("invalid metric name")
|
|
| 376 |
+ return nil |
|
| 377 |
+ } |
|
| 378 |
+ p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPairs...) |
|
| 379 |
+ p.currentLabelPairs = nil |
|
| 374 | 380 |
if p.skipBlankTab(); p.err != nil {
|
| 375 | 381 |
return nil // Unexpected end of input. |
| 376 | 382 |
} |
| 377 | 383 |
return p.readingValue |
| 378 | 384 |
default: |
| 379 | 385 |
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue()))
|
| 386 |
+ p.currentLabelPairs = nil |
|
| 380 | 387 |
return nil |
| 381 | 388 |
} |
| 382 | 389 |
} |
| ... | ... |
@@ -585,6 +638,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
|
| 585 | 585 |
p.currentToken.WriteByte(p.currentByte) |
| 586 | 586 |
case 'n': |
| 587 | 587 |
p.currentToken.WriteByte('\n')
|
| 588 |
+ case '"': |
|
| 589 |
+ p.currentToken.WriteByte('"')
|
|
| 588 | 590 |
default: |
| 589 | 591 |
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
|
| 590 | 592 |
return |
| ... | ... |
@@ -610,13 +665,45 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
|
| 610 | 610 |
// but not into p.currentToken. |
| 611 | 611 |
func (p *TextParser) readTokenAsMetricName() {
|
| 612 | 612 |
p.currentToken.Reset() |
| 613 |
+ // A UTF-8 metric name must be quoted and may have escaped characters. |
|
| 614 |
+ quoted := false |
|
| 615 |
+ escaped := false |
|
| 613 | 616 |
if !isValidMetricNameStart(p.currentByte) {
|
| 614 | 617 |
return |
| 615 | 618 |
} |
| 616 |
- for {
|
|
| 617 |
- p.currentToken.WriteByte(p.currentByte) |
|
| 619 |
+ for p.err == nil {
|
|
| 620 |
+ if escaped {
|
|
| 621 |
+ switch p.currentByte {
|
|
| 622 |
+ case '\\': |
|
| 623 |
+ p.currentToken.WriteByte(p.currentByte) |
|
| 624 |
+ case 'n': |
|
| 625 |
+ p.currentToken.WriteByte('\n')
|
|
| 626 |
+ case '"': |
|
| 627 |
+ p.currentToken.WriteByte('"')
|
|
| 628 |
+ default: |
|
| 629 |
+ p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
|
|
| 630 |
+ return |
|
| 631 |
+ } |
|
| 632 |
+ escaped = false |
|
| 633 |
+ } else {
|
|
| 634 |
+ switch p.currentByte {
|
|
| 635 |
+ case '"': |
|
| 636 |
+ quoted = !quoted |
|
| 637 |
+ if !quoted {
|
|
| 638 |
+ p.currentByte, p.err = p.buf.ReadByte() |
|
| 639 |
+ return |
|
| 640 |
+ } |
|
| 641 |
+ case '\n': |
|
| 642 |
+ p.parseError(fmt.Sprintf("metric name %q contains unescaped new-line", p.currentToken.String()))
|
|
| 643 |
+ return |
|
| 644 |
+ case '\\': |
|
| 645 |
+ escaped = true |
|
| 646 |
+ default: |
|
| 647 |
+ p.currentToken.WriteByte(p.currentByte) |
|
| 648 |
+ } |
|
| 649 |
+ } |
|
| 618 | 650 |
p.currentByte, p.err = p.buf.ReadByte() |
| 619 |
- if p.err != nil || !isValidMetricNameContinuation(p.currentByte) {
|
|
| 651 |
+ if !isValidMetricNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == ' ') {
|
|
| 620 | 652 |
return |
| 621 | 653 |
} |
| 622 | 654 |
} |
| ... | ... |
@@ -628,13 +715,45 @@ func (p *TextParser) readTokenAsMetricName() {
|
| 628 | 628 |
// but not into p.currentToken. |
| 629 | 629 |
func (p *TextParser) readTokenAsLabelName() {
|
| 630 | 630 |
p.currentToken.Reset() |
| 631 |
+ // A UTF-8 label name must be quoted and may have escaped characters. |
|
| 632 |
+ quoted := false |
|
| 633 |
+ escaped := false |
|
| 631 | 634 |
if !isValidLabelNameStart(p.currentByte) {
|
| 632 | 635 |
return |
| 633 | 636 |
} |
| 634 |
- for {
|
|
| 635 |
- p.currentToken.WriteByte(p.currentByte) |
|
| 637 |
+ for p.err == nil {
|
|
| 638 |
+ if escaped {
|
|
| 639 |
+ switch p.currentByte {
|
|
| 640 |
+ case '\\': |
|
| 641 |
+ p.currentToken.WriteByte(p.currentByte) |
|
| 642 |
+ case 'n': |
|
| 643 |
+ p.currentToken.WriteByte('\n')
|
|
| 644 |
+ case '"': |
|
| 645 |
+ p.currentToken.WriteByte('"')
|
|
| 646 |
+ default: |
|
| 647 |
+ p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
|
|
| 648 |
+ return |
|
| 649 |
+ } |
|
| 650 |
+ escaped = false |
|
| 651 |
+ } else {
|
|
| 652 |
+ switch p.currentByte {
|
|
| 653 |
+ case '"': |
|
| 654 |
+ quoted = !quoted |
|
| 655 |
+ if !quoted {
|
|
| 656 |
+ p.currentByte, p.err = p.buf.ReadByte() |
|
| 657 |
+ return |
|
| 658 |
+ } |
|
| 659 |
+ case '\n': |
|
| 660 |
+ p.parseError(fmt.Sprintf("label name %q contains unescaped new-line", p.currentToken.String()))
|
|
| 661 |
+ return |
|
| 662 |
+ case '\\': |
|
| 663 |
+ escaped = true |
|
| 664 |
+ default: |
|
| 665 |
+ p.currentToken.WriteByte(p.currentByte) |
|
| 666 |
+ } |
|
| 667 |
+ } |
|
| 636 | 668 |
p.currentByte, p.err = p.buf.ReadByte() |
| 637 |
- if p.err != nil || !isValidLabelNameContinuation(p.currentByte) {
|
|
| 669 |
+ if !isValidLabelNameContinuation(p.currentByte, quoted) || (!quoted && p.currentByte == '=') {
|
|
| 638 | 670 |
return |
| 639 | 671 |
} |
| 640 | 672 |
} |
| ... | ... |
@@ -660,6 +779,7 @@ func (p *TextParser) readTokenAsLabelValue() {
|
| 660 | 660 |
p.currentToken.WriteByte('\n')
|
| 661 | 661 |
default: |
| 662 | 662 |
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
|
| 663 |
+ p.currentLabelPairs = nil |
|
| 663 | 664 |
return |
| 664 | 665 |
} |
| 665 | 666 |
escaped = false |
| ... | ... |
@@ -718,19 +838,19 @@ func (p *TextParser) setOrCreateCurrentMF() {
|
| 718 | 718 |
} |
| 719 | 719 |
|
| 720 | 720 |
func isValidLabelNameStart(b byte) bool {
|
| 721 |
- return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' |
|
| 721 |
+ return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '"' |
|
| 722 | 722 |
} |
| 723 | 723 |
|
| 724 |
-func isValidLabelNameContinuation(b byte) bool {
|
|
| 725 |
- return isValidLabelNameStart(b) || (b >= '0' && b <= '9') |
|
| 724 |
+func isValidLabelNameContinuation(b byte, quoted bool) bool {
|
|
| 725 |
+ return isValidLabelNameStart(b) || (b >= '0' && b <= '9') || (quoted && utf8.ValidString(string(b))) |
|
| 726 | 726 |
} |
| 727 | 727 |
|
| 728 | 728 |
func isValidMetricNameStart(b byte) bool {
|
| 729 | 729 |
return isValidLabelNameStart(b) || b == ':' |
| 730 | 730 |
} |
| 731 | 731 |
|
| 732 |
-func isValidMetricNameContinuation(b byte) bool {
|
|
| 733 |
- return isValidLabelNameContinuation(b) || b == ':' |
|
| 732 |
+func isValidMetricNameContinuation(b byte, quoted bool) bool {
|
|
| 733 |
+ return isValidLabelNameContinuation(b, quoted) || b == ':' |
|
| 734 | 734 |
} |
| 735 | 735 |
|
| 736 | 736 |
func isBlankOrTab(b byte) bool {
|
| ... | ... |
@@ -775,7 +895,7 @@ func histogramMetricName(name string) string {
|
| 775 | 775 |
|
| 776 | 776 |
func parseFloat(s string) (float64, error) {
|
| 777 | 777 |
if strings.ContainsAny(s, "pP_") {
|
| 778 |
- return 0, fmt.Errorf("unsupported character in float")
|
|
| 778 |
+ return 0, errors.New("unsupported character in float")
|
|
| 779 | 779 |
} |
| 780 | 780 |
return strconv.ParseFloat(s, 64) |
| 781 | 781 |
} |
| ... | ... |
@@ -14,6 +14,7 @@ |
| 14 | 14 |
package model |
| 15 | 15 |
|
| 16 | 16 |
import ( |
| 17 |
+ "errors" |
|
| 17 | 18 |
"fmt" |
| 18 | 19 |
"time" |
| 19 | 20 |
) |
| ... | ... |
@@ -89,16 +90,16 @@ func (a *Alert) StatusAt(ts time.Time) AlertStatus {
|
| 89 | 89 |
// Validate checks whether the alert data is inconsistent. |
| 90 | 90 |
func (a *Alert) Validate() error {
|
| 91 | 91 |
if a.StartsAt.IsZero() {
|
| 92 |
- return fmt.Errorf("start time missing")
|
|
| 92 |
+ return errors.New("start time missing")
|
|
| 93 | 93 |
} |
| 94 | 94 |
if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) {
|
| 95 |
- return fmt.Errorf("start time must be before end time")
|
|
| 95 |
+ return errors.New("start time must be before end time")
|
|
| 96 | 96 |
} |
| 97 | 97 |
if err := a.Labels.Validate(); err != nil {
|
| 98 | 98 |
return fmt.Errorf("invalid label set: %w", err)
|
| 99 | 99 |
} |
| 100 | 100 |
if len(a.Labels) == 0 {
|
| 101 |
- return fmt.Errorf("at least one label pair required")
|
|
| 101 |
+ return errors.New("at least one label pair required")
|
|
| 102 | 102 |
} |
| 103 | 103 |
if err := a.Annotations.Validate(); err != nil {
|
| 104 | 104 |
return fmt.Errorf("invalid annotations: %w", err)
|
| ... | ... |
@@ -97,26 +97,35 @@ var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
| 97 | 97 |
// therewith. |
| 98 | 98 |
type LabelName string |
| 99 | 99 |
|
| 100 |
-// IsValid returns true iff name matches the pattern of LabelNameRE for legacy |
|
| 101 |
-// names, and iff it's valid UTF-8 if NameValidationScheme is set to |
|
| 102 |
-// UTF8Validation. For the legacy matching, it does not use LabelNameRE for the |
|
| 103 |
-// check but a much faster hardcoded implementation. |
|
| 100 |
+// IsValid returns true iff the name matches the pattern of LabelNameRE when |
|
| 101 |
+// NameValidationScheme is set to LegacyValidation, or valid UTF-8 if |
|
| 102 |
+// NameValidationScheme is set to UTF8Validation. |
|
| 104 | 103 |
func (ln LabelName) IsValid() bool {
|
| 105 | 104 |
if len(ln) == 0 {
|
| 106 | 105 |
return false |
| 107 | 106 |
} |
| 108 | 107 |
switch NameValidationScheme {
|
| 109 | 108 |
case LegacyValidation: |
| 110 |
- for i, b := range ln {
|
|
| 111 |
- if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) {
|
|
| 112 |
- return false |
|
| 113 |
- } |
|
| 114 |
- } |
|
| 109 |
+ return ln.IsValidLegacy() |
|
| 115 | 110 |
case UTF8Validation: |
| 116 | 111 |
return utf8.ValidString(string(ln)) |
| 117 | 112 |
default: |
| 118 | 113 |
panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme))
|
| 119 | 114 |
} |
| 115 |
+} |
|
| 116 |
+ |
|
| 117 |
+// IsValidLegacy returns true iff name matches the pattern of LabelNameRE for |
|
| 118 |
+// legacy names. It does not use LabelNameRE for the check but a much faster |
|
| 119 |
+// hardcoded implementation. |
|
| 120 |
+func (ln LabelName) IsValidLegacy() bool {
|
|
| 121 |
+ if len(ln) == 0 {
|
|
| 122 |
+ return false |
|
| 123 |
+ } |
|
| 124 |
+ for i, b := range ln {
|
|
| 125 |
+ if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) {
|
|
| 126 |
+ return false |
|
| 127 |
+ } |
|
| 128 |
+ } |
|
| 120 | 129 |
return true |
| 121 | 130 |
} |
| 122 | 131 |
|
| 19 | 17 |
deleted file mode 100644 |
| ... | ... |
@@ -1,39 +0,0 @@ |
| 1 |
-// Copyright 2024 The Prometheus Authors |
|
| 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 |
- |
|
| 14 |
-//go:build !go1.21 |
|
| 15 |
- |
|
| 16 |
-package model |
|
| 17 |
- |
|
| 18 |
-import ( |
|
| 19 |
- "fmt" |
|
| 20 |
- "sort" |
|
| 21 |
- "strings" |
|
| 22 |
-) |
|
| 23 |
- |
|
| 24 |
-// String was optimized using functions not available for go 1.20 |
|
| 25 |
-// or lower. We keep the old implementation for compatibility with client_golang. |
|
| 26 |
-// Once client golang drops support for go 1.20 (scheduled for August 2024), this |
|
| 27 |
-// file can be removed. |
|
| 28 |
-func (l LabelSet) String() string {
|
|
| 29 |
- labelNames := make([]string, 0, len(l)) |
|
| 30 |
- for name := range l {
|
|
| 31 |
- labelNames = append(labelNames, string(name)) |
|
| 32 |
- } |
|
| 33 |
- sort.Strings(labelNames) |
|
| 34 |
- lstrs := make([]string, 0, len(l)) |
|
| 35 |
- for _, name := range labelNames {
|
|
| 36 |
- lstrs = append(lstrs, fmt.Sprintf("%s=%q", name, l[LabelName(name)]))
|
|
| 37 |
- } |
|
| 38 |
- return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
|
|
| 39 |
-} |
| ... | ... |
@@ -14,9 +14,11 @@ |
| 14 | 14 |
package model |
| 15 | 15 |
|
| 16 | 16 |
import ( |
| 17 |
+ "errors" |
|
| 17 | 18 |
"fmt" |
| 18 | 19 |
"regexp" |
| 19 | 20 |
"sort" |
| 21 |
+ "strconv" |
|
| 20 | 22 |
"strings" |
| 21 | 23 |
"unicode/utf8" |
| 22 | 24 |
|
| ... | ... |
@@ -26,18 +28,21 @@ import ( |
| 26 | 26 |
|
| 27 | 27 |
var ( |
| 28 | 28 |
// NameValidationScheme determines the method of name validation to be used by |
| 29 |
- // all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 mode |
|
| 30 |
- // in isolation from other components that don't support UTF-8 may result in |
|
| 31 |
- // bugs or other undefined behavior. This value is intended to be set by |
|
| 32 |
- // UTF-8-aware binaries as part of their startup. To avoid need for locking, |
|
| 33 |
- // this value should be set once, ideally in an init(), before multiple |
|
| 34 |
- // goroutines are started. |
|
| 35 |
- NameValidationScheme = LegacyValidation |
|
| 36 |
- |
|
| 37 |
- // NameEscapingScheme defines the default way that names will be |
|
| 38 |
- // escaped when presented to systems that do not support UTF-8 names. If the |
|
| 39 |
- // Content-Type "escaping" term is specified, that will override this value. |
|
| 40 |
- NameEscapingScheme = ValueEncodingEscaping |
|
| 29 |
+ // all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 |
|
| 30 |
+ // mode in isolation from other components that don't support UTF-8 may result |
|
| 31 |
+ // in bugs or other undefined behavior. This value can be set to |
|
| 32 |
+ // LegacyValidation during startup if a binary is not UTF-8-aware binaries. To |
|
| 33 |
+ // avoid need for locking, this value should be set once, ideally in an |
|
| 34 |
+ // init(), before multiple goroutines are started. |
|
| 35 |
+ NameValidationScheme = UTF8Validation |
|
| 36 |
+ |
|
| 37 |
+ // NameEscapingScheme defines the default way that names will be escaped when |
|
| 38 |
+ // presented to systems that do not support UTF-8 names. If the Content-Type |
|
| 39 |
+ // "escaping" term is specified, that will override this value. |
|
| 40 |
+ // NameEscapingScheme should not be set to the NoEscaping value. That string |
|
| 41 |
+ // is used in content negotiation to indicate that a system supports UTF-8 and |
|
| 42 |
+ // has that feature enabled. |
|
| 43 |
+ NameEscapingScheme = UnderscoreEscaping |
|
| 41 | 44 |
) |
| 42 | 45 |
|
| 43 | 46 |
// ValidationScheme is a Go enum for determining how metric and label names will |
| ... | ... |
@@ -161,7 +166,7 @@ func (m Metric) FastFingerprint() Fingerprint {
|
| 161 | 161 |
func IsValidMetricName(n LabelValue) bool {
|
| 162 | 162 |
switch NameValidationScheme {
|
| 163 | 163 |
case LegacyValidation: |
| 164 |
- return IsValidLegacyMetricName(n) |
|
| 164 |
+ return IsValidLegacyMetricName(string(n)) |
|
| 165 | 165 |
case UTF8Validation: |
| 166 | 166 |
if len(n) == 0 {
|
| 167 | 167 |
return false |
| ... | ... |
@@ -176,7 +181,7 @@ func IsValidMetricName(n LabelValue) bool {
|
| 176 | 176 |
// legacy validation scheme regardless of the value of NameValidationScheme. |
| 177 | 177 |
// This function, however, does not use MetricNameRE for the check but a much |
| 178 | 178 |
// faster hardcoded implementation. |
| 179 |
-func IsValidLegacyMetricName(n LabelValue) bool {
|
|
| 179 |
+func IsValidLegacyMetricName(n string) bool {
|
|
| 180 | 180 |
if len(n) == 0 {
|
| 181 | 181 |
return false |
| 182 | 182 |
} |
| ... | ... |
@@ -208,7 +213,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF |
| 208 | 208 |
} |
| 209 | 209 |
|
| 210 | 210 |
// If the name is nil, copy as-is, don't try to escape. |
| 211 |
- if v.Name == nil || IsValidLegacyMetricName(LabelValue(v.GetName())) {
|
|
| 211 |
+ if v.Name == nil || IsValidLegacyMetricName(v.GetName()) {
|
|
| 212 | 212 |
out.Name = v.Name |
| 213 | 213 |
} else {
|
| 214 | 214 |
out.Name = proto.String(EscapeName(v.GetName(), scheme)) |
| ... | ... |
@@ -230,7 +235,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF |
| 230 | 230 |
|
| 231 | 231 |
for _, l := range m.Label {
|
| 232 | 232 |
if l.GetName() == MetricNameLabel {
|
| 233 |
- if l.Value == nil || IsValidLegacyMetricName(LabelValue(l.GetValue())) {
|
|
| 233 |
+ if l.Value == nil || IsValidLegacyMetricName(l.GetValue()) {
|
|
| 234 | 234 |
escaped.Label = append(escaped.Label, l) |
| 235 | 235 |
continue |
| 236 | 236 |
} |
| ... | ... |
@@ -240,7 +245,7 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF |
| 240 | 240 |
}) |
| 241 | 241 |
continue |
| 242 | 242 |
} |
| 243 |
- if l.Name == nil || IsValidLegacyMetricName(LabelValue(l.GetName())) {
|
|
| 243 |
+ if l.Name == nil || IsValidLegacyMetricName(l.GetName()) {
|
|
| 244 | 244 |
escaped.Label = append(escaped.Label, l) |
| 245 | 245 |
continue |
| 246 | 246 |
} |
| ... | ... |
@@ -256,20 +261,16 @@ func EscapeMetricFamily(v *dto.MetricFamily, scheme EscapingScheme) *dto.MetricF |
| 256 | 256 |
|
| 257 | 257 |
func metricNeedsEscaping(m *dto.Metric) bool {
|
| 258 | 258 |
for _, l := range m.Label {
|
| 259 |
- if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(LabelValue(l.GetValue())) {
|
|
| 259 |
+ if l.GetName() == MetricNameLabel && !IsValidLegacyMetricName(l.GetValue()) {
|
|
| 260 | 260 |
return true |
| 261 | 261 |
} |
| 262 |
- if !IsValidLegacyMetricName(LabelValue(l.GetName())) {
|
|
| 262 |
+ if !IsValidLegacyMetricName(l.GetName()) {
|
|
| 263 | 263 |
return true |
| 264 | 264 |
} |
| 265 | 265 |
} |
| 266 | 266 |
return false |
| 267 | 267 |
} |
| 268 | 268 |
|
| 269 |
-const ( |
|
| 270 |
- lowerhex = "0123456789abcdef" |
|
| 271 |
-) |
|
| 272 |
- |
|
| 273 | 269 |
// EscapeName escapes the incoming name according to the provided escaping |
| 274 | 270 |
// scheme. Depending on the rules of escaping, this may cause no change in the |
| 275 | 271 |
// string that is returned. (Especially NoEscaping, which by definition is a |
| ... | ... |
@@ -283,7 +284,7 @@ func EscapeName(name string, scheme EscapingScheme) string {
|
| 283 | 283 |
case NoEscaping: |
| 284 | 284 |
return name |
| 285 | 285 |
case UnderscoreEscaping: |
| 286 |
- if IsValidLegacyMetricName(LabelValue(name)) {
|
|
| 286 |
+ if IsValidLegacyMetricName(name) {
|
|
| 287 | 287 |
return name |
| 288 | 288 |
} |
| 289 | 289 |
for i, b := range name {
|
| ... | ... |
@@ -304,31 +305,25 @@ func EscapeName(name string, scheme EscapingScheme) string {
|
| 304 | 304 |
} else if isValidLegacyRune(b, i) {
|
| 305 | 305 |
escaped.WriteRune(b) |
| 306 | 306 |
} else {
|
| 307 |
- escaped.WriteRune('_')
|
|
| 307 |
+ escaped.WriteString("__")
|
|
| 308 | 308 |
} |
| 309 | 309 |
} |
| 310 | 310 |
return escaped.String() |
| 311 | 311 |
case ValueEncodingEscaping: |
| 312 |
- if IsValidLegacyMetricName(LabelValue(name)) {
|
|
| 312 |
+ if IsValidLegacyMetricName(name) {
|
|
| 313 | 313 |
return name |
| 314 | 314 |
} |
| 315 | 315 |
escaped.WriteString("U__")
|
| 316 | 316 |
for i, b := range name {
|
| 317 |
- if isValidLegacyRune(b, i) {
|
|
| 317 |
+ if b == '_' {
|
|
| 318 |
+ escaped.WriteString("__")
|
|
| 319 |
+ } else if isValidLegacyRune(b, i) {
|
|
| 318 | 320 |
escaped.WriteRune(b) |
| 319 | 321 |
} else if !utf8.ValidRune(b) {
|
| 320 | 322 |
escaped.WriteString("_FFFD_")
|
| 321 |
- } else if b < 0x100 {
|
|
| 322 |
- escaped.WriteRune('_')
|
|
| 323 |
- for s := 4; s >= 0; s -= 4 {
|
|
| 324 |
- escaped.WriteByte(lowerhex[b>>uint(s)&0xF]) |
|
| 325 |
- } |
|
| 326 |
- escaped.WriteRune('_')
|
|
| 327 |
- } else if b < 0x10000 {
|
|
| 323 |
+ } else {
|
|
| 328 | 324 |
escaped.WriteRune('_')
|
| 329 |
- for s := 12; s >= 0; s -= 4 {
|
|
| 330 |
- escaped.WriteByte(lowerhex[b>>uint(s)&0xF]) |
|
| 331 |
- } |
|
| 325 |
+ escaped.WriteString(strconv.FormatInt(int64(b), 16)) |
|
| 332 | 326 |
escaped.WriteRune('_')
|
| 333 | 327 |
} |
| 334 | 328 |
} |
| ... | ... |
@@ -386,8 +381,9 @@ func UnescapeName(name string, scheme EscapingScheme) string {
|
| 386 | 386 |
// We think we are in a UTF-8 code, process it. |
| 387 | 387 |
var utf8Val uint |
| 388 | 388 |
for j := 0; i < len(escapedName); j++ {
|
| 389 |
- // This is too many characters for a utf8 value. |
|
| 390 |
- if j > 4 {
|
|
| 389 |
+ // This is too many characters for a utf8 value based on the MaxRune |
|
| 390 |
+ // value of '\U0010FFFF'. |
|
| 391 |
+ if j >= 6 {
|
|
| 391 | 392 |
return name |
| 392 | 393 |
} |
| 393 | 394 |
// Found a closing underscore, convert to a rune, check validity, and append. |
| ... | ... |
@@ -440,7 +436,7 @@ func (e EscapingScheme) String() string {
|
| 440 | 440 |
|
| 441 | 441 |
func ToEscapingScheme(s string) (EscapingScheme, error) {
|
| 442 | 442 |
if s == "" {
|
| 443 |
- return NoEscaping, fmt.Errorf("got empty string instead of escaping scheme")
|
|
| 443 |
+ return NoEscaping, errors.New("got empty string instead of escaping scheme")
|
|
| 444 | 444 |
} |
| 445 | 445 |
switch s {
|
| 446 | 446 |
case AllowUTF8: |
| ... | ... |
@@ -452,6 +448,6 @@ func ToEscapingScheme(s string) (EscapingScheme, error) {
|
| 452 | 452 |
case EscapeValues: |
| 453 | 453 |
return ValueEncodingEscaping, nil |
| 454 | 454 |
default: |
| 455 |
- return NoEscaping, fmt.Errorf("unknown format scheme " + s)
|
|
| 455 |
+ return NoEscaping, fmt.Errorf("unknown format scheme %s", s)
|
|
| 456 | 456 |
} |
| 457 | 457 |
} |
| ... | ... |
@@ -15,6 +15,7 @@ package model |
| 15 | 15 |
|
| 16 | 16 |
import ( |
| 17 | 17 |
"encoding/json" |
| 18 |
+ "errors" |
|
| 18 | 19 |
"fmt" |
| 19 | 20 |
"regexp" |
| 20 | 21 |
"time" |
| ... | ... |
@@ -34,7 +35,7 @@ func (m *Matcher) UnmarshalJSON(b []byte) error {
|
| 34 | 34 |
} |
| 35 | 35 |
|
| 36 | 36 |
if len(m.Name) == 0 {
|
| 37 |
- return fmt.Errorf("label name in matcher must not be empty")
|
|
| 37 |
+ return errors.New("label name in matcher must not be empty")
|
|
| 38 | 38 |
} |
| 39 | 39 |
if m.IsRegex {
|
| 40 | 40 |
if _, err := regexp.Compile(m.Value); err != nil {
|
| ... | ... |
@@ -77,7 +78,7 @@ type Silence struct {
|
| 77 | 77 |
// Validate returns true iff all fields of the silence have valid values. |
| 78 | 78 |
func (s *Silence) Validate() error {
|
| 79 | 79 |
if len(s.Matchers) == 0 {
|
| 80 |
- return fmt.Errorf("at least one matcher required")
|
|
| 80 |
+ return errors.New("at least one matcher required")
|
|
| 81 | 81 |
} |
| 82 | 82 |
for _, m := range s.Matchers {
|
| 83 | 83 |
if err := m.Validate(); err != nil {
|
| ... | ... |
@@ -85,22 +86,22 @@ func (s *Silence) Validate() error {
|
| 85 | 85 |
} |
| 86 | 86 |
} |
| 87 | 87 |
if s.StartsAt.IsZero() {
|
| 88 |
- return fmt.Errorf("start time missing")
|
|
| 88 |
+ return errors.New("start time missing")
|
|
| 89 | 89 |
} |
| 90 | 90 |
if s.EndsAt.IsZero() {
|
| 91 |
- return fmt.Errorf("end time missing")
|
|
| 91 |
+ return errors.New("end time missing")
|
|
| 92 | 92 |
} |
| 93 | 93 |
if s.EndsAt.Before(s.StartsAt) {
|
| 94 |
- return fmt.Errorf("start time must be before end time")
|
|
| 94 |
+ return errors.New("start time must be before end time")
|
|
| 95 | 95 |
} |
| 96 | 96 |
if s.CreatedBy == "" {
|
| 97 |
- return fmt.Errorf("creator information missing")
|
|
| 97 |
+ return errors.New("creator information missing")
|
|
| 98 | 98 |
} |
| 99 | 99 |
if s.Comment == "" {
|
| 100 |
- return fmt.Errorf("comment missing")
|
|
| 100 |
+ return errors.New("comment missing")
|
|
| 101 | 101 |
} |
| 102 | 102 |
if s.CreatedAt.IsZero() {
|
| 103 |
- return fmt.Errorf("creation timestamp missing")
|
|
| 103 |
+ return errors.New("creation timestamp missing")
|
|
| 104 | 104 |
} |
| 105 | 105 |
return nil |
| 106 | 106 |
} |
| ... | ... |
@@ -15,6 +15,7 @@ package model |
| 15 | 15 |
|
| 16 | 16 |
import ( |
| 17 | 17 |
"encoding/json" |
| 18 |
+ "errors" |
|
| 18 | 19 |
"fmt" |
| 19 | 20 |
"math" |
| 20 | 21 |
"strconv" |
| ... | ... |
@@ -39,7 +40,7 @@ func (v SampleValue) MarshalJSON() ([]byte, error) {
|
| 39 | 39 |
// UnmarshalJSON implements json.Unmarshaler. |
| 40 | 40 |
func (v *SampleValue) UnmarshalJSON(b []byte) error {
|
| 41 | 41 |
if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
|
| 42 |
- return fmt.Errorf("sample value must be a quoted string")
|
|
| 42 |
+ return errors.New("sample value must be a quoted string")
|
|
| 43 | 43 |
} |
| 44 | 44 |
f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64) |
| 45 | 45 |
if err != nil {
|
| ... | ... |
@@ -15,6 +15,7 @@ package model |
| 15 | 15 |
|
| 16 | 16 |
import ( |
| 17 | 17 |
"encoding/json" |
| 18 |
+ "errors" |
|
| 18 | 19 |
"fmt" |
| 19 | 20 |
"strconv" |
| 20 | 21 |
"strings" |
| ... | ... |
@@ -32,7 +33,7 @@ func (v FloatString) MarshalJSON() ([]byte, error) {
|
| 32 | 32 |
|
| 33 | 33 |
func (v *FloatString) UnmarshalJSON(b []byte) error {
|
| 34 | 34 |
if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
|
| 35 |
- return fmt.Errorf("float value must be a quoted string")
|
|
| 35 |
+ return errors.New("float value must be a quoted string")
|
|
| 36 | 36 |
} |
| 37 | 37 |
f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64) |
| 38 | 38 |
if err != nil {
|
| ... | ... |
@@ -141,7 +142,7 @@ type SampleHistogramPair struct {
|
| 141 | 141 |
|
| 142 | 142 |
func (s SampleHistogramPair) MarshalJSON() ([]byte, error) {
|
| 143 | 143 |
if s.Histogram == nil {
|
| 144 |
- return nil, fmt.Errorf("histogram is nil")
|
|
| 144 |
+ return nil, errors.New("histogram is nil")
|
|
| 145 | 145 |
} |
| 146 | 146 |
t, err := json.Marshal(s.Timestamp) |
| 147 | 147 |
if err != nil {
|
| ... | ... |
@@ -164,7 +165,7 @@ func (s *SampleHistogramPair) UnmarshalJSON(buf []byte) error {
|
| 164 | 164 |
return fmt.Errorf("wrong number of fields: %d != %d", gotLen, wantLen)
|
| 165 | 165 |
} |
| 166 | 166 |
if s.Histogram == nil {
|
| 167 |
- return fmt.Errorf("histogram is null")
|
|
| 167 |
+ return errors.New("histogram is null")
|
|
| 168 | 168 |
} |
| 169 | 169 |
return nil |
| 170 | 170 |
} |
| ... | ... |
@@ -1117,18 +1117,19 @@ github.com/planetscale/vtprotobuf/vtproto |
| 1117 | 1117 |
# github.com/pmezard/go-difflib v1.0.0 |
| 1118 | 1118 |
## explicit |
| 1119 | 1119 |
github.com/pmezard/go-difflib/difflib |
| 1120 |
-# github.com/prometheus/client_golang v1.20.5 |
|
| 1121 |
-## explicit; go 1.20 |
|
| 1120 |
+# github.com/prometheus/client_golang v1.22.0 |
|
| 1121 |
+## explicit; go 1.22 |
|
| 1122 | 1122 |
github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil |
| 1123 | 1123 |
github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil/header |
| 1124 | 1124 |
github.com/prometheus/client_golang/prometheus |
| 1125 | 1125 |
github.com/prometheus/client_golang/prometheus/internal |
| 1126 | 1126 |
github.com/prometheus/client_golang/prometheus/promhttp |
| 1127 |
+github.com/prometheus/client_golang/prometheus/promhttp/internal |
|
| 1127 | 1128 |
# github.com/prometheus/client_model v0.6.1 |
| 1128 | 1129 |
## explicit; go 1.19 |
| 1129 | 1130 |
github.com/prometheus/client_model/go |
| 1130 |
-# github.com/prometheus/common v0.55.0 |
|
| 1131 |
-## explicit; go 1.20 |
|
| 1131 |
+# github.com/prometheus/common v0.62.0 |
|
| 1132 |
+## explicit; go 1.21 |
|
| 1132 | 1133 |
github.com/prometheus/common/expfmt |
| 1133 | 1134 |
github.com/prometheus/common/model |
| 1134 | 1135 |
# github.com/prometheus/procfs v0.15.1 |