From d5631d28580dcd7b0cca5c475a405fb3eeecacda Mon Sep 17 00:00:00 2001 From: dongjiang Date: Sat, 18 Jan 2025 18:09:20 +0800 Subject: [PATCH 01/78] making this map a public variable (#741) Signed-off-by: dongjiang --- config/headers.go | 6 +++--- config/headers_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/headers.go b/config/headers.go index 7276742ec..9beaae26c 100644 --- a/config/headers.go +++ b/config/headers.go @@ -24,9 +24,9 @@ import ( "strings" ) -// reservedHeaders that change the connection, are set by Prometheus, or can +// ReservedHeaders that change the connection, are set by Prometheus, or can // be changed otherwise. -var reservedHeaders = map[string]struct{}{ +var ReservedHeaders = map[string]struct{}{ "Authorization": {}, "Host": {}, "Content-Encoding": {}, @@ -72,7 +72,7 @@ func (h *Headers) SetDirectory(dir string) { // Validate validates the Headers config. func (h *Headers) Validate() error { for n := range h.Headers { - if _, ok := reservedHeaders[http.CanonicalHeaderKey(n)]; ok { + if _, ok := ReservedHeaders[http.CanonicalHeaderKey(n)]; ok { return fmt.Errorf("setting header %q is not allowed", http.CanonicalHeaderKey(n)) } } diff --git a/config/headers_test.go b/config/headers_test.go index 39c6f9ff3..c807fbc3b 100644 --- a/config/headers_test.go +++ b/config/headers_test.go @@ -22,10 +22,10 @@ import ( ) func TestReservedHeaders(t *testing.T) { - for k := range reservedHeaders { + for k := range ReservedHeaders { l := http.CanonicalHeaderKey(k) if k != l { - t.Errorf("reservedHeaders keys should be lowercase: got %q, expected %q", k, http.CanonicalHeaderKey(k)) + t.Errorf("ReservedHeaders keys should be lowercase: got %q, expected %q", k, http.CanonicalHeaderKey(k)) } } } From 1cc52972eb5e5f91ad03ab4b1b34ca085958bd32 Mon Sep 17 00:00:00 2001 From: Matthieu MOREL Date: Sat, 18 Jan 2025 12:27:44 +0100 Subject: [PATCH 02/78] setup ossf scorecard and codeql workflows (#564) * setup ossf scorecard and codql workflows --------- Signed-off-by: Matthieu MOREL Co-authored-by: Ben Kochie --- .github/workflows/codeql.yml | 50 +++++++++++++++++++++++++++++ .github/workflows/scorecard.yml | 56 +++++++++++++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 108 insertions(+) create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/scorecard.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..f7fc82f37 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,50 @@ +name: "CodeQL" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '24 1 * * 5' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + timeout-minutes: 360 + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 + with: + languages: ${{ matrix.language }} + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000..4793ce721 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,56 @@ +name: Scorecard supply-chain security + +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '26 18 * * 2' + push: + branches: [ "main" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 + with: + sarif_file: results.sarif diff --git a/README.md b/README.md index 954cc91b4..f7d5342dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Common ![circleci](https://circleci.com/gh/prometheus/common/tree/main.svg?style=shield) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/prometheus/common/badge)](https://securityscorecards.dev/viewer/?uri=github.com/prometheus/common) + This repository contains Go libraries that are shared across Prometheus components and libraries. They are considered internal to Prometheus, without From df14882cbf97c96ef87ddc456cde77d7be7e4352 Mon Sep 17 00:00:00 2001 From: TJ Hoplock <33664289+tjhop@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:26:12 -0500 Subject: [PATCH 03/78] feat(promslog): implement reserved keys, rename duplicates (#746) * feat(promslog): implement reserved keys, rename duplicates This commit adds support for "reserved" keys, where upon detecting a duplicate log attribute key, it will rename the key to prevent collisions/issues with core slog attribute keys in use. The reserved keys are: - `level` - `source` (for standard slog style) - `caller` (for legacy go-kit style) - `time` (for standard slog style) - `ts` (for legacy go-kit style) By supporting reserved keys, we allow users of the library to use these keys for their own supplemental log attributes. Fixes: #745 Example test output: ``` === RUN TestReservedKeys/slog_log_style time=2025-01-13T18:32:46.508Z level=INFO source=slog_test.go:212 msg="reserved keys test for slog_log_style" logged_level="surprise! I'm a string" logged_source="surprise! I'm a string" logged_time="surprise! I'm a string" === RUN TestReservedKeys/go-kit_log_style ts=2025-01-13T18:32:46.508Z level=info caller=slog_test.go:212 msg="reserved keys test for go-kit_log_style" logged_level="surprise! I'm a string" logged_caller="surprise! I'm a string" logged_ts="surprise! I'm a string" ``` Note: this implementation only technically removes duplicates of our core reserved keys. If a user adds multiple instances of a reserved key, the rest get renamed to `logged_$key`, which means there could be duplicate attributes with `logged_$key`. This is mostly to simplify implementation so we don't need to bother reference counting in the replace attr function to do things like `logged_$key1`, `logged_$key2`, etc. --------- Signed-off-by: TJ Hoplock --- promslog/slog.go | 97 +++++++++++++++++++++++++++++++++---------- promslog/slog_test.go | 39 +++++++++++++++++ 2 files changed, 115 insertions(+), 21 deletions(-) diff --git a/promslog/slog.go b/promslog/slog.go index 6e8fbabce..11c33fb66 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -25,6 +25,7 @@ import ( "path/filepath" "strconv" "strings" + "time" ) type LogStyle string @@ -32,6 +33,8 @@ type LogStyle string const ( SlogStyle LogStyle = "slog" GoKitStyle LogStyle = "go-kit" + + reservedKeyPrefix = "logged_" ) var ( @@ -43,26 +46,51 @@ var ( goKitStyleReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr { key := a.Key switch key { - case slog.TimeKey: - a.Key = "ts" - - // This timestamp format differs from RFC3339Nano by using .000 instead - // of .999999999 which changes the timestamp from 9 variable to 3 fixed - // decimals (.130 instead of .130987456). - t := a.Value.Time() - a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00")) - case slog.SourceKey: - a.Key = "caller" - src, _ := a.Value.Any().(*slog.Source) + case slog.TimeKey, "ts": + if t, ok := a.Value.Any().(time.Time); ok { + a.Key = "ts" - switch callerAddFunc { - case true: - a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line)) - default: - a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) + // This timestamp format differs from RFC3339Nano by using .000 instead + // of .999999999 which changes the timestamp from 9 variable to 3 fixed + // decimals (.130 instead of .130987456). + a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00")) + } else { + // If we can't cast the any from the value to a + // time.Time, it means the caller logged + // another attribute with a key of `ts`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to `logged_ts`. + a.Key = reservedKeyPrefix + key + } + case slog.SourceKey, "caller": + if src, ok := a.Value.Any().(*slog.Source); ok { + a.Key = "caller" + switch callerAddFunc { + case true: + a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line)) + default: + a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) + } + } else { + // If we can't cast the any from the value to + // an *slog.Source, it means the caller logged + // another attribute with a key of `caller`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to + // `logged_caller`. + a.Key = reservedKeyPrefix + key } case slog.LevelKey: - a.Value = slog.StringValue(strings.ToLower(a.Value.String())) + if lvl, ok := a.Value.Any().(slog.Level); ok { + a.Value = slog.StringValue(strings.ToLower(lvl.String())) + } else { + // If we can't cast the any from the value to + // an slog.Level, it means the caller logged + // another attribute with a key of `level`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to `logged_level`. + a.Key = reservedKeyPrefix + key + } default: } @@ -72,11 +100,38 @@ var ( key := a.Key switch key { case slog.TimeKey: - t := a.Value.Time() - a.Value = slog.TimeValue(t.UTC()) + if t, ok := a.Value.Any().(time.Time); ok { + a.Value = slog.TimeValue(t.UTC()) + } else { + // If we can't cast the any from the value to a + // time.Time, it means the caller logged + // another attribute with a key of `time`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to `logged_time`. + a.Key = reservedKeyPrefix + key + } case slog.SourceKey: - src, _ := a.Value.Any().(*slog.Source) - a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) + if src, ok := a.Value.Any().(*slog.Source); ok { + a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) + } else { + // If we can't cast the any from the value to + // an *slog.Source, it means the caller logged + // another attribute with a key of `source`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to + // `logged_source`. + a.Key = reservedKeyPrefix + key + } + case slog.LevelKey: + if _, ok := a.Value.Any().(slog.Level); !ok { + // If we can't cast the any from the value to + // an slog.Level, it means the caller logged + // another attribute with a key of `level`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to + // `logged_level`. + a.Key = reservedKeyPrefix + key + } default: } diff --git a/promslog/slog_test.go b/promslog/slog_test.go index fc824e04f..6da14d987 100644 --- a/promslog/slog_test.go +++ b/promslog/slog_test.go @@ -188,3 +188,42 @@ func TestTruncateSourceFileName_GoKitStyle(t *testing.T) { t.Errorf("Expected no directory separators in caller, got: %s", output) } } + +func TestReservedKeys(t *testing.T) { + var buf bytes.Buffer + reservedKeyTestVal := "surprise! I'm a string" + + tests := map[string]struct { + logStyle LogStyle + levelKey string + sourceKey string + timeKey string + }{ + "slog_log_style": {logStyle: SlogStyle, levelKey: "level", sourceKey: "source", timeKey: "time"}, + "go-kit_log_style": {logStyle: GoKitStyle, levelKey: "level", sourceKey: "caller", timeKey: "ts"}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + buf.Reset() // Ensure buf is reset prior to tests + config := &Config{Writer: &buf, Style: tc.logStyle} + logger := New(config) + + logger.LogAttrs(context.Background(), + slog.LevelInfo, + "reserved keys test for "+name, + slog.String(tc.levelKey, reservedKeyTestVal), + slog.String(tc.sourceKey, reservedKeyTestVal), + slog.String(tc.timeKey, reservedKeyTestVal), + ) + + output := buf.String() + require.Containsf(t, output, fmt.Sprintf("%s%s=\"%s\"", reservedKeyPrefix, tc.levelKey, reservedKeyTestVal), "Expected duplicate level key to be renamed") + require.Containsf(t, output, fmt.Sprintf("%s%s=\"%s\"", reservedKeyPrefix, tc.sourceKey, reservedKeyTestVal), "Expected duplicate source key to be renamed") + require.Containsf(t, output, fmt.Sprintf("%s%s=\"%s\"", reservedKeyPrefix, tc.timeKey, reservedKeyTestVal), "Expected duplicate time key to be renamed") + + // Print logs for humans to see, if needed. + fmt.Println(buf.String()) + }) + } +} From 1b10c090a2be38b11ec58f9063e52e42a767cbe9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:32:07 +0100 Subject: [PATCH 04/78] Bump golang.org/x/oauth2 from 0.24.0 to 0.25.0 (#750) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.24.0 to 0.25.0. - [Commits](https://github.com/golang/oauth2/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4d62719bb..a907b7e13 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.33.0 - golang.org/x/oauth2 v0.24.0 + golang.org/x/oauth2 v0.25.0 google.golang.org/protobuf v1.36.1 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index b5955f01f..106f42cf6 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= From 5b0d088c83bef75c876d51144b3ec684f7fb0c6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:43:46 +0100 Subject: [PATCH 05/78] Bump golang.org/x/net from 0.33.0 to 0.34.0 (#749) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.34.0. - [Commits](https://github.com/golang/net/compare/v0.33.0...v0.34.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a907b7e13..b9c113bc7 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.33.0 + golang.org/x/net v0.34.0 golang.org/x/oauth2 v0.25.0 google.golang.org/protobuf v1.36.1 gopkg.in/yaml.v2 v2.4.0 @@ -27,7 +27,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 106f42cf6..4365a3890 100644 --- a/go.sum +++ b/go.sum @@ -43,12 +43,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= From 7684929007b4a1722c382f95d06e597eec7956e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:43:57 +0100 Subject: [PATCH 06/78] Bump google.golang.org/protobuf from 1.36.1 to 1.36.3 (#751) Bumps google.golang.org/protobuf from 1.36.1 to 1.36.3. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b9c113bc7..5d2f2cbb3 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/stretchr/testify v1.10.0 golang.org/x/net v0.34.0 golang.org/x/oauth2 v0.25.0 - google.golang.org/protobuf v1.36.1 + google.golang.org/protobuf v1.36.3 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 4365a3890..17504316a 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 9fedd39e6baf15bb14c7230af364325633a012b5 Mon Sep 17 00:00:00 2001 From: Peter Nguyen Date: Tue, 28 Jan 2025 11:23:50 -0800 Subject: [PATCH 07/78] Fix typo 'the an' Signed-off-by: Peter Nguyen --- model/labels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/labels.go b/model/labels.go index 73b7aa3e6..f4a387605 100644 --- a/model/labels.go +++ b/model/labels.go @@ -22,7 +22,7 @@ import ( ) const ( - // AlertNameLabel is the name of the label containing the an alert's name. + // AlertNameLabel is the name of the label containing the alert's name. AlertNameLabel = "alertname" // ExportedLabelPrefix is the prefix to prepend to the label names present in From a784287e355f1b8850d454d9993c2aab112a6eaa Mon Sep 17 00:00:00 2001 From: Bartlomiej Plotka Date: Mon, 3 Feb 2025 09:50:08 +0100 Subject: [PATCH 08/78] promslog: Make AllowedLevel concurrency safe. (#754) * promslog: Make AllowedLevel concurrency safe. Needed for https://github.com/prometheus/prometheus/issues/10352 Also I renamed AllowedLevel and AllowedFormat to Level and Format. Default level (and String()) is also now 'info' not empty. It's a breaking change, but I suspect nobody was using those constructs directly, WDYT? Signed-off-by: bwplotka * info is by default, so no need for the set in New Signed-off-by: bwplotka --------- Signed-off-by: bwplotka --- promslog/flag/flag.go | 4 +- promslog/slog.go | 271 ++++++++++++++++++++++-------------------- promslog/slog_test.go | 18 +-- 3 files changed, 154 insertions(+), 139 deletions(-) diff --git a/promslog/flag/flag.go b/promslog/flag/flag.go index 0a164fcc1..85c67b250 100644 --- a/promslog/flag/flag.go +++ b/promslog/flag/flag.go @@ -42,12 +42,12 @@ var FormatFlagHelp = "Output format of log messages. One of: [" + strings.Join(p // AddFlags adds the flags used by this package to the Kingpin application. // To use the default Kingpin application, call AddFlags(kingpin.CommandLine) func AddFlags(a *kingpin.Application, config *promslog.Config) { - config.Level = &promslog.AllowedLevel{} + config.Level = promslog.NewLevel() a.Flag(LevelFlagName, LevelFlagHelp). Default("info").HintOptions(promslog.LevelFlagOptions...). SetValue(config.Level) - config.Format = &promslog.AllowedFormat{} + config.Format = promslog.NewFormat() a.Flag(FormatFlagName, FormatFlagHelp). Default("logfmt").HintOptions(promslog.FormatFlagOptions...). SetValue(config.Format) diff --git a/promslog/slog.go b/promslog/slog.go index 11c33fb66..f9f899663 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -28,6 +28,7 @@ import ( "time" ) +// LogStyle represents the common logging formats in the Prometheus ecosystem. type LogStyle string const ( @@ -38,115 +39,29 @@ const ( ) var ( - LevelFlagOptions = []string{"debug", "info", "warn", "error"} + // LevelFlagOptions represents allowed logging levels. + LevelFlagOptions = []string{"debug", "info", "warn", "error"} + // FormatFlagOptions represents allowed formats. FormatFlagOptions = []string{"logfmt", "json"} - callerAddFunc = false - defaultWriter = os.Stderr - goKitStyleReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr { - key := a.Key - switch key { - case slog.TimeKey, "ts": - if t, ok := a.Value.Any().(time.Time); ok { - a.Key = "ts" - - // This timestamp format differs from RFC3339Nano by using .000 instead - // of .999999999 which changes the timestamp from 9 variable to 3 fixed - // decimals (.130 instead of .130987456). - a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00")) - } else { - // If we can't cast the any from the value to a - // time.Time, it means the caller logged - // another attribute with a key of `ts`. - // Prevent duplicate keys (necessary for proper - // JSON) by renaming the key to `logged_ts`. - a.Key = reservedKeyPrefix + key - } - case slog.SourceKey, "caller": - if src, ok := a.Value.Any().(*slog.Source); ok { - a.Key = "caller" - switch callerAddFunc { - case true: - a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line)) - default: - a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) - } - } else { - // If we can't cast the any from the value to - // an *slog.Source, it means the caller logged - // another attribute with a key of `caller`. - // Prevent duplicate keys (necessary for proper - // JSON) by renaming the key to - // `logged_caller`. - a.Key = reservedKeyPrefix + key - } - case slog.LevelKey: - if lvl, ok := a.Value.Any().(slog.Level); ok { - a.Value = slog.StringValue(strings.ToLower(lvl.String())) - } else { - // If we can't cast the any from the value to - // an slog.Level, it means the caller logged - // another attribute with a key of `level`. - // Prevent duplicate keys (necessary for proper - // JSON) by renaming the key to `logged_level`. - a.Key = reservedKeyPrefix + key - } - default: - } - - return a - } - defaultReplaceAttrFunc = func(groups []string, a slog.Attr) slog.Attr { - key := a.Key - switch key { - case slog.TimeKey: - if t, ok := a.Value.Any().(time.Time); ok { - a.Value = slog.TimeValue(t.UTC()) - } else { - // If we can't cast the any from the value to a - // time.Time, it means the caller logged - // another attribute with a key of `time`. - // Prevent duplicate keys (necessary for proper - // JSON) by renaming the key to `logged_time`. - a.Key = reservedKeyPrefix + key - } - case slog.SourceKey: - if src, ok := a.Value.Any().(*slog.Source); ok { - a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) - } else { - // If we can't cast the any from the value to - // an *slog.Source, it means the caller logged - // another attribute with a key of `source`. - // Prevent duplicate keys (necessary for proper - // JSON) by renaming the key to - // `logged_source`. - a.Key = reservedKeyPrefix + key - } - case slog.LevelKey: - if _, ok := a.Value.Any().(slog.Level); !ok { - // If we can't cast the any from the value to - // an slog.Level, it means the caller logged - // another attribute with a key of `level`. - // Prevent duplicate keys (necessary for proper - // JSON) by renaming the key to - // `logged_level`. - a.Key = reservedKeyPrefix + key - } - default: - } - - return a - } + defaultWriter = os.Stderr ) -// AllowedLevel is a settable identifier for the minimum level a log entry -// must be have. -type AllowedLevel struct { - s string +// Level controls a logging level, with an info default. +// It wraps slog.LevelVar with string-based level control. +// Level is safe to be used concurrently. +type Level struct { lvl *slog.LevelVar } -func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { +// NewLevel returns a new Level. +func NewLevel() *Level { + return &Level{ + lvl: &slog.LevelVar{}, + } +} + +func (l *Level) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string type plain string if err := unmarshal((*plain)(&s)); err != nil { @@ -155,55 +70,60 @@ func (l *AllowedLevel) UnmarshalYAML(unmarshal func(interface{}) error) error { if s == "" { return nil } - lo := &AllowedLevel{} - if err := lo.Set(s); err != nil { + if err := l.Set(s); err != nil { return err } - *l = *lo return nil } -func (l *AllowedLevel) String() string { - return l.s -} - -// Set updates the value of the allowed level. -func (l *AllowedLevel) Set(s string) error { - if l.lvl == nil { - l.lvl = &slog.LevelVar{} +// String returns the current level. +func (l *Level) String() string { + switch l.lvl.Level() { + case slog.LevelDebug: + return "debug" + case slog.LevelInfo: + return "info" + case slog.LevelWarn: + return "warn" + case slog.LevelError: + return "error" + default: + return "" } +} +// Set updates the logging level with the validation. +func (l *Level) Set(s string) error { switch strings.ToLower(s) { case "debug": l.lvl.Set(slog.LevelDebug) - callerAddFunc = true case "info": l.lvl.Set(slog.LevelInfo) - callerAddFunc = false case "warn": l.lvl.Set(slog.LevelWarn) - callerAddFunc = false case "error": l.lvl.Set(slog.LevelError) - callerAddFunc = false default: return fmt.Errorf("unrecognized log level %s", s) } - l.s = s return nil } -// AllowedFormat is a settable identifier for the output format that the logger can have. -type AllowedFormat struct { +// Format controls a logging output format. +// Not concurrency-safe. +type Format struct { s string } -func (f *AllowedFormat) String() string { +// NewFormat creates a new Format. +func NewFormat() *Format { return &Format{} } + +func (f *Format) String() string { return f.s } // Set updates the value of the allowed format. -func (f *AllowedFormat) Set(s string) error { +func (f *Format) Set(s string) error { switch s { case "logfmt", "json": f.s = s @@ -215,18 +135,113 @@ func (f *AllowedFormat) Set(s string) error { // Config is a struct containing configurable settings for the logger type Config struct { - Level *AllowedLevel - Format *AllowedFormat + Level *Level + Format *Format Style LogStyle Writer io.Writer } +func newGoKitStyleReplaceAttrFunc(lvl *Level) func(groups []string, a slog.Attr) slog.Attr { + return func(groups []string, a slog.Attr) slog.Attr { + key := a.Key + switch key { + case slog.TimeKey, "ts": + if t, ok := a.Value.Any().(time.Time); ok { + a.Key = "ts" + + // This timestamp format differs from RFC3339Nano by using .000 instead + // of .999999999 which changes the timestamp from 9 variable to 3 fixed + // decimals (.130 instead of .130987456). + a.Value = slog.StringValue(t.UTC().Format("2006-01-02T15:04:05.000Z07:00")) + } else { + // If we can't cast the any from the value to a + // time.Time, it means the caller logged + // another attribute with a key of `ts`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to `logged_ts`. + a.Key = reservedKeyPrefix + key + } + case slog.SourceKey, "caller": + if src, ok := a.Value.Any().(*slog.Source); ok { + a.Key = "caller" + switch lvl.String() { + case "debug": + a.Value = slog.StringValue(filepath.Base(src.File) + "(" + filepath.Base(src.Function) + "):" + strconv.Itoa(src.Line)) + default: + a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) + } + } else { + // If we can't cast the any from the value to + // an *slog.Source, it means the caller logged + // another attribute with a key of `caller`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to + // `logged_caller`. + a.Key = reservedKeyPrefix + key + } + case slog.LevelKey: + if lvl, ok := a.Value.Any().(slog.Level); ok { + a.Value = slog.StringValue(strings.ToLower(lvl.String())) + } else { + // If we can't cast the any from the value to + // an slog.Level, it means the caller logged + // another attribute with a key of `level`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to `logged_level`. + a.Key = reservedKeyPrefix + key + } + default: + } + return a + } +} + +func defaultReplaceAttr(_ []string, a slog.Attr) slog.Attr { + key := a.Key + switch key { + case slog.TimeKey: + if t, ok := a.Value.Any().(time.Time); ok { + a.Value = slog.TimeValue(t.UTC()) + } else { + // If we can't cast the any from the value to a + // time.Time, it means the caller logged + // another attribute with a key of `time`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to `logged_time`. + a.Key = reservedKeyPrefix + key + } + case slog.SourceKey: + if src, ok := a.Value.Any().(*slog.Source); ok { + a.Value = slog.StringValue(filepath.Base(src.File) + ":" + strconv.Itoa(src.Line)) + } else { + // If we can't cast the any from the value to + // an *slog.Source, it means the caller logged + // another attribute with a key of `source`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to + // `logged_source`. + a.Key = reservedKeyPrefix + key + } + case slog.LevelKey: + if _, ok := a.Value.Any().(slog.Level); !ok { + // If we can't cast the any from the value to + // an slog.Level, it means the caller logged + // another attribute with a key of `level`. + // Prevent duplicate keys (necessary for proper + // JSON) by renaming the key to + // `logged_level`. + a.Key = reservedKeyPrefix + key + } + default: + } + return a +} + // New returns a new slog.Logger. Each logged line will be annotated // with a timestamp. The output always goes to stderr. func New(config *Config) *slog.Logger { if config.Level == nil { - config.Level = &AllowedLevel{} - _ = config.Level.Set("info") + config.Level = NewLevel() } if config.Writer == nil { @@ -236,11 +251,11 @@ func New(config *Config) *slog.Logger { logHandlerOpts := &slog.HandlerOptions{ Level: config.Level.lvl, AddSource: true, - ReplaceAttr: defaultReplaceAttrFunc, + ReplaceAttr: defaultReplaceAttr, } if config.Style == GoKitStyle { - logHandlerOpts.ReplaceAttr = goKitStyleReplaceAttrFunc + logHandlerOpts.ReplaceAttr = newGoKitStyleReplaceAttrFunc(config.Level) } if config.Format != nil && config.Format.s == "json" { diff --git a/promslog/slog_test.go b/promslog/slog_test.go index 6da14d987..ea4e176c2 100644 --- a/promslog/slog_test.go +++ b/promslog/slog_test.go @@ -43,29 +43,29 @@ func TestDefaultConfig(t *testing.T) { } func TestUnmarshallLevel(t *testing.T) { - l := &AllowedLevel{} + l := NewLevel() err := yaml.Unmarshal([]byte(`debug`), l) if err != nil { t.Error(err) } - if l.s != "debug" { - t.Errorf("expected %s, got %s", "debug", l.s) + if got := l.String(); got != "debug" { + t.Errorf("expected %s, got %s", "debug", got) } } func TestUnmarshallEmptyLevel(t *testing.T) { - l := &AllowedLevel{} + l := NewLevel() err := yaml.Unmarshal([]byte(``), l) if err != nil { t.Error(err) } - if l.s != "" { - t.Errorf("expected empty level, got %s", l.s) + if got := l.String(); got != "info" { + t.Errorf("expected info (default) level, got %s", got) } } func TestUnmarshallBadLevel(t *testing.T) { - l := &AllowedLevel{} + l := NewLevel() err := yaml.Unmarshal([]byte(`debugg`), l) if err == nil { t.Error("expected error") @@ -74,8 +74,8 @@ func TestUnmarshallBadLevel(t *testing.T) { if err.Error() != expErr { t.Errorf("expected error %s, got %s", expErr, err.Error()) } - if l.s != "" { - t.Errorf("expected empty level, got %s", l.s) + if got := l.String(); got != "info" { + t.Errorf("expected info (default) level, got %s", got) } } From cc17dab08e7c33f70e3b5ab865d6789f0ff95761 Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Sun, 9 Feb 2025 09:19:44 +0100 Subject: [PATCH 09/78] Update common Prometheus files (#757) Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 0c00c410a..def9007ac 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -26,14 +26,14 @@ jobs: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Go - uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version: 1.23.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 + uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0 with: args: --verbose version: v1.63.4 From f220878c9338f4778cd224229b669e0787a10190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Tue, 11 Feb 2025 18:57:12 +0100 Subject: [PATCH 10/78] test validation with dots. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miroslav Šedivý --- model/labels_test.go | 5 +++++ model/metric_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/model/labels_test.go b/model/labels_test.go index 233954326..70a9128c9 100644 --- a/model/labels_test.go +++ b/model/labels_test.go @@ -141,6 +141,11 @@ func TestLabelNameIsValid(t *testing.T) { legacyValid: false, utf8Valid: false, }, + { + ln: "dot.in.name", + legacyValid: false, + utf8Valid: true, + }, } for _, s := range scenarios { diff --git a/model/metric_test.go b/model/metric_test.go index 6152c5481..5209a643b 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -145,6 +145,11 @@ func TestMetricNameIsLegacyValid(t *testing.T) { legacyValid: false, utf8Valid: false, }, + { + mn: "dot.in.name", + legacyValid: false, + utf8Valid: true, + }, } for _, s := range scenarios { From ca40aa08f025cc1a857cec6d6773363e959b4ea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 10:07:44 +0100 Subject: [PATCH 11/78] build(deps): bump google.golang.org/protobuf from 1.36.3 to 1.36.4 (#756) Bumps google.golang.org/protobuf from 1.36.3 to 1.36.4. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5d2f2cbb3..adf229778 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/stretchr/testify v1.10.0 golang.org/x/net v0.34.0 golang.org/x/oauth2 v0.25.0 - google.golang.org/protobuf v1.36.3 + google.golang.org/protobuf v1.36.4 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 17504316a..8766963dd 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 0db99daa6f43d55a09dbb714fe3173182977bf8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:40:43 +0100 Subject: [PATCH 12/78] build(deps): bump google.golang.org/protobuf from 1.36.4 to 1.36.5 (#761) Bumps google.golang.org/protobuf from 1.36.4 to 1.36.5. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index adf229778..43cfb6b9e 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/stretchr/testify v1.10.0 golang.org/x/net v0.34.0 golang.org/x/oauth2 v0.25.0 - google.golang.org/protobuf v1.36.4 + google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 8766963dd..51b5de64e 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From b516f6d622f0d210e24036fe64e2009fcc44885f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:41:00 +0100 Subject: [PATCH 13/78] build(deps): bump github.com/google/go-cmp from 0.6.0 to 0.7.0 (#763) Bumps [github.com/google/go-cmp](https://github.com/google/go-cmp) from 0.6.0 to 0.7.0. - [Release notes](https://github.com/google/go-cmp/releases) - [Commits](https://github.com/google/go-cmp/compare/v0.6.0...v0.7.0) --- updated-dependencies: - dependency-name: github.com/google/go-cmp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 43cfb6b9e..753e5efa7 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/alecthomas/kingpin/v2 v2.4.0 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/julienschmidt/httprouter v1.3.0 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f diff --git a/go.sum b/go.sum index 51b5de64e..8c8f76d62 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= From 56f6f3853a55cff5c626bc5a04993b3871ff260c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:46:46 +0100 Subject: [PATCH 14/78] build(deps): bump golang.org/x/net from 0.34.0 to 0.35.0 (#762) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.34.0 to 0.35.0. - [Commits](https://github.com/golang/net/compare/v0.34.0...v0.35.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 753e5efa7..b924a3f70 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.34.0 + golang.org/x/net v0.35.0 golang.org/x/oauth2 v0.25.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v2 v2.4.0 @@ -27,8 +27,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8c8f76d62..009bd785d 100644 --- a/go.sum +++ b/go.sum @@ -43,14 +43,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 6b9636ca14ecb6f8bb49d70aea6019c4cef93aab Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 4 Mar 2025 14:44:18 -0500 Subject: [PATCH 15/78] model: Clarify the purpose of model.NameValidationScheme (#765) There is a lot of confusion around this global variable. Clarify that it is meant to indicate that a project is aware of UTF-8 support, and that those projects should have their own flags to control validation mode. Signed-off-by: Owen Williams --- expfmt/decode_test.go | 4 ++-- model/metric.go | 28 ++++++++++++++++++++-------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/expfmt/decode_test.go b/expfmt/decode_test.go index 10b12b667..759ff7461 100644 --- a/expfmt/decode_test.go +++ b/expfmt/decode_test.go @@ -369,7 +369,7 @@ func TestProtoDecoder(t *testing.T) { var all model.Vector for { - model.NameValidationScheme = model.LegacyValidation + model.NameValidationScheme = model.LegacyValidation //nolint:staticcheck var smpls model.Vector err := dec.Decode(&smpls) if err != nil && errors.Is(err, io.EOF) { @@ -377,7 +377,7 @@ func TestProtoDecoder(t *testing.T) { } if scenario.legacyNameFail { require.Errorf(t, err, "Expected error when decoding without UTF-8 support enabled but got none") - model.NameValidationScheme = model.UTF8Validation + model.NameValidationScheme = model.UTF8Validation //nolint:staticcheck dec = &SampleDecoder{ Dec: &protoDecoder{r: strings.NewReader(scenario.in)}, Opts: &DecodeOptions{ diff --git a/model/metric.go b/model/metric.go index 5766107cf..a6b01755b 100644 --- a/model/metric.go +++ b/model/metric.go @@ -27,13 +27,25 @@ import ( ) var ( - // NameValidationScheme determines the method of name validation to be used by - // all calls to IsValidMetricName() and LabelName IsValid(). Setting UTF-8 - // mode in isolation from other components that don't support UTF-8 may result - // in bugs or other undefined behavior. This value can be set to - // LegacyValidation during startup if a binary is not UTF-8-aware binaries. To - // avoid need for locking, this value should be set once, ideally in an - // init(), before multiple goroutines are started. + // NameValidationScheme determines the global default method of the name + // validation to be used by all calls to IsValidMetricName() and LabelName + // IsValid(). + // + // Deprecated: This variable should not be used and might be removed in the + // far future. If you wish to stick to the legacy name validation use + // `IsValidLegacyMetricName()` and `LabelName.IsValidLegacy()` methods + // instead. This variable is here as an escape hatch for emergency cases, + // given the recent change from `LegacyValidation` to `UTF8Validation`, e.g., + // to delay UTF-8 migrations in time or aid in debugging unforeseen results of + // the change. In such a case, a temporary assignment to `LegacyValidation` + // value in the `init()` function in your main.go or so, could be considered. + // + // Historically we opted for a global variable for feature gating different + // validation schemes in operations that were not otherwise easily adjustable + // (e.g. Labels yaml unmarshaling). That could have been a mistake, a separate + // Labels structure or package might have been a better choice. Given the + // change was made and many upgraded the common already, we live this as-is + // with this warning and learning for the future. NameValidationScheme = UTF8Validation // NameEscapingScheme defines the default way that names will be escaped when @@ -50,7 +62,7 @@ var ( type ValidationScheme int const ( - // LegacyValidation is a setting that requirets that metric and label names + // LegacyValidation is a setting that requires that all metric and label names // conform to the original Prometheus character requirements described by // MetricNameRE and LabelNameRE. LegacyValidation ValidationScheme = iota From 0decf1fe7a23d909d3f5cd400b76b6d178eef576 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Fri, 7 Mar 2025 09:34:37 +0000 Subject: [PATCH 16/78] Fix spelling mistake in godoc (#766) This commit fixes a spelling mistake in the godoc for ResolvedAt. Signed-off-by: George Robinson --- model/alert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/alert.go b/model/alert.go index bd3a39e3e..460f554f2 100644 --- a/model/alert.go +++ b/model/alert.go @@ -65,7 +65,7 @@ func (a *Alert) Resolved() bool { return a.ResolvedAt(time.Now()) } -// ResolvedAt returns true off the activity interval ended before +// ResolvedAt returns true iff the activity interval ended before // the given timestamp. func (a *Alert) ResolvedAt(ts time.Time) bool { if a.EndsAt.IsZero() { From a9cc7f7df30d52a26a3937c9d32953ef2bf452c6 Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Sat, 8 Mar 2025 15:05:08 +0100 Subject: [PATCH 17/78] Update common Prometheus files (#767) Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 6 +++--- Makefile.common | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index def9007ac..e36a9f1a4 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -28,12 +28,12 @@ jobs: - name: Install Go uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: - go-version: 1.23.x + go-version: 1.24.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae # v6.2.0 + uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 # v6.5.0 with: args: --verbose - version: v1.63.4 + version: v1.64.6 diff --git a/Makefile.common b/Makefile.common index d1576bb31..8cb383859 100644 --- a/Makefile.common +++ b/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v1.64.6 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) From 227989ceacbb831801f90965ed5e8c9c55749774 Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Wed, 12 Mar 2025 14:48:42 -0300 Subject: [PATCH 18/78] otlptranslator: Add dependency free package that translator OTLP data into Prometheus metric/label names Signed-off-by: Arthur Silva Sens --- otlptranslator/constants.go | 58 +++ otlptranslator/doc.go | 24 + otlptranslator/label_builder.go | 54 +++ otlptranslator/label_builder_bench_test.go | 35 ++ otlptranslator/label_builder_test.go | 44 ++ otlptranslator/metric_name_builder.go | 286 ++++++++++++ .../metric_name_builder_bench_test.go | 134 ++++++ otlptranslator/metric_name_builder_test.go | 420 ++++++++++++++++++ 8 files changed, 1055 insertions(+) create mode 100644 otlptranslator/constants.go create mode 100644 otlptranslator/doc.go create mode 100644 otlptranslator/label_builder.go create mode 100644 otlptranslator/label_builder_bench_test.go create mode 100644 otlptranslator/label_builder_test.go create mode 100644 otlptranslator/metric_name_builder.go create mode 100644 otlptranslator/metric_name_builder_bench_test.go create mode 100644 otlptranslator/metric_name_builder_test.go diff --git a/otlptranslator/constants.go b/otlptranslator/constants.go new file mode 100644 index 000000000..d719daa1e --- /dev/null +++ b/otlptranslator/constants.go @@ -0,0 +1,58 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package otlptranslator + +const ( + // MetricMetadataTypeKey is the key used to store the original Prometheus + // type in metric metadata: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata + MetricMetadataTypeKey = "prometheus.type" + // ExemplarTraceIDKey is the key used to store the trace ID in Prometheus + // exemplars: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#exemplars + ExemplarTraceIDKey = "trace_id" + // ExemplarSpanIDKey is the key used to store the Span ID in Prometheus + // exemplars: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#exemplars + ExemplarSpanIDKey = "span_id" + // ScopeInfoMetricName is the name of the metric used to preserve scope + // attributes in Prometheus format: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope + ScopeInfoMetricName = "otel_scope_info" + // ScopeNameLabelKey is the name of the label key used to identify the name + // of the OpenTelemetry scope which produced the metric: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope + ScopeNameLabelKey = "otel_scope_name" + // ScopeVersionLabelKey is the name of the label key used to identify the + // version of the OpenTelemetry scope which produced the metric: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope + ScopeVersionLabelKey = "otel_scope_version" + // TargetInfoMetricName is the name of the metric used to preserve resource + // attributes in Prometheus format: + // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#resource-attributes-1 + // It originates from OpenMetrics: + // https://github.com/OpenObservability/OpenMetrics/blob/1386544931307dff279688f332890c31b6c5de36/specification/OpenMetrics.md#supporting-target-metadata-in-both-push-based-and-pull-based-systems + TargetInfoMetricName = "target_info" +) + +type MetricType string + +const ( + MetricTypeNonMonotonicCounter MetricType = "non-monotonic-counter" + MetricTypeMonotonicCounter MetricType = "monotonic-counter" + MetricTypeGauge MetricType = "gauge" + MetricTypeHistogram MetricType = "histogram" + MetricTypeExponentialHistogram MetricType = "exponential-histogram" + MetricTypeSummary MetricType = "summary" + MetricTypeUnknown MetricType = "unknown" +) diff --git a/otlptranslator/doc.go b/otlptranslator/doc.go new file mode 100644 index 000000000..88a7fed4b --- /dev/null +++ b/otlptranslator/doc.go @@ -0,0 +1,24 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// otlptranslator is a dependency free package that contains the logic for translating information, such as metric name, unit and type, +// from OpenTelemetry metrics to valid Prometheus metric and label names. +// +// Use BuildCompliantMetricName to build a metric name that complies with traditional Prometheus naming conventions. +// Such conventions exist from a time when Prometheus didn't have support for full UTF-8 characters in metric names. +// For more details see: https://prometheus.io/docs/practices/naming/ +// +// Use BuildMetricName to build a metric name that will be accepted by Prometheus with full UTF-8 support. +// +// Use NormalizeLabel to normalize a label name to a valid format that can be used in Prometheus before UTF-8 characters were supported. +package otlptranslator diff --git a/otlptranslator/label_builder.go b/otlptranslator/label_builder.go new file mode 100644 index 000000000..deb3bbd0c --- /dev/null +++ b/otlptranslator/label_builder.go @@ -0,0 +1,54 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package otlptranslator + +import ( + "regexp" + "strings" + "unicode" +) + +var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) + +// Normalizes the specified label to follow Prometheus label names standard. +// +// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels. +// +// Labels that start with non-letter rune will be prefixed with "key_". +// An exception is made for double-underscores which are allowed. +func NormalizeLabel(label string) string { + // Trivial case. + if len(label) == 0 { + return label + } + + label = SanitizeLabelName(label) + + // If label starts with a number, prepend with "key_". + if unicode.IsDigit(rune(label[0])) { + label = "key_" + label + } else if strings.HasPrefix(label, "_") && !strings.HasPrefix(label, "__") { + label = "key" + label + } + + return label +} + +// SanitizeLabelName replaces anything that doesn't match +// client_label.LabelNameRE with an underscore. +// Note: this does not handle all Prometheus label name restrictions (such as +// not starting with a digit 0-9), and hence should only be used if the label +// name is prefixed with a known valid string. +func SanitizeLabelName(name string) string { + return invalidLabelCharRE.ReplaceAllString(name, "_") +} diff --git a/otlptranslator/label_builder_bench_test.go b/otlptranslator/label_builder_bench_test.go new file mode 100644 index 000000000..6f2ba120e --- /dev/null +++ b/otlptranslator/label_builder_bench_test.go @@ -0,0 +1,35 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package otlptranslator + +import "testing" + +var labelBenchmarkInputs = []string{ + "", + "label:with:colons", + "LabelWithCapitalLetters", + "label!with&special$chars)", + "label_with_foreign_characters_字符", + "label.with.dots", + "123label", + "_label_starting_with_underscore", + "__label_starting_with_2underscores", +} + +func BenchmarkNormalizeLabel(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, input := range labelBenchmarkInputs { + NormalizeLabel(input) + } + } +} diff --git a/otlptranslator/label_builder_test.go b/otlptranslator/label_builder_test.go new file mode 100644 index 000000000..48856a215 --- /dev/null +++ b/otlptranslator/label_builder_test.go @@ -0,0 +1,44 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package otlptranslator + +import ( + "fmt" + "testing" +) + +func TestNormalizeLabel(t *testing.T) { + tests := []struct { + label string + expected string + }{ + {"", ""}, + {"label:with:colons", "label_with_colons"}, + {"LabelWithCapitalLetters", "LabelWithCapitalLetters"}, + {"label!with&special$chars)", "label_with_special_chars_"}, + {"label_with_foreign_characters_字符", "label_with_foreign_characters___"}, + {"label.with.dots", "label_with_dots"}, + {"123label", "key_123label"}, + {"_label_starting_with_underscore", "key_label_starting_with_underscore"}, + {"__label_starting_with_2underscores", "__label_starting_with_2underscores"}, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { + result := NormalizeLabel(test.label) + if test.expected != result { + t.Errorf("expected %s, got %s", test.expected, result) + } + }) + } +} diff --git a/otlptranslator/metric_name_builder.go b/otlptranslator/metric_name_builder.go new file mode 100644 index 000000000..a6b7d2757 --- /dev/null +++ b/otlptranslator/metric_name_builder.go @@ -0,0 +1,286 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlptranslator + +import ( + "regexp" + "slices" + "strings" + "unicode" +) + +// The map to translate OTLP units to Prometheus units +// OTLP metrics use the c/s notation as specified at https://ucum.org/ucum.html +// (See also https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/README.md#instrument-units) +// Prometheus best practices for units: https://prometheus.io/docs/practices/naming/#base-units +// OpenMetrics specification for units: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#units-and-base-units +var unitMap = map[string]string{ + // Time + "d": "days", + "h": "hours", + "min": "minutes", + "s": "seconds", + "ms": "milliseconds", + "us": "microseconds", + "ns": "nanoseconds", + + // Bytes + "By": "bytes", + "KiBy": "kibibytes", + "MiBy": "mebibytes", + "GiBy": "gibibytes", + "TiBy": "tibibytes", + "KBy": "kilobytes", + "MBy": "megabytes", + "GBy": "gigabytes", + "TBy": "terabytes", + + // SI + "m": "meters", + "V": "volts", + "A": "amperes", + "J": "joules", + "W": "watts", + "g": "grams", + + // Misc + "Cel": "celsius", + "Hz": "hertz", + "1": "", + "%": "percent", +} + +// The map that translates the "per" unit +// Example: s => per second (singular) +var perUnitMap = map[string]string{ + "s": "second", + "m": "minute", + "h": "hour", + "d": "day", + "w": "week", + "mo": "month", + "y": "year", +} + +var ( + nonMetricNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9:]`) + // Regexp for metric name characters that should be replaced with _. + invalidMetricCharRE = regexp.MustCompile(`[^a-zA-Z0-9:_]`) + multipleUnderscoresRE = regexp.MustCompile(`__+`) +) + +// BuildMetricName builds a valid metric name but without following Prometheus naming conventions. +// It doesn't do any character transformation, it only prefixes the metric name with the namespace, if any, +// and adds metric type suffixes, e.g. "_total" for counters and unit suffixes. +// +// Differently from BuildCompliantMetricName, it doesn't check for the presence of unit and type suffixes. +// If "addMetricSuffixes" is true, it will add them anyway. +// +// Please use BuildCompliantMetricName for a metric name that follows Prometheus naming conventions. +func BuildMetricName(name, unit string, metricType MetricType, addMetricSuffixes bool) string { + if addMetricSuffixes { + mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit) + if mainUnitSuffix != "" { + name = name + "_" + mainUnitSuffix + } + if perUnitSuffix != "" { + name = name + "_" + perUnitSuffix + } + + // Append _total for Counters + if metricType == MetricTypeMonotonicCounter { + name = name + "_total" + } + + // Append _ratio for metrics with unit "1" + // Some OTel receivers improperly use unit "1" for counters of objects + // See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions + // Until these issues have been fixed, we're appending `_ratio` for gauges ONLY + // Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons) + if unit == "1" && metricType == MetricTypeGauge { + name = name + "_ratio" + } + } + return name +} + +// BuildCompliantMetricName builds a Prometheus-compliant metric name for the specified metric. +// +// Metric name is prefixed with specified namespace and underscore (if any). +// Namespace is not cleaned up. Make sure specified namespace follows Prometheus +// naming convention. +// +// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels, +// https://prometheus.io/docs/practices/naming/#metric-and-label-naming +// and https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. +func BuildCompliantMetricName(name, unit string, metricType MetricType, addMetricSuffixes bool) string { + // Full normalization following standard Prometheus naming conventions + if addMetricSuffixes { + return normalizeName(name, unit, metricType) + } + + // Simple case (no full normalization, no units, etc.). + metricName := strings.Join(strings.FieldsFunc(name, func(r rune) bool { + return invalidMetricCharRE.MatchString(string(r)) + }), "_") + + // Metric name starts with a digit? Prefix it with an underscore. + if metricName != "" && unicode.IsDigit(rune(metricName[0])) { + metricName = "_" + metricName + } + + return metricName +} + +// Build a normalized name for the specified metric. +func normalizeName(metric, unit string, metricType MetricType) string { + // Split metric name into "tokens" (of supported metric name runes). + // Note that this has the side effect of replacing multiple consecutive underscores with a single underscore. + // This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. + nameTokens := strings.FieldsFunc( + metric, + func(r rune) bool { return nonMetricNameCharRE.MatchString(string(r)) }, + ) + + mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit) + nameTokens = addUnitTokens(nameTokens, CleanUpString(mainUnitSuffix), CleanUpString(perUnitSuffix)) + + // Append _total for Counters + if metricType == MetricTypeMonotonicCounter { + nameTokens = append(removeItem(nameTokens, "total"), "total") + } + + // Append _ratio for metrics with unit "1" + // Some OTel receivers improperly use unit "1" for counters of objects + // See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions + // Until these issues have been fixed, we're appending `_ratio` for gauges ONLY + // Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons) + if unit == "1" && metricType == MetricTypeGauge { + nameTokens = append(removeItem(nameTokens, "ratio"), "ratio") + } + + // Build the string from the tokens, separated with underscores + normalizedName := strings.Join(nameTokens, "_") + + // Metric name cannot start with a digit, so prefix it with "_" in this case + if normalizedName != "" && unicode.IsDigit(rune(normalizedName[0])) { + normalizedName = "_" + normalizedName + } + + return normalizedName +} + +// buildUnitSuffixes builds the main and per unit suffixes for the specified unit +// but doesn't do any special character transformation to accommodate Prometheus naming conventions. +// Removing trailing underscores or appending suffixes is done in the caller. +func buildUnitSuffixes(unit string) (mainUnitSuffix, perUnitSuffix string) { + // Split unit at the '/' if any + unitTokens := strings.SplitN(unit, "/", 2) + + if len(unitTokens) > 0 { + // Main unit + // Update if not blank and doesn't contain '{}' + mainUnitOTel := strings.TrimSpace(unitTokens[0]) + if mainUnitOTel != "" && !strings.ContainsAny(mainUnitOTel, "{}") { + mainUnitSuffix = unitMapGetOrDefault(mainUnitOTel) + } + + // Per unit + // Update if not blank and doesn't contain '{}' + if len(unitTokens) > 1 && unitTokens[1] != "" { + perUnitOTel := strings.TrimSpace(unitTokens[1]) + if perUnitOTel != "" && !strings.ContainsAny(perUnitOTel, "{}") { + perUnitSuffix = perUnitMapGetOrDefault(perUnitOTel) + } + if perUnitSuffix != "" { + perUnitSuffix = "per_" + perUnitSuffix + } + } + } + + return mainUnitSuffix, perUnitSuffix +} + +// Retrieve the Prometheus "basic" unit corresponding to the specified "basic" unit +// Returns the specified unit if not found in unitMap +func unitMapGetOrDefault(unit string) string { + if promUnit, ok := unitMap[unit]; ok { + return promUnit + } + return unit +} + +// Retrieve the Prometheus "per" unit corresponding to the specified "per" unit +// Returns the specified unit if not found in perUnitMap +func perUnitMapGetOrDefault(perUnit string) string { + if promPerUnit, ok := perUnitMap[perUnit]; ok { + return promPerUnit + } + return perUnit +} + +// addUnitTokens will add the suffixes to the nameTokens if they are not already present. +// It will also remove trailing underscores from the main suffix to avoid double underscores +// when joining the tokens. +// +// If the 'per' unit ends with underscore, the underscore will be removed. If the per unit is just +// 'per_', it will be entirely removed. +func addUnitTokens(nameTokens []string, mainUnitSuffix, perUnitSuffix string) []string { + if slices.Contains(nameTokens, mainUnitSuffix) { + mainUnitSuffix = "" + } + + if perUnitSuffix == "per_" { + perUnitSuffix = "" + } else { + perUnitSuffix = strings.TrimSuffix(perUnitSuffix, "_") + if slices.Contains(nameTokens, perUnitSuffix) { + perUnitSuffix = "" + } + } + + if perUnitSuffix != "" { + mainUnitSuffix = strings.TrimSuffix(mainUnitSuffix, "_") + } + + if mainUnitSuffix != "" { + nameTokens = append(nameTokens, mainUnitSuffix) + } + if perUnitSuffix != "" { + nameTokens = append(nameTokens, perUnitSuffix) + } + return nameTokens +} + +// CleanUpString cleans up a string so it matches model.LabelNameRE. +// CleanUpString is usually used to clean up unit strings, but can be used for any string, e.g. namespaces. +func CleanUpString(s string) string { + // Multiple consecutive underscores are replaced with a single underscore. + // This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. + return strings.TrimPrefix(multipleUnderscoresRE.ReplaceAllString( + nonMetricNameCharRE.ReplaceAllString(s, "_"), + "_", + ), "_") +} + +// Remove the specified value from the slice +func removeItem(slice []string, value string) []string { + newSlice := make([]string, 0, len(slice)) + for _, sliceEntry := range slice { + if sliceEntry != value { + newSlice = append(newSlice, sliceEntry) + } + } + return newSlice +} diff --git a/otlptranslator/metric_name_builder_bench_test.go b/otlptranslator/metric_name_builder_bench_test.go new file mode 100644 index 000000000..bc0851d39 --- /dev/null +++ b/otlptranslator/metric_name_builder_bench_test.go @@ -0,0 +1,134 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package otlptranslator + +import ( + "fmt" + "testing" +) + +var benchmarkInputs = []struct { + name string + metricName string + unit string + metricType MetricType +}{ + { + name: "simple_metric", + metricName: "http_requests", + unit: "", + metricType: MetricTypeGauge, + }, + { + name: "compound_unit", + metricName: "request_throughput", + unit: "By/s", + metricType: MetricTypeMonotonicCounter, + }, + { + name: "complex_unit", + metricName: "disk_usage", + unit: "KiBy/m", + metricType: MetricTypeGauge, + }, + { + name: "ratio_metric", + metricName: "cpu_utilization", + unit: "1", + metricType: MetricTypeGauge, + }, + { + name: "metric_with_dots", + metricName: "system.cpu.usage.idle", + unit: "%", + metricType: MetricTypeGauge, + }, + { + name: "metric_with_unicode", + metricName: "メモリ使用率", + unit: "By", + metricType: MetricTypeGauge, + }, + { + name: "metric_with_special_chars", + metricName: "error-rate@host{instance}/service#component", + unit: "ms", + metricType: MetricTypeMonotonicCounter, + }, + { + name: "metric_with_multiple_slashes", + metricName: "network/throughput/total", + unit: "By/s/min", + metricType: MetricTypeGauge, + }, + { + name: "metric_with_spaces", + metricName: "api response time total", + unit: "ms", + metricType: MetricTypeMonotonicCounter, + }, + { + name: "metric_with_curly_braces", + metricName: "custom_{tag}_metric", + unit: "{custom}/s", + metricType: MetricTypeGauge, + }, + { + name: "metric_starting_with_digit", + metricName: "5xx_error_count", + unit: "1", + metricType: MetricTypeMonotonicCounter, + }, + { + name: "empty_metric", + metricName: "", + unit: "", + metricType: MetricTypeGauge, + }, + { + name: "metric_with_SI_units", + metricName: "power_consumption", + unit: "W", + metricType: MetricTypeGauge, + }, + { + name: "metric_with_temperature", + metricName: "server_temperature", + unit: "Cel", + metricType: MetricTypeGauge, + }, +} + +func BenchmarkBuildMetricName(b *testing.B) { + for _, addSuffixes := range []bool{true, false} { + b.Run(fmt.Sprintf("with_metric_suffixes=%t", addSuffixes), func(b *testing.B) { + for _, input := range benchmarkInputs { + for i := 0; i < b.N; i++ { + BuildMetricName(input.metricName, input.unit, input.metricType, addSuffixes) + } + } + }) + } +} + +func BenchmarkBuildCompliantMetricName(b *testing.B) { + for _, addSuffixes := range []bool{true, false} { + b.Run(fmt.Sprintf("with_metric_suffixes=%t", addSuffixes), func(b *testing.B) { + for _, input := range benchmarkInputs { + for i := 0; i < b.N; i++ { + BuildCompliantMetricName(input.metricName, input.unit, input.metricType, addSuffixes) + } + } + }) + } +} diff --git a/otlptranslator/metric_name_builder_test.go b/otlptranslator/metric_name_builder_test.go new file mode 100644 index 000000000..59e12d708 --- /dev/null +++ b/otlptranslator/metric_name_builder_test.go @@ -0,0 +1,420 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package otlptranslator + +import ( + "reflect" + "testing" +) + +func TestBuildMetricName(t *testing.T) { + tests := []struct { + name string + metricName string + unit string + metricType MetricType + addMetricSuffixes bool + expected string + }{ + { + name: "simple metric without suffixes", + metricName: "http_requests", + unit: "", + metricType: MetricTypeGauge, + addMetricSuffixes: false, + expected: "http_requests", + }, + { + name: "counter with total suffix", + metricName: "http_requests", + unit: "", + metricType: MetricTypeMonotonicCounter, + addMetricSuffixes: true, + expected: "http_requests_total", + }, + { + name: "gauge with time unit", + metricName: "request_duration", + unit: "s", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "request_duration_seconds", + }, + { + name: "counter with time unit", + metricName: "request_duration", + unit: "ms", + metricType: MetricTypeMonotonicCounter, + addMetricSuffixes: true, + expected: "request_duration_milliseconds_total", + }, + { + name: "gauge with compound unit", + metricName: "throughput", + unit: "By/s", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "throughput_bytes_per_second", + }, + { + name: "ratio metric", + metricName: "cpu_utilization", + unit: "1", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "cpu_utilization_ratio", + }, + { + name: "counter with unit 1 (no ratio suffix)", + metricName: "error_count", + unit: "1", + metricType: MetricTypeMonotonicCounter, + addMetricSuffixes: true, + expected: "error_count_total", + }, + { + name: "metric with byte units", + metricName: "memory_usage", + unit: "MiBy", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "memory_usage_mebibytes", + }, + { + name: "metric with SI units", + metricName: "temperature", + unit: "Cel", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "temperature_celsius", + }, + { + name: "metric with dots", + metricName: "system.cpu.usage", + unit: "1", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "system.cpu.usage_ratio", + }, + { + name: "metric with japanese characters (memory usage rate)", + metricName: "メモリ使用率", // memori shiyouritsu (memory usage rate) xD + unit: "By", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "メモリ使用率_bytes", + }, + { + name: "metric with mixed special characters (system.memory.usage.rate)", + metricName: "system.メモリ.usage.率", // system.memory.usage.rate + unit: "By/s", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "system.メモリ.usage.率_bytes_per_second", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := BuildMetricName(tt.metricName, tt.unit, tt.metricType, tt.addMetricSuffixes) + if tt.expected != result { + t.Errorf("expected %s, got %s", tt.expected, result) + } + }) + } +} + +func TestBuildUnitSuffixes(t *testing.T) { + tests := []struct { + name string + unit string + expectedMain string + expectedPerUnit string + }{ + { + name: "empty unit", + unit: "", + expectedMain: "", + expectedPerUnit: "", + }, + { + name: "simple time unit", + unit: "s", + expectedMain: "seconds", + expectedPerUnit: "", + }, + { + name: "compound unit", + unit: "By/s", + expectedMain: "bytes", + expectedPerUnit: "per_second", + }, + { + name: "complex compound unit", + unit: "KiBy/m", + expectedMain: "kibibytes", + expectedPerUnit: "per_minute", + }, + { + name: "unit with spaces", + unit: " ms / s ", + expectedMain: "milliseconds", + expectedPerUnit: "per_second", + }, + { + name: "invalid unit", + unit: "invalid", + expectedMain: "invalid", + expectedPerUnit: "", + }, + { + name: "unit with curly braces", + unit: "{custom}/s", + expectedMain: "", + expectedPerUnit: "per_second", + }, + { + name: "multiple slashes", + unit: "By/s/h", + expectedMain: "bytes", + expectedPerUnit: "per_s/h", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mainUnit, perUnit := buildUnitSuffixes(tt.unit) + if tt.expectedMain != mainUnit { + t.Errorf("expected main unit %s, got %s", tt.expectedMain, mainUnit) + } + if tt.expectedPerUnit != perUnit { + t.Errorf("expected per unit %s, got %s", tt.expectedPerUnit, perUnit) + } + }) + } +} + +func TestBuildCompliantMetricName(t *testing.T) { + tests := []struct { + name string + metricName string + unit string + metricType MetricType + addMetricSuffixes bool + expected string + }{ + { + name: "simple valid metric name", + metricName: "http_requests", + unit: "", + metricType: MetricTypeGauge, + addMetricSuffixes: false, + expected: "http_requests", + }, + { + name: "metric name with invalid characters", + metricName: "http-requests@in_flight", + unit: "", + metricType: MetricTypeNonMonotonicCounter, + addMetricSuffixes: false, + expected: "http_requests_in_flight", + }, + { + name: "metric name starting with digit", + metricName: "5xx_errors", + unit: "", + metricType: MetricTypeGauge, + addMetricSuffixes: false, + expected: "_5xx_errors", + }, + { + name: "metric name with multiple consecutive invalid chars", + metricName: "api..//request--time", + unit: "", + metricType: MetricTypeGauge, + addMetricSuffixes: false, + expected: "api_request_time", + }, + { + name: "full normalization with units and type", + metricName: "system.cpu-utilization", + unit: "ms/s", + metricType: MetricTypeMonotonicCounter, + addMetricSuffixes: true, + expected: "system_cpu_utilization_milliseconds_per_second_total", + }, + { + name: "metric with special characters and ratio", + metricName: "memory.usage%rate", + unit: "1", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "memory_usage_rate_ratio", + }, + { + name: "metric with unicode characters", + metricName: "error_rate_£_€_¥", + unit: "", + metricType: MetricTypeGauge, + addMetricSuffixes: false, + expected: "error_rate_____", + }, + { + name: "metric with multiple spaces", + metricName: "api response time", + unit: "ms", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "api_response_time_milliseconds", + }, + { + name: "metric with colons (valid prometheus chars)", + metricName: "app:request:latency", + unit: "s", + metricType: MetricTypeGauge, + addMetricSuffixes: true, + expected: "app:request:latency_seconds", + }, + { + name: "empty metric name", + metricName: "", + unit: "", + metricType: MetricTypeGauge, + addMetricSuffixes: false, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := BuildCompliantMetricName(tt.metricName, tt.unit, tt.metricType, tt.addMetricSuffixes) + if tt.expected != result { + t.Errorf("expected %s, got %s", tt.expected, result) + } + }) + } +} + +func TestAddUnitTokens(t *testing.T) { + tests := []struct { + nameTokens []string + mainUnitSuffix string + perUnitSuffix string + expected []string + }{ + {[]string{}, "", "", []string{}}, + {[]string{"token1"}, "main", "", []string{"token1", "main"}}, + {[]string{"token1"}, "", "per", []string{"token1", "per"}}, + {[]string{"token1"}, "main", "per", []string{"token1", "main", "per"}}, + {[]string{"token1", "per"}, "main", "per", []string{"token1", "per", "main"}}, + {[]string{"token1", "main"}, "main", "per", []string{"token1", "main", "per"}}, + {[]string{"token1"}, "main_", "per", []string{"token1", "main", "per"}}, + {[]string{"token1"}, "main_unit", "per_seconds_", []string{"token1", "main_unit", "per_seconds"}}, // trailing underscores are removed + {[]string{"token1"}, "main_unit", "per_", []string{"token1", "main_unit"}}, // 'per_' is removed entirely + } + + for _, test := range tests { + result := addUnitTokens(test.nameTokens, test.mainUnitSuffix, test.perUnitSuffix) + if !reflect.DeepEqual(test.expected, result) { + t.Errorf("expected %v, got %v", test.expected, result) + } + } +} + +func TestRemoveItem(t *testing.T) { + if !reflect.DeepEqual([]string{}, removeItem([]string{}, "test")) { + t.Errorf("expected %v, got %v", []string{}, removeItem([]string{}, "test")) + } + if !reflect.DeepEqual([]string{}, removeItem([]string{}, "")) { + t.Errorf("expected %v, got %v", []string{}, removeItem([]string{}, "")) + } + if !reflect.DeepEqual([]string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "d")) { + t.Errorf("expected %v, got %v", []string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "d")) + } + if !reflect.DeepEqual([]string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "")) { + t.Errorf("expected %v, got %v", []string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "")) + } + if !reflect.DeepEqual([]string{"a", "b"}, removeItem([]string{"a", "b", "c"}, "c")) { + t.Errorf("expected %v, got %v", []string{"a", "b"}, removeItem([]string{"a", "b", "c"}, "c")) + } + if !reflect.DeepEqual([]string{"a", "c"}, removeItem([]string{"a", "b", "c"}, "b")) { + t.Errorf("expected %v, got %v", []string{"a", "c"}, removeItem([]string{"a", "b", "c"}, "b")) + } + if !reflect.DeepEqual([]string{"b", "c"}, removeItem([]string{"a", "b", "c"}, "a")) { + t.Errorf("expected %v, got %v", []string{"b", "c"}, removeItem([]string{"a", "b", "c"}, "a")) + } +} + +func TestCleanUpStrings(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "already valid string", + input: "valid_metric_name", + expected: "valid_metric_name", + }, + { + name: "invalid characters", + input: "metric-name@with#special$chars", + expected: "metric_name_with_special_chars", + }, + { + name: "multiple consecutive invalid chars", + input: "metric---name###special", + expected: "metric_name_special", + }, + { + name: "leading invalid chars", + input: "@#$metric_name", + expected: "metric_name", + }, + { + name: "trailing invalid chars", + input: "metric_name@#$", + expected: "metric_name_", + }, + { + name: "multiple consecutive underscores", + input: "metric___name____test", + expected: "metric_name_test", + }, + { + name: "empty string", + input: "", + expected: "", + }, + { + name: "only invalid chars", + input: "@#$%^&", + expected: "", + }, + { + name: "colons are valid", + input: "system.cpu:usage.rate", + expected: "system_cpu:usage_rate", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := CleanUpString(tt.input) + if result != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, result) + } + }) + } +} From b35ad995d20d280cd0bc1a30386a4e3bbdc032f5 Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Thu, 13 Mar 2025 13:27:31 -0300 Subject: [PATCH 19/78] Add test case for BuildCompliantMetricName with a metric that starts with a digit and addMetricSuffixes is true Signed-off-by: Arthur Silva Sens --- otlptranslator/metric_name_builder_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/otlptranslator/metric_name_builder_test.go b/otlptranslator/metric_name_builder_test.go index 59e12d708..49b729b43 100644 --- a/otlptranslator/metric_name_builder_test.go +++ b/otlptranslator/metric_name_builder_test.go @@ -238,6 +238,14 @@ func TestBuildCompliantMetricName(t *testing.T) { addMetricSuffixes: false, expected: "_5xx_errors", }, + { + name: "metric name starting with digit, with suffixes", + metricName: "5xx_errors", + unit: "", + metricType: MetricTypeMonotonicCounter, + addMetricSuffixes: true, + expected: "_5xx_errors_total", + }, { name: "metric name with multiple consecutive invalid chars", metricName: "api..//request--time", From 64474d220f8031d6d6976edfff0fba866b09132f Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Fri, 21 Mar 2025 19:15:05 -0300 Subject: [PATCH 20/78] Add deprecation notice to otlptranslator Signed-off-by: Arthur Silva Sens --- otlptranslator/doc.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/otlptranslator/doc.go b/otlptranslator/doc.go index 88a7fed4b..e50880649 100644 --- a/otlptranslator/doc.go +++ b/otlptranslator/doc.go @@ -21,4 +21,6 @@ // Use BuildMetricName to build a metric name that will be accepted by Prometheus with full UTF-8 support. // // Use NormalizeLabel to normalize a label name to a valid format that can be used in Prometheus before UTF-8 characters were supported. +// +// Deprecated: Use github.com/prometheus/otlptranslator instead. package otlptranslator From e4b37769578d9607593f5dc76d0e28f8cfbf180c Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Sun, 23 Mar 2025 17:00:22 +0100 Subject: [PATCH 21/78] Update common Prometheus files (#774) Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e36a9f1a4..b404ce7c7 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -33,7 +33,7 @@ jobs: run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 # v6.5.0 + uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 with: args: --verbose version: v1.64.6 From 6071fb30e0ea565f9b12daa7f180bdd14665a1f7 Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Mon, 31 Mar 2025 11:54:04 +0200 Subject: [PATCH 22/78] Update common Prometheus files (#775) Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index b404ce7c7..5342cbe08 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -26,7 +26,7 @@ jobs: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Go - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 + uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 with: go-version: 1.24.x - name: Install snmp_exporter/generator dependencies From 3874acf0e5935acaf512d09647a76755e3565973 Mon Sep 17 00:00:00 2001 From: Ben Kochie Date: Mon, 31 Mar 2025 12:16:02 +0200 Subject: [PATCH 23/78] Update Go (#770) * Update Go to support 1.24. * Update minimum Go version to 1.23.0. * Bump Go modules. Signed-off-by: SuperQ --- .circleci/config.yml | 7 +++---- assets/go.mod | 2 +- go.mod | 10 +++++----- go.sum | 16 ++++++++-------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8f035514d..95fb827c5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,15 +93,14 @@ workflows: matrix: parameters: go_version: - - "1.21" - - "1.22" - "1.23" + - "1.24" - test-assets: name: assets-go-<< matrix.go_version >> matrix: parameters: go_version: - - "1.23" + - "1.24" - style: name: style - go_version: "1.23" + go_version: "1.24" diff --git a/assets/go.mod b/assets/go.mod index 6e1afe3f6..9698dd659 100644 --- a/assets/go.mod +++ b/assets/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/common/assets -go 1.21 +go 1.23.0 require github.com/stretchr/testify v1.10.0 diff --git a/go.mod b/go.mod index b924a3f70..04895422c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/common -go 1.21 +go 1.23.0 require ( github.com/alecthomas/kingpin/v2 v2.4.0 @@ -10,8 +10,8 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.35.0 - golang.org/x/oauth2 v0.25.0 + golang.org/x/net v0.37.0 + golang.org/x/oauth2 v0.28.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v2 v2.4.0 ) @@ -27,8 +27,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 009bd785d..1f913df82 100644 --- a/go.sum +++ b/go.sum @@ -43,14 +43,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From cffef2dc8a5c3958afcfd83db827ef2d8680521f Mon Sep 17 00:00:00 2001 From: Kemal Akkoyun Date: Tue, 8 Apr 2025 11:10:51 +0200 Subject: [PATCH 24/78] chore: Upgrade golangci-lint to v2 (#779) - Migrate the configuration file - Update the tooling version and CI - Apply auto-fixes Signed-off-by: Kemal Akkoyun --- .github/workflows/golangci-lint.yml | 4 +- .golangci.yml | 92 +++++++++++++++++------------ Makefile.common | 2 +- config/http_config.go | 16 ++--- expfmt/text_parse.go | 4 +- model/labels.go | 3 +- route/route_test.go | 2 +- 7 files changed, 69 insertions(+), 54 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 5342cbe08..3893ef86b 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -33,7 +33,7 @@ jobs: run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@55c2c1448f86e01eaae002a5a3a9624417608d84 # v6.5.2 + uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 with: args: --verbose - version: v1.64.6 + version: v2.0.2 diff --git a/.golangci.yml b/.golangci.yml index e2f3e9459..844fc2e55 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,46 +1,60 @@ -issues: - max-issues-per-linter: 0 - max-same-issues: 0 +version: "2" linters: enable: - - errcheck - errorlint - - gofumpt - - goimports - - gosimple - - govet - - ineffassign - misspell - perfsprint - revive - - staticcheck - testifylint - - unused -linters-settings: - goimports: - local-prefixes: github.com/prometheus/common - perfsprint: - # Optimizes even if it requires an int or uint type cast. - int-conversion: true - # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. - err-error: true - # Optimizes `fmt.Errorf`. - errorf: true - # Optimizes `fmt.Sprintf` with only one argument. - sprintf1: true - # Optimizes into strings concatenation. - strconcat: false - revive: - rules: - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter - - name: unused-parameter - severity: warning - disabled: true - testifylint: - disable: - - go-require - enable-all: true - formatter: - require-f-funcs: true -run: - timeout: 5m \ No newline at end of file + settings: + perfsprint: + # Optimizes even if it requires an int or uint type cast. + int-conversion: true + # Optimizes into `err.Error()` even if it is only equivalent for non-nil errors. + err-error: true + # Optimizes `fmt.Errorf`. + errorf: true + # Optimizes `fmt.Sprintf` with only one argument. + sprintf1: true + # Optimizes into strings concatenation. + strconcat: false + revive: + rules: + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + - name: unused-parameter + severity: warning + disabled: true + testifylint: + enable-all: true + disable: + - go-require + formatter: + require-f-funcs: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 0 + max-same-issues: 0 +formatters: + enable: + - gofumpt + - goimports + settings: + goimports: + local-prefixes: + - github.com/prometheus/common + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile.common b/Makefile.common index 8cb383859..81bad5f42 100644 --- a/Makefile.common +++ b/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.64.6 +GOLANGCI_LINT_VERSION ?= v2.0.2 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) diff --git a/config/http_config.go b/config/http_config.go index 63809083a..5d3f1941b 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -225,7 +225,7 @@ func (u *URL) UnmarshalJSON(data []byte) error { // MarshalJSON implements the json.Marshaler interface for URL. func (u URL) MarshalJSON() ([]byte, error) { if u.URL != nil { - return json.Marshal(u.URL.String()) + return json.Marshal(u.String()) } return []byte("null"), nil } @@ -251,7 +251,7 @@ func (o *OAuth2) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal((*plain)(o)); err != nil { return err } - return o.ProxyConfig.Validate() + return o.Validate() } // UnmarshalJSON implements the json.Marshaler interface for URL. @@ -260,7 +260,7 @@ func (o *OAuth2) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, (*plain)(o)); err != nil { return err } - return o.ProxyConfig.Validate() + return o.Validate() } // SetDirectory joins any relative file paths with dir. @@ -604,8 +604,8 @@ func NewRoundTripperFromConfigWithContext(ctx context.Context, cfg HTTPClientCon // The only timeout we care about is the configured scrape timeout. // It is applied on request. So we leave out any timings here. var rt http.RoundTripper = &http.Transport{ - Proxy: cfg.ProxyConfig.Proxy(), - ProxyConnectHeader: cfg.ProxyConfig.GetProxyConnectHeader(), + Proxy: cfg.Proxy(), + ProxyConnectHeader: cfg.GetProxyConnectHeader(), MaxIdleConns: 20000, MaxIdleConnsPerHost: 1000, // see https://github.com/golang/go/issues/13801 DisableKeepAlives: !opts.keepAlivesEnabled, @@ -914,8 +914,8 @@ func (rt *oauth2RoundTripper) newOauth2TokenSource(req *http.Request, secret str tlsTransport := func(tlsConfig *tls.Config) (http.RoundTripper, error) { return &http.Transport{ TLSClientConfig: tlsConfig, - Proxy: rt.config.ProxyConfig.Proxy(), - ProxyConnectHeader: rt.config.ProxyConfig.GetProxyConnectHeader(), + Proxy: rt.config.Proxy(), + ProxyConnectHeader: rt.config.GetProxyConnectHeader(), DisableKeepAlives: !rt.opts.keepAlivesEnabled, MaxIdleConns: 20, MaxIdleConnsPerHost: 1, // see https://github.com/golang/go/issues/13801 @@ -1508,7 +1508,7 @@ func (c *ProxyConfig) Proxy() (fn func(*http.Request) (*url.URL, error)) { } return } - if c.ProxyURL.URL != nil && c.ProxyURL.URL.String() != "" { + if c.ProxyURL.URL != nil && c.ProxyURL.String() != "" { if c.NoProxy == "" { c.proxyFunc = http.ProxyURL(c.ProxyURL.URL) return diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index b4607fe4d..4067978a1 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -345,8 +345,8 @@ func (p *TextParser) startLabelName() stateFn { } // Special summary/histogram treatment. Don't add 'quantile' and 'le' // labels to 'real' labels. - if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) && - !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) { + if (p.currentMF.GetType() != dto.MetricType_SUMMARY || p.currentLabelPair.GetName() != model.QuantileLabel) && + (p.currentMF.GetType() != dto.MetricType_HISTOGRAM || p.currentLabelPair.GetName() != model.BucketLabel) { p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair) } // Check for duplicate label names. diff --git a/model/labels.go b/model/labels.go index f4a387605..de83afe93 100644 --- a/model/labels.go +++ b/model/labels.go @@ -122,7 +122,8 @@ func (ln LabelName) IsValidLegacy() bool { return false } for i, b := range ln { - if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { + // TODO: Apply De Morgan's law. Make sure there are tests for this. + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck return false } } diff --git a/route/route_test.go b/route/route_test.go index 24977b32a..87c32efd1 100644 --- a/route/route_test.go +++ b/route/route_test.go @@ -154,7 +154,7 @@ func TestInstrumentations(t *testing.T) { r, err := http.NewRequest("GET", "http://localhost:9090/foo", nil) require.NoErrorf(t, err, "Error building test request: %s", err) c.router.ServeHTTP(nil, r) - require.Equalf(t, len(c.want), len(got), "Unexpected value: want %q, got %q", c.want, got) + require.Lenf(t, got, len(c.want), "Unexpected value: want %q, got %q", c.want, got) for i, v := range c.want { require.Equalf(t, v, got[i], "Unexpected value: want %q, got %q", c.want, got) } From 09eca2df699a72f3533dd26415f5a3d5eff00fd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:11:21 +0200 Subject: [PATCH 25/78] build(deps): bump golang.org/x/net from 0.37.0 to 0.38.0 (#777) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.37.0 to 0.38.0. - [Commits](https://github.com/golang/net/compare/v0.37.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 +++- go.sum | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 04895422c..24f545ccb 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/prometheus/common go 1.23.0 +toolchain go1.24.1 + require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/google/go-cmp v0.7.0 @@ -10,7 +12,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.37.0 + golang.org/x/net v0.38.0 golang.org/x/oauth2 v0.28.0 google.golang.org/protobuf v1.36.5 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 1f913df82..f04f8df9b 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= From 318ef65e16ea1982f0133b858bf0bdf89ddc3f91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:11:33 +0200 Subject: [PATCH 26/78] build(deps): bump google.golang.org/protobuf from 1.36.5 to 1.36.6 (#776) Bumps google.golang.org/protobuf from 1.36.5 to 1.36.6. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 24f545ccb..f62b8a0ec 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/stretchr/testify v1.10.0 golang.org/x/net v0.38.0 golang.org/x/oauth2 v0.28.0 - google.golang.org/protobuf v1.36.5 + google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index f04f8df9b..ca08dc8c0 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 31ee791410e1daf5df0bb760fdc3b3ab8ba44492 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Thu, 10 Apr 2025 13:39:48 +0200 Subject: [PATCH 27/78] promslog: Use the default timezone (again) As per the dev-summit discussion, we want to use the default timezone as selected by slog after all rather than a hardcoded UTC. (This was accidentally introduced earlier when moving from gokit to slog, but then switched back to hardcoded UTC in PR #735.) Environments that have configured UTC anyway won't see any difference, but those with other timezones configured will see the configured timezone for the timestamps in their logs. This is in line with how slog behaves by default. Example timestamp _before_ this change and also _after_ this change with UTC configured in the environment via a suitable way (e.g. setting `TZ=UTC` on Linux): time=2025-04-10T12:00:38.179Z Example timestamp _after_ this change with location Europe/Berlin, i.e. timezone is CET or CEST: time=2025-04-10T14:00:03.120+02:00 Note that the precise delta to UTC is in the timestamp. So this change will also be transparent to the usual logs processing tools. Signed-off-by: beorn7 --- promslog/slog.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/promslog/slog.go b/promslog/slog.go index f9f899663..acce21a3e 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -200,9 +200,8 @@ func defaultReplaceAttr(_ []string, a slog.Attr) slog.Attr { key := a.Key switch key { case slog.TimeKey: - if t, ok := a.Value.Any().(time.Time); ok { - a.Value = slog.TimeValue(t.UTC()) - } else { + // Note that we do not change the timezone to UTC anymore. + if _, ok := a.Value.Any().(time.Time); !ok { // If we can't cast the any from the value to a // time.Time, it means the caller logged // another attribute with a key of `time`. From 8c1fb2e840ea276dde21472d212a8f2187964946 Mon Sep 17 00:00:00 2001 From: prombot Date: Sun, 4 May 2025 17:48:38 +0000 Subject: [PATCH 28/78] Update common Prometheus files Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 2 +- Makefile.common | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 3893ef86b..672dd424d 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -36,4 +36,4 @@ jobs: uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 with: args: --verbose - version: v2.0.2 + version: v2.1.5 diff --git a/Makefile.common b/Makefile.common index 81bad5f42..d8b798909 100644 --- a/Makefile.common +++ b/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v2.0.2 +GOLANGCI_LINT_VERSION ?= v2.1.5 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) From 4ca345a532a22eb32a996799d63112c7fa5bba4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 12:57:14 +0200 Subject: [PATCH 29/78] build(deps): bump github.com/prometheus/client_model from 0.6.1 to 0.6.2 (#784) Bumps [github.com/prometheus/client_model](https://github.com/prometheus/client_model) from 0.6.1 to 0.6.2. - [Release notes](https://github.com/prometheus/client_model/releases) - [Commits](https://github.com/prometheus/client_model/compare/v0.6.1...v0.6.2) --- updated-dependencies: - dependency-name: github.com/prometheus/client_model dependency-version: 0.6.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f62b8a0ec..ab159483f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/julienschmidt/httprouter v1.3.0 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f - github.com/prometheus/client_model v0.6.1 + github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.38.0 golang.org/x/oauth2 v0.28.0 diff --git a/go.sum b/go.sum index ca08dc8c0..85bd5561e 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= From 804803180a6b754f7836e3c10824167cbaafc7a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 12:57:25 +0200 Subject: [PATCH 30/78] build(deps): bump golang.org/x/oauth2 from 0.28.0 to 0.29.0 (#785) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.28.0 to 0.29.0. - [Commits](https://github.com/golang/oauth2/compare/v0.28.0...v0.29.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ab159483f..2afcba53f 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.38.0 - golang.org/x/oauth2 v0.28.0 + golang.org/x/oauth2 v0.29.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 85bd5561e..b62133096 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= From b8eddd7c08882601859559bd50f37717ba289807 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 13:10:30 +0200 Subject: [PATCH 31/78] build(deps): bump golang.org/x/net from 0.38.0 to 0.39.0 (#786) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.38.0 to 0.39.0. - [Commits](https://github.com/golang/net/compare/v0.38.0...v0.39.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 2afcba53f..c4f92e7b7 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.38.0 + golang.org/x/net v0.40.0 golang.org/x/oauth2 v0.29.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v2 v2.4.0 @@ -29,8 +29,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b62133096..c8102f6df 100644 --- a/go.sum +++ b/go.sum @@ -43,14 +43,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 487c1803a6305728de302498dadc0cab81bed0f2 Mon Sep 17 00:00:00 2001 From: TJ Hoplock <33664289+tjhop@users.noreply.github.com> Date: Thu, 15 May 2025 07:10:56 -0400 Subject: [PATCH 32/78] refactor(promslog): make `NewNopLogger()` wrapper around `New()` (#783) * refactor(promslog): make `NewNopLogger()` wrapper around `New()` While discussing the fix for prometheus/prometheus#16466, it was pointed out that our `promslog.NewNopLogger()` convenience function would benefit from using the same logic used when initializing loggers with `promslog.New()`. By refactoring NewNopLogger to wrap New, it inherits that same setup logic. Signed-off-by: TJ Hoplock * fix(promslog): don't set NewNopLogger to debug level Signed-off-by: TJ Hoplock --------- Signed-off-by: TJ Hoplock --- promslog/slog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/promslog/slog.go b/promslog/slog.go index acce21a3e..3bd817328 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -266,5 +266,5 @@ func New(config *Config) *slog.Logger { // NewNopLogger is a convenience function to return an slog.Logger that writes // to io.Discard. func NewNopLogger() *slog.Logger { - return slog.New(slog.NewTextHandler(io.Discard, nil)) + return New(&Config{Writer: io.Discard}) } From 6a35e025c070cb79caad69d0ee61efe25f4742cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 14:11:55 +0200 Subject: [PATCH 33/78] build(deps): bump golang.org/x/oauth2 from 0.29.0 to 0.30.0 (#788) Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.29.0 to 0.30.0. - [Commits](https://github.com/golang/oauth2/compare/v0.29.0...v0.30.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.30.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c4f92e7b7..366bf8312 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 golang.org/x/net v0.40.0 - golang.org/x/oauth2 v0.29.0 + golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index c8102f6df..7a637f691 100644 --- a/go.sum +++ b/go.sum @@ -45,8 +45,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= From 098669c08af8eee3da33d1d4a4f242a5242cccb8 Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Sat, 17 May 2025 12:06:11 +0200 Subject: [PATCH 34/78] Update common Prometheus files (#789) Signed-off-by: prombot --- Makefile.common | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile.common b/Makefile.common index d8b798909..4de21512f 100644 --- a/Makefile.common +++ b/Makefile.common @@ -62,6 +62,7 @@ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v2.1.5 +GOLANGCI_FMT_OPTS ?= # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) @@ -156,9 +157,13 @@ $(GOTEST_DIR): @mkdir -p $@ .PHONY: common-format -common-format: +common-format: $(GOLANGCI_LINT) @echo ">> formatting code" $(GO) fmt $(pkgs) +ifdef GOLANGCI_LINT + @echo ">> formatting code with golangci-lint" + $(GOLANGCI_LINT) fmt $(GOLANGCI_FMT_OPTS) +endif .PHONY: common-vet common-vet: @@ -248,8 +253,8 @@ $(PROMU): cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) -.PHONY: proto -proto: +.PHONY: common-proto +common-proto: @echo ">> generating code from proto files" @./scripts/genproto.sh From 1de8cfa8452bf1c800b1e7a7705c1c37410d3326 Mon Sep 17 00:00:00 2001 From: Arthur Silva Sens Date: Tue, 27 May 2025 14:54:38 -0300 Subject: [PATCH 35/78] Remove otlptranslator package Signed-off-by: Arthur Silva Sens --- otlptranslator/constants.go | 58 --- otlptranslator/doc.go | 26 -- otlptranslator/label_builder.go | 54 --- otlptranslator/label_builder_bench_test.go | 35 -- otlptranslator/label_builder_test.go | 44 -- otlptranslator/metric_name_builder.go | 286 ------------ .../metric_name_builder_bench_test.go | 134 ------ otlptranslator/metric_name_builder_test.go | 428 ------------------ 8 files changed, 1065 deletions(-) delete mode 100644 otlptranslator/constants.go delete mode 100644 otlptranslator/doc.go delete mode 100644 otlptranslator/label_builder.go delete mode 100644 otlptranslator/label_builder_bench_test.go delete mode 100644 otlptranslator/label_builder_test.go delete mode 100644 otlptranslator/metric_name_builder.go delete mode 100644 otlptranslator/metric_name_builder_bench_test.go delete mode 100644 otlptranslator/metric_name_builder_test.go diff --git a/otlptranslator/constants.go b/otlptranslator/constants.go deleted file mode 100644 index d719daa1e..000000000 --- a/otlptranslator/constants.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package otlptranslator - -const ( - // MetricMetadataTypeKey is the key used to store the original Prometheus - // type in metric metadata: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#metric-metadata - MetricMetadataTypeKey = "prometheus.type" - // ExemplarTraceIDKey is the key used to store the trace ID in Prometheus - // exemplars: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#exemplars - ExemplarTraceIDKey = "trace_id" - // ExemplarSpanIDKey is the key used to store the Span ID in Prometheus - // exemplars: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#exemplars - ExemplarSpanIDKey = "span_id" - // ScopeInfoMetricName is the name of the metric used to preserve scope - // attributes in Prometheus format: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope - ScopeInfoMetricName = "otel_scope_info" - // ScopeNameLabelKey is the name of the label key used to identify the name - // of the OpenTelemetry scope which produced the metric: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope - ScopeNameLabelKey = "otel_scope_name" - // ScopeVersionLabelKey is the name of the label key used to identify the - // version of the OpenTelemetry scope which produced the metric: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope - ScopeVersionLabelKey = "otel_scope_version" - // TargetInfoMetricName is the name of the metric used to preserve resource - // attributes in Prometheus format: - // https://github.com/open-telemetry/opentelemetry-specification/blob/e6eccba97ebaffbbfad6d4358408a2cead0ec2df/specification/compatibility/prometheus_and_openmetrics.md#resource-attributes-1 - // It originates from OpenMetrics: - // https://github.com/OpenObservability/OpenMetrics/blob/1386544931307dff279688f332890c31b6c5de36/specification/OpenMetrics.md#supporting-target-metadata-in-both-push-based-and-pull-based-systems - TargetInfoMetricName = "target_info" -) - -type MetricType string - -const ( - MetricTypeNonMonotonicCounter MetricType = "non-monotonic-counter" - MetricTypeMonotonicCounter MetricType = "monotonic-counter" - MetricTypeGauge MetricType = "gauge" - MetricTypeHistogram MetricType = "histogram" - MetricTypeExponentialHistogram MetricType = "exponential-histogram" - MetricTypeSummary MetricType = "summary" - MetricTypeUnknown MetricType = "unknown" -) diff --git a/otlptranslator/doc.go b/otlptranslator/doc.go deleted file mode 100644 index e50880649..000000000 --- a/otlptranslator/doc.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// otlptranslator is a dependency free package that contains the logic for translating information, such as metric name, unit and type, -// from OpenTelemetry metrics to valid Prometheus metric and label names. -// -// Use BuildCompliantMetricName to build a metric name that complies with traditional Prometheus naming conventions. -// Such conventions exist from a time when Prometheus didn't have support for full UTF-8 characters in metric names. -// For more details see: https://prometheus.io/docs/practices/naming/ -// -// Use BuildMetricName to build a metric name that will be accepted by Prometheus with full UTF-8 support. -// -// Use NormalizeLabel to normalize a label name to a valid format that can be used in Prometheus before UTF-8 characters were supported. -// -// Deprecated: Use github.com/prometheus/otlptranslator instead. -package otlptranslator diff --git a/otlptranslator/label_builder.go b/otlptranslator/label_builder.go deleted file mode 100644 index deb3bbd0c..000000000 --- a/otlptranslator/label_builder.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package otlptranslator - -import ( - "regexp" - "strings" - "unicode" -) - -var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) - -// Normalizes the specified label to follow Prometheus label names standard. -// -// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels. -// -// Labels that start with non-letter rune will be prefixed with "key_". -// An exception is made for double-underscores which are allowed. -func NormalizeLabel(label string) string { - // Trivial case. - if len(label) == 0 { - return label - } - - label = SanitizeLabelName(label) - - // If label starts with a number, prepend with "key_". - if unicode.IsDigit(rune(label[0])) { - label = "key_" + label - } else if strings.HasPrefix(label, "_") && !strings.HasPrefix(label, "__") { - label = "key" + label - } - - return label -} - -// SanitizeLabelName replaces anything that doesn't match -// client_label.LabelNameRE with an underscore. -// Note: this does not handle all Prometheus label name restrictions (such as -// not starting with a digit 0-9), and hence should only be used if the label -// name is prefixed with a known valid string. -func SanitizeLabelName(name string) string { - return invalidLabelCharRE.ReplaceAllString(name, "_") -} diff --git a/otlptranslator/label_builder_bench_test.go b/otlptranslator/label_builder_bench_test.go deleted file mode 100644 index 6f2ba120e..000000000 --- a/otlptranslator/label_builder_bench_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package otlptranslator - -import "testing" - -var labelBenchmarkInputs = []string{ - "", - "label:with:colons", - "LabelWithCapitalLetters", - "label!with&special$chars)", - "label_with_foreign_characters_字符", - "label.with.dots", - "123label", - "_label_starting_with_underscore", - "__label_starting_with_2underscores", -} - -func BenchmarkNormalizeLabel(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, input := range labelBenchmarkInputs { - NormalizeLabel(input) - } - } -} diff --git a/otlptranslator/label_builder_test.go b/otlptranslator/label_builder_test.go deleted file mode 100644 index 48856a215..000000000 --- a/otlptranslator/label_builder_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package otlptranslator - -import ( - "fmt" - "testing" -) - -func TestNormalizeLabel(t *testing.T) { - tests := []struct { - label string - expected string - }{ - {"", ""}, - {"label:with:colons", "label_with_colons"}, - {"LabelWithCapitalLetters", "LabelWithCapitalLetters"}, - {"label!with&special$chars)", "label_with_special_chars_"}, - {"label_with_foreign_characters_字符", "label_with_foreign_characters___"}, - {"label.with.dots", "label_with_dots"}, - {"123label", "key_123label"}, - {"_label_starting_with_underscore", "key_label_starting_with_underscore"}, - {"__label_starting_with_2underscores", "__label_starting_with_2underscores"}, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("test_%d", i), func(t *testing.T) { - result := NormalizeLabel(test.label) - if test.expected != result { - t.Errorf("expected %s, got %s", test.expected, result) - } - }) - } -} diff --git a/otlptranslator/metric_name_builder.go b/otlptranslator/metric_name_builder.go deleted file mode 100644 index a6b7d2757..000000000 --- a/otlptranslator/metric_name_builder.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package otlptranslator - -import ( - "regexp" - "slices" - "strings" - "unicode" -) - -// The map to translate OTLP units to Prometheus units -// OTLP metrics use the c/s notation as specified at https://ucum.org/ucum.html -// (See also https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/semantic_conventions/README.md#instrument-units) -// Prometheus best practices for units: https://prometheus.io/docs/practices/naming/#base-units -// OpenMetrics specification for units: https://github.com/prometheus/OpenMetrics/blob/v1.0.0/specification/OpenMetrics.md#units-and-base-units -var unitMap = map[string]string{ - // Time - "d": "days", - "h": "hours", - "min": "minutes", - "s": "seconds", - "ms": "milliseconds", - "us": "microseconds", - "ns": "nanoseconds", - - // Bytes - "By": "bytes", - "KiBy": "kibibytes", - "MiBy": "mebibytes", - "GiBy": "gibibytes", - "TiBy": "tibibytes", - "KBy": "kilobytes", - "MBy": "megabytes", - "GBy": "gigabytes", - "TBy": "terabytes", - - // SI - "m": "meters", - "V": "volts", - "A": "amperes", - "J": "joules", - "W": "watts", - "g": "grams", - - // Misc - "Cel": "celsius", - "Hz": "hertz", - "1": "", - "%": "percent", -} - -// The map that translates the "per" unit -// Example: s => per second (singular) -var perUnitMap = map[string]string{ - "s": "second", - "m": "minute", - "h": "hour", - "d": "day", - "w": "week", - "mo": "month", - "y": "year", -} - -var ( - nonMetricNameCharRE = regexp.MustCompile(`[^a-zA-Z0-9:]`) - // Regexp for metric name characters that should be replaced with _. - invalidMetricCharRE = regexp.MustCompile(`[^a-zA-Z0-9:_]`) - multipleUnderscoresRE = regexp.MustCompile(`__+`) -) - -// BuildMetricName builds a valid metric name but without following Prometheus naming conventions. -// It doesn't do any character transformation, it only prefixes the metric name with the namespace, if any, -// and adds metric type suffixes, e.g. "_total" for counters and unit suffixes. -// -// Differently from BuildCompliantMetricName, it doesn't check for the presence of unit and type suffixes. -// If "addMetricSuffixes" is true, it will add them anyway. -// -// Please use BuildCompliantMetricName for a metric name that follows Prometheus naming conventions. -func BuildMetricName(name, unit string, metricType MetricType, addMetricSuffixes bool) string { - if addMetricSuffixes { - mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit) - if mainUnitSuffix != "" { - name = name + "_" + mainUnitSuffix - } - if perUnitSuffix != "" { - name = name + "_" + perUnitSuffix - } - - // Append _total for Counters - if metricType == MetricTypeMonotonicCounter { - name = name + "_total" - } - - // Append _ratio for metrics with unit "1" - // Some OTel receivers improperly use unit "1" for counters of objects - // See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions - // Until these issues have been fixed, we're appending `_ratio` for gauges ONLY - // Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons) - if unit == "1" && metricType == MetricTypeGauge { - name = name + "_ratio" - } - } - return name -} - -// BuildCompliantMetricName builds a Prometheus-compliant metric name for the specified metric. -// -// Metric name is prefixed with specified namespace and underscore (if any). -// Namespace is not cleaned up. Make sure specified namespace follows Prometheus -// naming convention. -// -// See rules at https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels, -// https://prometheus.io/docs/practices/naming/#metric-and-label-naming -// and https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. -func BuildCompliantMetricName(name, unit string, metricType MetricType, addMetricSuffixes bool) string { - // Full normalization following standard Prometheus naming conventions - if addMetricSuffixes { - return normalizeName(name, unit, metricType) - } - - // Simple case (no full normalization, no units, etc.). - metricName := strings.Join(strings.FieldsFunc(name, func(r rune) bool { - return invalidMetricCharRE.MatchString(string(r)) - }), "_") - - // Metric name starts with a digit? Prefix it with an underscore. - if metricName != "" && unicode.IsDigit(rune(metricName[0])) { - metricName = "_" + metricName - } - - return metricName -} - -// Build a normalized name for the specified metric. -func normalizeName(metric, unit string, metricType MetricType) string { - // Split metric name into "tokens" (of supported metric name runes). - // Note that this has the side effect of replacing multiple consecutive underscores with a single underscore. - // This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. - nameTokens := strings.FieldsFunc( - metric, - func(r rune) bool { return nonMetricNameCharRE.MatchString(string(r)) }, - ) - - mainUnitSuffix, perUnitSuffix := buildUnitSuffixes(unit) - nameTokens = addUnitTokens(nameTokens, CleanUpString(mainUnitSuffix), CleanUpString(perUnitSuffix)) - - // Append _total for Counters - if metricType == MetricTypeMonotonicCounter { - nameTokens = append(removeItem(nameTokens, "total"), "total") - } - - // Append _ratio for metrics with unit "1" - // Some OTel receivers improperly use unit "1" for counters of objects - // See https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aissue+some+metric+units+don%27t+follow+otel+semantic+conventions - // Until these issues have been fixed, we're appending `_ratio` for gauges ONLY - // Theoretically, counters could be ratios as well, but it's absurd (for mathematical reasons) - if unit == "1" && metricType == MetricTypeGauge { - nameTokens = append(removeItem(nameTokens, "ratio"), "ratio") - } - - // Build the string from the tokens, separated with underscores - normalizedName := strings.Join(nameTokens, "_") - - // Metric name cannot start with a digit, so prefix it with "_" in this case - if normalizedName != "" && unicode.IsDigit(rune(normalizedName[0])) { - normalizedName = "_" + normalizedName - } - - return normalizedName -} - -// buildUnitSuffixes builds the main and per unit suffixes for the specified unit -// but doesn't do any special character transformation to accommodate Prometheus naming conventions. -// Removing trailing underscores or appending suffixes is done in the caller. -func buildUnitSuffixes(unit string) (mainUnitSuffix, perUnitSuffix string) { - // Split unit at the '/' if any - unitTokens := strings.SplitN(unit, "/", 2) - - if len(unitTokens) > 0 { - // Main unit - // Update if not blank and doesn't contain '{}' - mainUnitOTel := strings.TrimSpace(unitTokens[0]) - if mainUnitOTel != "" && !strings.ContainsAny(mainUnitOTel, "{}") { - mainUnitSuffix = unitMapGetOrDefault(mainUnitOTel) - } - - // Per unit - // Update if not blank and doesn't contain '{}' - if len(unitTokens) > 1 && unitTokens[1] != "" { - perUnitOTel := strings.TrimSpace(unitTokens[1]) - if perUnitOTel != "" && !strings.ContainsAny(perUnitOTel, "{}") { - perUnitSuffix = perUnitMapGetOrDefault(perUnitOTel) - } - if perUnitSuffix != "" { - perUnitSuffix = "per_" + perUnitSuffix - } - } - } - - return mainUnitSuffix, perUnitSuffix -} - -// Retrieve the Prometheus "basic" unit corresponding to the specified "basic" unit -// Returns the specified unit if not found in unitMap -func unitMapGetOrDefault(unit string) string { - if promUnit, ok := unitMap[unit]; ok { - return promUnit - } - return unit -} - -// Retrieve the Prometheus "per" unit corresponding to the specified "per" unit -// Returns the specified unit if not found in perUnitMap -func perUnitMapGetOrDefault(perUnit string) string { - if promPerUnit, ok := perUnitMap[perUnit]; ok { - return promPerUnit - } - return perUnit -} - -// addUnitTokens will add the suffixes to the nameTokens if they are not already present. -// It will also remove trailing underscores from the main suffix to avoid double underscores -// when joining the tokens. -// -// If the 'per' unit ends with underscore, the underscore will be removed. If the per unit is just -// 'per_', it will be entirely removed. -func addUnitTokens(nameTokens []string, mainUnitSuffix, perUnitSuffix string) []string { - if slices.Contains(nameTokens, mainUnitSuffix) { - mainUnitSuffix = "" - } - - if perUnitSuffix == "per_" { - perUnitSuffix = "" - } else { - perUnitSuffix = strings.TrimSuffix(perUnitSuffix, "_") - if slices.Contains(nameTokens, perUnitSuffix) { - perUnitSuffix = "" - } - } - - if perUnitSuffix != "" { - mainUnitSuffix = strings.TrimSuffix(mainUnitSuffix, "_") - } - - if mainUnitSuffix != "" { - nameTokens = append(nameTokens, mainUnitSuffix) - } - if perUnitSuffix != "" { - nameTokens = append(nameTokens, perUnitSuffix) - } - return nameTokens -} - -// CleanUpString cleans up a string so it matches model.LabelNameRE. -// CleanUpString is usually used to clean up unit strings, but can be used for any string, e.g. namespaces. -func CleanUpString(s string) string { - // Multiple consecutive underscores are replaced with a single underscore. - // This is part of the OTel to Prometheus specification: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.38.0/specification/compatibility/prometheus_and_openmetrics.md#otlp-metric-points-to-prometheus. - return strings.TrimPrefix(multipleUnderscoresRE.ReplaceAllString( - nonMetricNameCharRE.ReplaceAllString(s, "_"), - "_", - ), "_") -} - -// Remove the specified value from the slice -func removeItem(slice []string, value string) []string { - newSlice := make([]string, 0, len(slice)) - for _, sliceEntry := range slice { - if sliceEntry != value { - newSlice = append(newSlice, sliceEntry) - } - } - return newSlice -} diff --git a/otlptranslator/metric_name_builder_bench_test.go b/otlptranslator/metric_name_builder_bench_test.go deleted file mode 100644 index bc0851d39..000000000 --- a/otlptranslator/metric_name_builder_bench_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -package otlptranslator - -import ( - "fmt" - "testing" -) - -var benchmarkInputs = []struct { - name string - metricName string - unit string - metricType MetricType -}{ - { - name: "simple_metric", - metricName: "http_requests", - unit: "", - metricType: MetricTypeGauge, - }, - { - name: "compound_unit", - metricName: "request_throughput", - unit: "By/s", - metricType: MetricTypeMonotonicCounter, - }, - { - name: "complex_unit", - metricName: "disk_usage", - unit: "KiBy/m", - metricType: MetricTypeGauge, - }, - { - name: "ratio_metric", - metricName: "cpu_utilization", - unit: "1", - metricType: MetricTypeGauge, - }, - { - name: "metric_with_dots", - metricName: "system.cpu.usage.idle", - unit: "%", - metricType: MetricTypeGauge, - }, - { - name: "metric_with_unicode", - metricName: "メモリ使用率", - unit: "By", - metricType: MetricTypeGauge, - }, - { - name: "metric_with_special_chars", - metricName: "error-rate@host{instance}/service#component", - unit: "ms", - metricType: MetricTypeMonotonicCounter, - }, - { - name: "metric_with_multiple_slashes", - metricName: "network/throughput/total", - unit: "By/s/min", - metricType: MetricTypeGauge, - }, - { - name: "metric_with_spaces", - metricName: "api response time total", - unit: "ms", - metricType: MetricTypeMonotonicCounter, - }, - { - name: "metric_with_curly_braces", - metricName: "custom_{tag}_metric", - unit: "{custom}/s", - metricType: MetricTypeGauge, - }, - { - name: "metric_starting_with_digit", - metricName: "5xx_error_count", - unit: "1", - metricType: MetricTypeMonotonicCounter, - }, - { - name: "empty_metric", - metricName: "", - unit: "", - metricType: MetricTypeGauge, - }, - { - name: "metric_with_SI_units", - metricName: "power_consumption", - unit: "W", - metricType: MetricTypeGauge, - }, - { - name: "metric_with_temperature", - metricName: "server_temperature", - unit: "Cel", - metricType: MetricTypeGauge, - }, -} - -func BenchmarkBuildMetricName(b *testing.B) { - for _, addSuffixes := range []bool{true, false} { - b.Run(fmt.Sprintf("with_metric_suffixes=%t", addSuffixes), func(b *testing.B) { - for _, input := range benchmarkInputs { - for i := 0; i < b.N; i++ { - BuildMetricName(input.metricName, input.unit, input.metricType, addSuffixes) - } - } - }) - } -} - -func BenchmarkBuildCompliantMetricName(b *testing.B) { - for _, addSuffixes := range []bool{true, false} { - b.Run(fmt.Sprintf("with_metric_suffixes=%t", addSuffixes), func(b *testing.B) { - for _, input := range benchmarkInputs { - for i := 0; i < b.N; i++ { - BuildCompliantMetricName(input.metricName, input.unit, input.metricType, addSuffixes) - } - } - }) - } -} diff --git a/otlptranslator/metric_name_builder_test.go b/otlptranslator/metric_name_builder_test.go deleted file mode 100644 index 49b729b43..000000000 --- a/otlptranslator/metric_name_builder_test.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2025 The Prometheus Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package otlptranslator - -import ( - "reflect" - "testing" -) - -func TestBuildMetricName(t *testing.T) { - tests := []struct { - name string - metricName string - unit string - metricType MetricType - addMetricSuffixes bool - expected string - }{ - { - name: "simple metric without suffixes", - metricName: "http_requests", - unit: "", - metricType: MetricTypeGauge, - addMetricSuffixes: false, - expected: "http_requests", - }, - { - name: "counter with total suffix", - metricName: "http_requests", - unit: "", - metricType: MetricTypeMonotonicCounter, - addMetricSuffixes: true, - expected: "http_requests_total", - }, - { - name: "gauge with time unit", - metricName: "request_duration", - unit: "s", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "request_duration_seconds", - }, - { - name: "counter with time unit", - metricName: "request_duration", - unit: "ms", - metricType: MetricTypeMonotonicCounter, - addMetricSuffixes: true, - expected: "request_duration_milliseconds_total", - }, - { - name: "gauge with compound unit", - metricName: "throughput", - unit: "By/s", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "throughput_bytes_per_second", - }, - { - name: "ratio metric", - metricName: "cpu_utilization", - unit: "1", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "cpu_utilization_ratio", - }, - { - name: "counter with unit 1 (no ratio suffix)", - metricName: "error_count", - unit: "1", - metricType: MetricTypeMonotonicCounter, - addMetricSuffixes: true, - expected: "error_count_total", - }, - { - name: "metric with byte units", - metricName: "memory_usage", - unit: "MiBy", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "memory_usage_mebibytes", - }, - { - name: "metric with SI units", - metricName: "temperature", - unit: "Cel", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "temperature_celsius", - }, - { - name: "metric with dots", - metricName: "system.cpu.usage", - unit: "1", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "system.cpu.usage_ratio", - }, - { - name: "metric with japanese characters (memory usage rate)", - metricName: "メモリ使用率", // memori shiyouritsu (memory usage rate) xD - unit: "By", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "メモリ使用率_bytes", - }, - { - name: "metric with mixed special characters (system.memory.usage.rate)", - metricName: "system.メモリ.usage.率", // system.memory.usage.rate - unit: "By/s", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "system.メモリ.usage.率_bytes_per_second", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := BuildMetricName(tt.metricName, tt.unit, tt.metricType, tt.addMetricSuffixes) - if tt.expected != result { - t.Errorf("expected %s, got %s", tt.expected, result) - } - }) - } -} - -func TestBuildUnitSuffixes(t *testing.T) { - tests := []struct { - name string - unit string - expectedMain string - expectedPerUnit string - }{ - { - name: "empty unit", - unit: "", - expectedMain: "", - expectedPerUnit: "", - }, - { - name: "simple time unit", - unit: "s", - expectedMain: "seconds", - expectedPerUnit: "", - }, - { - name: "compound unit", - unit: "By/s", - expectedMain: "bytes", - expectedPerUnit: "per_second", - }, - { - name: "complex compound unit", - unit: "KiBy/m", - expectedMain: "kibibytes", - expectedPerUnit: "per_minute", - }, - { - name: "unit with spaces", - unit: " ms / s ", - expectedMain: "milliseconds", - expectedPerUnit: "per_second", - }, - { - name: "invalid unit", - unit: "invalid", - expectedMain: "invalid", - expectedPerUnit: "", - }, - { - name: "unit with curly braces", - unit: "{custom}/s", - expectedMain: "", - expectedPerUnit: "per_second", - }, - { - name: "multiple slashes", - unit: "By/s/h", - expectedMain: "bytes", - expectedPerUnit: "per_s/h", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mainUnit, perUnit := buildUnitSuffixes(tt.unit) - if tt.expectedMain != mainUnit { - t.Errorf("expected main unit %s, got %s", tt.expectedMain, mainUnit) - } - if tt.expectedPerUnit != perUnit { - t.Errorf("expected per unit %s, got %s", tt.expectedPerUnit, perUnit) - } - }) - } -} - -func TestBuildCompliantMetricName(t *testing.T) { - tests := []struct { - name string - metricName string - unit string - metricType MetricType - addMetricSuffixes bool - expected string - }{ - { - name: "simple valid metric name", - metricName: "http_requests", - unit: "", - metricType: MetricTypeGauge, - addMetricSuffixes: false, - expected: "http_requests", - }, - { - name: "metric name with invalid characters", - metricName: "http-requests@in_flight", - unit: "", - metricType: MetricTypeNonMonotonicCounter, - addMetricSuffixes: false, - expected: "http_requests_in_flight", - }, - { - name: "metric name starting with digit", - metricName: "5xx_errors", - unit: "", - metricType: MetricTypeGauge, - addMetricSuffixes: false, - expected: "_5xx_errors", - }, - { - name: "metric name starting with digit, with suffixes", - metricName: "5xx_errors", - unit: "", - metricType: MetricTypeMonotonicCounter, - addMetricSuffixes: true, - expected: "_5xx_errors_total", - }, - { - name: "metric name with multiple consecutive invalid chars", - metricName: "api..//request--time", - unit: "", - metricType: MetricTypeGauge, - addMetricSuffixes: false, - expected: "api_request_time", - }, - { - name: "full normalization with units and type", - metricName: "system.cpu-utilization", - unit: "ms/s", - metricType: MetricTypeMonotonicCounter, - addMetricSuffixes: true, - expected: "system_cpu_utilization_milliseconds_per_second_total", - }, - { - name: "metric with special characters and ratio", - metricName: "memory.usage%rate", - unit: "1", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "memory_usage_rate_ratio", - }, - { - name: "metric with unicode characters", - metricName: "error_rate_£_€_¥", - unit: "", - metricType: MetricTypeGauge, - addMetricSuffixes: false, - expected: "error_rate_____", - }, - { - name: "metric with multiple spaces", - metricName: "api response time", - unit: "ms", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "api_response_time_milliseconds", - }, - { - name: "metric with colons (valid prometheus chars)", - metricName: "app:request:latency", - unit: "s", - metricType: MetricTypeGauge, - addMetricSuffixes: true, - expected: "app:request:latency_seconds", - }, - { - name: "empty metric name", - metricName: "", - unit: "", - metricType: MetricTypeGauge, - addMetricSuffixes: false, - expected: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := BuildCompliantMetricName(tt.metricName, tt.unit, tt.metricType, tt.addMetricSuffixes) - if tt.expected != result { - t.Errorf("expected %s, got %s", tt.expected, result) - } - }) - } -} - -func TestAddUnitTokens(t *testing.T) { - tests := []struct { - nameTokens []string - mainUnitSuffix string - perUnitSuffix string - expected []string - }{ - {[]string{}, "", "", []string{}}, - {[]string{"token1"}, "main", "", []string{"token1", "main"}}, - {[]string{"token1"}, "", "per", []string{"token1", "per"}}, - {[]string{"token1"}, "main", "per", []string{"token1", "main", "per"}}, - {[]string{"token1", "per"}, "main", "per", []string{"token1", "per", "main"}}, - {[]string{"token1", "main"}, "main", "per", []string{"token1", "main", "per"}}, - {[]string{"token1"}, "main_", "per", []string{"token1", "main", "per"}}, - {[]string{"token1"}, "main_unit", "per_seconds_", []string{"token1", "main_unit", "per_seconds"}}, // trailing underscores are removed - {[]string{"token1"}, "main_unit", "per_", []string{"token1", "main_unit"}}, // 'per_' is removed entirely - } - - for _, test := range tests { - result := addUnitTokens(test.nameTokens, test.mainUnitSuffix, test.perUnitSuffix) - if !reflect.DeepEqual(test.expected, result) { - t.Errorf("expected %v, got %v", test.expected, result) - } - } -} - -func TestRemoveItem(t *testing.T) { - if !reflect.DeepEqual([]string{}, removeItem([]string{}, "test")) { - t.Errorf("expected %v, got %v", []string{}, removeItem([]string{}, "test")) - } - if !reflect.DeepEqual([]string{}, removeItem([]string{}, "")) { - t.Errorf("expected %v, got %v", []string{}, removeItem([]string{}, "")) - } - if !reflect.DeepEqual([]string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "d")) { - t.Errorf("expected %v, got %v", []string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "d")) - } - if !reflect.DeepEqual([]string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "")) { - t.Errorf("expected %v, got %v", []string{"a", "b", "c"}, removeItem([]string{"a", "b", "c"}, "")) - } - if !reflect.DeepEqual([]string{"a", "b"}, removeItem([]string{"a", "b", "c"}, "c")) { - t.Errorf("expected %v, got %v", []string{"a", "b"}, removeItem([]string{"a", "b", "c"}, "c")) - } - if !reflect.DeepEqual([]string{"a", "c"}, removeItem([]string{"a", "b", "c"}, "b")) { - t.Errorf("expected %v, got %v", []string{"a", "c"}, removeItem([]string{"a", "b", "c"}, "b")) - } - if !reflect.DeepEqual([]string{"b", "c"}, removeItem([]string{"a", "b", "c"}, "a")) { - t.Errorf("expected %v, got %v", []string{"b", "c"}, removeItem([]string{"a", "b", "c"}, "a")) - } -} - -func TestCleanUpStrings(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - { - name: "already valid string", - input: "valid_metric_name", - expected: "valid_metric_name", - }, - { - name: "invalid characters", - input: "metric-name@with#special$chars", - expected: "metric_name_with_special_chars", - }, - { - name: "multiple consecutive invalid chars", - input: "metric---name###special", - expected: "metric_name_special", - }, - { - name: "leading invalid chars", - input: "@#$metric_name", - expected: "metric_name", - }, - { - name: "trailing invalid chars", - input: "metric_name@#$", - expected: "metric_name_", - }, - { - name: "multiple consecutive underscores", - input: "metric___name____test", - expected: "metric_name_test", - }, - { - name: "empty string", - input: "", - expected: "", - }, - { - name: "only invalid chars", - input: "@#$%^&", - expected: "", - }, - { - name: "colons are valid", - input: "system.cpu:usage.rate", - expected: "system_cpu:usage_rate", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := CleanUpString(tt.input) - if result != tt.expected { - t.Errorf("expected %q, got %q", tt.expected, result) - } - }) - } -} From 7bd5fff888acb05c04a857998772b9d7ffe3e8e4 Mon Sep 17 00:00:00 2001 From: TJ Hoplock <33664289+tjhop@users.noreply.github.com> Date: Sat, 21 Jun 2025 11:33:24 -0400 Subject: [PATCH 36/78] feat(promslog): add Level() method to get slog.Level (#795) Adds a `Level()` method to the promslog.Level type. We already expose the configured level with the `String()` method, but only in a specifically processed format. This means that downstream projects which need a true `slog.Level` value need to do shenanigans like we do in the blackbox_exporter here[1]. The slog.Level type is really just an int behind the scenes, but buys us access to the type's methods, obviously. If we want to make this a bit more flexible/"future proof", we could instead have the method return an slog.Leveler interface type. I don't think it's necessary though, as we'd probably only want to do that if we intended to expose access to promslog.Level's internal slog.LevelVar (since that also satisfies the slog.Leveler interface), and I don't think that's a good idea -- giving direct access would allow users to interact with the level var directly, providing an avenue to change level configurations outside of promslog's knowledge, resulting in unexpected behavior at best and incompatible behavior at worst. I think providing access to the value of the level as a proper slog.Level value as done here is useful enough on it's own and the safer option. 1. https://github.com/prometheus/blackbox_exporter/blob/f77c50ed7c0f39b734235931e773cf7b5af1fc8a/prober/handler.go#L236-L249 2. https://pkg.go.dev/log/slog#Level Signed-off-by: TJ Hoplock --- promslog/slog.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/promslog/slog.go b/promslog/slog.go index 3bd817328..8da43aef5 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -76,6 +76,11 @@ func (l *Level) UnmarshalYAML(unmarshal func(interface{}) error) error { return nil } +// Level returns the value of the logging level as an slog.Level. +func (l *Level) Level() slog.Level { + return l.lvl.Level() +} + // String returns the current level. func (l *Level) String() string { switch l.lvl.Level() { From 75c3814dc66c571cc82cee2d3a6bf5f37ee73f1a Mon Sep 17 00:00:00 2001 From: Dmitry Ponomaryov Date: Mon, 23 Jun 2025 03:58:37 +0500 Subject: [PATCH 37/78] feat: Support negative duration in new function ParseDurationAllowNegative (#793) Signed-off-by: Dmitry Ponomaryov --- model/time.go | 25 +++++- model/time_test.go | 203 +++++++++++++++++++++++++++++++++------------ 2 files changed, 173 insertions(+), 55 deletions(-) diff --git a/model/time.go b/model/time.go index 5727452c1..fed9e87b9 100644 --- a/model/time.go +++ b/model/time.go @@ -201,6 +201,7 @@ var unitMap = map[string]struct { // ParseDuration parses a string into a time.Duration, assuming that a year // always has 365d, a week always has 7d, and a day always has 24h. +// Negative durations are not supported. func ParseDuration(s string) (Duration, error) { switch s { case "0": @@ -253,18 +254,36 @@ func ParseDuration(s string) (Duration, error) { return 0, errors.New("duration out of range") } } + return Duration(dur), nil } +// ParseDurationAllowNegative is like ParseDuration but also accepts negative durations. +func ParseDurationAllowNegative(s string) (Duration, error) { + if s == "" || s[0] != '-' { + return ParseDuration(s) + } + + d, err := ParseDuration(s[1:]) + + return -d, err +} + func (d Duration) String() string { var ( - ms = int64(time.Duration(d) / time.Millisecond) - r = "" + ms = int64(time.Duration(d) / time.Millisecond) + r = "" + sign = "" ) + if ms == 0 { return "0s" } + if ms < 0 { + sign, ms = "-", -ms + } + f := func(unit string, mult int64, exact bool) { if exact && ms%mult != 0 { return @@ -286,7 +305,7 @@ func (d Duration) String() string { f("s", 1000, false) f("ms", 1, false) - return r + return sign + r } // MarshalJSON implements the json.Marshaler interface. diff --git a/model/time_test.go b/model/time_test.go index 70f383947..a4e9069f1 100644 --- a/model/time_test.go +++ b/model/time_test.go @@ -68,70 +68,171 @@ func TestDuration(t *testing.T) { } func TestParseDuration(t *testing.T) { - cases := []struct { - in string - out time.Duration + type testCase struct { + in string + out time.Duration + expectedString string + allowedNegative bool + } - expectedString string - }{ + baseCases := []testCase{ { - in: "0", - out: 0, - expectedString: "0s", - }, { - in: "0w", - out: 0, - expectedString: "0s", - }, { - in: "0s", - out: 0, - }, { - in: "324ms", - out: 324 * time.Millisecond, - }, { - in: "3s", - out: 3 * time.Second, - }, { - in: "5m", - out: 5 * time.Minute, - }, { - in: "1h", - out: time.Hour, - }, { - in: "4d", - out: 4 * 24 * time.Hour, - }, { - in: "4d1h", - out: 4*24*time.Hour + time.Hour, - }, { - in: "14d", - out: 14 * 24 * time.Hour, - expectedString: "2w", - }, { - in: "3w", - out: 3 * 7 * 24 * time.Hour, - }, { - in: "3w2d1h", - out: 3*7*24*time.Hour + 2*24*time.Hour + time.Hour, - expectedString: "23d1h", - }, { - in: "10y", - out: 10 * 365 * 24 * time.Hour, + in: "0", + out: 0, + expectedString: "0s", + allowedNegative: false, + }, + { + in: "0w", + out: 0, + expectedString: "0s", + allowedNegative: false, + }, + { + in: "0s", + out: 0, + expectedString: "", + allowedNegative: false, + }, + { + in: "324ms", + out: 324 * time.Millisecond, + expectedString: "", + allowedNegative: false, + }, + { + in: "3s", + out: 3 * time.Second, + expectedString: "", + allowedNegative: false, + }, + { + in: "5m", + out: 5 * time.Minute, + expectedString: "", + allowedNegative: false, + }, + { + in: "1h", + out: time.Hour, + expectedString: "", + allowedNegative: false, + }, + { + in: "4d", + out: 4 * 24 * time.Hour, + expectedString: "", + allowedNegative: false, + }, + { + in: "4d1h", + out: 4*24*time.Hour + time.Hour, + expectedString: "", + allowedNegative: false, + }, + { + in: "14d", + out: 14 * 24 * time.Hour, + expectedString: "2w", + allowedNegative: false, + }, + { + in: "3w", + out: 3 * 7 * 24 * time.Hour, + expectedString: "", + allowedNegative: false, + }, + { + in: "3w2d1h", + out: 3*7*24*time.Hour + 2*24*time.Hour + time.Hour, + expectedString: "23d1h", + allowedNegative: false, + }, + { + in: "10y", + out: 10 * 365 * 24 * time.Hour, + expectedString: "", + allowedNegative: false, }, } - for _, c := range cases { - d, err := ParseDuration(c.in) + negativeCases := []testCase{ + { + in: "-3s", + out: -3 * time.Second, + expectedString: "", + allowedNegative: true, + }, + { + in: "-5m", + out: -5 * time.Minute, + expectedString: "", + allowedNegative: true, + }, + { + in: "-1h", + out: -1 * time.Hour, + expectedString: "", + allowedNegative: true, + }, + { + in: "-2d", + out: -2 * 24 * time.Hour, + expectedString: "", + allowedNegative: true, + }, + { + in: "-1w", + out: -7 * 24 * time.Hour, + expectedString: "", + allowedNegative: true, + }, + { + in: "-3w2d1h", + out: -(3*7*24*time.Hour + 2*24*time.Hour + time.Hour), + expectedString: "-23d1h", + allowedNegative: true, + }, + { + in: "-10y", + out: -10 * 365 * 24 * time.Hour, + expectedString: "", + allowedNegative: true, + }, + } + + for _, c := range baseCases { + c.allowedNegative = true + negativeCases = append(negativeCases, c) + } + + allCases := append(baseCases, negativeCases...) + + for _, c := range allCases { + var ( + d Duration + err error + ) + + if c.allowedNegative { + d, err = ParseDurationAllowNegative(c.in) + } else { + d, err = ParseDuration(c.in) + } + if err != nil { t.Errorf("Unexpected error on input %q", c.in) } + if time.Duration(d) != c.out { t.Errorf("Expected %v but got %v", c.out, d) } + expectedString := c.expectedString - if c.expectedString == "" { + if expectedString == "" { expectedString = c.in } + if d.String() != expectedString { t.Errorf("Expected duration string %q but got %q", c.in, d.String()) } @@ -307,7 +408,6 @@ func TestParseBadDuration(t *testing.T) { cases := []string{ "1", "1y1m1d", - "-1w", "1.5d", "d", "294y", @@ -322,7 +422,6 @@ func TestParseBadDuration(t *testing.T) { if err == nil { t.Errorf("Expected error on input %s", c) } - } } From 0a409d601bf7b6b016f6344b375d2932d1047e80 Mon Sep 17 00:00:00 2001 From: Bartlomiej Plotka Date: Thu, 3 Jul 2025 12:44:57 +0100 Subject: [PATCH 38/78] model: add constants for type and unit labels. (#801) Useful to use e.g. in https://github.com/prometheus/prometheus/pull/16784 Signed-off-by: bwplotka --- model/labels.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/model/labels.go b/model/labels.go index de83afe93..e2ff83595 100644 --- a/model/labels.go +++ b/model/labels.go @@ -32,6 +32,12 @@ const ( // MetricNameLabel is the label name indicating the metric name of a // timeseries. MetricNameLabel = "__name__" + // MetricTypeLabel is the label name indicating the metric type of + // timeseries as per the PROM-39 proposal. + MetricTypeLabel = "__type__" + // MetricUnitLabel is the label name indicating the metric unit of + // timeseries as per the PROM-39 proposal. + MetricUnitLabel = "__unit__" // SchemeLabel is the name of the label that holds the scheme on which to // scrape a target. From 7f8b2a0d32d3ddca4ba3fb7ee5dad1ab3188ef50 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 3 Jul 2025 13:57:00 +0200 Subject: [PATCH 39/78] model.ValidationScheme: Support encoding as YAML (#799) * model.ValidationScheme: Support encoding as YAML --------- Signed-off-by: Arve Knudsen --- model/metric.go | 59 +++++++++++++++++++++- model/metric_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 2 deletions(-) diff --git a/model/metric.go b/model/metric.go index a6b01755b..2bd913fff 100644 --- a/model/metric.go +++ b/model/metric.go @@ -24,6 +24,7 @@ import ( dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" + "gopkg.in/yaml.v2" ) var ( @@ -62,16 +63,70 @@ var ( type ValidationScheme int const ( + // UnsetValidation represents an undefined ValidationScheme. + // Should not be used in practice. + UnsetValidation ValidationScheme = iota + // LegacyValidation is a setting that requires that all metric and label names // conform to the original Prometheus character requirements described by // MetricNameRE and LabelNameRE. - LegacyValidation ValidationScheme = iota + LegacyValidation // UTF8Validation only requires that metric and label names be valid UTF-8 // strings. UTF8Validation ) +var ( + _ yaml.Marshaler = UnsetValidation + _ fmt.Stringer = UnsetValidation +) + +// String returns the string representation of s. +func (s ValidationScheme) String() string { + switch s { + case UnsetValidation: + return "unset" + case LegacyValidation: + return "legacy" + case UTF8Validation: + return "utf8" + default: + panic(fmt.Errorf("unhandled ValidationScheme: %d", s)) + } +} + +// MarshalYAML implements the yaml.Marshaler interface. +func (s ValidationScheme) MarshalYAML() (any, error) { + switch s { + case UnsetValidation: + return "", nil + case LegacyValidation, UTF8Validation: + return s.String(), nil + default: + panic(fmt.Errorf("unhandled ValidationScheme: %d", s)) + } +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error { + var scheme string + if err := unmarshal(&scheme); err != nil { + return err + } + switch scheme { + case "": + // Don't change the value. + case "legacy": + *s = LegacyValidation + case "utf8": + *s = UTF8Validation + default: + return fmt.Errorf("unrecognized ValidationScheme: %q", scheme) + } + return nil +} + type EscapingScheme int const ( @@ -185,7 +240,7 @@ func IsValidMetricName(n LabelValue) bool { } return utf8.ValidString(string(n)) default: - panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme)) + panic(fmt.Sprintf("Invalid name validation scheme requested: %s", NameValidationScheme.String())) } } diff --git a/model/metric_test.go b/model/metric_test.go index 6152c5481..662a53d56 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -14,12 +14,16 @@ package model import ( + "errors" + "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" + "gopkg.in/yaml.v2" ) func testMetric(t testing.TB) { @@ -89,6 +93,115 @@ func BenchmarkMetric(b *testing.B) { } } +func TestValidationScheme(t *testing.T) { + var scheme ValidationScheme + require.Equal(t, UnsetValidation, scheme) +} + +func TestValidationScheme_String(t *testing.T) { + for _, tc := range []struct { + name string + scheme ValidationScheme + want string + }{ + { + name: "Unset", + scheme: UnsetValidation, + want: "unset", + }, + { + name: "Legacy", + scheme: LegacyValidation, + want: "legacy", + }, + { + name: "UTF8", + scheme: UTF8Validation, + want: "utf8", + }, + } { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.want, tc.scheme.String()) + }) + } +} + +func TestValidationScheme_MarshalYAML(t *testing.T) { + for _, tc := range []struct { + name string + scheme ValidationScheme + want string + }{ + { + name: "Unset", + scheme: UnsetValidation, + want: `""`, + }, + { + name: "Legacy", + scheme: LegacyValidation, + want: "legacy", + }, + { + name: "UTF8", + scheme: UTF8Validation, + want: "utf8", + }, + } { + t.Run(tc.name, func(t *testing.T) { + marshaled, err := yaml.Marshal(tc.scheme) + require.NoError(t, err) + require.Equal(t, tc.want, strings.TrimSpace(string(marshaled))) + }) + } +} + +func TestValidationScheme_UnmarshalYAML(t *testing.T) { + for _, tc := range []struct { + name string + input string + want ValidationScheme + wantError error + }{ + { + name: "Unset empty input", + input: "", + want: UnsetValidation, + }, + { + name: "Unset quoted input", + input: `""`, + want: UnsetValidation, + }, + { + name: "Legacy", + input: "legacy", + want: LegacyValidation, + }, + { + name: "UTF8", + input: "utf8", + want: UTF8Validation, + }, + { + name: "Invalid", + input: "invalid", + wantError: errors.New(`unrecognized ValidationScheme: "invalid"`), + }, + } { + t.Run(tc.name, func(t *testing.T) { + scheme := UnsetValidation + err := yaml.Unmarshal([]byte(tc.input), &scheme) + if tc.wantError == nil { + require.NoError(t, err) + require.Equal(t, tc.want, scheme) + } else { + require.EqualError(t, err, tc.wantError.Error()) + } + }) + } +} + func TestMetricNameIsLegacyValid(t *testing.T) { scenarios := []struct { mn LabelValue From e231fec9924fd94441bd5b7a0debe558189ef030 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jul 2025 08:25:28 +0200 Subject: [PATCH 40/78] build(deps): bump golang.org/x/net from 0.40.0 to 0.41.0 (#800) Bumps [golang.org/x/net](https://github.com/golang/net) from 0.40.0 to 0.41.0. - [Commits](https://github.com/golang/net/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 366bf8312..1381a9bbc 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.40.0 + golang.org/x/net v0.41.0 golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v2 v2.4.0 @@ -30,7 +30,7 @@ require ( github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/text v0.26.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7a637f691..3c89e3046 100644 --- a/go.sum +++ b/go.sum @@ -43,14 +43,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d5e1c6231507cbab41cddf2a18ef0bbeb6d25cda Mon Sep 17 00:00:00 2001 From: TJ Hoplock <33664289+tjhop@users.noreply.github.com> Date: Tue, 8 Jul 2025 06:29:01 -0400 Subject: [PATCH 41/78] fix(promslog): always print time.Duration values as go duration strings (#798) * fix(promslog): always print time.Duration values as go duration strings Addresses: prometheus/prometheus#16766 As brought up in the linked issue, it seems that upstream slog handles time.Duration values in a sub-optimal way in the JSON handler. To improve handling of duration values in a consistent way across the ecosystem, we should ensure that duration values are handled the same way across all supported log formats (json and logfmt). This change ensures that any key containing a value that is a time.Duration is reformatted as a boring string value, explicitly formatted as a Go duration string (ie, "1d2h3m") by calling the `String()` method on the duration. Example test output pre/post patch: Before: ``` ~/go/src/github.com/prometheus/common (main [ U ]) -> go test -v -race ./promslog -run TestDurationValues === RUN TestDurationValues time=2025-06-30T22:11:01.682-04:00 level=INFO source=slog_test.go:108 msg="duration testing" duration_raw=1m30s duration_string=1m30s {"time":"2025-06-30T22:11:01.683153372-04:00","level":"INFO","source":"slog_test.go:123","msg":"duration testing","duration_raw":90000000000,"duration_string":"1m30s"} slog_test.go:128: Error Trace: /home/tjhop/go/src/github.com/prometheus/common/promslog/slog_test.go:128 Error: "{\"time\":\"2025-06-30T22:11:01.683153372-04:00\",\"level\":\"INFO\",\"source\":\"slog_test.go:123\",\"msg\":\"duration testing\",\"duration_raw\":90000000000,\"duration_string\":\"1m30s\"}\n" should not contain "\"duration_raw\":90000000000" Test: TestDurationValues Messages: Expected duration to be output as Go duration string "1m30s", got "90000000000" --- FAIL: TestDurationValues (0.00s) FAIL FAIL github.com/prometheus/common/promslog 0.018s FAIL ``` After: ``` ~/go/src/github.com/prometheus/common (main [ U ]) -> go test -v -race ./promslog -run TestDurationValues === RUN TestDurationValues time=2025-06-30T22:13:03.714-04:00 level=INFO source=slog_test.go:108 msg="duration testing" duration_raw=1m30s duration_string=1m30s {"time":"2025-06-30T22:13:03.714880745-04:00","level":"INFO","source":"slog_test.go:123","msg":"duration testing","duration_raw":"1m30s","duration_string":"1m30s"} --- PASS: TestDurationValues (0.00s) PASS ok github.com/prometheus/common/promslog 1.014s ``` Signed-off-by: TJ Hoplock * test(promslog): improve duration tests, convert to table driven suite Addresses PR feedback Signed-off-by: TJ Hoplock * test(promslog): refactor duration tests Signed-off-by: TJ Hoplock * test(promslog): more specific string check for duration testing Signed-off-by: TJ Hoplock --------- Signed-off-by: TJ Hoplock --- promslog/slog.go | 14 ++++++++++++++ promslog/slog_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/promslog/slog.go b/promslog/slog.go index 8da43aef5..02370f175 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -197,6 +197,13 @@ func newGoKitStyleReplaceAttrFunc(lvl *Level) func(groups []string, a slog.Attr) } default: } + + // Ensure time.Duration values are _always_ formatted as a Go + // duration string (ie, "1d2h3m"). + if v, ok := a.Value.Any().(time.Duration); ok { + a.Value = slog.StringValue(v.String()) + } + return a } } @@ -238,6 +245,13 @@ func defaultReplaceAttr(_ []string, a slog.Attr) slog.Attr { } default: } + + // Ensure time.Duration values are _always_ formatted as a Go duration + // string (ie, "1d2h3m"). + if v, ok := a.Value.Any().(time.Duration); ok { + a.Value = slog.StringValue(v.String()) + } + return a } diff --git a/promslog/slog_test.go b/promslog/slog_test.go index ea4e176c2..91e79a9fb 100644 --- a/promslog/slog_test.go +++ b/promslog/slog_test.go @@ -21,6 +21,7 @@ import ( "regexp" "strings" "testing" + "time" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" @@ -94,6 +95,33 @@ func getLogEntryLevelCounts(s string, re *regexp.Regexp) map[string]int { return counters } +func TestDurationValues(t *testing.T) { + dur, err := time.ParseDuration("1m30s") + require.NoError(t, err) + + tests := map[string]struct { + logFormat string + want string + }{ + "logfmt_duration_testing": {want: "duration_raw=1m30s duration_string=1m30s", logFormat: "logfmt"}, + "json_duration_testing": {want: "\"duration_raw\":\"1m30s\",\"duration_string\":\"1m30s\"", logFormat: "json"}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + var buf bytes.Buffer + config := &Config{ + Writer: &buf, + Format: NewFormat(), + } + require.NoError(t, config.Format.Set(tc.logFormat)) + logger := New(config) + logger.Info("duration testing", "duration_raw", dur, "duration_string", dur.String()) + require.Contains(t, buf.String(), tc.want) + }) + } +} + func TestDynamicLevels(t *testing.T) { var buf bytes.Buffer wantedLevelCounts := map[string]int{"info": 1, "debug": 1} From 9fd5be9171bada1939aaae9565fa830234940934 Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Thu, 10 Jul 2025 22:59:47 +0200 Subject: [PATCH 42/78] Update common Prometheus files (#802) Signed-off-by: prombot --- Makefile.common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.common b/Makefile.common index 4de21512f..6f61bec48 100644 --- a/Makefile.common +++ b/Makefile.common @@ -139,7 +139,7 @@ common-deps: update-go-deps: @echo ">> updating Go dependencies" @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ - $(GO) get -d $$m; \ + $(GO) get $$m; \ done $(GO) mod tidy From 0e1982f10d4c758be240b6a5a41d31671d14138f Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Fri, 11 Jul 2025 20:37:25 +0200 Subject: [PATCH 43/78] Update common Prometheus files (#803) Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 8 +++++--- Makefile.common | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 672dd424d..d5d9ca2eb 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -25,15 +25,17 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Install Go - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 + uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version: 1.24.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd # v7.0.0 + uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 with: args: --verbose - version: v2.1.5 + version: v2.2.1 diff --git a/Makefile.common b/Makefile.common index 6f61bec48..1f4c9025a 100644 --- a/Makefile.common +++ b/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v2.1.5 +GOLANGCI_LINT_VERSION ?= v2.2.1 GOLANGCI_FMT_OPTS ?= # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. From c79a891c6c28ce135a2ac082b721c2dacc2269a8 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 1 Aug 2025 09:14:12 +0200 Subject: [PATCH 44/78] Add `ValidationScheme` methods `IsValidMetricName` and `IsValidLabelName` (#806) * Add ValidationScheme methods IsValidMetricName and IsValidLabelName --------- Signed-off-by: Arve Knudsen --- expfmt/decode.go | 2 + expfmt/openmetrics_create.go | 2 +- expfmt/text_create.go | 4 +- model/labels.go | 29 ++++---------- model/labels_test.go | 48 ++++++++++++++++------- model/metric.go | 75 ++++++++++++++++++++++++++---------- model/metric_test.go | 45 +++++++++++++++------- 7 files changed, 133 insertions(+), 72 deletions(-) diff --git a/expfmt/decode.go b/expfmt/decode.go index 1448439b7..24a24b53a 100644 --- a/expfmt/decode.go +++ b/expfmt/decode.go @@ -93,6 +93,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error { if err := opts.UnmarshalFrom(d.r, v); err != nil { return err } + //nolint:staticcheck // model.IsValidMetricName is deprecated. if !model.IsValidMetricName(model.LabelValue(v.GetName())) { return fmt.Errorf("invalid metric name %q", v.GetName()) } @@ -107,6 +108,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error { if !model.LabelValue(l.GetValue()).IsValid() { return fmt.Errorf("invalid label value %q", l.GetValue()) } + //nolint:staticcheck // model.LabelName.IsValid is deprecated. if !model.LabelName(l.GetName()).IsValid() { return fmt.Errorf("invalid label name %q", l.GetName()) } diff --git a/expfmt/openmetrics_create.go b/expfmt/openmetrics_create.go index a21ed4ec1..fed8253ca 100644 --- a/expfmt/openmetrics_create.go +++ b/expfmt/openmetrics_create.go @@ -477,7 +477,7 @@ func writeOpenMetricsNameAndLabelPairs( if name != "" { // If the name does not pass the legacy validity check, we must put the // metric name inside the braces, quoted. - if !model.IsValidLegacyMetricName(name) { + if !model.LegacyValidation.IsValidMetricName(name) { metricInsideBraces = true err := w.WriteByte(separator) written++ diff --git a/expfmt/text_create.go b/expfmt/text_create.go index 4b86434b3..e242b3720 100644 --- a/expfmt/text_create.go +++ b/expfmt/text_create.go @@ -354,7 +354,7 @@ func writeNameAndLabelPairs( if name != "" { // If the name does not pass the legacy validity check, we must put the // metric name inside the braces. - if !model.IsValidLegacyMetricName(name) { + if !model.LegacyValidation.IsValidMetricName(name) { metricInsideBraces = true err := w.WriteByte(separator) written++ @@ -498,7 +498,7 @@ func writeInt(w enhancedWriter, i int64) (int, error) { // writeName writes a string as-is if it complies with the legacy naming // scheme, or escapes it in double quotes if not. func writeName(w enhancedWriter, name string) (int, error) { - if model.IsValidLegacyMetricName(name) { + if model.LegacyValidation.IsValidMetricName(name) { return w.WriteString(name) } var written int diff --git a/model/labels.go b/model/labels.go index e2ff83595..dfeb34be5 100644 --- a/model/labels.go +++ b/model/labels.go @@ -106,34 +106,21 @@ type LabelName string // IsValid returns true iff the name matches the pattern of LabelNameRE when // NameValidationScheme is set to LegacyValidation, or valid UTF-8 if // NameValidationScheme is set to UTF8Validation. +// +// Deprecated: This method should not be used and may be removed in the future. +// Use [ValidationScheme.IsValidLabelName] instead. func (ln LabelName) IsValid() bool { - if len(ln) == 0 { - return false - } - switch NameValidationScheme { - case LegacyValidation: - return ln.IsValidLegacy() - case UTF8Validation: - return utf8.ValidString(string(ln)) - default: - panic(fmt.Sprintf("Invalid name validation scheme requested: %d", NameValidationScheme)) - } + return NameValidationScheme.IsValidLabelName(string(ln)) } // IsValidLegacy returns true iff name matches the pattern of LabelNameRE for // legacy names. It does not use LabelNameRE for the check but a much faster // hardcoded implementation. +// +// Deprecated: This method should not be used and may be removed in the future. +// Use [LegacyValidation.IsValidLabelName] instead. func (ln LabelName) IsValidLegacy() bool { - if len(ln) == 0 { - return false - } - for i, b := range ln { - // TODO: Apply De Morgan's law. Make sure there are tests for this. - if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck - return false - } - } - return true + return LegacyValidation.IsValidLabelName(string(ln)) } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/model/labels_test.go b/model/labels_test.go index 233954326..e66e3316a 100644 --- a/model/labels_test.go +++ b/model/labels_test.go @@ -14,6 +14,7 @@ package model import ( + "fmt" "sort" "testing" ) @@ -90,9 +91,9 @@ func BenchmarkLabelValues(b *testing.B) { } } -func TestLabelNameIsValid(t *testing.T) { +func TestValidationScheme_IsLabelNameValid(t *testing.T) { scenarios := []struct { - ln LabelName + ln string legacyValid bool utf8Valid bool }{ @@ -141,20 +142,39 @@ func TestLabelNameIsValid(t *testing.T) { legacyValid: false, utf8Valid: false, }, + { + ln: "", + legacyValid: false, + utf8Valid: false, + }, } - for _, s := range scenarios { - NameValidationScheme = LegacyValidation - if s.ln.IsValid() != s.legacyValid { - t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln) - } - if LabelNameRE.MatchString(string(s.ln)) != s.legacyValid { - t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln) - } - NameValidationScheme = UTF8Validation - if s.ln.IsValid() != s.utf8Valid { - t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.legacyValid, s.ln) - } + t.Run(fmt.Sprintf("%s,%t,%t", s.ln, s.legacyValid, s.utf8Valid), func(t *testing.T) { + if LegacyValidation.IsValidLabelName(s.ln) != s.legacyValid { + t.Errorf("Expected %v for %q using LegacyValidation.IsValidLabelName", s.legacyValid, s.ln) + } + if LabelNameRE.MatchString(s.ln) != s.legacyValid { + t.Errorf("Expected %v for %q using legacy regexp match", s.legacyValid, s.ln) + } + if UTF8Validation.IsValidLabelName(s.ln) != s.utf8Valid { + t.Errorf("Expected %v for %q using UTF8Validation.IsValidLabelName", s.utf8Valid, s.ln) + } + + // Test deprecated functions. + origScheme := NameValidationScheme + t.Cleanup(func() { + NameValidationScheme = origScheme + }) + NameValidationScheme = LegacyValidation + labelName := LabelName(s.ln) + if labelName.IsValid() != s.legacyValid { + t.Errorf("Expected %v for %q using legacy IsValid method", s.legacyValid, s.ln) + } + NameValidationScheme = UTF8Validation + if labelName.IsValid() != s.utf8Valid { + t.Errorf("Expected %v for %q using UTF-8 IsValid method", s.utf8Valid, s.ln) + } + }) } } diff --git a/model/metric.go b/model/metric.go index 2bd913fff..31981bb4e 100644 --- a/model/metric.go +++ b/model/metric.go @@ -127,6 +127,53 @@ func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error { return nil } +// IsValidMetricName returns whether metricName is valid according to s. +func (s ValidationScheme) IsValidMetricName(metricName string) bool { + switch s { + case LegacyValidation: + if len(metricName) == 0 { + return false + } + for i, b := range metricName { + if !isValidLegacyRune(b, i) { + return false + } + } + return true + case UTF8Validation: + if len(metricName) == 0 { + return false + } + return utf8.ValidString(metricName) + default: + panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s.String())) + } +} + +// IsValidLabelName returns whether labelName is valid according to s. +func (s ValidationScheme) IsValidLabelName(labelName string) bool { + switch s { + case LegacyValidation: + if len(labelName) == 0 { + return false + } + for i, b := range labelName { + // TODO: Apply De Morgan's law. Make sure there are tests for this. + if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) { //nolint:staticcheck + return false + } + } + return true + case UTF8Validation: + if len(labelName) == 0 { + return false + } + return utf8.ValidString(labelName) + default: + panic(fmt.Sprintf("Invalid name validation scheme requested: %s", s)) + } +} + type EscapingScheme int const ( @@ -230,34 +277,22 @@ func (m Metric) FastFingerprint() Fingerprint { // IsValidMetricName returns true iff name matches the pattern of MetricNameRE // for legacy names, and iff it's valid UTF-8 if the UTF8Validation scheme is // selected. +// +// Deprecated: This function should not be used and might be removed in the future. +// Use [ValidationScheme.IsValidMetricName] instead. func IsValidMetricName(n LabelValue) bool { - switch NameValidationScheme { - case LegacyValidation: - return IsValidLegacyMetricName(string(n)) - case UTF8Validation: - if len(n) == 0 { - return false - } - return utf8.ValidString(string(n)) - default: - panic(fmt.Sprintf("Invalid name validation scheme requested: %s", NameValidationScheme.String())) - } + return NameValidationScheme.IsValidMetricName(string(n)) } // IsValidLegacyMetricName is similar to IsValidMetricName but always uses the // legacy validation scheme regardless of the value of NameValidationScheme. // This function, however, does not use MetricNameRE for the check but a much // faster hardcoded implementation. +// +// Deprecated: This function should not be used and might be removed in the future. +// Use [LegacyValidation.IsValidMetricName] instead. func IsValidLegacyMetricName(n string) bool { - if len(n) == 0 { - return false - } - for i, b := range n { - if !isValidLegacyRune(b, i) { - return false - } - } - return true + return LegacyValidation.IsValidMetricName(n) } // EscapeMetricFamily escapes the given metric names and labels with the given diff --git a/model/metric_test.go b/model/metric_test.go index 662a53d56..b64c8a7e5 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -15,6 +15,7 @@ package model import ( "errors" + "fmt" "strings" "testing" @@ -202,9 +203,9 @@ func TestValidationScheme_UnmarshalYAML(t *testing.T) { } } -func TestMetricNameIsLegacyValid(t *testing.T) { +func TestValidationScheme_IsMetricNameValid(t *testing.T) { scenarios := []struct { - mn LabelValue + mn string legacyValid bool utf8Valid bool }{ @@ -259,19 +260,35 @@ func TestMetricNameIsLegacyValid(t *testing.T) { utf8Valid: false, }, } - for _, s := range scenarios { - NameValidationScheme = LegacyValidation - if IsValidMetricName(s.mn) != s.legacyValid { - t.Errorf("Expected %v for %q using legacy IsValidMetricName method", s.legacyValid, s.mn) - } - if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid { - t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn) - } - NameValidationScheme = UTF8Validation - if IsValidMetricName(s.mn) != s.utf8Valid { - t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.legacyValid, s.mn) - } + t.Run(fmt.Sprintf("%s,%t,%t", s.mn, s.legacyValid, s.utf8Valid), func(t *testing.T) { + if LegacyValidation.IsValidMetricName(s.mn) != s.legacyValid { + t.Errorf("Expected %v for %q using LegacyValidation.IsValidMetricName", s.legacyValid, s.mn) + } + if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid { + t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn) + } + if UTF8Validation.IsValidMetricName(s.mn) != s.utf8Valid { + t.Errorf("Expected %v for %q using UTF8Validation.IsValidMetricName", s.utf8Valid, s.mn) + } + + // Test deprecated functions. + if IsValidLegacyMetricName(s.mn) != s.legacyValid { + t.Errorf("Expected %v for %q using IsValidLegacyMetricNames", s.legacyValid, s.mn) + } + origScheme := NameValidationScheme + t.Cleanup(func() { + NameValidationScheme = origScheme + }) + NameValidationScheme = LegacyValidation + if IsValidMetricName(LabelValue(s.mn)) != s.legacyValid { + t.Errorf("Expected %v for %q using legacy IsValidMetricName", s.legacyValid, s.mn) + } + NameValidationScheme = UTF8Validation + if IsValidMetricName(LabelValue(s.mn)) != s.utf8Valid { + t.Errorf("Expected %v for %q using utf-8 IsValidMetricName method", s.utf8Valid, s.mn) + } + }) } } From 5546e8b086f313b69112f390b35eec4045fd8117 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:38:08 +0000 Subject: [PATCH 45/78] build(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.41.0 to 0.42.0. - [Commits](https://github.com/golang/net/compare/v0.41.0...v0.42.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.42.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 1381a9bbc..f712996b2 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 - golang.org/x/net v0.41.0 + golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v2 v2.4.0 @@ -29,8 +29,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 3c89e3046..6f72946c0 100644 --- a/go.sum +++ b/go.sum @@ -43,14 +43,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 96a9730ee2f027383b78d5648a36fe81aa84f712 Mon Sep 17 00:00:00 2001 From: Piotr <17101802+thampiotr@users.noreply.github.com> Date: Fri, 1 Aug 2025 14:15:38 +0100 Subject: [PATCH 46/78] Fix delimited proto not escaped correctly Signed-off-by: Piotr <17101802+thampiotr@users.noreply.github.com> --- expfmt/encode.go | 2 +- expfmt/encode_test.go | 98 ++++++++++++++++++------------------------- 2 files changed, 42 insertions(+), 58 deletions(-) diff --git a/expfmt/encode.go b/expfmt/encode.go index d7f3d76f5..547c05ee4 100644 --- a/expfmt/encode.go +++ b/expfmt/encode.go @@ -153,7 +153,7 @@ func NewEncoder(w io.Writer, format Format, options ...EncoderOption) Encoder { case TypeProtoDelim: return encoderCloser{ encode: func(v *dto.MetricFamily) error { - _, err := protodelim.MarshalTo(w, v) + _, err := protodelim.MarshalTo(w, model.EscapeMetricFamily(v, escapingScheme)) return err }, close: func() error { return nil }, diff --git a/expfmt/encode_test.go b/expfmt/encode_test.go index d91faaab6..df0b2ed42 100644 --- a/expfmt/encode_test.go +++ b/expfmt/encode_test.go @@ -18,6 +18,8 @@ import ( "net/http" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "github.com/prometheus/common/model" @@ -309,8 +311,32 @@ foo_metric 1.234 } func TestEscapedEncode(t *testing.T) { - var buff bytes.Buffer - delimEncoder := NewEncoder(&buff, FmtProtoDelim+"; escaping=underscores") + tests := []struct { + name string + format Format + }{ + { + name: "ProtoDelim", + format: FmtProtoDelim, + }, + { + name: "ProtoDelim with escaping underscores", + format: FmtProtoDelim + "; escaping=underscores", + }, + { + name: "ProtoCompact", + format: FmtProtoCompact, + }, + { + name: "ProtoText", + format: FmtProtoText, + }, + { + name: "Text", + format: FmtText, + }, + } + metric := &dto.MetricFamily{ Name: proto.String("foo.metric"), Type: dto.MetricType_UNTYPED.Enum(), @@ -334,61 +360,19 @@ func TestEscapedEncode(t *testing.T) { }, } - err := delimEncoder.Encode(metric) - if err != nil { - t.Errorf("unexpected error during encode: %s", err.Error()) - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buff bytes.Buffer + encoder := NewEncoder(&buff, tt.format) + err := encoder.Encode(metric) + require.NoError(t, err) - out := buff.Bytes() - if len(out) == 0 { - t.Errorf("expected the output bytes buffer to be non-empty") - } - - buff.Reset() - - compactEncoder := NewEncoder(&buff, FmtProtoCompact) - err = compactEncoder.Encode(metric) - if err != nil { - t.Errorf("unexpected error during encode: %s", err.Error()) - } - - out = buff.Bytes() - if len(out) == 0 { - t.Errorf("expected the output bytes buffer to be non-empty") - } - - buff.Reset() - - protoTextEncoder := NewEncoder(&buff, FmtProtoText) - err = protoTextEncoder.Encode(metric) - if err != nil { - t.Errorf("unexpected error during encode: %s", err.Error()) - } - - out = buff.Bytes() - if len(out) == 0 { - t.Errorf("expected the output bytes buffer to be non-empty") - } - - buff.Reset() - - textEncoder := NewEncoder(&buff, FmtText) - err = textEncoder.Encode(metric) - if err != nil { - t.Errorf("unexpected error during encode: %s", err.Error()) - } - - out = buff.Bytes() - if len(out) == 0 { - t.Errorf("expected the output bytes buffer to be non-empty") - } - - expected := `# TYPE foo_metric untyped -foo_metric 1.234 -foo_metric{dotted_label_name="my.label.value"} 8 -` - - if string(out) != expected { - t.Errorf("expected TextEncoder to return %s, but got %s instead", expected, string(out)) + s := buff.String() + assert.NotContains(t, s, "foo.metric") + assert.Contains(t, s, "foo_metric") + assert.NotContains(t, s, "dotted.label.name") + assert.Contains(t, s, "dotted_label_name") + assert.Contains(t, s, "my.label.value") + }) } } From 30ca184b395de1d347815c39e9df3c864c41adef Mon Sep 17 00:00:00 2001 From: Wil Cram Date: Wed, 13 Aug 2025 18:10:48 +0000 Subject: [PATCH 47/78] docs: fix typo in expfmt.Negotiate Signed-off-by: Wil Cram --- expfmt/encode.go | 2 +- expfmt/encode_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/expfmt/encode.go b/expfmt/encode.go index 547c05ee4..6af7e0014 100644 --- a/expfmt/encode.go +++ b/expfmt/encode.go @@ -61,7 +61,7 @@ func (ec encoderCloser) Close() error { // appropriate accepted type is found, FmtText is returned (which is the // Prometheus text format). This function will never negotiate FmtOpenMetrics, // as the support is still experimental. To include the option to negotiate -// FmtOpenMetrics, use NegotiateOpenMetrics. +// FmtOpenMetrics, use NegotiateIncludingOpenMetrics. func Negotiate(h http.Header) Format { escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String()))) for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) { diff --git a/expfmt/encode_test.go b/expfmt/encode_test.go index df0b2ed42..b7d3418f7 100644 --- a/expfmt/encode_test.go +++ b/expfmt/encode_test.go @@ -99,7 +99,7 @@ func TestNegotiate(t *testing.T) { } } -func TestNegotiateOpenMetrics(t *testing.T) { +func TestNegotiateIncludingOpenMetrics(t *testing.T) { acceptValuePrefix := "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily" tests := []struct { name string From a4976d6a8af752e80f69e9f84bd9b960904e59c4 Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Tue, 19 Aug 2025 10:45:51 -0400 Subject: [PATCH 48/78] ci: update upload-actions v3.X is deprecated and automatically fails. Signed-off-by: Owen Williams --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4793ce721..e7437b3f6 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -43,7 +43,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif From fc488aa6f985e54490a7e21e5d0db4f5f616438f Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 20 Aug 2025 04:35:01 -0400 Subject: [PATCH 49/78] Decoder: Remove use of global name validation and add validation (#808) This change removes the use of the global variable to determine name validation, using the provided format instead. Also: * adds validity checks to the text parser, which previously did not check for name validity. * Returns error if the caller tries to create a decoder for prototext format, which is not supported. Signed-off-by: Owen Williams --- expfmt/bench_test.go | 4 +- expfmt/decode.go | 41 +++++++-- expfmt/decode_test.go | 11 +-- expfmt/encode_test.go | 90 +++++++++++++++++++ expfmt/text_parse.go | 27 ++++++ expfmt/text_parse_test.go | 185 +++++++++++++++++++++++--------------- 6 files changed, 270 insertions(+), 88 deletions(-) diff --git a/expfmt/bench_test.go b/expfmt/bench_test.go index 4f691b310..90d9373d9 100644 --- a/expfmt/bench_test.go +++ b/expfmt/bench_test.go @@ -26,9 +26,11 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + + "github.com/prometheus/common/model" ) -var parser TextParser +var parser = TextParser{scheme: model.UTF8Validation} // Benchmarks to show how much penalty text format parsing actually inflicts. // diff --git a/expfmt/decode.go b/expfmt/decode.go index 24a24b53a..c4a701c50 100644 --- a/expfmt/decode.go +++ b/expfmt/decode.go @@ -70,19 +70,34 @@ func ResponseFormat(h http.Header) Format { return FmtUnknown } -// NewDecoder returns a new decoder based on the given input format. -// If the input format does not imply otherwise, a text format decoder is returned. +// NewDecoder returns a new decoder based on the given input format. Metric +// names are validated based on the provided Format -- if the format requires +// escaping, raditional Prometheues validity checking is used. Otherwise, names +// are checked for UTF-8 validity. Supported formats include delimited protobuf +// and Prometheus text format. For historical reasons, this decoder fallbacks +// to classic text decoding for any other format. This decoder does not fully +// support OpenMetrics although it may often succeed due to the similarities +// between the formats. This decoder may not support the latest features of +// Prometheus text format and is not intended for high-performance applications. +// See: https://github.com/prometheus/common/issues/812 func NewDecoder(r io.Reader, format Format) Decoder { + scheme := model.LegacyValidation + if format.ToEscapingScheme() == model.NoEscaping { + scheme = model.UTF8Validation + } switch format.FormatType() { case TypeProtoDelim: - return &protoDecoder{r: bufio.NewReader(r)} + return &protoDecoder{r: bufio.NewReader(r), s: scheme} + case TypeProtoText, TypeProtoCompact: + return &errDecoder{err: fmt.Errorf("format %s not supported for decoding", format)} } - return &textDecoder{r: r} + return &textDecoder{r: r, s: scheme} } // protoDecoder implements the Decoder interface for protocol buffers. type protoDecoder struct { r protodelim.Reader + s model.ValidationScheme } // Decode implements the Decoder interface. @@ -93,8 +108,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error { if err := opts.UnmarshalFrom(d.r, v); err != nil { return err } - //nolint:staticcheck // model.IsValidMetricName is deprecated. - if !model.IsValidMetricName(model.LabelValue(v.GetName())) { + if !d.s.IsValidMetricName(v.GetName()) { return fmt.Errorf("invalid metric name %q", v.GetName()) } for _, m := range v.GetMetric() { @@ -108,8 +122,7 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error { if !model.LabelValue(l.GetValue()).IsValid() { return fmt.Errorf("invalid label value %q", l.GetValue()) } - //nolint:staticcheck // model.LabelName.IsValid is deprecated. - if !model.LabelName(l.GetName()).IsValid() { + if !d.s.IsValidLabelName(l.GetName()) { return fmt.Errorf("invalid label name %q", l.GetName()) } } @@ -117,10 +130,20 @@ func (d *protoDecoder) Decode(v *dto.MetricFamily) error { return nil } +// errDecoder is an error-state decoder that always returns the same error. +type errDecoder struct { + err error +} + +func (d *errDecoder) Decode(v *dto.MetricFamily) error { + return d.err +} + // textDecoder implements the Decoder interface for the text protocol. type textDecoder struct { r io.Reader fams map[string]*dto.MetricFamily + s model.ValidationScheme err error } @@ -128,7 +151,7 @@ type textDecoder struct { func (d *textDecoder) Decode(v *dto.MetricFamily) error { if d.err == nil { // Read all metrics in one shot. - var p TextParser + p := TextParser{scheme: d.s} d.fams, d.err = p.TextToMetricFamilies(d.r) // If we don't get an error, store io.EOF for the end. if d.err == nil { diff --git a/expfmt/decode_test.go b/expfmt/decode_test.go index 759ff7461..baa761db1 100644 --- a/expfmt/decode_test.go +++ b/expfmt/decode_test.go @@ -80,7 +80,10 @@ mf2 4 ) dec := &SampleDecoder{ - Dec: &textDecoder{r: strings.NewReader(in)}, + Dec: &textDecoder{ + s: model.UTF8Validation, + r: strings.NewReader(in), + }, Opts: &DecodeOptions{ Timestamp: ts, }, @@ -361,7 +364,7 @@ func TestProtoDecoder(t *testing.T) { for i, scenario := range scenarios { dec := &SampleDecoder{ - Dec: &protoDecoder{r: strings.NewReader(scenario.in)}, + Dec: &protoDecoder{r: strings.NewReader(scenario.in), s: model.LegacyValidation}, Opts: &DecodeOptions{ Timestamp: testTime, }, @@ -369,7 +372,6 @@ func TestProtoDecoder(t *testing.T) { var all model.Vector for { - model.NameValidationScheme = model.LegacyValidation //nolint:staticcheck var smpls model.Vector err := dec.Decode(&smpls) if err != nil && errors.Is(err, io.EOF) { @@ -377,9 +379,8 @@ func TestProtoDecoder(t *testing.T) { } if scenario.legacyNameFail { require.Errorf(t, err, "Expected error when decoding without UTF-8 support enabled but got none") - model.NameValidationScheme = model.UTF8Validation //nolint:staticcheck dec = &SampleDecoder{ - Dec: &protoDecoder{r: strings.NewReader(scenario.in)}, + Dec: &protoDecoder{r: strings.NewReader(scenario.in), s: model.UTF8Validation}, Opts: &DecodeOptions{ Timestamp: testTime, }, diff --git a/expfmt/encode_test.go b/expfmt/encode_test.go index b7d3418f7..ea3cbfac7 100644 --- a/expfmt/encode_test.go +++ b/expfmt/encode_test.go @@ -376,3 +376,93 @@ func TestEscapedEncode(t *testing.T) { }) } } + +func TestDottedEncode(t *testing.T) { + //nolint:staticcheck + model.NameValidationScheme = model.UTF8Validation + metric := &dto.MetricFamily{ + Name: proto.String("foo.metric"), + Type: dto.MetricType_COUNTER.Enum(), + Metric: []*dto.Metric{ + { + Counter: &dto.Counter{ + Value: proto.Float64(1.234), + }, + }, + { + Label: []*dto.LabelPair{ + { + Name: proto.String("dotted.label.name"), + Value: proto.String("my.label.value"), + }, + }, + Counter: &dto.Counter{ + Value: proto.Float64(8), + }, + }, + }, + } + + scenarios := []struct { + format Format + expectMetricName string + expectLabelName string + }{ + { + format: FmtProtoDelim, + expectMetricName: "foo_metric", + expectLabelName: "dotted_label_name", + }, + { + format: FmtProtoDelim.WithEscapingScheme(model.NoEscaping), + expectMetricName: "foo.metric", + expectLabelName: "dotted.label.name", + }, + { + format: FmtProtoDelim.WithEscapingScheme(model.DotsEscaping), + expectMetricName: "foo_dot_metric", + expectLabelName: "dotted_dot_label_dot_name", + }, + { + format: FmtText, + expectMetricName: "foo_metric", + expectLabelName: "dotted_label_name", + }, + { + format: FmtText.WithEscapingScheme(model.NoEscaping), + expectMetricName: "foo.metric", + expectLabelName: "dotted.label.name", + }, + { + format: FmtText.WithEscapingScheme(model.DotsEscaping), + expectMetricName: "foo_dot_metric", + expectLabelName: "dotted_dot_label_dot_name", + }, + // common library does not support proto text or open metrics parsing so we + // do not test those here. + } + + for i, scenario := range scenarios { + out := bytes.NewBuffer(make([]byte, 0)) + enc := NewEncoder(out, scenario.format) + err := enc.Encode(metric) + if err != nil { + t.Errorf("%d. error: %s", i, err) + continue + } + + dec := NewDecoder(bytes.NewReader(out.Bytes()), scenario.format) + var gotFamily dto.MetricFamily + err = dec.Decode(&gotFamily) + if err != nil { + t.Errorf("%v: unexpected error during decode: %s", scenario.format, err.Error()) + } + if gotFamily.GetName() != scenario.expectMetricName { + t.Errorf("%v: incorrect encoded metric name, want %v, got %v", scenario.format, scenario.expectMetricName, gotFamily.GetName()) + } + lName := gotFamily.GetMetric()[1].Label[0].GetName() + if lName != scenario.expectLabelName { + t.Errorf("%v: incorrect encoded label name, want %v, got %v", scenario.format, scenario.expectLabelName, lName) + } + } +} diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 4067978a1..4799f8358 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -78,6 +78,9 @@ type TextParser struct { // These indicate if the metric name from the current line being parsed is inside // braces and if that metric name was found respectively. currentMetricIsInsideBraces, currentMetricInsideBracesIsPresent bool + // scheme sets the desired ValidationScheme for names. Defaults to the invalid + // UnsetValidation. + scheme model.ValidationScheme } // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange @@ -126,6 +129,7 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF func (p *TextParser) reset(in io.Reader) { p.metricFamiliesByName = map[string]*dto.MetricFamily{} + p.currentLabelPairs = nil if p.buf == nil { p.buf = bufio.NewReader(in) } else { @@ -216,6 +220,9 @@ func (p *TextParser) startComment() stateFn { return nil } p.setOrCreateCurrentMF() + if p.err != nil { + return nil + } if p.skipBlankTab(); p.err != nil { return nil // Unexpected end of input. } @@ -244,6 +251,9 @@ func (p *TextParser) readingMetricName() stateFn { return nil } p.setOrCreateCurrentMF() + if p.err != nil { + return nil + } // Now is the time to fix the type if it hasn't happened yet. if p.currentMF.Type == nil { p.currentMF.Type = dto.MetricType_UNTYPED.Enum() @@ -311,6 +321,9 @@ func (p *TextParser) startLabelName() stateFn { switch p.currentByte { case ',': p.setOrCreateCurrentMF() + if p.err != nil { + return nil + } if p.currentMF.Type == nil { p.currentMF.Type = dto.MetricType_UNTYPED.Enum() } @@ -319,6 +332,10 @@ func (p *TextParser) startLabelName() stateFn { return p.startLabelName case '}': p.setOrCreateCurrentMF() + if p.err != nil { + p.currentLabelPairs = nil + return nil + } if p.currentMF.Type == nil { p.currentMF.Type = dto.MetricType_UNTYPED.Enum() } @@ -341,6 +358,12 @@ func (p *TextParser) startLabelName() stateFn { p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())} if p.currentLabelPair.GetName() == string(model.MetricNameLabel) { p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel)) + p.currentLabelPairs = nil + return nil + } + if !p.scheme.IsValidLabelName(p.currentLabelPair.GetName()) { + p.parseError(fmt.Sprintf("invalid label name %q", p.currentLabelPair.GetName())) + p.currentLabelPairs = nil return nil } // Special summary/histogram treatment. Don't add 'quantile' and 'le' @@ -805,6 +828,10 @@ func (p *TextParser) setOrCreateCurrentMF() { p.currentIsHistogramCount = false p.currentIsHistogramSum = false name := p.currentToken.String() + if !p.scheme.IsValidMetricName(name) { + p.parseError(fmt.Sprintf("invalid metric name %q", name)) + return + } if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil { return } diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index fac60ba6f..49fcfe778 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -21,6 +21,8 @@ import ( dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" + + "github.com/prometheus/common/model" ) func testTextParse(t testing.TB) { @@ -191,10 +193,10 @@ my_summary{n1="val3", quantile="0.2"} 4711 my_summary{n1="val1",n2="val2",quantile="-12.34",} NaN # some # funny comments -# HELP # HELP +# HELP +# HELP my_summary # HELP my_summary -# HELP my_summary `, out: []*dto.MetricFamily{ { @@ -682,20 +684,22 @@ func BenchmarkTextParse(b *testing.B) { func testTextParseError(t testing.TB) { scenarios := []struct { - in string - err string + in string + errUTF8 string + // if errLegacy is blank, it is assumed to be the same as errUTF8 + errLegacy string }{ // 0: No new-line at end of input. { in: ` bla 3.14 blubber 42`, - err: "text format parsing error in line 3: unexpected end of input stream", + errUTF8: "text format parsing error in line 3: unexpected end of input stream", }, // 1: Invalid escape sequence in label value. { - in: `metric{label="\t"} 3.14`, - err: "text format parsing error in line 1: invalid escape sequence", + in: `metric{label="\t"} 3.14`, + errUTF8: "text format parsing error in line 1: invalid escape sequence", }, // 2: Newline in label value. { @@ -703,27 +707,27 @@ blubber 42`, metric{label="new line"} 3.14 `, - err: `text format parsing error in line 2: label value "new" contains unescaped new-line`, + errUTF8: `text format parsing error in line 2: label value "new" contains unescaped new-line`, }, // 3: { - in: `metric{@="bla"} 3.14`, - err: "text format parsing error in line 1: invalid label name for metric", + in: `metric{@="bla"} 3.14`, + errUTF8: "text format parsing error in line 1: invalid label name for metric", }, // 4: { - in: `metric{__name__="bla"} 3.14`, - err: `text format parsing error in line 1: label name "__name__" is reserved`, + in: `metric{__name__="bla"} 3.14`, + errUTF8: `text format parsing error in line 1: label name "__name__" is reserved`, }, // 5: { - in: `metric{label+="bla"} 3.14`, - err: "text format parsing error in line 1: expected '=' after label name", + in: `metric{label+="bla"} 3.14`, + errUTF8: "text format parsing error in line 1: expected '=' after label name", }, // 6: { - in: `metric{label=bla} 3.14`, - err: "text format parsing error in line 1: expected '\"' at start of label value", + in: `metric{label=bla} 3.14`, + errUTF8: "text format parsing error in line 1: expected '\"' at start of label value", }, // 7: { @@ -731,30 +735,30 @@ line"} 3.14 # TYPE metric summary metric{quantile="bla"} 3.14 `, - err: "text format parsing error in line 3: expected float as value for 'quantile' label", + errUTF8: "text format parsing error in line 3: expected float as value for 'quantile' label", }, // 8: { - in: `metric{label="bla"+} 3.14`, - err: "text format parsing error in line 1: unexpected end of label value", + in: `metric{label="bla"+} 3.14`, + errUTF8: "text format parsing error in line 1: unexpected end of label value", }, // 9: { in: `metric{label="bla"} 3.14 2.72 `, - err: "text format parsing error in line 1: expected integer as timestamp", + errUTF8: "text format parsing error in line 1: expected integer as timestamp", }, // 10: { in: `metric{label="bla"} 3.14 2 3 `, - err: "text format parsing error in line 1: spurious string after timestamp", + errUTF8: "text format parsing error in line 1: spurious string after timestamp", }, // 11: { in: `metric{label="bla"} blubb `, - err: "text format parsing error in line 1: expected float as value", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 12: { @@ -762,7 +766,7 @@ metric{quantile="bla"} 3.14 # HELP metric one # HELP metric two `, - err: "text format parsing error in line 3: second HELP line for metric name", + errUTF8: "text format parsing error in line 3: second HELP line for metric name", }, // 13: { @@ -770,7 +774,7 @@ metric{quantile="bla"} 3.14 # TYPE metric counter # TYPE metric untyped `, - err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, + errUTF8: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, }, // 14: { @@ -778,31 +782,31 @@ metric{quantile="bla"} 3.14 metric 4.12 # TYPE metric counter `, - err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, + errUTF8: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, }, // 14: { in: ` # TYPE metric bla `, - err: "text format parsing error in line 2: unknown metric type", + errUTF8: "text format parsing error in line 2: unknown metric type", }, // 15: { in: ` # TYPE met-ric `, - err: "text format parsing error in line 2: invalid metric name in comment", + errUTF8: "text format parsing error in line 2: invalid metric name in comment", }, // 16: { - in: `@invalidmetric{label="bla"} 3.14 2`, - err: "text format parsing error in line 1: invalid metric name", + in: `@invalidmetric{label="bla"} 3.14 2`, + errUTF8: "text format parsing error in line 1: invalid metric name", }, // 17: { - in: `{label="bla"} 3.14 2`, - err: "text format parsing error in line 1: invalid metric name", + in: `{label="bla"} 3.14 2`, + errUTF8: "text format parsing error in line 1: invalid metric name", }, // 18: { @@ -810,67 +814,67 @@ metric 4.12 # TYPE metric histogram metric_bucket{le="bla"} 3.14 `, - err: "text format parsing error in line 3: expected float as value for 'le' label", + errUTF8: "text format parsing error in line 3: expected float as value for 'le' label", }, // 19: Invalid UTF-8 in label value. { - in: "metric{l=\"\xbd\"} 3.14\n", - err: "text format parsing error in line 1: invalid label value \"\\xbd\"", + in: "metric{l=\"\xbd\"} 3.14\n", + errUTF8: "text format parsing error in line 1: invalid label value \"\\xbd\"", }, // 20: Go 1.13 sometimes allows underscores in numbers. { - in: "foo 1_2\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 1_2\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 21: Go 1.13 supports hex floating point. { - in: "foo 0x1p-3\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0x1p-3\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 22: Check for various other literals variants, just in case. { - in: "foo 0x1P-3\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0x1P-3\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 23: { - in: "foo 0B1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0B1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 24: { - in: "foo 0O1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0O1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 25: { - in: "foo 0X1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0X1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 26: { - in: "foo 0x1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0x1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 27: { - in: "foo 0b1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0b1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 28: { - in: "foo 0o1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0o1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 29: { - in: "foo 0x1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0x1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 30: { - in: "foo 0x1\n", - err: "text format parsing error in line 1: expected float as value", + in: "foo 0x1\n", + errUTF8: "text format parsing error in line 1: expected float as value", }, // 31: Check histogram label. { @@ -878,7 +882,7 @@ metric_bucket{le="bla"} 3.14 # TYPE metric histogram metric_bucket{le="0x1p-3"} 3.14 `, - err: "text format parsing error in line 3: expected float as value for 'le' label", + errUTF8: "text format parsing error in line 3: expected float as value for 'le' label", }, // 32: Check quantile label. { @@ -886,27 +890,28 @@ metric_bucket{le="0x1p-3"} 3.14 # TYPE metric summary metric{quantile="0x1p-3"} 3.14 `, - err: "text format parsing error in line 3: expected float as value for 'quantile' label", + errUTF8: "text format parsing error in line 3: expected float as value for 'quantile' label", }, // 33: Check duplicate label. { - in: `metric{label="bla",label="bla"} 3.14`, - err: "text format parsing error in line 1: duplicate label names for metric", + in: `metric{label="bla",label="bla"} 3.14`, + errUTF8: "text format parsing error in line 1: duplicate label names for metric", }, // 34: Multiple quoted metric names. { - in: `{"one.name","another.name"} 3.14`, - err: "text format parsing error in line 1: multiple metric names", + in: `{"one.name","another.name"} 3.14`, + errUTF8: "text format parsing error in line 1: multiple metric names", + errLegacy: `text format parsing error in line 1: invalid metric name "one.name"`, }, // 35: Invalid escape sequence in quoted metric name. { - in: `{"a\xc5z",label="bla"} 3.14`, - err: "text format parsing error in line 1: invalid escape sequence", + in: `{"a\xc5z",label="bla"} 3.14`, + errUTF8: "text format parsing error in line 1: invalid escape sequence", }, // 36: Unexpected end of quoted metric name. { - in: `{"metric.name".label="bla"} 3.14`, - err: "text format parsing error in line 1: unexpected end of metric name", + in: `{"metric.name".label="bla"} 3.14`, + errUTF8: "text format parsing error in line 1: unexpected end of metric name", }, // 37: Invalid escape sequence in quoted metric name. { @@ -914,7 +919,7 @@ metric{quantile="0x1p-3"} 3.14 # TYPE "metric.name\t" counter {"metric.name\t",label="bla"} 3.14 `, - err: "text format parsing error in line 2: invalid escape sequence", + errUTF8: "text format parsing error in line 2: invalid escape sequence", }, // 38: Newline in quoted metric name. { @@ -924,7 +929,7 @@ name" counter {"metric name",label="bla"} 3.14 `, - err: `text format parsing error in line 2: metric name "metric" contains unescaped new-line`, + errUTF8: `text format parsing error in line 2: metric name "metric" contains unescaped new-line`, }, // 39: Newline in quoted label name. { @@ -932,21 +937,55 @@ name",label="bla"} 3.14 {"metric.name","new line"="bla"} 3.14 `, - err: `text format parsing error in line 2: label name "new" contains unescaped new-line`, + errUTF8: `text format parsing error in line 2: label name "new" contains unescaped new-line`, + errLegacy: `text format parsing error in line 2: invalid metric name "metric.name"`, + }, + // 40: dotted name fails legacy validation. + { + in: `{"metric.name",foo="bla"} 3.14 +`, + errUTF8: ``, + errLegacy: `text format parsing error in line 1: invalid metric name "metric.name"`, + }, + { + in: `metric_name{"foo"="bar", "dotted.label"="bla"} 3.14 +`, + errUTF8: ``, + errLegacy: `text format parsing error in line 1: invalid label name "dotted.label"`, }, } for i, scenario := range scenarios { + parser.scheme = model.UTF8Validation _, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in)) if err == nil { - t.Errorf("%d. expected error, got nil", i) - continue - } - if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 { + if scenario.errUTF8 != "" { + t.Errorf("%d. expected error, got nil", i) + } + } else if expected, got := scenario.errUTF8, err.Error(); strings.Index(got, expected) != 0 { t.Errorf( "%d. expected error starting with %q, got %q", i, expected, got, ) } + + parser.scheme = model.LegacyValidation + _, err = parser.TextToMetricFamilies(strings.NewReader(scenario.in)) + if err == nil { + if scenario.errLegacy != "" { + t.Errorf("%d. expected error, got nil", i) + } + } else { + expected := scenario.errUTF8 + if scenario.errLegacy != "" { + expected = scenario.errLegacy + } + if got := err.Error(); strings.Index(got, expected) != 0 { + t.Errorf( + "%d. expected error starting with %q, got %q", + i, expected, got, + ) + } + } } } From 053ba9ab38de824a8bd2ab3483dbe33f3343e340 Mon Sep 17 00:00:00 2001 From: Julius Hinze Date: Mon, 25 Aug 2025 10:07:05 +0200 Subject: [PATCH 50/78] ValidationScheme implements pflag.Value and json.Marshaler/Unmarshaler interfaces (#807) * feat(model): ValidationScheme implements pflag.Value and json.Marshaler/Unmarshaler interfaces --------- Signed-off-by: Julius Hinze Signed-off-by: Arve Knudsen Co-authored-by: Arve Knudsen --- model/metric.go | 51 +++++++++++++++++++++---- model/metric_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 8 deletions(-) diff --git a/model/metric.go b/model/metric.go index 31981bb4e..2aa78900e 100644 --- a/model/metric.go +++ b/model/metric.go @@ -14,6 +14,7 @@ package model import ( + "encoding/json" "errors" "fmt" "regexp" @@ -77,10 +78,13 @@ const ( UTF8Validation ) -var ( - _ yaml.Marshaler = UnsetValidation - _ fmt.Stringer = UnsetValidation -) +var _ interface { + yaml.Marshaler + yaml.Unmarshaler + json.Marshaler + json.Unmarshaler + fmt.Stringer +} = new(ValidationScheme) // String returns the string representation of s. func (s ValidationScheme) String() string { @@ -114,15 +118,41 @@ func (s *ValidationScheme) UnmarshalYAML(unmarshal func(any) error) error { if err := unmarshal(&scheme); err != nil { return err } - switch scheme { + return s.Set(scheme) +} + +// MarshalJSON implements the json.Marshaler interface. +func (s ValidationScheme) MarshalJSON() ([]byte, error) { + switch s { + case UnsetValidation: + return json.Marshal("") + case UTF8Validation, LegacyValidation: + return json.Marshal(s.String()) + default: + return nil, fmt.Errorf("unhandled ValidationScheme: %d", s) + } +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (s *ValidationScheme) UnmarshalJSON(bytes []byte) error { + var repr string + if err := json.Unmarshal(bytes, &repr); err != nil { + return err + } + return s.Set(repr) +} + +// Set implements the pflag.Value interface. +func (s *ValidationScheme) Set(text string) error { + switch text { case "": // Don't change the value. - case "legacy": + case LegacyValidation.String(): *s = LegacyValidation - case "utf8": + case UTF8Validation.String(): *s = UTF8Validation default: - return fmt.Errorf("unrecognized ValidationScheme: %q", scheme) + return fmt.Errorf("unrecognized ValidationScheme: %q", text) } return nil } @@ -174,6 +204,11 @@ func (s ValidationScheme) IsValidLabelName(labelName string) bool { } } +// Type implements the pflag.Value interface. +func (s ValidationScheme) Type() string { + return "validationScheme" +} + type EscapingScheme int const ( diff --git a/model/metric_test.go b/model/metric_test.go index b64c8a7e5..161f93142 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -14,6 +14,7 @@ package model import ( + "encoding/json" "errors" "fmt" "strings" @@ -203,6 +204,94 @@ func TestValidationScheme_UnmarshalYAML(t *testing.T) { } } +func TestValidationScheme_UnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + input string + want ValidationScheme + wantErr bool + }{ + { + name: "invalid", + input: `invalid`, + wantErr: true, + }, + { + name: "empty", + input: `""`, + want: UnsetValidation, + }, + { + name: "legacy validation", + input: `"legacy"`, + want: LegacyValidation, + }, + { + name: "utf8 validation", + input: `"utf8"`, + want: UTF8Validation, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got ValidationScheme + err := json.Unmarshal([]byte(tc.input), &got) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, got) + + output, err := json.Marshal(got) + require.NoError(t, err) + require.Equal(t, tc.input, string(output)) + }) + } +} + +func TestValidationScheme_Set(t *testing.T) { + testCases := []struct { + name string + input string + want ValidationScheme + wantErr bool + }{ + { + name: "invalid", + input: `invalid`, + wantErr: true, + }, + { + name: "empty", + input: ``, + want: UnsetValidation, + }, + { + name: "legacy validation", + input: `legacy`, + want: LegacyValidation, + }, + { + name: "utf8 validation", + input: `utf8`, + want: UTF8Validation, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var got ValidationScheme + err := got.Set(tc.input) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + func TestValidationScheme_IsMetricNameValid(t *testing.T) { scenarios := []struct { mn string From 9deefbac48205e9412a31b23da9c33e063bdc834 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Mon, 25 Aug 2025 10:29:53 +0200 Subject: [PATCH 51/78] expfmt: Add NewTextParser function (#816) Add expfmt.NewTextParser function that allows providing name validation scheme. Signed-off-by: Arve Knudsen --- expfmt/decode.go | 2 +- expfmt/fuzz.go | 8 ++++++-- expfmt/text_parse.go | 5 +++++ expfmt/text_parse_test.go | 21 +++++++++++++++++++-- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/expfmt/decode.go b/expfmt/decode.go index c4a701c50..98d278c13 100644 --- a/expfmt/decode.go +++ b/expfmt/decode.go @@ -151,7 +151,7 @@ type textDecoder struct { func (d *textDecoder) Decode(v *dto.MetricFamily) error { if d.err == nil { // Read all metrics in one shot. - p := TextParser{scheme: d.s} + p := NewTextParser(d.s) d.fams, d.err = p.TextToMetricFamilies(d.r) // If we don't get an error, store io.EOF for the end. if d.err == nil { diff --git a/expfmt/fuzz.go b/expfmt/fuzz.go index dfac962a4..ef9004640 100644 --- a/expfmt/fuzz.go +++ b/expfmt/fuzz.go @@ -17,7 +17,11 @@ package expfmt -import "bytes" +import ( + "bytes" + + "github.com/prometheus/common/model" +) // Fuzz text metric parser with with github.com/dvyukov/go-fuzz: // @@ -26,7 +30,7 @@ import "bytes" // // Further input samples should go in the folder fuzz/corpus. func Fuzz(in []byte) int { - parser := TextParser{} + parser := NewTextParser(model.UTF8Validation) _, err := parser.TextToMetricFamilies(bytes.NewReader(in)) if err != nil { diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 4799f8358..26bd1c511 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -83,6 +83,11 @@ type TextParser struct { scheme model.ValidationScheme } +// NewTextParser returns a new TextParser with the provided nameValidationScheme. +func NewTextParser(nameValidationScheme model.ValidationScheme) TextParser { + return TextParser{scheme: nameValidationScheme} +} + // TextToMetricFamilies reads 'in' as the simple and flat text-based exchange // format and creates MetricFamily proto messages. It returns the MetricFamily // proto messages in a map where the metric names are the keys, along with any diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index 49fcfe778..5b848c4a1 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -25,6 +25,23 @@ import ( "github.com/prometheus/common/model" ) +func TestNewTextParser(t *testing.T) { + p := NewTextParser(model.UTF8Validation) + if p.scheme != model.UTF8Validation { + t.Errorf("expected NewTextParser to return a TextParser with scheme %s - got %s", model.UTF8Validation, p.scheme) + } + + p = NewTextParser(model.LegacyValidation) + if p.scheme != model.LegacyValidation { + t.Errorf("expected NewTextParser to return a TextParser with scheme %s - got %s", model.LegacyValidation, p.scheme) + } + + p = NewTextParser(model.UnsetValidation) + if p.scheme != model.UnsetValidation { + t.Errorf("expected NewTextParser to return a TextParser with scheme %s - got %s", model.UnsetValidation, p.scheme) + } +} + func testTextParse(t testing.TB) { scenarios := []struct { in string @@ -1001,7 +1018,7 @@ func BenchmarkParseError(b *testing.B) { func TestTextParserStartOfLine(t *testing.T) { t.Run("EOF", func(t *testing.T) { - p := TextParser{} + p := NewTextParser(model.UTF8Validation) in := strings.NewReader("") p.reset(in) fn := p.startOfLine() @@ -1014,7 +1031,7 @@ func TestTextParserStartOfLine(t *testing.T) { }) t.Run("OtherError", func(t *testing.T) { - p := TextParser{} + p := NewTextParser(model.UTF8Validation) in := &errReader{err: errors.New("unexpected error")} p.reset(in) fn := p.startOfLine() From efa2e6bf18793631cd44342a57686195a23c011d Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 26 Aug 2025 15:10:33 +0200 Subject: [PATCH 52/78] Sync .golangci.yml with prometheus/prometheus (#817) * Sync .golangci.yml with prometheus/prometheus Signed-off-by: Arve Knudsen * Fix formatting in some *.go files Signed-off-by: Arve Knudsen --------- Signed-off-by: Arve Knudsen --- .golangci.yml | 158 +++++++++++++++++++++++++++--- expfmt/bench_test.go | 3 +- expfmt/encode.go | 6 +- expfmt/encode_test.go | 3 +- expfmt/expfmt_test.go | 4 +- expfmt/openmetrics_create.go | 3 +- expfmt/openmetrics_create_test.go | 5 +- expfmt/text_create.go | 4 +- expfmt/text_create_test.go | 3 +- 9 files changed, 158 insertions(+), 31 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 844fc2e55..f22a4bcdf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,103 @@ version: "2" linters: + # Keep this list sorted alphabetically enable: + - depguard - errorlint + - exptostd + #- fatcontext + #- gocritic + #- godot + - govet + - loggercheck - misspell + - nilnesserr + # TODO(bwplotka): Enable once https://github.com/golangci/golangci-lint/issues/3228 is fixed. + # - nolintlint - perfsprint + - predeclared - revive + - sloglint - testifylint + #- unconvert + - unused + #- usestdlibvars + - whitespace + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ + rules: + - linters: + - errcheck + # Taken from the default exclusions in v1. + text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked + - linters: + - govet + # We use many Seek methods that do not follow the usual pattern. + text: "stdmethods: method Seek.* should have signature Seek" + - linters: + - revive + # We have stopped at some point to write doc comments on exported symbols. + # TODO(beorn7): Maybe we should enforce this again? + text: exported (.+) should have comment( \(or a comment on this block\))? or be unexported + - linters: + - gocritic + text: "appendAssign" + - linters: + - errcheck + path: _test.go + - linters: + - errorlint + path: "tsdb/head_wal.go" + - linters: + - godot + source: "^// ===" + warn-unused: true settings: + depguard: + rules: + main: + deny: + #- pkg: "sync/atomic" + #desc: "Use go.uber.org/atomic instead of sync/atomic" + - pkg: "github.com/go-kit/kit/log" + desc: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" + - pkg: "io/ioutil" + desc: "Use corresponding 'os' or 'io' functions instead." + #- pkg: "regexp" + #desc: "Use github.com/grafana/regexp instead of regexp" + - pkg: "github.com/pkg/errors" + desc: "Use 'errors' or 'fmt' instead of github.com/pkg/errors" + - pkg: "gzip" + desc: "Use github.com/klauspost/compress instead of gzip" + - pkg: "zlib" + desc: "Use github.com/klauspost/compress instead of zlib" + - pkg: "golang.org/x/exp/slices" + desc: "Use 'slices' instead." + errcheck: + exclude-functions: + # Don't flag lines such as "io.Copy(io.Discard, resp.Body)". + - io.Copy + # The next two are used in HTTP handlers, any error is handled by the server itself. + - io.WriteString + - (net/http.ResponseWriter).Write + # No need to check for errors on server's shutdown. + - (*net/http.Server).Shutdown + # Never check for rollback errors as Rollback() is called when a previous error was detected. + - (github.com/prometheus/prometheus/storage.Appender).Rollback + govet: + disable: + - shadow + - fieldalignment + enable-all: true perfsprint: # Optimizes even if it requires an int or uint type cast. int-conversion: true @@ -19,36 +110,79 @@ linters: # Optimizes into strings concatenation. strconcat: false revive: + # By default, revive will enable only the linting rules that are named in the configuration file. + # So, it's needed to explicitly enable all required rules here. rules: - # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter + # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md + - name: blank-imports + - name: comment-spacings + - name: context-as-argument + arguments: + # Allow functions with test or bench signatures. + - allowTypesBefore: '*testing.T,testing.TB' + - name: context-keys-type + #- name: dot-imports + #- name: early-return + # arguments: + # - "preserveScope" + # A lot of false positives: incorrectly identifies channel draining as "empty code block". + # See https://github.com/mgechev/revive/issues/386 + - name: empty-block + disabled: true + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + #- name: exported + #- name: increment-decrement + #- name: indent-error-flow + # arguments: + # - "preserveScope" + - name: package-comments + # TODO(beorn7): Currently, we have a lot of missing package doc comments. Maybe we should have them. + disabled: true + - name: range + #- name: receiver-naming + - name: redefines-builtin-id + - name: superfluous-else + arguments: + - "preserveScope" + - name: time-naming + #- name: unexported-return + - name: unreachable-code - name: unused-parameter severity: warning disabled: true + #- name: unused-receiver + #- name: var-declaration + #- name: var-naming testifylint: enable-all: true disable: + - float-compare - go-require formatter: require-f-funcs: true - exclusions: - generated: lax - presets: - - comments - - common-false-positives - - legacy - - std-error-handling - paths: - - third_party$ - - builtin$ - - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 +output: + show-stats: false +run: + timeout: 15m formatters: enable: + - gci - gofumpt - goimports settings: + gci: + sections: + - standard + - default + - prefix(github.com/prometheus/common) + gofumpt: + extra-rules: true goimports: local-prefixes: - github.com/prometheus/common diff --git a/expfmt/bench_test.go b/expfmt/bench_test.go index 90d9373d9..1acb19965 100644 --- a/expfmt/bench_test.go +++ b/expfmt/bench_test.go @@ -22,10 +22,9 @@ import ( "os" "testing" - "google.golang.org/protobuf/encoding/protodelim" - dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protodelim" "github.com/prometheus/common/model" ) diff --git a/expfmt/encode.go b/expfmt/encode.go index 6af7e0014..73c24dfbc 100644 --- a/expfmt/encode.go +++ b/expfmt/encode.go @@ -18,14 +18,12 @@ import ( "io" "net/http" + "github.com/munnerz/goautoneg" + dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/encoding/protodelim" "google.golang.org/protobuf/encoding/prototext" "github.com/prometheus/common/model" - - "github.com/munnerz/goautoneg" - - dto "github.com/prometheus/client_model/go" ) // Encoder types encode metric families into an underlying wire protocol. diff --git a/expfmt/encode_test.go b/expfmt/encode_test.go index ea3cbfac7..6a801629a 100644 --- a/expfmt/encode_test.go +++ b/expfmt/encode_test.go @@ -18,13 +18,12 @@ import ( "net/http" "testing" + dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "github.com/prometheus/common/model" - - dto "github.com/prometheus/client_model/go" ) func TestNegotiate(t *testing.T) { diff --git a/expfmt/expfmt_test.go b/expfmt/expfmt_test.go index d9373bcf3..0a5ef55b9 100644 --- a/expfmt/expfmt_test.go +++ b/expfmt/expfmt_test.go @@ -16,9 +16,9 @@ package expfmt import ( "testing" - "github.com/prometheus/common/model" - "github.com/stretchr/testify/require" + + "github.com/prometheus/common/model" ) // Test Format to Escapting Scheme conversion diff --git a/expfmt/openmetrics_create.go b/expfmt/openmetrics_create.go index fed8253ca..2a1ced3f5 100644 --- a/expfmt/openmetrics_create.go +++ b/expfmt/openmetrics_create.go @@ -22,11 +22,10 @@ import ( "strconv" "strings" + dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/types/known/timestamppb" "github.com/prometheus/common/model" - - dto "github.com/prometheus/client_model/go" ) type encoderOption struct { diff --git a/expfmt/openmetrics_create_test.go b/expfmt/openmetrics_create_test.go index a81bfed3f..eb9a9d387 100644 --- a/expfmt/openmetrics_create_test.go +++ b/expfmt/openmetrics_create_test.go @@ -20,11 +20,10 @@ import ( "testing" "time" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/timestamppb" - dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/prometheus/common/model" ) diff --git a/expfmt/text_create.go b/expfmt/text_create.go index e242b3720..c4e9c1bbc 100644 --- a/expfmt/text_create.go +++ b/expfmt/text_create.go @@ -22,9 +22,9 @@ import ( "strings" "sync" - "github.com/prometheus/common/model" - dto "github.com/prometheus/client_model/go" + + "github.com/prometheus/common/model" ) // enhancedWriter has all the enhanced write functions needed here. bufio.Writer diff --git a/expfmt/text_create_test.go b/expfmt/text_create_test.go index bb1c8f77a..967c46a2e 100644 --- a/expfmt/text_create_test.go +++ b/expfmt/text_create_test.go @@ -19,10 +19,9 @@ import ( "strings" "testing" - "google.golang.org/protobuf/proto" - dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "github.com/prometheus/common/model" ) From e8184e327b44b0e4ff458567da6ce3c8c9667f73 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 26 Aug 2025 19:47:32 +0200 Subject: [PATCH 53/78] Enable the godot linter (#821) * Enable the godot linter Signed-off-by: Arve Knudsen * Update expfmt/expfmt_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Arve Knudsen --------- Signed-off-by: Arve Knudsen Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .golangci.yml | 2 +- config/http_config.go | 4 ++-- config/http_config_test.go | 2 +- expfmt/expfmt_test.go | 4 +--- model/metric.go | 4 ++-- model/time.go | 4 ++-- promslog/flag/flag.go | 2 +- promslog/slog.go | 2 +- 8 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f22a4bcdf..3d97d7482 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,7 @@ linters: - exptostd #- fatcontext #- gocritic - #- godot + - godot - govet - loggercheck - misspell diff --git a/config/http_config.go b/config/http_config.go index 5d3f1941b..5660cc31e 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -245,7 +245,7 @@ type OAuth2 struct { ProxyConfig `yaml:",inline"` } -// UnmarshalYAML implements the yaml.Unmarshaler interface +// UnmarshalYAML implements the yaml.Unmarshaler interface. func (o *OAuth2) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain OAuth2 if err := unmarshal((*plain)(o)); err != nil { @@ -423,7 +423,7 @@ func (c *HTTPClientConfig) Validate() error { return nil } -// UnmarshalYAML implements the yaml.Unmarshaler interface +// UnmarshalYAML implements the yaml.Unmarshaler interface. func (c *HTTPClientConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain HTTPClientConfig *c = DefaultHTTPClientConfig diff --git a/config/http_config_test.go b/config/http_config_test.go index 58d13b0dc..d959ab089 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -1748,7 +1748,7 @@ func TestUnmarshalEmptyURL(t *testing.T) { } } -// checks if u equals to &url.URL{} +// checks if u equals to &url.URL{}. func isEmptyNonNilURL(u *url.URL) bool { return u != nil && *u == url.URL{} } diff --git a/expfmt/expfmt_test.go b/expfmt/expfmt_test.go index 0a5ef55b9..7ac054f23 100644 --- a/expfmt/expfmt_test.go +++ b/expfmt/expfmt_test.go @@ -21,9 +21,7 @@ import ( "github.com/prometheus/common/model" ) -// Test Format to Escapting Scheme conversion -// Path: expfmt/expfmt_test.go -// Compare this snippet from expfmt/expfmt.go: +// Test Format to Escaping Scheme conversion. func TestToFormatType(t *testing.T) { tests := []struct { format Format diff --git a/model/metric.go b/model/metric.go index 2aa78900e..1cc7d7d3b 100644 --- a/model/metric.go +++ b/model/metric.go @@ -238,7 +238,7 @@ const ( // Accept header, the default NameEscapingScheme will be used. EscapingKey = "escaping" - // Possible values for Escaping Key: + // Possible values for Escaping Key. AllowUTF8 = "allow-utf-8" // No escaping required. EscapeUnderscores = "underscores" EscapeDots = "dots" @@ -470,7 +470,7 @@ func EscapeName(name string, scheme EscapingScheme) string { } } -// lower function taken from strconv.atoi +// lower function taken from strconv.atoi. func lower(c byte) byte { return c | ('x' - 'X') } diff --git a/model/time.go b/model/time.go index fed9e87b9..43e352a42 100644 --- a/model/time.go +++ b/model/time.go @@ -170,14 +170,14 @@ func (t *Time) UnmarshalJSON(b []byte) error { // This type should not propagate beyond the scope of input/output processing. type Duration time.Duration -// Set implements pflag/flag.Value +// Set implements pflag/flag.Value. func (d *Duration) Set(s string) error { var err error *d, err = ParseDuration(s) return err } -// Type implements pflag.Value +// Type implements pflag.Value. func (d *Duration) Type() string { return "duration" } diff --git a/promslog/flag/flag.go b/promslog/flag/flag.go index 85c67b250..40187cfcd 100644 --- a/promslog/flag/flag.go +++ b/promslog/flag/flag.go @@ -40,7 +40,7 @@ const FormatFlagName = "log.format" var FormatFlagHelp = "Output format of log messages. One of: [" + strings.Join(promslog.FormatFlagOptions, ", ") + "]" // AddFlags adds the flags used by this package to the Kingpin application. -// To use the default Kingpin application, call AddFlags(kingpin.CommandLine) +// To use the default Kingpin application, call AddFlags(kingpin.CommandLine). func AddFlags(a *kingpin.Application, config *promslog.Config) { config.Level = promslog.NewLevel() a.Flag(LevelFlagName, LevelFlagHelp). diff --git a/promslog/slog.go b/promslog/slog.go index 02370f175..33da53f82 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -138,7 +138,7 @@ func (f *Format) Set(s string) error { return nil } -// Config is a struct containing configurable settings for the logger +// Config is a struct containing configurable settings for the logger. type Config struct { Level *Level Format *Format From b775ce48e9888ac529a4fa0d37f837e8a0f504d8 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 26 Aug 2025 19:47:45 +0200 Subject: [PATCH 54/78] Enable usestdlibvars linter (#820) Signed-off-by: Arve Knudsen --- .golangci.yml | 2 +- config/http_config_test.go | 10 +++++----- route/route_test.go | 12 ++++++------ server/static_file_server_test.go | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 3d97d7482..c3eb8d26e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,7 +21,7 @@ linters: - testifylint #- unconvert - unused - #- usestdlibvars + - usestdlibvars - whitespace exclusions: generated: lax diff --git a/config/http_config_test.go b/config/http_config_test.go index d959ab089..09ad38809 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -705,7 +705,7 @@ func TestBearerAuthRoundTripper(t *testing.T) { // Normal flow. bearerAuthRoundTripper := NewAuthorizationCredentialsRoundTripper("Bearer", NewInlineSecret(BearerToken), fakeRoundTripper) - request, _ := http.NewRequest("GET", "/hitchhiker", nil) + request, _ := http.NewRequest(http.MethodGet, "/hitchhiker", nil) request.Header.Set("User-Agent", "Douglas Adams mind") _, err := bearerAuthRoundTripper.RoundTrip(request) if err != nil { @@ -714,7 +714,7 @@ func TestBearerAuthRoundTripper(t *testing.T) { // Should honor already Authorization header set. bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewAuthorizationCredentialsRoundTripper("Bearer", NewInlineSecret(newBearerToken), fakeRoundTripper) - request, _ = http.NewRequest("GET", "/hitchhiker", nil) + request, _ = http.NewRequest(http.MethodGet, "/hitchhiker", nil) request.Header.Set("Authorization", ExpectedBearer) _, err = bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request) if err != nil { @@ -733,7 +733,7 @@ func TestBearerAuthFileRoundTripper(t *testing.T) { // Normal flow. bearerAuthRoundTripper := NewAuthorizationCredentialsRoundTripper("Bearer", &FileSecret{file: BearerTokenFile}, fakeRoundTripper) - request, _ := http.NewRequest("GET", "/hitchhiker", nil) + request, _ := http.NewRequest(http.MethodGet, "/hitchhiker", nil) request.Header.Set("User-Agent", "Douglas Adams mind") _, err := bearerAuthRoundTripper.RoundTrip(request) if err != nil { @@ -742,7 +742,7 @@ func TestBearerAuthFileRoundTripper(t *testing.T) { // Should honor already Authorization header set. bearerAuthRoundTripperShouldNotModifyExistingAuthorization := NewAuthorizationCredentialsRoundTripper("Bearer", &FileSecret{file: MissingBearerTokenFile}, fakeRoundTripper) - request, _ = http.NewRequest("GET", "/hitchhiker", nil) + request, _ = http.NewRequest(http.MethodGet, "/hitchhiker", nil) request.Header.Set("Authorization", ExpectedBearer) _, err = bearerAuthRoundTripperShouldNotModifyExistingAuthorization.RoundTrip(request) if err != nil { @@ -2104,7 +2104,7 @@ no_proxy: promcon.io,cncf.io`, proxyServer.URL), os.Setenv("NO_PROXY", tc.noProxyEnv) } - req := httptest.NewRequest("GET", tc.targetURL, nil) + req := httptest.NewRequest(http.MethodGet, tc.targetURL, nil) proxyFunc := proxyConfig.Proxy() resultURL, err := proxyFunc(req) diff --git a/route/route_test.go b/route/route_test.go index 87c32efd1..dd3303074 100644 --- a/route/route_test.go +++ b/route/route_test.go @@ -24,7 +24,7 @@ import ( func TestRedirect(t *testing.T) { router := New().WithPrefix("/test/prefix") w := httptest.NewRecorder() - r, err := http.NewRequest("GET", "http://localhost:9090/foo", nil) + r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/foo", nil) require.NoErrorf(t, err, "Error building test request: %s", err) router.Redirect(w, r, "/some/endpoint", http.StatusFound) @@ -43,7 +43,7 @@ func TestContext(t *testing.T) { require.Equalf(t, want, got, "Unexpected context value: want %q, got %q", want, got) }) - r, err := http.NewRequest("GET", "http://localhost:9090/test/bar/", nil) + r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/test/bar/", nil) require.NoErrorf(t, err, "Error building test request: %s", err) router.ServeHTTP(nil, r) } @@ -62,7 +62,7 @@ func TestContextWithValue(t *testing.T) { require.Equalf(t, want, got, "Unexpected context value: want %q, got %q", want, got) }) - r, err := http.NewRequest("GET", "http://localhost:9090/test/bar/", nil) + r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/test/bar/", nil) require.NoErrorf(t, err, "Error building test request: %s", err) params := map[string]string{ "lorem": "ipsum", @@ -85,7 +85,7 @@ func TestContextWithoutValue(t *testing.T) { require.Equalf(t, want, got, "Unexpected context value: want %q, got %q", want, got) }) - r, err := http.NewRequest("GET", "http://localhost:9090/test", nil) + r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/test", nil) require.NoErrorf(t, err, "Error building test request: %s", err) router.ServeHTTP(nil, r) } @@ -111,7 +111,7 @@ func TestInstrumentation(t *testing.T) { for _, c := range cases { c.router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {}) - r, err := http.NewRequest("GET", "http://localhost:9090/foo", nil) + r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/foo", nil) require.NoErrorf(t, err, "Error building test request: %s", err) c.router.ServeHTTP(nil, r) require.Equalf(t, c.want, got, "Unexpected value: want %q, got %q", c.want, got) @@ -151,7 +151,7 @@ func TestInstrumentations(t *testing.T) { for _, c := range cases { c.router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {}) - r, err := http.NewRequest("GET", "http://localhost:9090/foo", nil) + r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/foo", nil) require.NoErrorf(t, err, "Error building test request: %s", err) c.router.ServeHTTP(nil, r) require.Lenf(t, got, len(c.want), "Unexpected value: want %q, got %q", c.want, got) diff --git a/server/static_file_server_test.go b/server/static_file_server_test.go index 9aa8d76fb..254ca1c59 100644 --- a/server/static_file_server_test.go +++ b/server/static_file_server_test.go @@ -68,7 +68,7 @@ func TestServeHttp(t *testing.T) { for _, c := range cases { t.Run(c.name, func(t *testing.T) { rr := httptest.NewRecorder() - req, err := http.NewRequest("GET", "http://localhost/"+c.path, nil) + req, err := http.NewRequest(http.MethodGet, "http://localhost/"+c.path, nil) require.NoError(t, err) s := StaticFileServer(dummyFileSystem{}) From 15ce6eea803f702d7ace980ddf8ff2b91405ddb8 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Tue, 26 Aug 2025 19:49:39 +0200 Subject: [PATCH 55/78] Enable unconvert linter (#819) Signed-off-by: Arve Knudsen --- .golangci.yml | 2 +- config/http_config.go | 6 +++--- model/metric_test.go | 2 +- model/time.go | 4 ++-- model/value.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c3eb8d26e..6d3e8a937 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,7 +19,7 @@ linters: - revive - sloglint - testifylint - #- unconvert + - unconvert - unused - usestdlibvars - whitespace diff --git a/config/http_config.go b/config/http_config.go index 5660cc31e..ee59c1b40 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -72,7 +72,7 @@ var TLSVersions = map[string]TLSVersion{ func (tv *TLSVersion) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string - err := unmarshal((*string)(&s)) + err := unmarshal(&s) if err != nil { return err } @@ -363,7 +363,7 @@ func (c *HTTPClientConfig) Validate() error { if (c.BasicAuth != nil || c.OAuth2 != nil) && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { return errors.New("at most one of basic_auth, oauth2, bearer_token & bearer_token_file must be configured") } - if c.BasicAuth != nil && nonZeroCount(string(c.BasicAuth.Username) != "", c.BasicAuth.UsernameFile != "", c.BasicAuth.UsernameRef != "") > 1 { + if c.BasicAuth != nil && nonZeroCount(c.BasicAuth.Username != "", c.BasicAuth.UsernameFile != "", c.BasicAuth.UsernameRef != "") > 1 { return errors.New("at most one of basic_auth username, username_file & username_ref must be configured") } if c.BasicAuth != nil && nonZeroCount(string(c.BasicAuth.Password) != "", c.BasicAuth.PasswordFile != "", c.BasicAuth.PasswordRef != "") > 1 { @@ -1224,7 +1224,7 @@ func (c *TLSConfig) getClientCertificate(ctx context.Context, secretManager Secr } } - keySecret, err := toSecret(secretManager, Secret(c.Key), c.KeyFile, c.KeyRef) + keySecret, err := toSecret(secretManager, c.Key, c.KeyFile, c.KeyRef) if err != nil { return nil, fmt.Errorf("unable to use client key: %w", err) } diff --git a/model/metric_test.go b/model/metric_test.go index 161f93142..7a4ee7b7a 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -354,7 +354,7 @@ func TestValidationScheme_IsMetricNameValid(t *testing.T) { if LegacyValidation.IsValidMetricName(s.mn) != s.legacyValid { t.Errorf("Expected %v for %q using LegacyValidation.IsValidMetricName", s.legacyValid, s.mn) } - if MetricNameRE.MatchString(string(s.mn)) != s.legacyValid { + if MetricNameRE.MatchString(s.mn) != s.legacyValid { t.Errorf("Expected %v for %q using regexp matching", s.legacyValid, s.mn) } if UTF8Validation.IsValidMetricName(s.mn) != s.utf8Valid { diff --git a/model/time.go b/model/time.go index 43e352a42..af8caaa7b 100644 --- a/model/time.go +++ b/model/time.go @@ -126,14 +126,14 @@ func (t *Time) UnmarshalJSON(b []byte) error { p := strings.Split(string(b), ".") switch len(p) { case 1: - v, err := strconv.ParseInt(string(p[0]), 10, 64) + v, err := strconv.ParseInt(p[0], 10, 64) if err != nil { return err } *t = Time(v * second) case 2: - v, err := strconv.ParseInt(string(p[0]), 10, 64) + v, err := strconv.ParseInt(p[0], 10, 64) if err != nil { return err } diff --git a/model/value.go b/model/value.go index 8050637d8..09cb84984 100644 --- a/model/value.go +++ b/model/value.go @@ -258,7 +258,7 @@ func (s Scalar) String() string { // MarshalJSON implements json.Marshaler. func (s Scalar) MarshalJSON() ([]byte, error) { v := strconv.FormatFloat(float64(s.Value), 'f', -1, 64) - return json.Marshal([...]interface{}{s.Timestamp, string(v)}) + return json.Marshal([...]interface{}{s.Timestamp, v}) } // UnmarshalJSON implements json.Unmarshaler. From a9690faff2ec1f2c7fb86418fe73d69375b1fbbc Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Aug 2025 06:54:21 +0200 Subject: [PATCH 56/78] Enable the fatcontext linter (#822) Signed-off-by: Arve Knudsen --- .golangci.yml | 2 +- route/route.go | 2 +- route/route_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6d3e8a937..18e5b8e80 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ linters: - depguard - errorlint - exptostd - #- fatcontext + - fatcontext #- gocritic - godot - govet diff --git a/route/route.go b/route/route.go index e89fe7ebc..18039fac0 100644 --- a/route/route.go +++ b/route/route.go @@ -78,7 +78,7 @@ func (r *Router) handle(handlerName string, h http.HandlerFunc) httprouter.Handl defer cancel() for _, p := range params { - ctx = context.WithValue(ctx, param(p.Key), p.Value) + ctx = context.WithValue(ctx, param(p.Key), p.Value) //nolint:fatcontext } h(w, req.WithContext(ctx)) } diff --git a/route/route_test.go b/route/route_test.go index dd3303074..773be4891 100644 --- a/route/route_test.go +++ b/route/route_test.go @@ -71,7 +71,7 @@ func TestContextWithValue(t *testing.T) { ctx := r.Context() for p, v := range params { - ctx = WithParam(ctx, p, v) + ctx = WithParam(ctx, p, v) //nolint:fatcontext } r = r.WithContext(ctx) router.ServeHTTP(nil, r) From ff6516819c881fa1f3ae30f03214892d4a7c1974 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Aug 2025 06:57:45 +0200 Subject: [PATCH 57/78] Enable gocritic linter (#818) Signed-off-by: Arve Knudsen --- .golangci.yml | 2 +- config/http_config.go | 12 ++++++------ config/http_config_test.go | 9 +++++---- expfmt/openmetrics_create.go | 6 +++--- expfmt/text_parse.go | 7 ++++--- model/metric.go | 25 ++++++++++++++----------- model/time.go | 2 +- model/value.go | 7 ++++--- 8 files changed, 38 insertions(+), 32 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 18e5b8e80..16d2b51a1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -6,7 +6,7 @@ linters: - errorlint - exptostd - fatcontext - #- gocritic + - gocritic - godot - govet - loggercheck diff --git a/config/http_config.go b/config/http_config.go index ee59c1b40..2e605ffde 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -1362,9 +1362,9 @@ func (t *tlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { } t.mtx.RLock() - equal := bytes.Equal(caHash[:], t.hashCAData) && - bytes.Equal(certHash[:], t.hashCertData) && - bytes.Equal(keyHash[:], t.hashKeyData) + equal := bytes.Equal(caHash, t.hashCAData) && + bytes.Equal(certHash, t.hashCertData) && + bytes.Equal(keyHash, t.hashKeyData) rt := t.rt t.mtx.RUnlock() if equal { @@ -1387,9 +1387,9 @@ func (t *tlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { t.mtx.Lock() t.rt = rt - t.hashCAData = caHash[:] - t.hashCertData = certHash[:] - t.hashKeyData = keyHash[:] + t.hashCAData = caHash + t.hashCertData = certHash + t.hashKeyData = keyHash t.mtx.Unlock() return rt.RoundTrip(req) diff --git a/config/http_config_test.go b/config/http_config_test.go index 09ad38809..bd9e7dcf7 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -369,13 +369,14 @@ func TestNewClientFromConfig(t *testing.T) { }, handler: func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() - if !ok { + switch { + case !ok: fmt.Fprintf(w, "The Authorization header wasn't set") - } else if ExpectedUsername != username { + case ExpectedUsername != username: fmt.Fprintf(w, "The expected username (%s) differs from the obtained username (%s).", ExpectedUsername, username) - } else if ExpectedPassword != password { + case ExpectedPassword != password: fmt.Fprintf(w, "The expected password (%s) differs from the obtained password (%s).", ExpectedPassword, password) - } else { + default: fmt.Fprint(w, ExpectedMessage) } }, diff --git a/expfmt/openmetrics_create.go b/expfmt/openmetrics_create.go index 2a1ced3f5..8dbf6d04e 100644 --- a/expfmt/openmetrics_create.go +++ b/expfmt/openmetrics_create.go @@ -248,7 +248,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E // Finally the samples, one line for each. if metricType == dto.MetricType_COUNTER && strings.HasSuffix(name, "_total") { - compliantName = compliantName + "_total" + compliantName += "_total" } for _, metric := range in.Metric { switch metricType { @@ -640,11 +640,11 @@ func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) { if err != nil { return written, err } - err = (*e).Timestamp.CheckValid() + err = e.Timestamp.CheckValid() if err != nil { return written, err } - ts := (*e).Timestamp.AsTime() + ts := e.Timestamp.AsTime() // TODO(beorn7): Format this directly from components of ts to // avoid overflow/underflow and precision issues of the float // conversion. diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 26bd1c511..99e47e042 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -468,7 +468,8 @@ func (p *TextParser) readingValue() stateFn { // When we are here, we have read all the labels, so for the // special case of a summary/histogram, we can finally find out // if the metric already exists. - if p.currentMF.GetType() == dto.MetricType_SUMMARY { + switch p.currentMF.GetType() { + case dto.MetricType_SUMMARY: signature := model.LabelsToSignature(p.currentLabels) if summary := p.summaries[signature]; summary != nil { p.currentMetric = summary @@ -476,7 +477,7 @@ func (p *TextParser) readingValue() stateFn { p.summaries[signature] = p.currentMetric p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } - } else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + case dto.MetricType_HISTOGRAM: signature := model.LabelsToSignature(p.currentLabels) if histogram := p.histograms[signature]; histogram != nil { p.currentMetric = histogram @@ -484,7 +485,7 @@ func (p *TextParser) readingValue() stateFn { p.histograms[signature] = p.currentMetric p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } - } else { + default: p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } if p.readTokenUntilWhitespace(); p.err != nil { diff --git a/model/metric.go b/model/metric.go index 1cc7d7d3b..b88bde517 100644 --- a/model/metric.go +++ b/model/metric.go @@ -435,13 +435,14 @@ func EscapeName(name string, scheme EscapingScheme) string { case DotsEscaping: // Do not early return for legacy valid names, we still escape underscores. for i, b := range name { - if b == '_' { + switch { + case b == '_': escaped.WriteString("__") - } else if b == '.' { + case b == '.': escaped.WriteString("_dot_") - } else if isValidLegacyRune(b, i) { + case isValidLegacyRune(b, i): escaped.WriteRune(b) - } else { + default: escaped.WriteString("__") } } @@ -452,13 +453,14 @@ func EscapeName(name string, scheme EscapingScheme) string { } escaped.WriteString("U__") for i, b := range name { - if b == '_' { + switch { + case b == '_': escaped.WriteString("__") - } else if isValidLegacyRune(b, i) { + case isValidLegacyRune(b, i): escaped.WriteRune(b) - } else if !utf8.ValidRune(b) { + case !utf8.ValidRune(b): escaped.WriteString("_FFFD_") - } else { + default: escaped.WriteRune('_') escaped.WriteString(strconv.FormatInt(int64(b), 16)) escaped.WriteRune('_') @@ -534,11 +536,12 @@ func UnescapeName(name string, scheme EscapingScheme) string { } r := lower(escapedName[i]) utf8Val *= 16 - if r >= '0' && r <= '9' { + switch { + case r >= '0' && r <= '9': utf8Val += uint(r) - '0' - } else if r >= 'a' && r <= 'f' { + case r >= 'a' && r <= 'f': utf8Val += uint(r) - 'a' + 10 - } else { + default: return name } i++ diff --git a/model/time.go b/model/time.go index af8caaa7b..2a0379fcf 100644 --- a/model/time.go +++ b/model/time.go @@ -143,7 +143,7 @@ func (t *Time) UnmarshalJSON(b []byte) error { if prec < 0 { p[1] = p[1][:dotPrecision] } else if prec > 0 { - p[1] = p[1] + strings.Repeat("0", prec) + p[1] += strings.Repeat("0", prec) } va, err := strconv.ParseInt(p[1], 10, 32) diff --git a/model/value.go b/model/value.go index 09cb84984..15c34ee6f 100644 --- a/model/value.go +++ b/model/value.go @@ -191,7 +191,8 @@ func (ss SampleStream) String() string { } func (ss SampleStream) MarshalJSON() ([]byte, error) { - if len(ss.Histograms) > 0 && len(ss.Values) > 0 { + switch { + case len(ss.Histograms) > 0 && len(ss.Values) > 0: v := struct { Metric Metric `json:"metric"` Values []SamplePair `json:"values"` @@ -202,7 +203,7 @@ func (ss SampleStream) MarshalJSON() ([]byte, error) { Histograms: ss.Histograms, } return json.Marshal(&v) - } else if len(ss.Histograms) > 0 { + case len(ss.Histograms) > 0: v := struct { Metric Metric `json:"metric"` Histograms []SampleHistogramPair `json:"histograms"` @@ -211,7 +212,7 @@ func (ss SampleStream) MarshalJSON() ([]byte, error) { Histograms: ss.Histograms, } return json.Marshal(&v) - } else { + default: v := struct { Metric Metric `json:"metric"` Values []SamplePair `json:"values"` From 149efd6d704a94178a9123536aabadcf01a6b09a Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Aug 2025 07:06:05 +0200 Subject: [PATCH 58/78] Use github.com/grafana/regexp instead of regexp Signed-off-by: Arve Knudsen --- .golangci.yml | 4 ++-- expfmt/fuzz.go | 1 - go.mod | 1 + go.sum | 2 ++ model/labels.go | 3 ++- model/metric.go | 2 +- model/silence.go | 3 ++- model/value_histogram_test.go | 2 +- promslog/slog_test.go | 2 +- 9 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 16d2b51a1..7fb514f9b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -72,8 +72,8 @@ linters: desc: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" - pkg: "io/ioutil" desc: "Use corresponding 'os' or 'io' functions instead." - #- pkg: "regexp" - #desc: "Use github.com/grafana/regexp instead of regexp" + - pkg: "regexp" + desc: "Use github.com/grafana/regexp instead of regexp" - pkg: "github.com/pkg/errors" desc: "Use 'errors' or 'fmt' instead of github.com/pkg/errors" - pkg: "gzip" diff --git a/expfmt/fuzz.go b/expfmt/fuzz.go index ef9004640..0290f6abc 100644 --- a/expfmt/fuzz.go +++ b/expfmt/fuzz.go @@ -32,7 +32,6 @@ import ( func Fuzz(in []byte) int { parser := NewTextParser(model.UTF8Validation) _, err := parser.TextToMetricFamilies(bytes.NewReader(in)) - if err != nil { return 0 } diff --git a/go.mod b/go.mod index f712996b2..191017ed8 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.24.1 require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/google/go-cmp v0.7.0 + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc github.com/julienschmidt/httprouter v1.3.0 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f diff --git a/go.sum b/go.sum index 6f72946c0..e2c51fcf0 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= diff --git a/model/labels.go b/model/labels.go index dfeb34be5..93498c9ec 100644 --- a/model/labels.go +++ b/model/labels.go @@ -16,9 +16,10 @@ package model import ( "encoding/json" "fmt" - "regexp" "strings" "unicode/utf8" + + "github.com/grafana/regexp" ) const ( diff --git a/model/metric.go b/model/metric.go index b88bde517..bd846b5fe 100644 --- a/model/metric.go +++ b/model/metric.go @@ -17,12 +17,12 @@ import ( "encoding/json" "errors" "fmt" - "regexp" "sort" "strconv" "strings" "unicode/utf8" + "github.com/grafana/regexp" dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" "gopkg.in/yaml.v2" diff --git a/model/silence.go b/model/silence.go index 8f91a9702..8c51dbaac 100644 --- a/model/silence.go +++ b/model/silence.go @@ -17,8 +17,9 @@ import ( "encoding/json" "errors" "fmt" - "regexp" "time" + + "github.com/grafana/regexp" ) // Matcher describes a matches the value of a given label. diff --git a/model/value_histogram_test.go b/model/value_histogram_test.go index e239f4630..75f65c664 100644 --- a/model/value_histogram_test.go +++ b/model/value_histogram_test.go @@ -16,9 +16,9 @@ package model import ( "encoding/json" "reflect" - "regexp" "testing" + "github.com/grafana/regexp" "github.com/stretchr/testify/require" ) diff --git a/promslog/slog_test.go b/promslog/slog_test.go index 91e79a9fb..0ab7c6309 100644 --- a/promslog/slog_test.go +++ b/promslog/slog_test.go @@ -18,11 +18,11 @@ import ( "context" "fmt" "log/slog" - "regexp" "strings" "testing" "time" + "github.com/grafana/regexp" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" ) From 6d40fe16ed5f8f21fae8f9e53fd8b54eb08c2d44 Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Aug 2025 08:16:15 +0200 Subject: [PATCH 59/78] Use go.uber.org/atomic instead of sync/atomic (#825) Signed-off-by: Arve Knudsen --- .golangci.yml | 4 ++-- config/http_config_test.go | 9 +++++---- go.mod | 1 + go.sum | 2 ++ 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 7fb514f9b..0afcc4a34 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -66,8 +66,8 @@ linters: rules: main: deny: - #- pkg: "sync/atomic" - #desc: "Use go.uber.org/atomic instead of sync/atomic" + - pkg: "sync/atomic" + desc: "Use go.uber.org/atomic instead of sync/atomic" - pkg: "github.com/go-kit/kit/log" desc: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" - pkg: "io/ioutil" diff --git a/config/http_config_test.go b/config/http_config_test.go index bd9e7dcf7..3804bfa85 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -31,11 +31,11 @@ import ( "strconv" "strings" "sync" - "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" + "go.uber.org/atomic" "gopkg.in/yaml.v2" ) @@ -1309,7 +1309,8 @@ func TestTLSRoundTripperRaces(t *testing.T) { var wg sync.WaitGroup ch := make(chan struct{}) - var total, ok int64 + total := atomic.NewInt64(0) + ok := atomic.NewInt64(0) // Spawn 10 Go routines polling the server concurrently. for i := 0; i < 10; i++ { wg.Add(1) @@ -1320,11 +1321,11 @@ func TestTLSRoundTripperRaces(t *testing.T) { case <-ch: return default: - atomic.AddInt64(&total, 1) + total.Add(1) r, err := c.Get(testServer.URL) if err == nil { r.Body.Close() - atomic.AddInt64(&ok, 1) + ok.Add(1) } } } diff --git a/go.mod b/go.mod index 191017ed8..756e81182 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 + go.uber.org/atomic v1.11.0 golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.6 diff --git a/go.sum b/go.sum index e2c51fcf0..c60f10d1e 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= From 7937ffb6417e5f2b14252beebc724e216c5b51fe Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Aug 2025 08:30:50 +0200 Subject: [PATCH 60/78] Enable revive rule unused-parameter (#824) Signed-off-by: Arve Knudsen --- .golangci.yml | 2 -- config/http_config.go | 2 +- config/http_config_test.go | 16 ++++++++-------- expfmt/decode.go | 2 +- expfmt/text_parse_test.go | 2 +- promslog/slog.go | 2 +- route/route_test.go | 10 +++++----- server/static_file_server_test.go | 2 +- 8 files changed, 18 insertions(+), 20 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 0afcc4a34..0a54de980 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -151,8 +151,6 @@ linters: #- name: unexported-return - name: unreachable-code - name: unused-parameter - severity: warning - disabled: true #- name: unused-receiver #- name: var-declaration #- name: var-naming diff --git a/config/http_config.go b/config/http_config.go index 2e605ffde..eb4f22015 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -742,7 +742,7 @@ func NewFileSecret(file string) *FileSecret { return &FileSecret{file: file} } -func (s *FileSecret) Fetch(ctx context.Context) (string, error) { +func (s *FileSecret) Fetch(context.Context) (string, error) { fileBytes, err := os.ReadFile(s.file) if err != nil { return "", fmt.Errorf("unable to read file %s: %w", s.file, err) diff --git a/config/http_config_test.go b/config/http_config_test.go index 3804bfa85..093ddedae 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -181,7 +181,7 @@ func TestNewClientFromConfig(t *testing.T) { InsecureSkipVerify: true, }, }, - handler: func(w http.ResponseWriter, r *http.Request) { + handler: func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, ExpectedMessage) }, }, @@ -195,7 +195,7 @@ func TestNewClientFromConfig(t *testing.T) { InsecureSkipVerify: false, }, }, - handler: func(w http.ResponseWriter, r *http.Request) { + handler: func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, ExpectedMessage) }, }, @@ -933,7 +933,7 @@ type secretManager struct { data map[string]string } -func (m *secretManager) Fetch(ctx context.Context, secretRef string) (string, error) { +func (m *secretManager) Fetch(_ context.Context, secretRef string) (string, error) { secretData, ok := m.data[secretRef] if !ok { return "", fmt.Errorf("unknown secret %s", secretRef) @@ -1044,7 +1044,7 @@ func TestTLSRoundTripper(t *testing.T) { ca, cert, key := filepath.Join(tmpDir, "ca"), filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key") - handler := func(w http.ResponseWriter, r *http.Request) { + handler := func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, ExpectedMessage) } testServer, err := newTestServer(handler) @@ -1162,7 +1162,7 @@ func TestTLSRoundTripper(t *testing.T) { } func TestTLSRoundTripper_Inline(t *testing.T) { - handler := func(w http.ResponseWriter, r *http.Request) { + handler := func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, ExpectedMessage) } testServer, err := newTestServer(handler) @@ -1284,7 +1284,7 @@ func TestTLSRoundTripperRaces(t *testing.T) { ca, cert, key := filepath.Join(tmpDir, "ca"), filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key") - handler := func(w http.ResponseWriter, r *http.Request) { + handler := func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, ExpectedMessage) } testServer, err := newTestServer(handler) @@ -1403,7 +1403,7 @@ type roundTrip struct { theError error } -func (rt *roundTrip) RoundTrip(r *http.Request) (*http.Response, error) { +func (rt *roundTrip) RoundTrip(*http.Request) (*http.Response, error) { return rt.theResponse, rt.theError } @@ -1876,7 +1876,7 @@ func TestModifyTLSCertificates(t *testing.T) { defer os.RemoveAll(tmpDir) ca, cert, key := filepath.Join(tmpDir, "ca"), filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key") - handler := func(w http.ResponseWriter, r *http.Request) { + handler := func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, ExpectedMessage) } testServer, err := newTestServer(handler) diff --git a/expfmt/decode.go b/expfmt/decode.go index 98d278c13..7b762370e 100644 --- a/expfmt/decode.go +++ b/expfmt/decode.go @@ -135,7 +135,7 @@ type errDecoder struct { err error } -func (d *errDecoder) Decode(v *dto.MetricFamily) error { +func (d *errDecoder) Decode(*dto.MetricFamily) error { return d.err } diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index 5b848c4a1..f9c6d4cd7 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -1048,6 +1048,6 @@ type errReader struct { err error } -func (r *errReader) Read(p []byte) (int, error) { +func (r *errReader) Read([]byte) (int, error) { return 0, r.err } diff --git a/promslog/slog.go b/promslog/slog.go index 33da53f82..f5b9e98ba 100644 --- a/promslog/slog.go +++ b/promslog/slog.go @@ -147,7 +147,7 @@ type Config struct { } func newGoKitStyleReplaceAttrFunc(lvl *Level) func(groups []string, a slog.Attr) slog.Attr { - return func(groups []string, a slog.Attr) slog.Attr { + return func(_ []string, a slog.Attr) slog.Attr { key := a.Key switch key { case slog.TimeKey, "ts": diff --git a/route/route_test.go b/route/route_test.go index 773be4891..e20e1db17 100644 --- a/route/route_test.go +++ b/route/route_test.go @@ -37,7 +37,7 @@ func TestRedirect(t *testing.T) { func TestContext(t *testing.T) { router := New() - router.Get("/test/:foo/", func(w http.ResponseWriter, r *http.Request) { + router.Get("/test/:foo/", func(_ http.ResponseWriter, r *http.Request) { want := "bar" got := Param(r.Context(), "foo") require.Equalf(t, want, got, "Unexpected context value: want %q, got %q", want, got) @@ -50,7 +50,7 @@ func TestContext(t *testing.T) { func TestContextWithValue(t *testing.T) { router := New() - router.Get("/test/:foo/", func(w http.ResponseWriter, r *http.Request) { + router.Get("/test/:foo/", func(_ http.ResponseWriter, r *http.Request) { want := "bar" got := Param(r.Context(), "foo") require.Equalf(t, want, got, "Unexpected context value: want %q, got %q", want, got) @@ -79,7 +79,7 @@ func TestContextWithValue(t *testing.T) { func TestContextWithoutValue(t *testing.T) { router := New() - router.Get("/test", func(w http.ResponseWriter, r *http.Request) { + router.Get("/test", func(_ http.ResponseWriter, r *http.Request) { want := "" got := Param(r.Context(), "foo") require.Equalf(t, want, got, "Unexpected context value: want %q, got %q", want, got) @@ -109,7 +109,7 @@ func TestInstrumentation(t *testing.T) { } for _, c := range cases { - c.router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {}) + c.router.Get("/foo", func(_ http.ResponseWriter, _ *http.Request) {}) r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/foo", nil) require.NoErrorf(t, err, "Error building test request: %s", err) @@ -149,7 +149,7 @@ func TestInstrumentations(t *testing.T) { } for _, c := range cases { - c.router.Get("/foo", func(w http.ResponseWriter, r *http.Request) {}) + c.router.Get("/foo", func(_ http.ResponseWriter, _ *http.Request) {}) r, err := http.NewRequest(http.MethodGet, "http://localhost:9090/foo", nil) require.NoErrorf(t, err, "Error building test request: %s", err) diff --git a/server/static_file_server_test.go b/server/static_file_server_test.go index 254ca1c59..9a1bf5e49 100644 --- a/server/static_file_server_test.go +++ b/server/static_file_server_test.go @@ -23,7 +23,7 @@ import ( type dummyFileSystem struct{} -func (fs dummyFileSystem) Open(path string) (http.File, error) { +func (fs dummyFileSystem) Open(string) (http.File, error) { return http.Dir(".").Open(".") } From 3d3db3963ec8179db45fc57b4f5b8a487236cf1f Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Wed, 27 Aug 2025 15:21:06 +0200 Subject: [PATCH 61/78] Enable revive rules (#823) * Enable revive rule early-return * Enable revive rule increment-decrement * Enable revive rule receiver-naming * Enable revive rule unexported-return * Enable revive rule unused-receiver * Enable revive rule var-declaration * Enable revive rule var-naming --------- Signed-off-by: Arve Knudsen --- .golangci.yml | 28 ++++++++++++++-------------- config/config.go | 2 +- config/http_config.go | 18 ++++++++++++------ expfmt/expfmt.go | 12 ++++++++---- expfmt/text_parse.go | 5 ++--- model/labelset.go | 10 +++++----- model/metric.go | 2 +- model/time.go | 2 +- model/value.go | 6 +++--- model/value_histogram.go | 10 +++++----- model/value_type.go | 4 ++-- server/static_file_server_test.go | 2 +- 12 files changed, 55 insertions(+), 46 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 0a54de980..9aae39de8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -121,10 +121,10 @@ linters: # Allow functions with test or bench signatures. - allowTypesBefore: '*testing.T,testing.TB' - name: context-keys-type - #- name: dot-imports - #- name: early-return - # arguments: - # - "preserveScope" + - name: dot-imports + - name: early-return + arguments: + - "preserveScope" # A lot of false positives: incorrectly identifies channel draining as "empty code block". # See https://github.com/mgechev/revive/issues/386 - name: empty-block @@ -133,27 +133,27 @@ linters: - name: error-return - name: error-strings - name: errorf - #- name: exported - #- name: increment-decrement - #- name: indent-error-flow - # arguments: - # - "preserveScope" + - name: exported + - name: increment-decrement + - name: indent-error-flow + arguments: + - "preserveScope" - name: package-comments # TODO(beorn7): Currently, we have a lot of missing package doc comments. Maybe we should have them. disabled: true - name: range - #- name: receiver-naming + - name: receiver-naming - name: redefines-builtin-id - name: superfluous-else arguments: - "preserveScope" - name: time-naming - #- name: unexported-return + - name: unexported-return - name: unreachable-code - name: unused-parameter - #- name: unused-receiver - #- name: var-declaration - #- name: var-naming + - name: unused-receiver + - name: var-declaration + - name: var-naming testifylint: enable-all: true disable: diff --git a/config/config.go b/config/config.go index 7588da555..ff54cdd82 100644 --- a/config/config.go +++ b/config/config.go @@ -30,7 +30,7 @@ type Secret string // MarshalSecretValue if set to true will expose Secret type // through the marshal interfaces. Useful for outside projects // that load and marshal the Prometheus config. -var MarshalSecretValue bool = false +var MarshalSecretValue = false // MarshalYAML implements the yaml.Marshaler interface for Secrets. func (s Secret) MarshalYAML() (interface{}, error) { diff --git a/config/http_config.go b/config/http_config.go index eb4f22015..ad40fd936 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -346,7 +346,7 @@ func nonZeroCount[T comparable](values ...T) int { var zero T for _, value := range values { if value != zero { - count += 1 + count++ } } return count @@ -542,8 +542,14 @@ func (s *secretManagerOption) applyToTLSConfigOptions(opts *tlsConfigOptions) { opts.secretManager = s.secretManager } +// SecretManagerOption is an option for providing a SecretManager. +type SecretManagerOption interface { + TLSConfigOption + HTTPClientOption +} + // WithSecretManager allows setting the secret manager. -func WithSecretManager(manager SecretManager) *secretManagerOption { +func WithSecretManager(manager SecretManager) SecretManagerOption { return &secretManagerOption{ secretManager: manager, } @@ -726,11 +732,11 @@ func (s *InlineSecret) Fetch(context.Context) (string, error) { return s.text, nil } -func (s *InlineSecret) Description() string { +func (*InlineSecret) Description() string { return "inline" } -func (s *InlineSecret) Immutable() bool { +func (*InlineSecret) Immutable() bool { return true } @@ -754,7 +760,7 @@ func (s *FileSecret) Description() string { return "file " + s.file } -func (s *FileSecret) Immutable() bool { +func (*FileSecret) Immutable() bool { return false } @@ -772,7 +778,7 @@ func (s *refSecret) Description() string { return "ref " + s.ref } -func (s *refSecret) Immutable() bool { +func (*refSecret) Immutable() bool { return false } diff --git a/expfmt/expfmt.go b/expfmt/expfmt.go index b26886560..c34c7de43 100644 --- a/expfmt/expfmt.go +++ b/expfmt/expfmt.go @@ -36,9 +36,11 @@ const ( ProtoType = `application/vnd.google.protobuf` ProtoProtocol = `io.prometheus.client.MetricFamily` // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. - ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" - OpenMetricsType = `application/openmetrics-text` + ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" + OpenMetricsType = `application/openmetrics-text` + //nolint:revive // Allow for underscores. OpenMetricsVersion_0_0_1 = "0.0.1" + //nolint:revive // Allow for underscores. OpenMetricsVersion_1_0_0 = "1.0.0" // The Content-Type values for the different wire protocols. Do not do direct @@ -54,8 +56,10 @@ const ( // Deprecated: Use expfmt.NewFormat(expfmt.TypeProtoCompact) instead. FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text` // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + //nolint:revive // Allow for underscores. FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8` // Deprecated: Use expfmt.NewFormat(expfmt.TypeOpenMetrics) instead. + //nolint:revive // Allow for underscores. FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8` ) @@ -188,8 +192,8 @@ func (f Format) FormatType() FormatType { // Format contains a escaping=allow-utf-8 term, it will select NoEscaping. If a valid // "escaping" term exists, that will be used. Otherwise, the global default will // be returned. -func (format Format) ToEscapingScheme() model.EscapingScheme { - for _, p := range strings.Split(string(format), ";") { +func (f Format) ToEscapingScheme() model.EscapingScheme { + for _, p := range strings.Split(string(f), ";") { toks := strings.Split(p, "=") if len(toks) != 2 { continue diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 99e47e042..8f2edde32 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -381,13 +381,12 @@ func (p *TextParser) startLabelName() stateFn { labels := make(map[string]struct{}) for _, l := range p.currentLabelPairs { lName := l.GetName() - if _, exists := labels[lName]; !exists { - labels[lName] = struct{}{} - } else { + if _, exists := labels[lName]; exists { p.parseError(fmt.Sprintf("duplicate label names for metric %q", p.currentMF.GetName())) p.currentLabelPairs = nil return nil } + labels[lName] = struct{}{} } return p.startLabelValue } diff --git a/model/labelset.go b/model/labelset.go index d0ad88da3..9de47b256 100644 --- a/model/labelset.go +++ b/model/labelset.go @@ -114,10 +114,10 @@ func (ls LabelSet) Clone() LabelSet { } // Merge is a helper function to non-destructively merge two label sets. -func (l LabelSet) Merge(other LabelSet) LabelSet { - result := make(LabelSet, len(l)) +func (ls LabelSet) Merge(other LabelSet) LabelSet { + result := make(LabelSet, len(ls)) - for k, v := range l { + for k, v := range ls { result[k] = v } @@ -140,7 +140,7 @@ func (ls LabelSet) FastFingerprint() Fingerprint { } // UnmarshalJSON implements the json.Unmarshaler interface. -func (l *LabelSet) UnmarshalJSON(b []byte) error { +func (ls *LabelSet) UnmarshalJSON(b []byte) error { var m map[LabelName]LabelValue if err := json.Unmarshal(b, &m); err != nil { return err @@ -153,6 +153,6 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error { return fmt.Errorf("%q is not a valid label name", ln) } } - *l = LabelSet(m) + *ls = LabelSet(m) return nil } diff --git a/model/metric.go b/model/metric.go index bd846b5fe..f7f61c5d5 100644 --- a/model/metric.go +++ b/model/metric.go @@ -205,7 +205,7 @@ func (s ValidationScheme) IsValidLabelName(labelName string) bool { } // Type implements the pflag.Value interface. -func (s ValidationScheme) Type() string { +func (ValidationScheme) Type() string { return "validationScheme" } diff --git a/model/time.go b/model/time.go index 2a0379fcf..1730b0fdc 100644 --- a/model/time.go +++ b/model/time.go @@ -178,7 +178,7 @@ func (d *Duration) Set(s string) error { } // Type implements pflag.Value. -func (d *Duration) Type() string { +func (*Duration) Type() string { return "duration" } diff --git a/model/value.go b/model/value.go index 15c34ee6f..a9995a37e 100644 --- a/model/value.go +++ b/model/value.go @@ -350,9 +350,9 @@ func (m Matrix) Len() int { return len(m) } func (m Matrix) Less(i, j int) bool { return m[i].Metric.Before(m[j].Metric) } func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] } -func (mat Matrix) String() string { - matCp := make(Matrix, len(mat)) - copy(matCp, mat) +func (m Matrix) String() string { + matCp := make(Matrix, len(m)) + copy(matCp, m) sort.Sort(matCp) strs := make([]string, len(matCp)) diff --git a/model/value_histogram.go b/model/value_histogram.go index 895e6a3e8..91ce5b7a4 100644 --- a/model/value_histogram.go +++ b/model/value_histogram.go @@ -86,22 +86,22 @@ func (s *HistogramBucket) Equal(o *HistogramBucket) bool { return s == o || (s.Boundaries == o.Boundaries && s.Lower == o.Lower && s.Upper == o.Upper && s.Count == o.Count) } -func (b HistogramBucket) String() string { +func (s HistogramBucket) String() string { var sb strings.Builder - lowerInclusive := b.Boundaries == 1 || b.Boundaries == 3 - upperInclusive := b.Boundaries == 0 || b.Boundaries == 3 + lowerInclusive := s.Boundaries == 1 || s.Boundaries == 3 + upperInclusive := s.Boundaries == 0 || s.Boundaries == 3 if lowerInclusive { sb.WriteRune('[') } else { sb.WriteRune('(') } - fmt.Fprintf(&sb, "%g,%g", b.Lower, b.Upper) + fmt.Fprintf(&sb, "%g,%g", s.Lower, s.Upper) if upperInclusive { sb.WriteRune(']') } else { sb.WriteRune(')') } - fmt.Fprintf(&sb, ":%v", b.Count) + fmt.Fprintf(&sb, ":%v", s.Count) return sb.String() } diff --git a/model/value_type.go b/model/value_type.go index 726c50ee6..078910f46 100644 --- a/model/value_type.go +++ b/model/value_type.go @@ -66,8 +66,8 @@ func (et *ValueType) UnmarshalJSON(b []byte) error { return nil } -func (e ValueType) String() string { - switch e { +func (et ValueType) String() string { + switch et { case ValNone: return "" case ValScalar: diff --git a/server/static_file_server_test.go b/server/static_file_server_test.go index 9a1bf5e49..ae8bf599d 100644 --- a/server/static_file_server_test.go +++ b/server/static_file_server_test.go @@ -23,7 +23,7 @@ import ( type dummyFileSystem struct{} -func (fs dummyFileSystem) Open(string) (http.File, error) { +func (dummyFileSystem) Open(string) (http.File, error) { return http.Dir(".").Open(".") } From b9c5f39310d0e459ebe42f828c6b6f140bbccf4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 09:35:35 +0000 Subject: [PATCH 62/78] build(deps): bump github.com/stretchr/testify in /assets Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- assets/go.mod | 2 +- assets/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/go.mod b/assets/go.mod index 9698dd659..0977f6e90 100644 --- a/assets/go.mod +++ b/assets/go.mod @@ -2,7 +2,7 @@ module github.com/prometheus/common/assets go 1.23.0 -require github.com/stretchr/testify v1.10.0 +require github.com/stretchr/testify v1.11.1 require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/assets/go.sum b/assets/go.sum index 713a0b4f0..c4c1710c4 100644 --- a/assets/go.sum +++ b/assets/go.sum @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From fc7cce03b058da462034931ac278017ade567c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:30:51 +0000 Subject: [PATCH 63/78] build(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.8 Bumps google.golang.org/protobuf from 1.36.6 to 1.36.8. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 756e81182..90158bf4c 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( go.uber.org/atomic v1.11.0 golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.8 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index c60f10d1e..9ecb4102b 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From a1ba2a74339d419fd4783d24a9c2eb146ceb02a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:16:52 +0000 Subject: [PATCH 64/78] build(deps): bump golang.org/x/net from 0.42.0 to 0.43.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.42.0 to 0.43.0. - [Commits](https://github.com/golang/net/compare/v0.42.0...v0.43.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 90158bf4c..c713f2c01 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.10.0 go.uber.org/atomic v1.11.0 - golang.org/x/net v0.42.0 + golang.org/x/net v0.43.0 golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.8 gopkg.in/yaml.v2 v2.4.0 @@ -31,8 +31,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9ecb4102b..8f660d379 100644 --- a/go.sum +++ b/go.sum @@ -47,14 +47,14 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 9e19a90705c59ee1edbb039a867148a245466317 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 17:17:13 +0000 Subject: [PATCH 65/78] build(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 90158bf4c..f62f0f269 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 go.uber.org/atomic v1.11.0 golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 diff --git a/go.sum b/go.sum index 9ecb4102b..c03cd5fc8 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= From e1204536873f2dba6ab81c0421a640311c5e278f Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Wed, 3 Sep 2025 16:45:07 +0200 Subject: [PATCH 66/78] Retract v1.20.3 Fixes #831 Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- go.mod | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 25378d139..05ed079e7 100644 --- a/go.mod +++ b/go.mod @@ -37,4 +37,42 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -retract v0.50.0 // Critical bug in counter suffixes, please read issue https://github.com/prometheus/common/issues/605 +retract ( + v1.20.99 // This tag is needed to retract accidental tags below, but is retracted directly. + v1.20.3 // Tags pushed accidentally, see https://github.com/prometheus/common/issues/831 + v1.20.3 + v1.20.2 + v1.20.1 + v1.20.0 + v1.19.1 + v1.19.0 + v1.18.0 + v1.17.0 + v1.16.0 + v1.15.1 + v1.15.0 + v1.14.0 + v1.13.1 + v1.13.0 + v1.12.2 + v1.12.1 + v1.12.0 + v1.11.1 + v1.11.0 + v1.10.0 + v1.9.0 + v1.8.0 + v1.7.1 + v1.7.0 + v1.6.0 + v1.5.1 + v1.5.0 + v1.4.1 + v1.4.0 + v1.3.0 + v1.2.1 + v1.2.0 + v1.1.0 + v1.0.0 + v0.50.0 // Critical bug in counter suffixes, please read issue https://github.com/prometheus/common/issues/605 +) From 80e275ee7926f0530609de6797a5ecdd0ebb84ab Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Thu, 4 Sep 2025 18:02:30 +0200 Subject: [PATCH 67/78] Revert "Use github.com/grafana/regexp instead of regexp" (#835) This reverts commit 149efd6d704a94178a9123536aabadcf01a6b09a. Signed-off-by: Arve Knudsen --- .golangci.yml | 2 -- go.mod | 1 - go.sum | 2 -- model/labels.go | 3 +-- model/metric.go | 2 +- model/silence.go | 3 +-- model/value_histogram_test.go | 2 +- promslog/slog_test.go | 2 +- 8 files changed, 5 insertions(+), 12 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 9aae39de8..4ddcfcbfa 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -72,8 +72,6 @@ linters: desc: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" - pkg: "io/ioutil" desc: "Use corresponding 'os' or 'io' functions instead." - - pkg: "regexp" - desc: "Use github.com/grafana/regexp instead of regexp" - pkg: "github.com/pkg/errors" desc: "Use 'errors' or 'fmt' instead of github.com/pkg/errors" - pkg: "gzip" diff --git a/go.mod b/go.mod index 05ed079e7..9991fdeae 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ toolchain go1.24.1 require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/google/go-cmp v0.7.0 - github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc github.com/julienschmidt/httprouter v1.3.0 github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f diff --git a/go.sum b/go.sum index 79afe519c..f90f40561 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= -github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= diff --git a/model/labels.go b/model/labels.go index 93498c9ec..dfeb34be5 100644 --- a/model/labels.go +++ b/model/labels.go @@ -16,10 +16,9 @@ package model import ( "encoding/json" "fmt" + "regexp" "strings" "unicode/utf8" - - "github.com/grafana/regexp" ) const ( diff --git a/model/metric.go b/model/metric.go index f7f61c5d5..2beb3e2a4 100644 --- a/model/metric.go +++ b/model/metric.go @@ -17,12 +17,12 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "sort" "strconv" "strings" "unicode/utf8" - "github.com/grafana/regexp" dto "github.com/prometheus/client_model/go" "google.golang.org/protobuf/proto" "gopkg.in/yaml.v2" diff --git a/model/silence.go b/model/silence.go index 8c51dbaac..8f91a9702 100644 --- a/model/silence.go +++ b/model/silence.go @@ -17,9 +17,8 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "time" - - "github.com/grafana/regexp" ) // Matcher describes a matches the value of a given label. diff --git a/model/value_histogram_test.go b/model/value_histogram_test.go index 75f65c664..e239f4630 100644 --- a/model/value_histogram_test.go +++ b/model/value_histogram_test.go @@ -16,9 +16,9 @@ package model import ( "encoding/json" "reflect" + "regexp" "testing" - "github.com/grafana/regexp" "github.com/stretchr/testify/require" ) diff --git a/promslog/slog_test.go b/promslog/slog_test.go index 0ab7c6309..91e79a9fb 100644 --- a/promslog/slog_test.go +++ b/promslog/slog_test.go @@ -18,11 +18,11 @@ import ( "context" "fmt" "log/slog" + "regexp" "strings" "testing" "time" - "github.com/grafana/regexp" "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" ) From 08d7f66dd94af29937cc9ebf3df2b1d459a2755a Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Thu, 4 Sep 2025 12:28:32 -0400 Subject: [PATCH 68/78] Move to supported version of yaml parser (#834) Signed-off-by: Davanum Srinivas --- config/config_test.go | 2 +- config/http_config.go | 2 +- config/http_config_test.go | 2 +- config/tls_config_test.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- model/metric.go | 2 +- model/metric_test.go | 2 +- promslog/slog_test.go | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index f62b266af..9b0d0480a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v2" ) func TestJSONMarshalSecret(t *testing.T) { diff --git a/config/http_config.go b/config/http_config.go index ad40fd936..4e5ff92a2 100644 --- a/config/http_config.go +++ b/config/http_config.go @@ -32,11 +32,11 @@ import ( "time" conntrack "github.com/mwitkow/go-conntrack" + "go.yaml.in/yaml/v2" "golang.org/x/net/http/httpproxy" "golang.org/x/net/http2" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" - "gopkg.in/yaml.v2" ) var ( diff --git a/config/http_config_test.go b/config/http_config_test.go index 093ddedae..707d8880d 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -36,7 +36,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/atomic" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v2" ) const ( diff --git a/config/tls_config_test.go b/config/tls_config_test.go index b43a1a166..f02c87fdd 100644 --- a/config/tls_config_test.go +++ b/config/tls_config_test.go @@ -26,7 +26,7 @@ import ( "testing" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v2" ) // LoadTLSConfig parses the given file into a tls.Config. diff --git a/go.mod b/go.mod index 9991fdeae..e19bd6dd8 100644 --- a/go.mod +++ b/go.mod @@ -13,10 +13,10 @@ require ( github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.11.1 go.uber.org/atomic v1.11.0 + go.yaml.in/yaml/v2 v2.4.2 golang.org/x/net v0.43.0 golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.8 - gopkg.in/yaml.v2 v2.4.0 ) require ( diff --git a/go.sum b/go.sum index f90f40561..749a3b090 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8 github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= @@ -59,7 +61,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/model/metric.go b/model/metric.go index 2beb3e2a4..3feebf328 100644 --- a/model/metric.go +++ b/model/metric.go @@ -24,8 +24,8 @@ import ( "unicode/utf8" dto "github.com/prometheus/client_model/go" + "go.yaml.in/yaml/v2" "google.golang.org/protobuf/proto" - "gopkg.in/yaml.v2" ) var ( diff --git a/model/metric_test.go b/model/metric_test.go index 7a4ee7b7a..e3573ed2c 100644 --- a/model/metric_test.go +++ b/model/metric_test.go @@ -24,8 +24,8 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v2" "google.golang.org/protobuf/proto" - "gopkg.in/yaml.v2" ) func testMetric(t testing.TB) { diff --git a/promslog/slog_test.go b/promslog/slog_test.go index 91e79a9fb..0ea1a31c2 100644 --- a/promslog/slog_test.go +++ b/promslog/slog_test.go @@ -24,7 +24,7 @@ import ( "time" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" + "go.yaml.in/yaml/v2" ) var ( From 8975dde6db7208309e9872891f24c7301aa77dfb Mon Sep 17 00:00:00 2001 From: Arve Knudsen Date: Fri, 5 Sep 2025 09:53:47 +0200 Subject: [PATCH 69/78] Revert "Use go.uber.org/atomic instead of sync/atomic (#825)" (#838) This reverts commit 6d40fe16ed5f8f21fae8f9e53fd8b54eb08c2d44. Signed-off-by: Arve Knudsen --- .golangci.yml | 2 -- config/http_config_test.go | 9 ++++----- go.mod | 1 - go.sum | 2 -- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 4ddcfcbfa..02bf7fb0f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -66,8 +66,6 @@ linters: rules: main: deny: - - pkg: "sync/atomic" - desc: "Use go.uber.org/atomic instead of sync/atomic" - pkg: "github.com/go-kit/kit/log" desc: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" - pkg: "io/ioutil" diff --git a/config/http_config_test.go b/config/http_config_test.go index 707d8880d..c8853592e 100644 --- a/config/http_config_test.go +++ b/config/http_config_test.go @@ -31,11 +31,11 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "testing" "time" "github.com/stretchr/testify/require" - "go.uber.org/atomic" "go.yaml.in/yaml/v2" ) @@ -1309,8 +1309,7 @@ func TestTLSRoundTripperRaces(t *testing.T) { var wg sync.WaitGroup ch := make(chan struct{}) - total := atomic.NewInt64(0) - ok := atomic.NewInt64(0) + var total, ok int64 // Spawn 10 Go routines polling the server concurrently. for i := 0; i < 10; i++ { wg.Add(1) @@ -1321,11 +1320,11 @@ func TestTLSRoundTripperRaces(t *testing.T) { case <-ch: return default: - total.Add(1) + atomic.AddInt64(&total, 1) r, err := c.Get(testServer.URL) if err == nil { r.Body.Close() - ok.Add(1) + atomic.AddInt64(&ok, 1) } } } diff --git a/go.mod b/go.mod index e19bd6dd8..a91acac50 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.11.1 - go.uber.org/atomic v1.11.0 go.yaml.in/yaml/v2 v2.4.2 golang.org/x/net v0.43.0 golang.org/x/oauth2 v0.30.0 diff --git a/go.sum b/go.sum index 749a3b090..654a30102 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= From e9e8fe5783fd8e8c1b903b8d80466dd72feea1bb Mon Sep 17 00:00:00 2001 From: Owen Williams Date: Wed, 3 Sep 2025 14:49:02 -0400 Subject: [PATCH 70/78] Create CHANGELOG.md for easier communication of library changes, especially possible breaking changes. Signed-off-by: Owen Williams --- CHANGELOG.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ RELEASE.md | 12 ++++++++++- VERSION | 1 + 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 VERSION diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..2c47511cf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +# Changelog + +## main / unreleased + +### What's Changed + +* [ENHANCEMENT] Create CHANGELOG.md for easier communication of library changes, especially possible breaking changes. https://github.com/prometheus/common/pull/833 + +## v0.66.1 / 2025-09-05 + +This release has no functional changes, it just drops the dependencies `github.com/grafana/regexp` and `go.uber.org/atomic` and replaces `gopkg.in/yaml.v2` with `go.yaml.in/yaml/v2` (a drop-in replacement). + +### What's Changed +* Revert "Use github.com/grafana/regexp instead of regexp" by @aknuds1 in https://github.com/prometheus/common/pull/835 +* Move to supported version of yaml parser by @dims in https://github.com/prometheus/common/pull/834 +* Revert "Use go.uber.org/atomic instead of sync/atomic (#825)" by @aknuds1 in https://github.com/prometheus/common/pull/838 + +**Full Changelog**: https://github.com/prometheus/common/compare/v1.20.99...v0.66.1 + +## v0.66.0 / 2025-09-02 + +### ⚠️ Breaking Changes ⚠️ + +* A default-constructed TextParser will be invalid. It must have a valid `scheme` set, so users should use the NewTextParser function to create a valid TextParser. Otherwise parsing will panic with "Invalid name validation scheme requested: unset". + +### What's Changed +* model: add constants for type and unit labels. by @bwplotka in https://github.com/prometheus/common/pull/801 +* model.ValidationScheme: Support encoding as YAML by @aknuds1 in https://github.com/prometheus/common/pull/799 +* fix(promslog): always print time.Duration values as go duration strings by @tjhop in https://github.com/prometheus/common/pull/798 +* Add `ValidationScheme` methods `IsValidMetricName` and `IsValidLabelName` by @aknuds1 in https://github.com/prometheus/common/pull/806 +* Fix delimited proto not escaped correctly by @thampiotr in https://github.com/prometheus/common/pull/809 +* Decoder: Remove use of global name validation and add validation by @ywwg in https://github.com/prometheus/common/pull/808 +* ValidationScheme implements pflag.Value and json.Marshaler/Unmarshaler interfaces by @juliusmh in https://github.com/prometheus/common/pull/807 +* expfmt: Add NewTextParser function by @aknuds1 in https://github.com/prometheus/common/pull/816 + +* Enable the godot linter by @aknuds1 in https://github.com/prometheus/common/pull/821 +* Enable usestdlibvars linter by @aknuds1 in https://github.com/prometheus/common/pull/820 +* Enable unconvert linter by @aknuds1 in https://github.com/prometheus/common/pull/819 +* Enable the fatcontext linter by @aknuds1 in https://github.com/prometheus/common/pull/822 +* Enable gocritic linter by @aknuds1 in https://github.com/prometheus/common/pull/818 +* Use go.uber.org/atomic instead of sync/atomic by @aknuds1 in https://github.com/prometheus/common/pull/825 +* Enable revive rule unused-parameter by @aknuds1 in https://github.com/prometheus/common/pull/824 +* Enable revive rules by @aknuds1 in https://github.com/prometheus/common/pull/823 +* Synchronize common files from prometheus/prometheus by @prombot in https://github.com/prometheus/common/pull/802 +* Synchronize common files from prometheus/prometheus by @prombot in https://github.com/prometheus/common/pull/803 +* Sync .golangci.yml with prometheus/prometheus by @aknuds1 in https://github.com/prometheus/common/pull/817 +* ci: update upload-actions by @ywwg in https://github.com/prometheus/common/pull/814 +* docs: fix typo in expfmt.Negotiate by @wmcram in https://github.com/prometheus/common/pull/813 +* build(deps): bump golang.org/x/net from 0.40.0 to 0.41.0 by @dependabot[bot] in https://github.com/prometheus/common/pull/800 +* build(deps): bump golang.org/x/net from 0.41.0 to 0.42.0 by @dependabot[bot] in https://github.com/prometheus/common/pull/810 +* build(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 in /assets by @dependabot[bot] in https://github.com/prometheus/common/pull/826 +* build(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.8 by @dependabot[bot] in https://github.com/prometheus/common/pull/830 +* build(deps): bump golang.org/x/net from 0.42.0 to 0.43.0 by @dependabot[bot] in https://github.com/prometheus/common/pull/829 +* build(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.1 by @dependabot[bot] in https://github.com/prometheus/common/pull/827 + +### New Contributors +* @aknuds1 made their first contribution in https://github.com/prometheus/common/pull/799 +* @thampiotr made their first contribution in https://github.com/prometheus/common/pull/809 +* @wmcram made their first contribution in https://github.com/prometheus/common/pull/813 +* @juliusmh made their first contribution in https://github.com/prometheus/common/pull/807 + diff --git a/RELEASE.md b/RELEASE.md index ba63828e1..066a36e29 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -11,7 +11,17 @@ With those steps done, you can proceed to cut a release. ## How to cut an individual release -There is no automated process for cutting a release in `prometheus/common`. A manual release using GitHub's release feature via [this link](https://github.com/prometheus/prometheus/releases/new) is the best way to go. The tag name must be prefixed with a `v` e.g. `v0.53.0` and then you can use the "Generate release notes" button to generate the release note automagically ✨. No need to create a discussion or mark it a pre-release, please do mark it as the latest release if needed. +There is no automated process for cutting a release in `prometheus/common`. +The primary trigger for announcing a release is pushing a new version tag. + +NOTE: As soon as a new tag is created, many downstream projects will automatically create pull requests to update their dependency of prom/common. +Make sure the release is ready to go, with an updated changelog including notices of any breaking changes, before pushing a new tag. + +Here are the basic steps: + +1. Update VERSION, applying the next logical version number, without any `v` prefix (e.g. `0.53.0`). +2. Update CHANGELOG.md, applying the new version number (this time including the `v` prefix, e.g. `v0.53.0`) and date to the changes listed under ``## main / unreleased`, and commit those changes to the main branch. +2. Use GitHub's release feature via [this link](https://github.com/prometheus/prometheus/releases/new) to apply a new tag. The tag name must be prefixed with a `v` e.g. `v0.53.0` and then use the "Generate release notes" button to generate the release notes automagically ✨. No need to create a discussion or mark it a pre-release, please do make sure it is marked as the latest release. ## Versioning strategy diff --git a/VERSION b/VERSION new file mode 100644 index 000000000..d3072c439 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.66.1 \ No newline at end of file From b83b57ffb65586f82d7c8733102f44f863ca47c8 Mon Sep 17 00:00:00 2001 From: prombot Date: Tue, 9 Sep 2025 17:48:30 +0000 Subject: [PATCH 71/78] Update common Prometheus files Signed-off-by: prombot --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index d5d9ca2eb..1816a556b 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -30,7 +30,7 @@ jobs: - name: Install Go uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: - go-version: 1.24.x + go-version: 1.25.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' From 85a216b7fccf8548cf788d2bbca2351c29d05072 Mon Sep 17 00:00:00 2001 From: Markus Rudy Date: Tue, 23 Sep 2025 17:19:10 +0200 Subject: [PATCH 72/78] expfmt: document NewTextParser as required Signed-off-by: Markus Rudy --- expfmt/text_parse.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index 8f2edde32..f3888687c 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -48,8 +48,10 @@ func (e ParseError) Error() string { return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg) } -// TextParser is used to parse the simple and flat text-based exchange format. Its -// zero value is ready to use. +// TextParser is used to parse the simple and flat text-based exchange format. +// +// TextParser instances must be created with NewTextParser, the zero value of +// TextParser is invalid. type TextParser struct { metricFamiliesByName map[string]*dto.MetricFamily buf *bufio.Reader // Where the parsed input is read through. From 401a4e970055579ddd4bb2b4dcbd6f2a00a44dc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:02:23 +0000 Subject: [PATCH 73/78] build(deps): bump go.yaml.in/yaml/v2 from 2.4.2 to 2.4.3 Bumps [go.yaml.in/yaml/v2](https://github.com/yaml/go-yaml) from 2.4.2 to 2.4.3. - [Commits](https://github.com/yaml/go-yaml/compare/v2.4.2...v2.4.3) --- updated-dependencies: - dependency-name: go.yaml.in/yaml/v2 dependency-version: 2.4.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a91acac50..50bee34ba 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.11.1 - go.yaml.in/yaml/v2 v2.4.2 + go.yaml.in/yaml/v2 v2.4.3 golang.org/x/net v0.43.0 golang.org/x/oauth2 v0.30.0 google.golang.org/protobuf v1.36.8 diff --git a/go.sum b/go.sum index 654a30102..843012ce0 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= From 45ae715ba734a22f43c3008842705640e6da5d42 Mon Sep 17 00:00:00 2001 From: beorn7 Date: Fri, 26 Sep 2025 00:02:37 +0200 Subject: [PATCH 74/78] expfmt: Add support for float histograms and gauge histograms Signed-off-by: beorn7 --- expfmt/decode.go | 14 +- expfmt/openmetrics_create.go | 19 +- expfmt/openmetrics_create_test.go | 85 ++++++ expfmt/text_create.go | 28 +- expfmt/text_create_test.go | 180 +++++++++++++ expfmt/text_parse.go | 96 +++++-- expfmt/text_parse_test.go | 416 ++++++++++++++++++++++++++++-- 7 files changed, 782 insertions(+), 56 deletions(-) diff --git a/expfmt/decode.go b/expfmt/decode.go index 7b762370e..8f8dc65d3 100644 --- a/expfmt/decode.go +++ b/expfmt/decode.go @@ -220,7 +220,7 @@ func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) return extractSummary(o, f), nil case dto.MetricType_UNTYPED: return extractUntyped(o, f), nil - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: return extractHistogram(o, f), nil } return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType()) @@ -403,9 +403,13 @@ func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector { infSeen = true } + v := q.GetCumulativeCountFloat() + if v <= 0 { + v = float64(q.GetCumulativeCount()) + } samples = append(samples, &model.Sample{ Metric: model.Metric(lset), - Value: model.SampleValue(q.GetCumulativeCount()), + Value: model.SampleValue(v), Timestamp: timestamp, }) } @@ -428,9 +432,13 @@ func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector { } lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") + v := m.Histogram.GetSampleCountFloat() + if v <= 0 { + v = float64(m.Histogram.GetSampleCount()) + } count := &model.Sample{ Metric: model.Metric(lset), - Value: model.SampleValue(m.Histogram.GetSampleCount()), + Value: model.SampleValue(v), Timestamp: timestamp, } samples = append(samples, count) diff --git a/expfmt/openmetrics_create.go b/expfmt/openmetrics_create.go index 8dbf6d04e..8c8bbaa62 100644 --- a/expfmt/openmetrics_create.go +++ b/expfmt/openmetrics_create.go @@ -208,6 +208,8 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E n, err = w.WriteString(" unknown\n") case dto.MetricType_HISTOGRAM: n, err = w.WriteString(" histogram\n") + case dto.MetricType_GAUGE_HISTOGRAM: + n, err = w.WriteString(" gaugehistogram\n") default: return written, fmt.Errorf("unknown metric type %s", metricType.String()) } @@ -325,7 +327,7 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E createdTsBytesWritten, err = writeOpenMetricsCreated(w, compliantName, "", metric, "", 0, metric.Summary.GetCreatedTimestamp()) n += createdTsBytesWritten } - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( "expected histogram in metric %s %s", compliantName, metric, @@ -333,6 +335,12 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E } infSeen := false for _, b := range metric.Histogram.Bucket { + if b.GetCumulativeCountFloat() > 0 { + return written, fmt.Errorf( + "OpenMetrics v1.0 does not support float histogram %s %s", + compliantName, metric, + ) + } n, err = writeOpenMetricsSample( w, compliantName, "_bucket", metric, model.BucketLabel, b.GetUpperBound(), @@ -354,6 +362,9 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E 0, metric.Histogram.GetSampleCount(), true, nil, ) + // We do not check for a float sample count here + // because we will check for it below (and error + // out if needed). written += n if err != nil { return @@ -368,6 +379,12 @@ func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily, options ...E if err != nil { return } + if metric.Histogram.GetSampleCountFloat() > 0 { + return written, fmt.Errorf( + "OpenMetrics v1.0 does not support float histogram %s %s", + compliantName, metric, + ) + } n, err = writeOpenMetricsSample( w, compliantName, "_count", metric, "", 0, 0, metric.Histogram.GetSampleCount(), true, diff --git a/expfmt/openmetrics_create_test.go b/expfmt/openmetrics_create_test.go index eb9a9d387..cd941f844 100644 --- a/expfmt/openmetrics_create_test.go +++ b/expfmt/openmetrics_create_test.go @@ -743,6 +743,50 @@ request_duration_microseconds_count 2693 # UNIT some_measure_seconds seconds some_measure_seconds_total{labelname="val1",basename="basevalue"} 42.0 some_measure_seconds_total{labelname="val2",basename="basevalue"} 0.23 1.23456789e+06 +`, + }, + // 11: Gauge histogram. + { + in: &dto.MetricFamily{ + Name: proto.String("name"), + Help: proto.String("doc string"), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCount: proto.Uint64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCount: proto.Uint64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCount: proto.Uint64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCount: proto.Uint64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCount: proto.Uint64(1524), + }, + }, + }, + }, + }, + }, + out: `# HELP name doc string +# TYPE name gaugehistogram +name_bucket{le="100.0"} 123 +name_bucket{le="120.0"} 412 +name_bucket{le="144.0"} 592 +name_bucket{le="172.8"} 1524 +name_bucket{le="+Inf"} 2693 +name_sum 1.7560473e+06 +name_count 2693 `, }, } @@ -903,6 +947,47 @@ func TestOpenMetricsCreateError(t *testing.T) { }, err: "expected counter in metric", }, + // 2: Float histogram. + { + in: &dto.MetricFamily{ + Name: proto.String("name"), + Help: proto.String("doc string"), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + // Note that it is enough to fill the float fields even + // if the values are integers. + SampleCountFloat: proto.Float64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCountFloat: proto.Float64(2693), + }, + }, + }, + }, + }, + }, + err: "OpenMetrics v1.0 does not support float histogram name histogram", + }, } for i, scenario := range scenarios { diff --git a/expfmt/text_create.go b/expfmt/text_create.go index c4e9c1bbc..7e1d23cab 100644 --- a/expfmt/text_create.go +++ b/expfmt/text_create.go @@ -151,7 +151,10 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e n, err = w.WriteString(" summary\n") case dto.MetricType_UNTYPED: n, err = w.WriteString(" untyped\n") - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: + // The classic Prometheus text format has no notion of a gauge + // histogram. We render a gauge histogram in the same way as a + // regular histogram. n, err = w.WriteString(" histogram\n") default: return written, fmt.Errorf("unknown metric type %s", metricType.String()) @@ -223,7 +226,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e w, name, "_count", metric, "", 0, float64(metric.Summary.GetSampleCount()), ) - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: if metric.Histogram == nil { return written, fmt.Errorf( "expected histogram in metric %s %s", name, metric, @@ -231,10 +234,14 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e } infSeen := false for _, b := range metric.Histogram.Bucket { + v := b.GetCumulativeCountFloat() + if v == 0 { + v = float64(b.GetCumulativeCount()) + } n, err = writeSample( w, name, "_bucket", metric, model.BucketLabel, b.GetUpperBound(), - float64(b.GetCumulativeCount()), + v, ) written += n if err != nil { @@ -245,10 +252,14 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e } } if !infSeen { + v := metric.Histogram.GetSampleCountFloat() + if v == 0 { + v = float64(metric.Histogram.GetSampleCount()) + } n, err = writeSample( w, name, "_bucket", metric, model.BucketLabel, math.Inf(+1), - float64(metric.Histogram.GetSampleCount()), + v, ) written += n if err != nil { @@ -263,10 +274,11 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err e if err != nil { return } - n, err = writeSample( - w, name, "_count", metric, "", 0, - float64(metric.Histogram.GetSampleCount()), - ) + v := metric.Histogram.GetSampleCountFloat() + if v == 0 { + v = float64(metric.Histogram.GetSampleCount()) + } + n, err = writeSample(w, name, "_count", metric, "", 0, v) default: return written, fmt.Errorf( "unexpected type in metric %s %s", name, metric, diff --git a/expfmt/text_create_test.go b/expfmt/text_create_test.go index 967c46a2e..dc214966b 100644 --- a/expfmt/text_create_test.go +++ b/expfmt/text_create_test.go @@ -383,6 +383,186 @@ request_duration_microseconds_count 2693 out: `# HELP name doc string # TYPE name counter name -Inf +`, + }, + // 8: Float histogram with +Inf. + { + in: &dto.MetricFamily{ + Name: proto.String("name"), + Help: proto.String("doc string"), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693.123), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123.123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCountFloat: proto.Float64(2693.123), + }, + }, + }, + }, + }, + }, + out: `# HELP name doc string +# TYPE name histogram +name_bucket{le="100"} 123.123 +name_bucket{le="120"} 412 +name_bucket{le="144"} 592 +name_bucket{le="172.8"} 1524 +name_bucket{le="+Inf"} 2693.123 +name_sum 1.7560473e+06 +name_count 2693.123 +`, + }, + // 9: Float histogram without +Inf. + { + in: &dto.MetricFamily{ + Name: proto.String("name"), + Help: proto.String("doc string"), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693.123), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123.123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524), + }, + }, + }, + }, + }, + }, + out: `# HELP name doc string +# TYPE name histogram +name_bucket{le="100"} 123.123 +name_bucket{le="120"} 412 +name_bucket{le="144"} 592 +name_bucket{le="172.8"} 1524 +name_bucket{le="+Inf"} 2693.123 +name_sum 1.7560473e+06 +name_count 2693.123 +`, + }, + // 10: Gauge histogram (rendered as regular histogram) + { + in: &dto.MetricFamily{ + Name: proto.String("name"), + Help: proto.String("doc string"), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCount: proto.Uint64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCount: proto.Uint64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCount: proto.Uint64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCount: proto.Uint64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCount: proto.Uint64(1524), + }, + }, + }, + }, + }, + }, + out: `# HELP name doc string +# TYPE name histogram +name_bucket{le="100"} 123 +name_bucket{le="120"} 412 +name_bucket{le="144"} 592 +name_bucket{le="172.8"} 1524 +name_bucket{le="+Inf"} 2693 +name_sum 1.7560473e+06 +name_count 2693 +`, + }, + // 11: Gauge float histogram (rendered as regular histogram) + { + in: &dto.MetricFamily{ + Name: proto.String("name"), + Help: proto.String("doc string"), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693.123), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123.123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524), + }, + }, + }, + }, + }, + }, + out: `# HELP name doc string +# TYPE name histogram +name_bucket{le="100"} 123.123 +name_bucket{le="120"} 412 +name_bucket{le="144"} 592 +name_bucket{le="172.8"} 1524 +name_bucket{le="+Inf"} 2693.123 +name_sum 1.7560473e+06 +name_count 2693.123 `, }, } diff --git a/expfmt/text_parse.go b/expfmt/text_parse.go index f3888687c..00c8841a1 100644 --- a/expfmt/text_parse.go +++ b/expfmt/text_parse.go @@ -131,9 +131,44 @@ func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricF if p.err != nil && errors.Is(p.err, io.EOF) { p.parseError("unexpected end of input stream") } + for _, histogramMetric := range p.histograms { + normalizeHistogram(histogramMetric.GetHistogram()) + } return p.metricFamiliesByName, p.err } +// normalizeHistogram makes sure that all the buckets and the count in each +// histogram is either completely float or completely integer. +func normalizeHistogram(histogram *dto.Histogram) { + if histogram == nil { + return + } + anyFloats := false + if histogram.GetSampleCountFloat() != 0 { + anyFloats = true + } else { + for _, b := range histogram.GetBucket() { + if b.GetCumulativeCountFloat() != 0 { + anyFloats = true + break + } + } + } + if !anyFloats { + return + } + if histogram.GetSampleCountFloat() == 0 { + histogram.SampleCountFloat = proto.Float64(float64(histogram.GetSampleCount())) + histogram.SampleCount = nil + } + for _, b := range histogram.GetBucket() { + if b.GetCumulativeCountFloat() == 0 { + b.CumulativeCountFloat = proto.Float64(float64(b.GetCumulativeCount())) + b.CumulativeCount = nil + } + } +} + func (p *TextParser) reset(in io.Reader) { p.metricFamiliesByName = map[string]*dto.MetricFamily{} p.currentLabelPairs = nil @@ -283,7 +318,9 @@ func (p *TextParser) readingLabels() stateFn { // Summaries/histograms are special. We have to reset the // currentLabels map, currentQuantile and currentBucket before starting to // read labels. - if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + if p.currentMF.GetType() == dto.MetricType_SUMMARY || + p.currentMF.GetType() == dto.MetricType_HISTOGRAM || + p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM { p.currentLabels = map[string]string{} p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName() p.currentQuantile = math.NaN() @@ -376,7 +413,9 @@ func (p *TextParser) startLabelName() stateFn { // Special summary/histogram treatment. Don't add 'quantile' and 'le' // labels to 'real' labels. if (p.currentMF.GetType() != dto.MetricType_SUMMARY || p.currentLabelPair.GetName() != model.QuantileLabel) && - (p.currentMF.GetType() != dto.MetricType_HISTOGRAM || p.currentLabelPair.GetName() != model.BucketLabel) { + ((p.currentMF.GetType() != dto.MetricType_HISTOGRAM && + p.currentMF.GetType() != dto.MetricType_GAUGE_HISTOGRAM) || + p.currentLabelPair.GetName() != model.BucketLabel) { p.currentLabelPairs = append(p.currentLabelPairs, p.currentLabelPair) } // Check for duplicate label names. @@ -427,7 +466,7 @@ func (p *TextParser) startLabelValue() stateFn { } } // Similar special treatment of histograms. - if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + if p.currentMF.GetType() == dto.MetricType_HISTOGRAM || p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM { if p.currentLabelPair.GetName() == model.BucketLabel { if p.currentBucket, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil { // Create a more helpful error message. @@ -478,7 +517,7 @@ func (p *TextParser) readingValue() stateFn { p.summaries[signature] = p.currentMetric p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric) } - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: signature := model.LabelsToSignature(p.currentLabels) if histogram := p.histograms[signature]; histogram != nil { p.currentMetric = histogram @@ -524,24 +563,38 @@ func (p *TextParser) readingValue() stateFn { }, ) } - case dto.MetricType_HISTOGRAM: + case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM: // *sigh* if p.currentMetric.Histogram == nil { p.currentMetric.Histogram = &dto.Histogram{} } switch { case p.currentIsHistogramCount: - p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value)) + if uintValue := uint64(value); value == float64(uintValue) { + p.currentMetric.Histogram.SampleCount = proto.Uint64(uintValue) + } else { + if value < 0 { + p.parseError(fmt.Sprintf("negative count for histogram %q", p.currentMF.GetName())) + return nil + } + p.currentMetric.Histogram.SampleCountFloat = proto.Float64(value) + } case p.currentIsHistogramSum: p.currentMetric.Histogram.SampleSum = proto.Float64(value) case !math.IsNaN(p.currentBucket): - p.currentMetric.Histogram.Bucket = append( - p.currentMetric.Histogram.Bucket, - &dto.Bucket{ - UpperBound: proto.Float64(p.currentBucket), - CumulativeCount: proto.Uint64(uint64(value)), - }, - ) + b := &dto.Bucket{ + UpperBound: proto.Float64(p.currentBucket), + } + if uintValue := uint64(value); value == float64(uintValue) { + b.CumulativeCount = proto.Uint64(uintValue) + } else { + if value < 0 { + p.parseError(fmt.Sprintf("negative bucket population for histogram %q", p.currentMF.GetName())) + return nil + } + b.CumulativeCountFloat = proto.Float64(value) + } + p.currentMetric.Histogram.Bucket = append(p.currentMetric.Histogram.Bucket, b) } default: p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName()) @@ -604,10 +657,18 @@ func (p *TextParser) readingType() stateFn { if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } - metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())] + typ := strings.ToUpper(p.currentToken.String()) // Tolerate any combination of upper and lower case. + metricType, ok := dto.MetricType_value[typ] // Tolerate "gauge_histogram" (not originally part of the text format). if !ok { - p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String())) - return nil + // We also want to tolerate "gaugehistogram" to mark a gauge + // histogram, because that string is used in OpenMetrics. Note, + // however, that gauge histograms do not officially exist in the + // classic text format. + if typ != "GAUGEHISTOGRAM" { + p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String())) + return nil + } + metricType = int32(dto.MetricType_GAUGE_HISTOGRAM) } p.currentMF.Type = dto.MetricType(metricType).Enum() return p.startOfLine @@ -857,7 +918,8 @@ func (p *TextParser) setOrCreateCurrentMF() { } histogramName := histogramMetricName(name) if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil { - if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { + if p.currentMF.GetType() == dto.MetricType_HISTOGRAM || + p.currentMF.GetType() == dto.MetricType_GAUGE_HISTOGRAM { if isCount(name) { p.currentIsHistogramCount = true } diff --git a/expfmt/text_parse_test.go b/expfmt/text_parse_test.go index f9c6d4cd7..edcee13a0 100644 --- a/expfmt/text_parse_test.go +++ b/expfmt/text_parse_test.go @@ -656,6 +656,307 @@ request_duration_microseconds_count 2693 }, }, }, + // 12: A full float histogram. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123.5 +request_duration_microseconds_bucket{le="120"} 412.6 +request_duration_microseconds_bucket{le="144"} 592.7 +request_duration_microseconds_bucket{le="172.8"} 1524.8 +request_duration_microseconds_bucket{le="+Inf"} 2693.9 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693.9 +`, + out: []*dto.MetricFamily{ + { + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693.9), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123.5), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412.6), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592.7), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524.8), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCountFloat: proto.Float64(2693.9), + }, + }, + }, + }, + }, + }, + }, + }, + // 13: A float histogram where only the count is really a float. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693.9 +`, + out: []*dto.MetricFamily{ + { + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693.9), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524), + }, + }, + }, + }, + }, + }, + }, + }, + // 14: A float histogram where only one of the buckets is really a float. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524.8 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 +`, + out: []*dto.MetricFamily{ + { + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524.8), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCountFloat: proto.Float64(2693), + }, + }, + }, + }, + }, + }, + }, + }, + // 15: A gauge histogram. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds gaugehistogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 +`, + out: []*dto.MetricFamily{ + { + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCount: proto.Uint64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCount: proto.Uint64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCount: proto.Uint64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCount: proto.Uint64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCount: proto.Uint64(1524), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCount: proto.Uint64(2693), + }, + }, + }, + }, + }, + }, + }, + }, + // 16: A gauge histogram with alternative spelling. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds gauge_histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 +`, + out: []*dto.MetricFamily{ + { + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCount: proto.Uint64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCount: proto.Uint64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCount: proto.Uint64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCount: proto.Uint64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCount: proto.Uint64(1524), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCount: proto.Uint64(2693), + }, + }, + }, + }, + }, + }, + }, + }, + // 17: A float gauge histogram where only one of the buckets is really a float and with alternative spelling. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds gauge_histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524.8 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 +`, + out: []*dto.MetricFamily{ + { + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_GAUGE_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + { + UpperBound: proto.Float64(100), + CumulativeCountFloat: proto.Float64(123), + }, + { + UpperBound: proto.Float64(120), + CumulativeCountFloat: proto.Float64(412), + }, + { + UpperBound: proto.Float64(144), + CumulativeCountFloat: proto.Float64(592), + }, + { + UpperBound: proto.Float64(172.8), + CumulativeCountFloat: proto.Float64(1524.8), + }, + { + UpperBound: proto.Float64(math.Inf(+1)), + CumulativeCountFloat: proto.Float64(2693), + }, + }, + }, + }, + }, + }, + }, + }, } for i, scenario := range scenarios { @@ -801,31 +1102,31 @@ metric 4.12 `, errUTF8: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`, }, - // 14: + // 15: { in: ` # TYPE metric bla `, errUTF8: "text format parsing error in line 2: unknown metric type", }, - // 15: + // 16: { in: ` # TYPE met-ric `, errUTF8: "text format parsing error in line 2: invalid metric name in comment", }, - // 16: + // 17: { in: `@invalidmetric{label="bla"} 3.14 2`, errUTF8: "text format parsing error in line 1: invalid metric name", }, - // 17: + // 18: { in: `{label="bla"} 3.14 2`, errUTF8: "text format parsing error in line 1: invalid metric name", }, - // 18: + // 19: { in: ` # TYPE metric histogram @@ -833,67 +1134,67 @@ metric_bucket{le="bla"} 3.14 `, errUTF8: "text format parsing error in line 3: expected float as value for 'le' label", }, - // 19: Invalid UTF-8 in label value. + // 20: Invalid UTF-8 in label value. { in: "metric{l=\"\xbd\"} 3.14\n", errUTF8: "text format parsing error in line 1: invalid label value \"\\xbd\"", }, - // 20: Go 1.13 sometimes allows underscores in numbers. + // 21: Go 1.13 sometimes allows underscores in numbers. { in: "foo 1_2\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 21: Go 1.13 supports hex floating point. + // 22: Go 1.13 supports hex floating point. { in: "foo 0x1p-3\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 22: Check for various other literals variants, just in case. + // 23: Check for various other literals variants, just in case. { in: "foo 0x1P-3\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 23: + // 24: { in: "foo 0B1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 24: + // 25: { in: "foo 0O1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 25: + // 26: { in: "foo 0X1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 26: + // 27: { in: "foo 0x1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 27: + // 28: { in: "foo 0b1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 28: + // 29: { in: "foo 0o1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 29: + // 30: { in: "foo 0x1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 30: + // 31: { in: "foo 0x1\n", errUTF8: "text format parsing error in line 1: expected float as value", }, - // 31: Check histogram label. + // 32: Check histogram label. { in: ` # TYPE metric histogram @@ -901,7 +1202,7 @@ metric_bucket{le="0x1p-3"} 3.14 `, errUTF8: "text format parsing error in line 3: expected float as value for 'le' label", }, - // 32: Check quantile label. + // 33: Check quantile label. { in: ` # TYPE metric summary @@ -909,28 +1210,28 @@ metric{quantile="0x1p-3"} 3.14 `, errUTF8: "text format parsing error in line 3: expected float as value for 'quantile' label", }, - // 33: Check duplicate label. + // 34: Check duplicate label. { in: `metric{label="bla",label="bla"} 3.14`, errUTF8: "text format parsing error in line 1: duplicate label names for metric", }, - // 34: Multiple quoted metric names. + // 35: Multiple quoted metric names. { in: `{"one.name","another.name"} 3.14`, errUTF8: "text format parsing error in line 1: multiple metric names", errLegacy: `text format parsing error in line 1: invalid metric name "one.name"`, }, - // 35: Invalid escape sequence in quoted metric name. + // 36: Invalid escape sequence in quoted metric name. { in: `{"a\xc5z",label="bla"} 3.14`, errUTF8: "text format parsing error in line 1: invalid escape sequence", }, - // 36: Unexpected end of quoted metric name. + // 37: Unexpected end of quoted metric name. { in: `{"metric.name".label="bla"} 3.14`, errUTF8: "text format parsing error in line 1: unexpected end of metric name", }, - // 37: Invalid escape sequence in quoted metric name. + // 38: Invalid escape sequence in quoted metric name. { in: ` # TYPE "metric.name\t" counter @@ -938,7 +1239,7 @@ metric{quantile="0x1p-3"} 3.14 `, errUTF8: "text format parsing error in line 2: invalid escape sequence", }, - // 38: Newline in quoted metric name. + // 39: Newline in quoted metric name. { in: ` # TYPE "metric @@ -948,7 +1249,7 @@ name",label="bla"} 3.14 `, errUTF8: `text format parsing error in line 2: metric name "metric" contains unescaped new-line`, }, - // 39: Newline in quoted label name. + // 40: Newline in quoted label name. { in: ` {"metric.name","new @@ -957,19 +1258,80 @@ line"="bla"} 3.14 errUTF8: `text format parsing error in line 2: label name "new" contains unescaped new-line`, errLegacy: `text format parsing error in line 2: invalid metric name "metric.name"`, }, - // 40: dotted name fails legacy validation. + // 41: Dotted metric name fails legacy validation. { in: `{"metric.name",foo="bla"} 3.14 `, errUTF8: ``, errLegacy: `text format parsing error in line 1: invalid metric name "metric.name"`, }, + // 42: Dotted label name fails legacy validation. { in: `metric_name{"foo"="bar", "dotted.label"="bla"} 3.14 `, errUTF8: ``, errLegacy: `text format parsing error in line 1: invalid label name "dotted.label"`, }, + // 43: Histogram with negative count. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count -2693 +`, + errUTF8: `text format parsing error in line 10: negative count for histogram "request_duration_microseconds"`, + }, + // 44: Histogram with negative bucket. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} -412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 +`, + errUTF8: `text format parsing error in line 5: negative bucket population for histogram "request_duration_microseconds"`, + }, + // 45: Histogram with negative float count. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count -2693.123 +`, + errUTF8: `text format parsing error in line 10: negative count for histogram "request_duration_microseconds"`, + }, + // 46: Histogram with negative float bucket. + { + in: ` +# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} -412.456 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 +`, + errUTF8: `text format parsing error in line 5: negative bucket population for histogram "request_duration_microseconds"`, + }, } for i, scenario := range scenarios { parser.scheme = model.UTF8Validation From 797fd9708ae8e6786d8b7153480b89542ae7e52e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:52:32 +0000 Subject: [PATCH 75/78] build(deps): bump google.golang.org/protobuf from 1.36.8 to 1.36.9 Bumps google.golang.org/protobuf from 1.36.8 to 1.36.9. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-version: 1.36.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 50bee34ba..c4f7b1573 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( go.yaml.in/yaml/v2 v2.4.3 golang.org/x/net v0.43.0 golang.org/x/oauth2 v0.30.0 - google.golang.org/protobuf v1.36.8 + google.golang.org/protobuf v1.36.10 ) require ( diff --git a/go.sum b/go.sum index 843012ce0..cab37a84c 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,8 @@ golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From a2774686af97196fdadc4d80325f936809c11303 Mon Sep 17 00:00:00 2001 From: SuperQ Date: Tue, 7 Oct 2025 08:55:59 +0200 Subject: [PATCH 76/78] Update Go * Update Go in CI to current supported versions. * Update minimum Go version to 1.24.0. * Bump Go modules. Signed-off-by: SuperQ --- .circleci/config.yml | 6 +++--- assets/go.mod | 2 +- go.mod | 14 ++++++-------- go.sum | 30 ++++++++++++++++++------------ 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 95fb827c5..23a770e5b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -93,14 +93,14 @@ workflows: matrix: parameters: go_version: - - "1.23" - "1.24" + - "1.25" - test-assets: name: assets-go-<< matrix.go_version >> matrix: parameters: go_version: - - "1.24" + - "1.25" - style: name: style - go_version: "1.24" + go_version: "1.25" diff --git a/assets/go.mod b/assets/go.mod index 0977f6e90..33f87bcea 100644 --- a/assets/go.mod +++ b/assets/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/common/assets -go 1.23.0 +go 1.24.0 require github.com/stretchr/testify v1.11.1 diff --git a/go.mod b/go.mod index c4f7b1573..f0eb3d693 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/prometheus/common -go 1.23.0 - -toolchain go1.24.1 +go 1.24.0 require ( github.com/alecthomas/kingpin/v2 v2.4.0 @@ -13,13 +11,13 @@ require ( github.com/prometheus/client_model v0.6.2 github.com/stretchr/testify v1.11.1 go.yaml.in/yaml/v2 v2.4.3 - golang.org/x/net v0.43.0 - golang.org/x/oauth2 v0.30.0 + golang.org/x/net v0.44.0 + golang.org/x/oauth2 v0.31.0 google.golang.org/protobuf v1.36.10 ) require ( - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -29,8 +27,8 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.28.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index cab37a84c..3c5194f10 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -38,26 +38,32 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 6035595774f21b170a1a4de53076a447c4fc54db Mon Sep 17 00:00:00 2001 From: beorn7 Date: Mon, 6 Oct 2025 23:40:49 +0200 Subject: [PATCH 77/78] Cut v0.67.0 Signed-off-by: beorn7 --- CHANGELOG.md | 15 ++++++++++++++- VERSION | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c47511cf..b1cdfdc5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,20 @@ ### What's Changed -* [ENHANCEMENT] Create CHANGELOG.md for easier communication of library changes, especially possible breaking changes. https://github.com/prometheus/common/pull/833 +## v0.67.0 / 2025-10-07 + +## What's Changed +* Create CHANGELOG.md for easier communication of library changes, especially possible breaking changes. by @ywwg in https://github.com/prometheus/common/pull/833 +* model: New test for validation with dots by @m1k1o in https://github.com/prometheus/common/pull/759 +* expfmt: document NewTextParser as required by @burgerdev in https://github.com/prometheus/common/pull/842 +* expfmt: Add support for float histograms and gauge histograms by @beorn7 in https://github.com/prometheus/common/pull/843 +* Updated minimum Go version to 1.24.0, updated Go dependecies by @SuperQ in https://github.com/prometheus/common/pull/849 + +## New Contributors +* @m1k1o made their first contribution in https://github.com/prometheus/common/pull/759 +* @burgerdev made their first contribution in https://github.com/prometheus/common/pull/842 + +**Full Changelog**: https://github.com/prometheus/common/compare/v0.66.1...v0.67.0 ## v0.66.1 / 2025-09-05 diff --git a/VERSION b/VERSION index d3072c439..328185caa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.66.1 \ No newline at end of file +0.67.0 From f5de376c9c4962000e06bfd66e7bbea836e06ed4 Mon Sep 17 00:00:00 2001 From: SuperQ Date: Tue, 7 Oct 2025 13:59:48 +0200 Subject: [PATCH 78/78] Fix Go case-insensitive file name collision Remove the `VERSION` file to avoid "case-insensitive file name collision" error importing this package. Fixes: https://github.com/prometheus/common/issues/850 Signed-off-by: SuperQ --- CHANGELOG.md | 7 +++++++ RELEASE.md | 3 +-- VERSION | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 VERSION diff --git a/CHANGELOG.md b/CHANGELOG.md index b1cdfdc5e..9f96ecbe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ ### What's Changed +## v0.67.1 / 2025-10-07 + +## What's Changed +* Remove VERSION file to avoid Go conflict error in https://github.com/prometheus/common/pull/853 + +**Full Changelog**: https://github.com/prometheus/common/compare/v0.67.0...v0.67.1 + ## v0.67.0 / 2025-10-07 ## What's Changed diff --git a/RELEASE.md b/RELEASE.md index 066a36e29..42e76cafa 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -19,8 +19,7 @@ Make sure the release is ready to go, with an updated changelog including notice Here are the basic steps: -1. Update VERSION, applying the next logical version number, without any `v` prefix (e.g. `0.53.0`). -2. Update CHANGELOG.md, applying the new version number (this time including the `v` prefix, e.g. `v0.53.0`) and date to the changes listed under ``## main / unreleased`, and commit those changes to the main branch. +1. Update CHANGELOG.md, applying the new version number (this time including the `v` prefix, e.g. `v0.53.0`) and date to the changes listed under ``## main / unreleased`, and commit those changes to the main branch. 2. Use GitHub's release feature via [this link](https://github.com/prometheus/prometheus/releases/new) to apply a new tag. The tag name must be prefixed with a `v` e.g. `v0.53.0` and then use the "Generate release notes" button to generate the release notes automagically ✨. No need to create a discussion or mark it a pre-release, please do make sure it is marked as the latest release. ## Versioning strategy diff --git a/VERSION b/VERSION deleted file mode 100644 index 328185caa..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.67.0