From 99e464c239611882fc606981156b8ebd08af117b Mon Sep 17 00:00:00 2001 From: Pranshu Srivastava Date: Wed, 4 Feb 2026 01:04:02 +0530 Subject: [PATCH 01/23] discovery/kubernetes: Support node role selectors for pod roles The node informers for pod roles respected filtering based on selectors ([1]). However, node selectors for pod roles were not allowed. This patch address that, and additionally adds pod filtering logic, to not populate their target groups when they belong to a filtered node. [1]:https://github.com/prometheus/prometheus/pull/10080/changes#diff-e9ca22962ad7d9a7bb4cb82d209dc2dd5301f457d8242da5535557866d2ea1eaR667 Signed-off-by: Pranshu Srivastava --- config/config_test.go | 2 +- .../testdata/kubernetes_selectors_pod.bad.yml | 3 + .../kubernetes_selectors_pod.good.yml | 5 ++ discovery/kubernetes/kubernetes.go | 2 +- discovery/kubernetes/pod.go | 13 ++++ discovery/kubernetes/pod_test.go | 71 +++++++++++++++++++ 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index 968b563e1e..0ded7b079e 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2315,7 +2315,7 @@ var expectedErrors = []struct { }, { filename: "kubernetes_selectors_pod.bad.yml", - errMsg: "pod role supports only pod selectors", + errMsg: "pod role supports only pod, node selectors", }, { filename: "kubernetes_selectors_service.bad.yml", diff --git a/config/testdata/kubernetes_selectors_pod.bad.yml b/config/testdata/kubernetes_selectors_pod.bad.yml index 3a1a83abd2..24285c0ce0 100644 --- a/config/testdata/kubernetes_selectors_pod.bad.yml +++ b/config/testdata/kubernetes_selectors_pod.bad.yml @@ -6,3 +6,6 @@ scrape_configs: - role: "node" label: "foo=bar" field: "metadata.status=Running" + - role: "service" + label: "baz=que" + field: "metadata.status=Running" diff --git a/config/testdata/kubernetes_selectors_pod.good.yml b/config/testdata/kubernetes_selectors_pod.good.yml index 91da6ada17..49c17d72ce 100644 --- a/config/testdata/kubernetes_selectors_pod.good.yml +++ b/config/testdata/kubernetes_selectors_pod.good.yml @@ -11,3 +11,8 @@ scrape_configs: - role: "pod" label: "foo in (bar,baz)" field: "metadata.status=Running" + - role: pod + selectors: + - role: "node" + label: "foo=bar" + field: "metadata.status=Running" diff --git a/discovery/kubernetes/kubernetes.go b/discovery/kubernetes/kubernetes.go index 678f287ef5..6bbbafe8ea 100644 --- a/discovery/kubernetes/kubernetes.go +++ b/discovery/kubernetes/kubernetes.go @@ -194,7 +194,7 @@ func (c *SDConfig) UnmarshalYAML(unmarshal func(any) error) error { foundSelectorRoles := make(map[Role]struct{}) allowedSelectors := map[Role][]string{ - RolePod: {string(RolePod)}, + RolePod: {string(RolePod), string(RoleNode)}, RoleService: {string(RoleService)}, RoleEndpointSlice: {string(RolePod), string(RoleService), string(RoleEndpointSlice)}, RoleEndpoint: {string(RolePod), string(RoleService), string(RoleEndpoint)}, diff --git a/discovery/kubernetes/pod.go b/discovery/kubernetes/pod.go index 1fed78b3a7..05b778bb59 100644 --- a/discovery/kubernetes/pod.go +++ b/discovery/kubernetes/pod.go @@ -286,6 +286,19 @@ func (p *Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group { return tg } + // Filter out pods scheduled on nodes that are not in the node store, as + // these were filtered out by node selectors. + if p.withNodeMetadata { + _, exists, err := p.nodeInf.GetStore().GetByKey(pod.Spec.NodeName) + if err != nil { + p.logger.Error("failed to get node from store", "node", pod.Spec.NodeName, "err", err) + return tg + } + if !exists { + return tg + } + } + tg.Labels = podLabels(pod) tg.Labels[namespaceLabel] = lv(pod.Namespace) if p.withNodeMetadata { diff --git a/discovery/kubernetes/pod_test.go b/discovery/kubernetes/pod_test.go index db5db546d0..df8f42fcd3 100644 --- a/discovery/kubernetes/pod_test.go +++ b/discovery/kubernetes/pod_test.go @@ -627,3 +627,74 @@ func TestPodDiscoveryWithUpdatedNamespaceMetadata(t *testing.T) { }, }.Run(t) } + +func TestPodDiscoveryWithNodeSelector(t *testing.T) { + t.Parallel() + + workerNode := makeNode("worker-node", "10.0.0.1", "", map[string]string{"node-type": "worker"}, nil) + filteredNode := makeNode("filtered-node", "10.0.0.2", "", map[string]string{"node-type": "master"}, nil) + + attachMetadata := AttachMetadataConfig{ + Node: true, // necessary for node role selectos to work for pod role + } + n, c := makeDiscoveryWithMetadata(RolePod, NamespaceDiscovery{}, attachMetadata, workerNode, filteredNode) + n.selectors = roleSelector{ + node: resourceSelector{ + label: "node-type=worker", + }, + } + + podOnWorker := makePods("default") + podOnWorker.Name = "pod-on-worker" + podOnWorker.UID = types.UID("worker-pod-123") + podOnWorker.Spec.NodeName = "worker-node" + podOnWorker.Status.PodIP = "192.168.1.1" + + podOnFilteredNode := makePods("default") + podOnFilteredNode.Name = "pod-on-filtered-node" + podOnFilteredNode.UID = types.UID("filtered-pod-456") + podOnFilteredNode.Spec.NodeName = "filtered-node" + podOnFilteredNode.Status.PodIP = "192.168.1.2" + + k8sDiscoveryTest{ + discovery: n, + beforeRun: func() { + c.CoreV1().Pods("default").Create(context.Background(), podOnWorker, metav1.CreateOptions{}) + c.CoreV1().Pods("default").Create(context.Background(), podOnFilteredNode, metav1.CreateOptions{}) + }, + expectedMaxItems: 2, + expectedRes: map[string]*targetgroup.Group{ + "pod/default/pod-on-worker": { + Targets: []model.LabelSet{ + { + "__address__": "192.168.1.1:9000", + "__meta_kubernetes_pod_container_image": "testcontainer:latest", + "__meta_kubernetes_pod_container_name": "testcontainer", + "__meta_kubernetes_pod_container_port_name": "testport", + "__meta_kubernetes_pod_container_port_number": "9000", + "__meta_kubernetes_pod_container_port_protocol": "TCP", + "__meta_kubernetes_pod_container_init": "false", + "__meta_kubernetes_pod_container_id": "docker://a1b2c3d4e5f6", + }, + }, + Labels: model.LabelSet{ + "__meta_kubernetes_namespace": "default", + "__meta_kubernetes_pod_name": "pod-on-worker", + "__meta_kubernetes_pod_ip": "192.168.1.1", + "__meta_kubernetes_pod_ready": "true", + "__meta_kubernetes_pod_phase": "Running", + "__meta_kubernetes_pod_node_name": "worker-node", + "__meta_kubernetes_pod_host_ip": "2.3.4.5", + "__meta_kubernetes_pod_uid": "worker-pod-123", + "__meta_kubernetes_node_name": "worker-node", + "__meta_kubernetes_node_label_node_type": "worker", + "__meta_kubernetes_node_labelpresent_node_type": "true", + }, + Source: "pod/default/pod-on-worker", + }, + "pod/default/pod-on-filtered-node": { + Source: "pod/default/pod-on-filtered-node", + }, + }, + }.Run(t) +} From 7cca0755c3141db255460f48fdd042ca37f81605 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Tue, 10 Feb 2026 17:21:49 -0800 Subject: [PATCH 02/23] Upgrade Go to 1.26.0 for building Prometheus Signed-off-by: Ganesh Vernekar --- .github/workflows/ci.yml | 19 +++++++------------ .github/workflows/fuzzing.yml | 2 +- .promu.yml | 2 +- scripts/golangci-lint.yml | 2 +- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1553bdba19..3133fc102b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: container: # Whenever the Go version is updated here, .promu.yml # should also be updated. - image: quay.io/prometheus/golang-builder:1.25-base + image: quay.io/prometheus/golang-builder:1.26-base steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: @@ -34,7 +34,7 @@ jobs: name: More Go tests runs-on: ubuntu-latest container: - image: quay.io/prometheus/golang-builder:1.25-base + image: quay.io/prometheus/golang-builder:1.26-base steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: @@ -55,8 +55,6 @@ jobs: env: # Enforce the Go version. GOTOOLCHAIN: local - # TODO: remove once 1.25 is the min version. - GOEXPERIMENT: synctest container: # The go version in this image should be N-1 wrt test_go. image: quay.io/prometheus/golang-builder:1.25-base @@ -67,9 +65,6 @@ jobs: - run: make build # Don't run NPM build; don't run race-detector. - run: make test GO_ONLY=1 test-flags="" - # TODO: remove once 1.25 is the min version. - # ensure we can build without the tag. - - run: GOEXPERIMENT="" make build test_ui: name: UI tests @@ -77,7 +72,7 @@ jobs: # Whenever the Go version is updated here, .promu.yml # should also be updated. container: - image: quay.io/prometheus/golang-builder:1.25-base + image: quay.io/prometheus/golang-builder:1.26-base steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -104,7 +99,7 @@ jobs: persist-credentials: false - uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: - go-version: 1.25.x + go-version: 1.26.x - run: | $TestTargets = go list ./... | Where-Object { $_ -NotMatch "(github.com/prometheus/prometheus/config|github.com/prometheus/prometheus/web)"} go test $TestTargets -vet=off -v @@ -116,7 +111,7 @@ jobs: # Whenever the Go version is updated here, .promu.yml # should also be updated. container: - image: quay.io/prometheus/golang-builder:1.25-base + image: quay.io/prometheus/golang-builder:1.26-base steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: @@ -208,7 +203,7 @@ jobs: name: Check generated parser runs-on: ubuntu-latest container: - image: quay.io/prometheus/golang-builder:1.25-base + image: quay.io/prometheus/golang-builder:1.26-base steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -231,7 +226,7 @@ jobs: - name: Install Go uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: - go-version: 1.25.x + go-version: 1.26.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' diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 55ab70dbac..579b53411e 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -19,7 +19,7 @@ jobs: - name: Install Go uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: - go-version: 1.25.x + go-version: 1.26.x - name: Run Fuzzing run: go test -fuzz=${{ matrix.fuzz_test }}$ -fuzztime=5m ./util/fuzzing continue-on-error: true diff --git a/.promu.yml b/.promu.yml index d5205b4fdf..0e1747a62b 100644 --- a/.promu.yml +++ b/.promu.yml @@ -1,7 +1,7 @@ go: # Whenever the Go version is updated here, # .github/workflows should also be updated. - version: 1.25 + version: 1.26 repository: path: github.com/prometheus/prometheus build: diff --git a/scripts/golangci-lint.yml b/scripts/golangci-lint.yml index ae5fdc80ec..abf2529f03 100644 --- a/scripts/golangci-lint.yml +++ b/scripts/golangci-lint.yml @@ -30,7 +30,7 @@ jobs: - name: Install Go uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0 with: - go-version: 1.25.x + go-version: 1.26.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 631282ac1824cb75527c8ec71afa2dee9caf55e0 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Wed, 11 Feb 2026 11:27:16 -0800 Subject: [PATCH 03/23] Upgrade golangci-lint to v2.9.0 Signed-off-by: Ganesh Vernekar --- Makefile.common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.common b/Makefile.common index b8c9b3844c..38a058c1e7 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.7.2 +GOLANGCI_LINT_VERSION ?= v2.9.0 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 64b97bf4884eb8b8271b2d78b454a3618780f806 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Wed, 11 Feb 2026 11:52:51 -0800 Subject: [PATCH 04/23] Disable 'newexpr' in golangci-lint Signed-off-by: Ganesh Vernekar --- .golangci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 8cb3265f4f..d885374fa2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -130,6 +130,9 @@ linters: - omitzero # Disable waitgroup check until we really move to Go 1.25. - waitgroup + # Disable newexpr check for now since it introduces too many changes in our existing codebase. + # To be re-enabled as a part of https://github.com/prometheus/prometheus/issues/18066. + - newexpr perfsprint: # Optimizes even if it requires an int or uint type cast. int-conversion: true From 0d59a920bc2aa93d17ec410d10c6248373bf5a64 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Wed, 11 Feb 2026 13:53:10 -0800 Subject: [PATCH 05/23] Remove the goexperiment.synctest build tag Signed-off-by: Ganesh Vernekar --- .github/workflows/ci.yml | 3 +-- util/testutil/synctest/disabled.go | 29 ---------------------------- util/testutil/synctest/enabled.go | 31 ------------------------------ util/testutil/synctest/synctest.go | 2 -- 4 files changed, 1 insertion(+), 64 deletions(-) delete mode 100644 util/testutil/synctest/disabled.go delete mode 100644 util/testutil/synctest/enabled.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3133fc102b..5fe956ca37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,8 +241,7 @@ jobs: - name: Lint with slicelabels uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 with: - # goexperiment.synctest to ensure we don't miss files that depend on it. - args: --verbose --build-tags=slicelabels,goexperiment.synctest + args: --verbose --build-tags=slicelabels version: ${{ steps.golangci-lint-version.outputs.version }} - name: Lint with dedupelabels uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 diff --git a/util/testutil/synctest/disabled.go b/util/testutil/synctest/disabled.go deleted file mode 100644 index 595b93c650..0000000000 --- a/util/testutil/synctest/disabled.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 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. - -//go:build !goexperiment.synctest && !go1.25 - -package synctest - -import ( - "testing" -) - -func Test(t *testing.T, _ func(t *testing.T)) { - t.Skip("goexperiment.synctest is not enabled") -} - -func Wait() { - // It isn't meant to be called outside of Test(). - panic("goexperiment.synctest is not enabled") -} diff --git a/util/testutil/synctest/enabled.go b/util/testutil/synctest/enabled.go deleted file mode 100644 index d219903809..0000000000 --- a/util/testutil/synctest/enabled.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 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. - -//go:build goexperiment.synctest && !go1.25 - -package synctest - -import ( - "testing" - "testing/synctest" -) - -func Test(t *testing.T, f func(t *testing.T)) { - synctest.Run(func() { - f(t) - }) -} - -func Wait() { - synctest.Wait() -} diff --git a/util/testutil/synctest/synctest.go b/util/testutil/synctest/synctest.go index 41750f9892..e3099c2d6a 100644 --- a/util/testutil/synctest/synctest.go +++ b/util/testutil/synctest/synctest.go @@ -11,8 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build go1.25 - package synctest import ( From 216ddb524e9ba66ed69a3939855cb42b364c9ede Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Wed, 11 Feb 2026 14:00:00 -0800 Subject: [PATCH 06/23] Rollback Go version to 1.25.0 in go.mod files Signed-off-by: Ganesh Vernekar --- documentation/examples/remote_storage/go.mod | 4 ++-- documentation/examples/remote_storage/go.sum | 6 ++++-- go.mod | 5 +++-- go.sum | 10 ++++++---- go.work | 2 +- internal/tools/go.mod | 2 +- web/ui/mantine-ui/src/promql/tools/go.mod | 2 +- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/documentation/examples/remote_storage/go.mod b/documentation/examples/remote_storage/go.mod index 0c80c6e7c6..7f1b581598 100644 --- a/documentation/examples/remote_storage/go.mod +++ b/documentation/examples/remote_storage/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/prometheus/documentation/examples/remote_storage -go 1.25.5 +go 1.25.0 require ( github.com/alecthomas/kingpin/v2 v2.4.0 @@ -64,7 +64,7 @@ require ( github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/gophercloud/gophercloud/v2 v2.10.0 // indirect github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect - github.com/hashicorp/consul/api v1.33.2 // indirect + github.com/hashicorp/consul/api v1.32.1 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/nomad/api v0.0.0-20260209224925-94b77491c895 // indirect github.com/hetznercloud/hcloud-go/v2 v2.36.0 // indirect diff --git a/documentation/examples/remote_storage/go.sum b/documentation/examples/remote_storage/go.sum index 6ebede1adf..c8909ca671 100644 --- a/documentation/examples/remote_storage/go.sum +++ b/documentation/examples/remote_storage/go.sum @@ -184,8 +184,8 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7EAJ0BHIethd+J6LqxFNw5mSiI2bM= github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= -github.com/hashicorp/consul/api v1.33.2 h1:Q6mE0WZsUTJerlnl9TuXzqrtZ0cKdOCsxcZhj5mKbMs= -github.com/hashicorp/consul/api v1.33.2/go.mod h1:K3yoL/vnIBcQV/25NeMZVokRvPPERiqp2Udtr4xAfhs= +github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE= +github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4= github.com/hashicorp/cronexpr v1.1.3 h1:rl5IkxXN2m681EfivTlccqIryzYJSXRGRNa0xeG7NA4= github.com/hashicorp/cronexpr v1.1.3/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= @@ -255,6 +255,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= diff --git a/go.mod b/go.mod index 89d468e874..e86caf656a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/prometheus -go 1.25.5 +go 1.25.0 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 @@ -40,7 +40,7 @@ require ( github.com/google/uuid v1.6.0 github.com/gophercloud/gophercloud/v2 v2.10.0 github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 - github.com/hashicorp/consul/api v1.33.2 + github.com/hashicorp/consul/api v1.32.1 github.com/hashicorp/nomad/api v0.0.0-20260205205048-8315996478d1 github.com/hetznercloud/hcloud-go/v2 v2.36.0 github.com/ionos-cloud/sdk-go/v6 v6.3.6 @@ -121,6 +121,7 @@ require ( github.com/go-openapi/swag/stringutils v0.25.4 // indirect github.com/go-openapi/swag/typeutils v0.25.4 // indirect github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pb33f/jsonpath v0.7.1 // indirect github.com/pb33f/ordered-map/v2 v2.3.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect diff --git a/go.sum b/go.sum index bcb7b8fcc1..e723e04200 100644 --- a/go.sum +++ b/go.sum @@ -278,10 +278,10 @@ github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 h1:cLN4IBkmkYZNnk7E github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= -github.com/hashicorp/consul/api v1.33.2 h1:Q6mE0WZsUTJerlnl9TuXzqrtZ0cKdOCsxcZhj5mKbMs= -github.com/hashicorp/consul/api v1.33.2/go.mod h1:K3yoL/vnIBcQV/25NeMZVokRvPPERiqp2Udtr4xAfhs= -github.com/hashicorp/consul/sdk v0.17.1 h1:LumAh8larSXmXw2wvw/lK5ZALkJ2wK8VRwWMLVV5M5c= -github.com/hashicorp/consul/sdk v0.17.1/go.mod h1:EngiixMhmw9T7wApycq6rDRFXXVUwjjf7HuLiGMH/Sw= +github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE= +github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s= github.com/hashicorp/cronexpr v1.1.3 h1:rl5IkxXN2m681EfivTlccqIryzYJSXRGRNa0xeG7NA4= github.com/hashicorp/cronexpr v1.1.3/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -410,6 +410,8 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= diff --git a/go.work b/go.work index 4d53344b16..c5ba5dfad6 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.25.5 +go 1.25.0 use ( . diff --git a/internal/tools/go.mod b/internal/tools/go.mod index f3853a86c6..d823d637cf 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/prometheus/internal/tools -go 1.25.5 +go 1.25.0 require ( github.com/bufbuild/buf v1.65.0 diff --git a/web/ui/mantine-ui/src/promql/tools/go.mod b/web/ui/mantine-ui/src/promql/tools/go.mod index 693b168206..c457864161 100644 --- a/web/ui/mantine-ui/src/promql/tools/go.mod +++ b/web/ui/mantine-ui/src/promql/tools/go.mod @@ -1,6 +1,6 @@ module github.com/prometheus/prometheus/web/ui/mantine-ui/src/promql/tools -go 1.25.5 +go 1.25.0 require ( github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 From 5504d39b3e2646abac9a6557353c0789139622c2 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Wed, 11 Feb 2026 16:49:19 -0800 Subject: [PATCH 07/23] tsdb: Fix locking in Head.deleteSeriesByID Signed-off-by: Ganesh Vernekar --- tsdb/head.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tsdb/head.go b/tsdb/head.go index 6fe42c8cf2..e6d75e109f 100644 --- a/tsdb/head.go +++ b/tsdb/head.go @@ -2156,15 +2156,24 @@ func (h *Head) deleteSeriesByID(refs []chunks.HeadSeriesRef) { ) for _, ref := range refs { + // Delete the reference from the series map. + // Copying getByID here to avoid locking and unlocking twice. refShard := int(ref) & (h.series.size - 1) h.series.locks[refShard].Lock() - - // Copying getByID here to avoid locking and unlocking twice. series := h.series.series[refShard][ref] if series == nil { h.series.locks[refShard].Unlock() continue } + delete(h.series.series[refShard], series.ref) + h.series.locks[refShard].Unlock() + + // Delete the reference from the hash. + hash := series.lset.Hash() + hashShard := int(hash) & (h.series.size - 1) + h.series.locks[hashShard].Lock() + h.series.hashes[hashShard].del(hash, series.ref) + h.series.locks[hashShard].Unlock() if value.IsStaleNaN(series.lastValue) || (series.lastHistogramValue != nil && value.IsStaleNaN(series.lastHistogramValue.Sum)) || @@ -2172,9 +2181,6 @@ func (h *Head) deleteSeriesByID(refs []chunks.HeadSeriesRef) { staleSeriesDeleted++ } - hash := series.lset.Hash() - hashShard := int(hash) & (h.series.size - 1) - chunksRemoved += len(series.mmappedChunks) if series.headChunks != nil { chunksRemoved += series.headChunks.len() @@ -2182,10 +2188,6 @@ func (h *Head) deleteSeriesByID(refs []chunks.HeadSeriesRef) { deleted[storage.SeriesRef(series.ref)] = struct{}{} series.lset.Range(func(l labels.Label) { affected[l] = struct{}{} }) - h.series.hashes[hashShard].del(hash, series.ref) - delete(h.series.series[refShard], series.ref) - - h.series.locks[refShard].Unlock() } h.metrics.seriesRemoved.Add(float64(len(deleted))) From daeef8b7b617bfc442048d07473514cdf73b7d6c Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Fri, 13 Feb 2026 12:18:59 -0800 Subject: [PATCH 08/23] Add unit test to detect race in Head.deleteSeriesByID Signed-off-by: Ganesh Vernekar --- tsdb/head_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tsdb/head_test.go b/tsdb/head_test.go index 7b8ae0ecbd..6dbe3599e4 100644 --- a/tsdb/head_test.go +++ b/tsdb/head_test.go @@ -7246,3 +7246,77 @@ func TestHistogramStalenessConversionMetrics(t *testing.T) { }) } } + +// TestWALReplayRaceWithStaleSeriesCompaction verifies that deleteSeriesByID correctly locks the +// hash shard (not only the ref shard) when deleting from the hashes map. +// The race only occurs when Prometheus restarts after having done a stale series compaction because +// deleteSeriesByID is not used otherwise. +func TestWALReplayRaceWithStaleSeriesCompaction(t *testing.T) { + opts := newTestHeadDefaultOptions(1000, false) + // A small stripe size ensures many series share hash shards, increasing + // the likelihood that deleteSeriesByID and getOrCreateWithOptionalID + // contend on the same shard during WAL replay. + opts.StripeSize = 32 + head, _ := newTestHeadWithOptions(t, compression.None, opts) + require.NoError(t, head.Init(0)) + + appendSample := func(lbls labels.Labels, ts int64, val float64) { + app := head.Appender(context.Background()) + _, err := app.Append(0, lbls, ts, val) + require.NoError(t, err) + require.NoError(t, app.Commit()) + } + + // Step 1: Create a batch of series and make them stale. + const numStaleSeries = 500 + staleLbls := make([]labels.Labels, numStaleSeries) + for i := range numStaleSeries { + staleLbls[i] = labels.FromStrings("__name__", "stale_metric", "i", strconv.Itoa(i)) + appendSample(staleLbls[i], 100, float64(i)) + } + for _, lbl := range staleLbls { + appendSample(lbl, 200, math.Float64frombits(value.StaleNaN)) + } + require.Equal(t, uint64(numStaleSeries), head.NumStaleSeries()) + + // Step 2: Truncate stale series. This removes them from the Head and + // writes tombstone records (with Mint=MinInt64, Maxt=MaxInt64) to the WAL. + staleRefs := make([]storage.SeriesRef, 0, numStaleSeries) + for i := range numStaleSeries { + ms := head.series.getByHash(staleLbls[i].Hash(), staleLbls[i]) + require.NotNil(t, ms) + staleRefs = append(staleRefs, storage.SeriesRef(ms.ref)) + } + require.NoError(t, head.truncateStaleSeries(staleRefs, 300)) + require.Equal(t, uint64(0), head.NumStaleSeries()) + require.Equal(t, uint64(0), head.NumSeries()) + + // Step 3: Add new series AFTER the truncation. In the WAL, these series + // records appear after the tombstone records. During replay, the main + // goroutine will create these series (via getOrCreateWithOptionalID, which + // accesses hashes[hashShard] under locks[hashShard]) concurrently with + // the walSubsetProcessor goroutines deleting the stale series (via + // deleteSeriesByID, which must also lock the correct hashShard). + const numNewSeries = 500 + for i := range numNewSeries { + lbl := labels.FromStrings("__name__", "new_metric", "i", strconv.Itoa(i)) + appendSample(lbl, 300, float64(i)) + } + require.Equal(t, uint64(numNewSeries), head.NumSeries()) + + // Step 4: Close and re-open the Head to trigger WAL replay. + // With the buggy locking, the race detector should catch the data race + // between the main goroutine (creating series) and worker goroutines + // (deleting stale series) during replay. + require.NoError(t, head.Close()) + + wal, err := wlog.NewSize(nil, nil, filepath.Join(head.opts.ChunkDirRoot, "wal"), 32768, compression.None) + require.NoError(t, err) + head, err = NewHead(nil, nil, wal, nil, head.opts, nil) + require.NoError(t, err) + require.NoError(t, head.Init(0)) // Should not cause a race here. + + require.Equal(t, uint64(0), head.NumStaleSeries()) + require.Equal(t, uint64(numNewSeries), head.NumSeries()) + require.NoError(t, head.Close()) +} From d4eef1c87f01c8f76cf396baa8e3f9772f89e900 Mon Sep 17 00:00:00 2001 From: Divyansh Mishra Date: Sat, 14 Feb 2026 14:33:17 +0530 Subject: [PATCH 09/23] tsdb: Optimize LabelValues for sparse intersections (Fixes #14551) Signed-off-by: Divyansh Mishra --- tsdb/index/postings.go | 14 +++--- tsdb/index/postings_test.go | 6 +-- tsdb/label_values_bench_test.go | 86 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 tsdb/label_values_bench_test.go diff --git a/tsdb/index/postings.go b/tsdb/index/postings.go index 31b93f850d..c0bf213c45 100644 --- a/tsdb/index/postings.go +++ b/tsdb/index/postings.go @@ -956,7 +956,7 @@ func FindIntersectingPostings(p Postings, candidates []Postings) (indexes []int, } if p.At() == h.at() { indexes = append(indexes, h.popIndex()) - } else if err := h.next(); err != nil { + } else if err := h.seekHead(p.At()); err != nil { return nil, err } } @@ -999,20 +999,18 @@ func (h *postingsWithIndexHeap) popIndex() int { // at provides the storage.SeriesRef where root Postings is pointing at this moment. func (h postingsWithIndexHeap) at() storage.SeriesRef { return h[0].p.At() } -// next performs the Postings.Next() operation on the root of the heap, performing the related operation on the heap -// and conveniently returning the result of calling Postings.Err() if the result of calling Next() was false. -// If Next() succeeds, heap is fixed to move the root to its new position, according to its Postings.At() value. -// If Next() returns fails and there's no error reported by Postings.Err(), then root is marked as removed and heap is fixed. -func (h *postingsWithIndexHeap) next() error { +// seekHead performs the Postings.Seek() operation on the root of the heap. +// If the root is exhausted or fails, it is removed from the heap. +func (h *postingsWithIndexHeap) seekHead(val storage.SeriesRef) error { pi := (*h)[0] - next := pi.p.Next() + next := pi.p.Seek(val) if next { heap.Fix(h, 0) return nil } if err := pi.p.Err(); err != nil { - return fmt.Errorf("postings %d: %w", pi.index, err) + return fmt.Errorf("seek postings %d: %w", pi.index, err) } h.popIndex() return nil diff --git a/tsdb/index/postings_test.go b/tsdb/index/postings_test.go index 77b43f76ab..5c67a2da6d 100644 --- a/tsdb/index/postings_test.go +++ b/tsdb/index/postings_test.go @@ -1192,7 +1192,7 @@ func (p *postingsFailingAfterNthCall) Err() error { } func TestPostingsWithIndexHeap(t *testing.T) { - t.Run("iterate", func(t *testing.T) { + t.Run("seekHead", func(t *testing.T) { h := postingsWithIndexHeap{ {index: 0, p: NewListPostings([]storage.SeriesRef{10, 20, 30})}, {index: 1, p: NewListPostings([]storage.SeriesRef{1, 5})}, @@ -1205,7 +1205,7 @@ func TestPostingsWithIndexHeap(t *testing.T) { for _, expected := range []storage.SeriesRef{1, 5, 10, 20, 25, 30, 50} { require.Equal(t, expected, h.at()) - require.NoError(t, h.next()) + require.NoError(t, h.seekHead(h.at()+1)) } require.True(t, h.empty()) }) @@ -1223,7 +1223,7 @@ func TestPostingsWithIndexHeap(t *testing.T) { for _, expected := range []storage.SeriesRef{1, 5, 10, 20} { require.Equal(t, expected, h.at()) - require.NoError(t, h.next()) + require.NoError(t, h.seekHead(h.at()+1)) } require.Equal(t, storage.SeriesRef(25), h.at()) node := heap.Pop(&h).(postingsWithIndex) diff --git a/tsdb/label_values_bench_test.go b/tsdb/label_values_bench_test.go new file mode 100644 index 0000000000..1e55cf80c0 --- /dev/null +++ b/tsdb/label_values_bench_test.go @@ -0,0 +1,86 @@ +// Copyright 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 tsdb + +import ( + "context" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/tsdb/wlog" +) + +// BenchmarkLabelValues_SlowPath benchmarks the performance of LabelValues when the matcher +// is far ahead of the candidate posting list. This reproduces the performance regression +// described in #14551 where dense candidates caused O(N) iteration instead of O(log N) seeking. +func BenchmarkLabelValues_SlowPath(b *testing.B) { + // Create a head with some data. + opts := DefaultHeadOptions() + opts.ChunkDirRoot = b.TempDir() + h, err := NewHead(nil, nil, nil, nil, opts, nil) + require.NoError(b, err) + defer h.Close() + + app := h.Appender(context.Background()) + // 1. Create a large number of series for a "candidate" label (e.g. "job"). + // We want these to NOT match the target matcher, but be candidates for a different label. + // We use "job=api" and "instance=..." + // We want the interaction to be: + // LabelValues("instance", "job"="api") + // "job"="api" will have 1 series at the END. + // "instance" will have 100k series. + + // Actually, let's stick to the reproduction case: + // distinct values for "val1". + // "b"="1" matcher. + + // Create 100k series with the same label value ("common") but without the matcher label. + // This results in a single large posting list for that value, simulating a dense candidate. + for i := range 100000 { + _, err := app.Append(0, labels.FromStrings("val1", "common", "extra", strconv.Itoa(i)), time.Now().UnixMilli(), 1) + require.NoError(b, err) + } + + // Create 1 series that matches the label "b=1", with a series ID greater than all previous ones. + // This forces the intersection to skip over all 100k previous candidates. + _, err = app.Append(0, labels.FromStrings("val1", "common", "b", "1"), time.Now().UnixMilli(), 1) + require.NoError(b, err) + + require.NoError(b, app.Commit()) + + ctx := context.Background() + matcher := labels.MustNewMatcher(labels.MatchEqual, "b", "1") + + // Use the correct method to access label values. + idx, err := h.Index() + require.NoError(b, err) + + b.ResetTimer() + b.ReportAllocs() + + for b.Loop() { + // "val1"="common" has 100k+1 postings. + // "b=1" has 1 posting (the last one). + vals, err := idx.LabelValues(ctx, "val1", nil, matcher) + require.NoError(b, err) + require.Equal(b, []string{"common"}, vals) + } +} + +// Ensure wlog/wal needed for NewHead. +var _ = wlog.WL{} From ad00ed0609bc701ba9d5afa7d2977204aeee3c91 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Tue, 17 Feb 2026 10:07:14 -0800 Subject: [PATCH 10/23] Optimize TestDiskFillingUpAfterDisablingOOO to run on i386 Signed-off-by: Ganesh Vernekar --- tsdb/db_test.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tsdb/db_test.go b/tsdb/db_test.go index 18e969f952..d87412d76c 100644 --- a/tsdb/db_test.go +++ b/tsdb/db_test.go @@ -8324,23 +8324,28 @@ func testDiskFillingUpAfterDisablingOOO(t *testing.T, scenario sampleTypeScenari opts := DefaultOptions() opts.OutOfOrderTimeWindow = 60 * time.Minute.Milliseconds() + // Use lower SamplesPerChunk and OutOfOrderCapMax so we need fewer samples + // to fill chunks, reducing the overall test time significantly + // (important for slow CI like i386 which can be 60x+ slower). + opts.SamplesPerChunk = 15 + opts.OutOfOrderCapMax = 5 db := newTestDB(t, withOpts(opts)) db.DisableCompactions() var ( - ctx = t.Context() - series1 = labels.FromStrings("foo", "bar1") - allSamples []chunks.Sample + ctx = t.Context() + series1 = labels.FromStrings("foo", "bar1") ) + // Use step of 5 minutes to reduce sample count while preserving time ranges + // needed for compaction triggers. This reduces total samples from ~411 to ~83. addSamples := func(fromMins, toMins int64) { app := appenderFn(db, ctx) - for m := fromMins; m <= toMins; m++ { + for m := fromMins; m <= toMins; m += 5 { ts := m * time.Minute.Milliseconds() - _, s, err := scenario.appendFunc(app, series1, ts, ts) + _, _, err := scenario.appendFunc(app, series1, ts, ts) require.NoError(t, err) - allSamples = append(allSamples, s) } require.NoError(t, app.Commit()) } From b494365aa7488fe46a5893689c20bdc3fcc2cdf2 Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Thu, 12 Feb 2026 18:09:21 -0800 Subject: [PATCH 11/23] Cut v3.10.0-rc.0 Signed-off-by: Ganesh Vernekar --- CHANGELOG.md | 51 ++++++++++++++++++++ VERSION | 2 +- web/ui/mantine-ui/package.json | 4 +- web/ui/module/codemirror-promql/package.json | 4 +- web/ui/module/lezer-promql/package.json | 2 +- web/ui/package-lock.json | 14 +++--- web/ui/package.json | 2 +- 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1afb0af59..bc081ef062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,56 @@ # Changelog +## 3.10.0-rc.0 / 2026-02-16 + +Prometheus now offers a distroless Docker image variant alongside the default +busybox image. The distroless variant provides enhanced security with a minimal +base image, uses UID/GID 65532 (nonroot) instead of nobody, and removes the +VOLUME declaration. Both variants are available with `-busybox` and `-distroless` +tag suffixes (e.g., `prom/prometheus:latest-busybox`, `prom/prometheus:latest-distroless`). +The busybox image remains the default with no suffix for backwards compatibility +(e.g., `prom/prometheus:latest` points to the busybox variant). + +For users migrating existing **named** volumes from the busybox image to the distroless variant, the ownership can be adjusted with: +``` +docker run --rm -v prometheus-data:/prometheus alpine chown -R 65532:65532 /prometheus +``` +Then, the container can be started with the old volume with: +``` +docker run -v prometheus-data:/prometheus prom/prometheus:latest-distroless +``` +User migrating from bind mounts might need to ajust permissions too, depending on their setup. + +- [CHANGE] Alerting: Add `alertmanager` dimension to following metrics: `prometheus_notifications_dropped_total`, `prometheus_notifications_queue_capacity`, `prometheus_notifications_queue_length`. #16355 +- [CHANGE] UI: Hide expanded alert annotations by default, enabling more information density on the `/alerts` page. #17611 +- [FEATURE] AWS SD: Add MSK Role. #17600 +- [FEATURE] PromQL: Add `fill()` / `fill_left()` / `fill_right()` binop modifiers for specifying default values for missing series. #17644 +- [FEATURE] Web: Add OpenAPI 3.2 specification for the HTTP API at `/api/v1/openapi.yaml`. #17825 +- [FEATURE] Dockerfile: Add distroless image variant using UID/GID 65532 and no VOLUME declaration. Busybox image remains default. #17876 +- [FEATURE] Web: Add on-demand wall time profiling under `/debug/pprof/fgprof`. #18027 +- [ENHANCEMENT] PromQL: Add more detail to histogram quantile monotonicity info annotations. #15578 +- [ENHANCEMENT] Alerting: Independent alertmanager sendloops. #16355 +- [ENHANCEMENT] TSDB: Experimental support for early compaction of stale series in the memory with configurable threshold `stale_series_compaction_threshold` in the config file. #16929 +- [ENHANCEMENT] Service Discovery: Service discoveries are now removable from the Prometheus binary through the Go build tag `remove_all_sd` and individual service discoveries can be re-added with the build tags `enable__sd`. Users can build a custom Prometheus with only the necessary SDs for a smaller binary size. #17736 +- [ENHANCEMENT] Promtool: Support promql syntax features `promql-duration-expr` and `promql-extended-range-selectors`. #17926 +- [PERF] PromQL: Avoid unnecessary label extraction in PromQL functions. #17676 +- [PERF] PromQL: Improve performance of regex matchers like `.*-.*-.*`. #17707 +- [PERF] OTLP: Add label caching for OTLP-to-Prometheus conversion to reduce allocations and improve latency. #17860 +- [PERF] API: Compute `/api/v1/targets/relabel_steps` in a single pass instead of re-running relabeling for each prefix. #17969 +- [PERF] tsdb: Optimize LabelValues intersection performance for matchers. #18069 +- [BUGFIX] PromQL: Prevent query strings containing only UTF-8 continuation bytes from crashing Prometheus. #17735 +- [BUGFIX] Web: Fix missing `X-Prometheus-Stopping` header for `/-/ready` endpoint in `NotReady` state. #17795 +- [BUGFIX] PromQL: Fix PromQL `info()` function returning empty results when filtering by a label that exists on both the input metric and `target_info`. #17817 +- [BUGFIX] TSDB: Fix a bug during exemplar buffer grow/shrink that could cause exemplars to be incorrectly discarded. #17863 +- [BUGFIX] UI: Fix broken graph display after page reload, due to broken Y axis min encoding/decoding. #17869 +- [BUGFIX] TSDB: Fix memory leaks in buffer pools by clearing reference fields (Labels, Histogram pointers, metadata strings) before returning buffers to pools. #17879 +- [BUGFIX] PromQL: info function: fix series without identifying labels not being returned. #17898 +- [BUGFIX] OTLP: Filter `__name__` from OTLP attributes to prevent duplicate labels. #17917 +- [BUGFIX] TSDB: Fix division by zero when computing stale series ratio with empty head. #17952 +- [BUGFIX] OTLP: Fix potential silent data loss for sum metrics. #17954 +- [BUGFIX] PromQL: Fix smoothed interpolation across counter resets. #17988 +- [BUGFIX] PromQL: Fix panic with `@` modifier on empty ranges. #18020 +- [BUGFIX] PromQL: Fix `avg_over_time` for a single native histogram. #18058 + ## 3.9.1 / 2026-01-07 - [BUGFIX] Agent: fix crash shortly after startup from invalid type of object. #17802 diff --git a/VERSION b/VERSION index 6bd10744ae..41e981e75a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.9.1 +3.10.0-rc.0 diff --git a/web/ui/mantine-ui/package.json b/web/ui/mantine-ui/package.json index 8f35318090..5cc129c3f6 100644 --- a/web/ui/mantine-ui/package.json +++ b/web/ui/mantine-ui/package.json @@ -1,7 +1,7 @@ { "name": "@prometheus-io/mantine-ui", "private": true, - "version": "0.309.1", + "version": "0.310.0-rc.0", "type": "module", "scripts": { "start": "vite", @@ -28,7 +28,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.309.1", + "@prometheus-io/codemirror-promql": "0.310.0-rc.0", "@reduxjs/toolkit": "^2.11.2", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.20", diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index 5208513eab..2f304fa21b 100644 --- a/web/ui/module/codemirror-promql/package.json +++ b/web/ui/module/codemirror-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/codemirror-promql", - "version": "0.309.1", + "version": "0.310.0-rc.0", "description": "a CodeMirror mode for the PromQL language", "types": "dist/esm/index.d.ts", "module": "dist/esm/index.js", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md", "dependencies": { - "@prometheus-io/lezer-promql": "0.309.1", + "@prometheus-io/lezer-promql": "0.310.0-rc.0", "lru-cache": "^11.2.5" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index 7a969b57e4..95ace2e254 100644 --- a/web/ui/module/lezer-promql/package.json +++ b/web/ui/module/lezer-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/lezer-promql", - "version": "0.309.1", + "version": "0.310.0-rc.0", "description": "lezer-based PromQL grammar", "main": "dist/index.cjs", "type": "module", diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 7669399b66..54a2bd8ba2 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.309.1", + "version": "0.310.0-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.309.1", + "version": "0.310.0-rc.0", "workspaces": [ "mantine-ui", "module/*" @@ -24,7 +24,7 @@ }, "mantine-ui": { "name": "@prometheus-io/mantine-ui", - "version": "0.309.1", + "version": "0.310.0-rc.0", "dependencies": { "@codemirror/autocomplete": "^6.20.0", "@codemirror/language": "^6.12.1", @@ -42,7 +42,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.309.1", + "@prometheus-io/codemirror-promql": "0.310.0-rc.0", "@reduxjs/toolkit": "^2.11.2", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.20", @@ -172,10 +172,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.309.1", + "version": "0.310.0-rc.0", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.309.1", + "@prometheus-io/lezer-promql": "0.310.0-rc.0", "lru-cache": "^11.2.5" }, "devDependencies": { @@ -205,7 +205,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.309.1", + "version": "0.310.0-rc.0", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.8.0", diff --git a/web/ui/package.json b/web/ui/package.json index 172e646aeb..1890e74b17 100644 --- a/web/ui/package.json +++ b/web/ui/package.json @@ -1,7 +1,7 @@ { "name": "prometheus-io", "description": "Monorepo for the Prometheus UI", - "version": "0.309.1", + "version": "0.310.0-rc.0", "private": true, "scripts": { "build": "bash build_ui.sh --all", From debc951f2abb38d34f01639f654fcddf776d3a11 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:24:29 +0100 Subject: [PATCH 12/23] chore: Exclude riskv64 from distroless images Upstream distroless does not support riskv64 yet Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- Makefile | 1 + Makefile.common | 72 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index ad4b90f020..f927649cb5 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ # Needs to be defined before including Makefile.common to auto-generate targets DOCKER_ARCHS ?= amd64 armv7 arm64 ppc64le riscv64 s390x +DOCKERFILE_ARCH_EXCLUSIONS ?= Dockerfile.distroless:riscv64 UI_PATH = web/ui UI_NODE_MODULES_PATH = $(UI_PATH)/node_modules diff --git a/Makefile.common b/Makefile.common index 38a058c1e7..61b7d15390 100644 --- a/Makefile.common +++ b/Makefile.common @@ -109,6 +109,14 @@ endif # Build variant:dockerfile pairs for shell iteration. DOCKERFILE_VARIANTS_WITH_NAMES := $(foreach df,$(DOCKERFILE_VARIANTS),$(call dockerfile_variant,$(df)):$(df)) +# Shell helper to check whether a dockerfile/arch pair is excluded. +define dockerfile_arch_is_excluded +case " $(DOCKERFILE_ARCH_EXCLUSIONS) " in \ + *" $$dockerfile:$(1) "*) true ;; \ + *) false ;; \ +esac +endef + BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) @@ -250,6 +258,10 @@ $(BUILD_DOCKER_ARCHS): common-docker-%: @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ dockerfile=$${variant#*:}; \ variant_name=$${variant%%:*}; \ + if $(call dockerfile_arch_is_excluded,$*); then \ + echo "Skipping $$variant_name variant for linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ distroless_arch="$*"; \ if [ "$*" = "armv7" ]; then \ distroless_arch="arm"; \ @@ -284,6 +296,10 @@ $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ dockerfile=$${variant#*:}; \ variant_name=$${variant%%:*}; \ + if $(call dockerfile_arch_is_excluded,$*); then \ + echo "Skipping push for $$variant_name variant on linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ echo "Pushing $$variant_name variant for linux-$*"; \ docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ @@ -311,6 +327,10 @@ $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: @for variant in $(DOCKERFILE_VARIANTS_WITH_NAMES); do \ dockerfile=$${variant#*:}; \ variant_name=$${variant%%:*}; \ + if $(call dockerfile_arch_is_excluded,$*); then \ + echo "Skipping tag for $$variant_name variant on linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ echo "Tagging $$variant_name variant for linux-$* as latest"; \ docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest-$$variant_name"; \ @@ -330,23 +350,67 @@ common-docker-manifest: variant_name=$${variant%%:*}; \ if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ echo "Creating manifest for $$variant_name variant"; \ - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name); \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + if $(call dockerfile_arch_is_excluded,$$arch); then \ + continue; \ + fi; \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping manifest for $$variant_name variant (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" $$refs; \ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ fi; \ if [ "$$dockerfile" = "Dockerfile" ]; then \ echo "Creating default variant ($$variant_name) manifest"; \ - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)); \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + if $(call dockerfile_arch_is_excluded,$$arch); then \ + continue; \ + fi; \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping default variant manifest (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $$refs; \ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)"; \ fi; \ if [ "$(DOCKER_IMAGE_TAG)" = "latest" ]; then \ if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ echo "Creating manifest for $$variant_name variant version tag"; \ - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name); \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + if $(call dockerfile_arch_is_excluded,$$arch); then \ + continue; \ + fi; \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping version-tag manifest for $$variant_name variant (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name" $$refs; \ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ fi; \ if [ "$$dockerfile" = "Dockerfile" ]; then \ echo "Creating default variant version tag manifest"; \ - DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):v$(DOCKER_MAJOR_VERSION_TAG)); \ + refs=""; \ + for arch in $(DOCKER_ARCHS); do \ + if $(call dockerfile_arch_is_excluded,$$arch); then \ + continue; \ + fi; \ + refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)"; \ + done; \ + if [ -z "$$refs" ]; then \ + echo "Skipping default variant version-tag manifest (no supported architectures)"; \ + continue; \ + fi; \ + DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)" $$refs; \ DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):v$(DOCKER_MAJOR_VERSION_TAG)"; \ fi; \ fi; \ From decde0b3648ad9ececb868a8b7a3e298db0cbf4b Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Thu, 19 Feb 2026 15:22:03 -0800 Subject: [PATCH 13/23] Cut v3.10.0-rc.1 Signed-off-by: Ganesh Vernekar --- CHANGELOG.md | 2 +- VERSION | 2 +- web/ui/mantine-ui/package.json | 4 ++-- web/ui/module/codemirror-promql/package.json | 4 ++-- web/ui/module/lezer-promql/package.json | 2 +- web/ui/package-lock.json | 14 +++++++------- web/ui/package.json | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc081ef062..e448d9b595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 3.10.0-rc.0 / 2026-02-16 +## 3.10.0-rc.1 / 2026-02-19 Prometheus now offers a distroless Docker image variant alongside the default busybox image. The distroless variant provides enhanced security with a minimal diff --git a/VERSION b/VERSION index 41e981e75a..62a2b17219 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.10.0-rc.0 +3.10.0-rc.1 diff --git a/web/ui/mantine-ui/package.json b/web/ui/mantine-ui/package.json index 5cc129c3f6..3d4dcf7040 100644 --- a/web/ui/mantine-ui/package.json +++ b/web/ui/mantine-ui/package.json @@ -1,7 +1,7 @@ { "name": "@prometheus-io/mantine-ui", "private": true, - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "type": "module", "scripts": { "start": "vite", @@ -28,7 +28,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.310.0-rc.0", + "@prometheus-io/codemirror-promql": "0.310.0-rc.1", "@reduxjs/toolkit": "^2.11.2", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.20", diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index 2f304fa21b..31a13a4624 100644 --- a/web/ui/module/codemirror-promql/package.json +++ b/web/ui/module/codemirror-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/codemirror-promql", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "description": "a CodeMirror mode for the PromQL language", "types": "dist/esm/index.d.ts", "module": "dist/esm/index.js", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md", "dependencies": { - "@prometheus-io/lezer-promql": "0.310.0-rc.0", + "@prometheus-io/lezer-promql": "0.310.0-rc.1", "lru-cache": "^11.2.5" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index 95ace2e254..94fcf55678 100644 --- a/web/ui/module/lezer-promql/package.json +++ b/web/ui/module/lezer-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/lezer-promql", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "description": "lezer-based PromQL grammar", "main": "dist/index.cjs", "type": "module", diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 54a2bd8ba2..655dbc27fc 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "workspaces": [ "mantine-ui", "module/*" @@ -24,7 +24,7 @@ }, "mantine-ui": { "name": "@prometheus-io/mantine-ui", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "dependencies": { "@codemirror/autocomplete": "^6.20.0", "@codemirror/language": "^6.12.1", @@ -42,7 +42,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.310.0-rc.0", + "@prometheus-io/codemirror-promql": "0.310.0-rc.1", "@reduxjs/toolkit": "^2.11.2", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.20", @@ -172,10 +172,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.310.0-rc.0", + "@prometheus-io/lezer-promql": "0.310.0-rc.1", "lru-cache": "^11.2.5" }, "devDependencies": { @@ -205,7 +205,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.8.0", diff --git a/web/ui/package.json b/web/ui/package.json index 1890e74b17..95edf947fd 100644 --- a/web/ui/package.json +++ b/web/ui/package.json @@ -1,7 +1,7 @@ { "name": "prometheus-io", "description": "Monorepo for the Prometheus UI", - "version": "0.310.0-rc.0", + "version": "0.310.0-rc.1", "private": true, "scripts": { "build": "bash build_ui.sh --all", From 6acbd1aa42131c7fcd22b84dc05cca9a1995fbb8 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:32:11 +0100 Subject: [PATCH 14/23] chore(ci): Add registry-specific architecture exclusions Fixes #18123 Introduces DOCKER_REGISTRY_ARCH_EXCLUSIONS to exclude specific architectures from specific registries. This allows riscv64 to be excluded from quay.io (which returns unauthorized) while still supporting it on docker.io. The new registry_arch_is_excluded function extracts the registry from DOCKER_REPO and checks if registry:arch is in the exclusion list. This is applied during push, tag, and manifest creation steps. This fix ensures s390x and other architectures are included in quay.io manifests even when riscv64 fails to push. Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- Makefile | 1 + Makefile.common | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Makefile b/Makefile index f927649cb5..59f0a71259 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ # Needs to be defined before including Makefile.common to auto-generate targets DOCKER_ARCHS ?= amd64 armv7 arm64 ppc64le riscv64 s390x DOCKERFILE_ARCH_EXCLUSIONS ?= Dockerfile.distroless:riscv64 +DOCKER_REGISTRY_ARCH_EXCLUSIONS ?= quay.io:riscv64 UI_PATH = web/ui UI_NODE_MODULES_PATH = $(UI_PATH)/node_modules diff --git a/Makefile.common b/Makefile.common index 61b7d15390..f97c518dcb 100644 --- a/Makefile.common +++ b/Makefile.common @@ -117,6 +117,16 @@ case " $(DOCKERFILE_ARCH_EXCLUSIONS) " in \ esac endef +# Shell helper to check whether a registry/arch pair is excluded. +# Extracts registry from DOCKER_REPO (e.g., quay.io/prometheus -> quay.io) +define registry_arch_is_excluded +registry=$$(echo "$(DOCKER_REPO)" | cut -d'/' -f1); \ +case " $(DOCKER_REGISTRY_ARCH_EXCLUSIONS) " in \ + *" $$registry:$(1) "*) true ;; \ + *) false ;; \ +esac +endef + BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) @@ -300,6 +310,10 @@ $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: echo "Skipping push for $$variant_name variant on linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ continue; \ fi; \ + if $(call registry_arch_is_excluded,$*); then \ + echo "Skipping push for $$variant_name variant on linux-$* to $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ echo "Pushing $$variant_name variant for linux-$*"; \ docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ @@ -331,6 +345,10 @@ $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: echo "Skipping tag for $$variant_name variant on linux-$* (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ continue; \ fi; \ + if $(call registry_arch_is_excluded,$*); then \ + echo "Skipping tag for $$variant_name variant on linux-$* for $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ if [ "$$dockerfile" != "Dockerfile" ] || [ "$$variant_name" != "default" ]; then \ echo "Tagging $$variant_name variant for linux-$* as latest"; \ docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest-$$variant_name"; \ @@ -353,6 +371,11 @@ common-docker-manifest: refs=""; \ for arch in $(DOCKER_ARCHS); do \ if $(call dockerfile_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for $$variant_name (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ + if $(call registry_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for $$variant_name on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \ continue; \ fi; \ refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)-$$variant_name"; \ @@ -369,6 +392,11 @@ common-docker-manifest: refs=""; \ for arch in $(DOCKER_ARCHS); do \ if $(call dockerfile_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for default variant (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ + if $(call registry_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for default variant on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \ continue; \ fi; \ refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:$(SANITIZED_DOCKER_IMAGE_TAG)"; \ @@ -386,6 +414,11 @@ common-docker-manifest: refs=""; \ for arch in $(DOCKER_ARCHS); do \ if $(call dockerfile_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for $$variant_name version tag (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ + if $(call registry_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for $$variant_name version tag on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \ continue; \ fi; \ refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)-$$variant_name"; \ @@ -402,6 +435,11 @@ common-docker-manifest: refs=""; \ for arch in $(DOCKER_ARCHS); do \ if $(call dockerfile_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for default variant version tag (excluded by DOCKERFILE_ARCH_EXCLUSIONS)"; \ + continue; \ + fi; \ + if $(call registry_arch_is_excluded,$$arch); then \ + echo " Skipping $$arch for default variant version tag on $(DOCKER_REPO) (excluded by DOCKER_REGISTRY_ARCH_EXCLUSIONS)"; \ continue; \ fi; \ refs="$$refs $(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$$arch:v$(DOCKER_MAJOR_VERSION_TAG)"; \ From d4b00499e8cf3bd1ca770202ef285c8b3e7d784a Mon Sep 17 00:00:00 2001 From: Craig Ringer Date: Wed, 25 Feb 2026 12:12:28 +1300 Subject: [PATCH 15/23] Add traceID to query logs The query log embeds a spanID, but omits the traceID, making log/trace correlation significantly more difficult. Add the trace ID as well. This might be better done with the otelslog wrapper in https://github.com/go-slog/otelslog but this change is more minimal. This does not add trace and span IDs to other logging emitted to Prometheus's standard logger during the processing of activities in which traces may be active. Fixes #18188 Signed-off-by: Craig Ringer --- promql/engine.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/promql/engine.go b/promql/engine.go index a769310e46..3505102b9b 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -700,7 +700,11 @@ func (ng *Engine) exec(ctx context.Context, q *query) (v parser.Value, ws annota } f = append(f, slog.Any("stats", stats.NewQueryStats(q.Stats()))) if span := trace.SpanFromContext(ctx); span != nil { - f = append(f, slog.Any("spanID", span.SpanContext().SpanID())) + spanCtx := span.SpanContext() + f = append(f, + slog.Any("spanID", spanCtx.SpanID()), + slog.Any("traceID", spanCtx.TraceID()), + ) } if origin := ctx.Value(QueryOrigin{}); origin != nil { for k, v := range origin.(map[string]any) { From c023066ca9a1045d5b4e4bdde19708052bd02edd Mon Sep 17 00:00:00 2001 From: Pranshu Srivastava Date: Wed, 25 Feb 2026 18:42:06 +0530 Subject: [PATCH 16/23] discovery: Introduce `prometheus_sd_last_update_timestamp_seconds` The metric tracks the last update sent to SD consumers, and includes the manager name. This allows for monitoring SD state based on far ago its last heartbeat was. Signed-off-by: Pranshu Srivastava --- discovery/manager.go | 1 + discovery/manager_test.go | 27 +++++++++++++++++++++++++++ discovery/metrics.go | 12 ++++++++++++ 3 files changed, 40 insertions(+) diff --git a/discovery/manager.go b/discovery/manager.go index 3f2b2db652..9994e0bf74 100644 --- a/discovery/manager.go +++ b/discovery/manager.go @@ -475,6 +475,7 @@ func (m *Manager) allGroups() map[string][]*targetgroup.Group { for setName, v := range n { m.metrics.DiscoveredTargets.WithLabelValues(setName).Set(float64(v)) + m.metrics.LastUpdated.WithLabelValues(setName).SetToCurrentTime() } return tSets diff --git a/discovery/manager_test.go b/discovery/manager_test.go index 8a49005100..f21ab2574a 100644 --- a/discovery/manager_test.go +++ b/discovery/manager_test.go @@ -1595,3 +1595,30 @@ func TestConfigReloadAndShutdownRace(t *testing.T) { cancel() wgBg.Wait() } + +func TestGaugeLastUpdateTimestamp(t *testing.T) { + ctx := t.Context() + + reg := prometheus.NewRegistry() + _, sdMetrics := NewTestMetrics(t, reg) + + discoveryManager := NewManager(ctx, promslog.NewNopLogger(), reg, sdMetrics) + require.NotNil(t, discoveryManager) + discoveryManager.updatert = 100 * time.Millisecond + go discoveryManager.Run() + + c := map[string]Configs{ + "prometheus": { + staticConfig("foo:9090"), + }, + } + discoveryManager.ApplyConfig(c) + + before := time.Now() + <-discoveryManager.SyncCh() + after := time.Now() + + ts := client_testutil.ToFloat64(discoveryManager.metrics.LastUpdated.WithLabelValues("prometheus")) + require.GreaterOrEqual(t, ts, float64(before.UnixNano())/1e9, "last update timestamp should be >= time before sync") + require.LessOrEqual(t, ts, float64(after.UnixNano())/1e9, "last update timestamp should be <= time after sync") +} diff --git a/discovery/metrics.go b/discovery/metrics.go index 2a3734fb2d..3eefd3d0bb 100644 --- a/discovery/metrics.go +++ b/discovery/metrics.go @@ -26,6 +26,7 @@ type Metrics struct { ReceivedUpdates prometheus.Counter DelayedUpdates prometheus.Counter SentUpdates prometheus.Counter + LastUpdated *prometheus.GaugeVec } func NewManagerMetrics(registerer prometheus.Registerer, sdManagerName string) (*Metrics, error) { @@ -72,12 +73,22 @@ func NewManagerMetrics(registerer prometheus.Registerer, sdManagerName string) ( }, ) + m.LastUpdated = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "prometheus_sd_last_update_timestamp_seconds", + Help: "Timestamp of the last update sent to the SD consumers.", + ConstLabels: prometheus.Labels{"name": sdManagerName}, + }, + []string{"config"}, + ) + metrics := []prometheus.Collector{ m.FailedConfigs, m.DiscoveredTargets, m.ReceivedUpdates, m.DelayedUpdates, m.SentUpdates, + m.LastUpdated, } for _, collector := range metrics { @@ -97,4 +108,5 @@ func (m *Metrics) Unregister(registerer prometheus.Registerer) { registerer.Unregister(m.ReceivedUpdates) registerer.Unregister(m.DelayedUpdates) registerer.Unregister(m.SentUpdates) + registerer.Unregister(m.LastUpdated) } From 54e010926b0a270cadb22be1113ad45fe9bcb90a Mon Sep 17 00:00:00 2001 From: Ganesh Vernekar Date: Wed, 25 Feb 2026 10:54:13 -0800 Subject: [PATCH 17/23] Cut v3.10.0 final release (#18184) Signed-off-by: Ganesh Vernekar --- CHANGELOG.md | 2 +- VERSION | 2 +- web/ui/mantine-ui/package.json | 4 ++-- web/ui/module/codemirror-promql/package.json | 4 ++-- web/ui/module/lezer-promql/package.json | 2 +- web/ui/package-lock.json | 14 +++++++------- web/ui/package.json | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e448d9b595..eabcf6d9fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 3.10.0-rc.1 / 2026-02-19 +## 3.10.0 / 2026-02-24 Prometheus now offers a distroless Docker image variant alongside the default busybox image. The distroless variant provides enhanced security with a minimal diff --git a/VERSION b/VERSION index 62a2b17219..30291cba22 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.10.0-rc.1 +3.10.0 diff --git a/web/ui/mantine-ui/package.json b/web/ui/mantine-ui/package.json index 3d4dcf7040..fd57c6f088 100644 --- a/web/ui/mantine-ui/package.json +++ b/web/ui/mantine-ui/package.json @@ -1,7 +1,7 @@ { "name": "@prometheus-io/mantine-ui", "private": true, - "version": "0.310.0-rc.1", + "version": "0.310.0", "type": "module", "scripts": { "start": "vite", @@ -28,7 +28,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.310.0-rc.1", + "@prometheus-io/codemirror-promql": "0.310.0", "@reduxjs/toolkit": "^2.11.2", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.20", diff --git a/web/ui/module/codemirror-promql/package.json b/web/ui/module/codemirror-promql/package.json index 31a13a4624..cbc0589469 100644 --- a/web/ui/module/codemirror-promql/package.json +++ b/web/ui/module/codemirror-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/codemirror-promql", - "version": "0.310.0-rc.1", + "version": "0.310.0", "description": "a CodeMirror mode for the PromQL language", "types": "dist/esm/index.d.ts", "module": "dist/esm/index.js", @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/prometheus/prometheus/blob/main/web/ui/module/codemirror-promql/README.md", "dependencies": { - "@prometheus-io/lezer-promql": "0.310.0-rc.1", + "@prometheus-io/lezer-promql": "0.310.0", "lru-cache": "^11.2.5" }, "devDependencies": { diff --git a/web/ui/module/lezer-promql/package.json b/web/ui/module/lezer-promql/package.json index 94fcf55678..5666e0bbd2 100644 --- a/web/ui/module/lezer-promql/package.json +++ b/web/ui/module/lezer-promql/package.json @@ -1,6 +1,6 @@ { "name": "@prometheus-io/lezer-promql", - "version": "0.310.0-rc.1", + "version": "0.310.0", "description": "lezer-based PromQL grammar", "main": "dist/index.cjs", "type": "module", diff --git a/web/ui/package-lock.json b/web/ui/package-lock.json index 655dbc27fc..a571c48405 100644 --- a/web/ui/package-lock.json +++ b/web/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "prometheus-io", - "version": "0.310.0-rc.1", + "version": "0.310.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prometheus-io", - "version": "0.310.0-rc.1", + "version": "0.310.0", "workspaces": [ "mantine-ui", "module/*" @@ -24,7 +24,7 @@ }, "mantine-ui": { "name": "@prometheus-io/mantine-ui", - "version": "0.310.0-rc.1", + "version": "0.310.0", "dependencies": { "@codemirror/autocomplete": "^6.20.0", "@codemirror/language": "^6.12.1", @@ -42,7 +42,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@nexucis/fuzzy": "^0.5.1", "@nexucis/kvsearch": "^0.9.1", - "@prometheus-io/codemirror-promql": "0.310.0-rc.1", + "@prometheus-io/codemirror-promql": "0.310.0", "@reduxjs/toolkit": "^2.11.2", "@tabler/icons-react": "^3.36.1", "@tanstack/react-query": "^5.90.20", @@ -172,10 +172,10 @@ }, "module/codemirror-promql": { "name": "@prometheus-io/codemirror-promql", - "version": "0.310.0-rc.1", + "version": "0.310.0", "license": "Apache-2.0", "dependencies": { - "@prometheus-io/lezer-promql": "0.310.0-rc.1", + "@prometheus-io/lezer-promql": "0.310.0", "lru-cache": "^11.2.5" }, "devDependencies": { @@ -205,7 +205,7 @@ }, "module/lezer-promql": { "name": "@prometheus-io/lezer-promql", - "version": "0.310.0-rc.1", + "version": "0.310.0", "license": "Apache-2.0", "devDependencies": { "@lezer/generator": "^1.8.0", diff --git a/web/ui/package.json b/web/ui/package.json index 95edf947fd..5b71a58550 100644 --- a/web/ui/package.json +++ b/web/ui/package.json @@ -1,7 +1,7 @@ { "name": "prometheus-io", "description": "Monorepo for the Prometheus UI", - "version": "0.310.0-rc.1", + "version": "0.310.0", "private": true, "scripts": { "build": "bash build_ui.sh --all", From 158647fb45216d0ee1b9e84aaeab30e413124286 Mon Sep 17 00:00:00 2001 From: Craig Ringer Date: Wed, 25 Feb 2026 13:13:59 +1300 Subject: [PATCH 18/23] Document traceID / spanID injection in query log Signed-off-by: Craig Ringer --- docs/configuration/configuration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/configuration/configuration.md b/docs/configuration/configuration.md index 060319ae92..1525269441 100644 --- a/docs/configuration/configuration.md +++ b/docs/configuration/configuration.md @@ -3746,3 +3746,6 @@ headers: tls_config: [ ] ``` + +If query logging and tracing are both enabled, a traceID and spanID will be injected +into the query log file for use in log/trace correlation. From 5600de6107d8c2dadb210b8536df3bc6f0ed55d0 Mon Sep 17 00:00:00 2001 From: Mohammad Varmazyar Date: Thu, 26 Feb 2026 11:42:39 +0100 Subject: [PATCH 19/23] Add Consul SD maintainer (#18193) Following the discussion in #17349, adding myself as maintainer for Consul service discovery. Signed-off-by: Mohammad Varmazyar --- CODEOWNERS | 1 + MAINTAINERS.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 2c5dedbffa..d09cbfe3c9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -22,6 +22,7 @@ /discovery/kubernetes @prometheus/default-maintainers @brancz /discovery/stackit @prometheus/default-maintainers @jkroepke /discovery/aws/ @prometheus/default-maintainers @matt-gp @sysadmind +/discovery/consul @prometheus/default-maintainers @mrvarmazyar # Pending # https://github.com/prometheus/prometheus/pull/15212#issuecomment-3575225179 # /discovery/aliyun @prometheus/default-maintainers @KeyOfSpectator diff --git a/MAINTAINERS.md b/MAINTAINERS.md index ae61059af5..213fd62df0 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -14,6 +14,7 @@ Maintainers for specific parts of the codebase: * `discovery` * `k8s`: Frederic Branczyk ( / @brancz) * `stackit`: Jan-Otto Kröpke ( / @jkroepke) + * `consul`: Mohammad Varmazyar ( / @mrvarmazyar) * `documentation` * `prometheus-mixin`: Matthias Loibl ( / @metalmatze) * `storage` From 8e118dd2b72dc3a192371a93c9225c48f3b0469d Mon Sep 17 00:00:00 2001 From: bwplotka Date: Thu, 26 Feb 2026 15:39:24 +0000 Subject: [PATCH 20/23] Move bwplotka to general maintainer Signed-off-by: bwplotka --- MAINTAINERS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 213fd62df0..d099221eb9 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -7,6 +7,7 @@ General maintainers: * Ayoub Mrini (ayoubmrini424@gmail.com / @machine424) * Julien Pivotto (roidelapluie@prometheus.io / @roidelapluie) * György Krajcsovits ( / @krajorama) +* Bartłomiej Płotka ( / @bwplotka) Maintainers for specific parts of the codebase: * `cmd` @@ -18,9 +19,9 @@ Maintainers for specific parts of the codebase: * `documentation` * `prometheus-mixin`: Matthias Loibl ( / @metalmatze) * `storage` - * `remote`: Callum Styan ( / @cstyan), Bartłomiej Płotka ( / @bwplotka), Tom Wilkie (tom.wilkie@gmail.com / @tomwilkie), Alex Greenbank ( / @alexgreenbank) + * `remote`: Callum Styan ( / @cstyan), Tom Wilkie (tom.wilkie@gmail.com / @tomwilkie), Alex Greenbank ( / @alexgreenbank) * `otlptranslator`: Arthur Silva Sens ( / @ArthurSens), Arve Knudsen ( / @aknuds1), Jesús Vázquez ( / @jesusvazquez) -* `tsdb`: Ganesh Vernekar ( / @codesome), Bartłomiej Płotka ( / @bwplotka), Jesús Vázquez ( / @jesusvazquez) +* `tsdb`: Ganesh Vernekar ( / @codesome), Jesús Vázquez ( / @jesusvazquez) * `web` * `ui`: Julius Volz ( / @juliusv) * `module`: Augustin Husson ( / @nexucis) From 2724d67761c18a80248b28a4ae13d6f6016c2824 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:17:17 +0100 Subject: [PATCH 21/23] fix(deps): update module github.com/pb33f/libopenapi-validator to v0.13.0 (#18141) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index ade1155fa6..860ac1bea4 100644 --- a/go.mod +++ b/go.mod @@ -57,8 +57,8 @@ require ( github.com/oklog/ulid/v2 v2.1.1 github.com/open-telemetry/opentelemetry-collector-contrib/processor/deltatocumulativeprocessor v0.145.0 github.com/ovh/go-ovh v1.9.0 - github.com/pb33f/libopenapi v0.33.4 - github.com/pb33f/libopenapi-validator v0.11.1 + github.com/pb33f/libopenapi v0.34.0 + github.com/pb33f/libopenapi-validator v0.13.0 github.com/prometheus/alertmanager v0.31.1 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang/exp v0.0.0-20260108101519-fb0838f53562 @@ -123,7 +123,7 @@ require ( github.com/go-openapi/swag/typeutils v0.25.4 // indirect github.com/go-openapi/swag/yamlutils v0.25.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pb33f/jsonpath v0.7.1 // indirect + github.com/pb33f/jsonpath v0.8.1 // indirect github.com/pb33f/ordered-map/v2 v2.3.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sirupsen/logrus v1.9.4 // indirect @@ -162,7 +162,7 @@ require ( github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect diff --git a/go.sum b/go.sum index b899a3e09e..74b1217d90 100644 --- a/go.sum +++ b/go.sum @@ -156,8 +156,8 @@ github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -467,12 +467,12 @@ github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pb33f/jsonpath v0.7.1 h1:dEp6oIZuJbpDSyuHAl9m7GonoDW4M20BcD5vT0tPYRE= -github.com/pb33f/jsonpath v0.7.1/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= -github.com/pb33f/libopenapi v0.33.4 h1:Rgczgrg4VQKXW/NtSj/nApmtYKS+TVpLgTsG692JxmE= -github.com/pb33f/libopenapi v0.33.4/go.mod h1:e/dmd2Pf1nkjqkI0r7guFSyt9T5V0IIQKgs0L6B/3b0= -github.com/pb33f/libopenapi-validator v0.11.1 h1:lTW738oB3lwpS9poDzmI3jpTPZSb5W46vklZqtyf7+Q= -github.com/pb33f/libopenapi-validator v0.11.1/go.mod h1:7CfboslU/utKhiuQRuenriGYZ+HQLDOvARxjqRwd57w= +github.com/pb33f/jsonpath v0.8.1 h1:84C6QRyx6HcSm6PZnsMpcqYot3IsZ+m0n95+0NbBbvs= +github.com/pb33f/jsonpath v0.8.1/go.mod h1:zBV5LJW4OQOPatmQE2QdKpGQJvhDTlE5IEj6ASaRNTo= +github.com/pb33f/libopenapi v0.34.0 h1:jY8pf4yBHRObnNBrjuVDhVpgKjSUE8hLFpeoYtyQ/eo= +github.com/pb33f/libopenapi v0.34.0/go.mod h1:YOP20KzYe3mhE5301aQzJtzQ9MnvhABBGO7RMttA4V4= +github.com/pb33f/libopenapi-validator v0.13.0 h1:an3BxwklmLF4bxacudLV8Vysvw1krlAjpYoUfyJUgw8= +github.com/pb33f/libopenapi-validator v0.13.0/go.mod h1:YZQRDh+8xap/H0GM0cJsBrqqT+XLlMivA/qwqRLiidQ= github.com/pb33f/ordered-map/v2 v2.3.0 h1:k2OhVEQkhTCQMhAicQ3Z6iInzoZNQ7L9MVomwKBZ5WQ= github.com/pb33f/ordered-map/v2 v2.3.0/go.mod h1:oe5ue+6ZNhy7QN9cPZvPA23Hx0vMHnNVeMg4fGdCANw= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= From 3e5a7c09d0c617916e308168390142aff6b61a51 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Fri, 27 Feb 2026 12:55:47 +0100 Subject: [PATCH 22/23] chore(main): Fix typo SuqueryInterval to SubqueryInterval Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- cmd/prometheus/main.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index debff1f6af..76e424281b 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -1597,7 +1597,7 @@ type reloader struct { reloader func(*config.Config) error } -func reloadConfig(filename string, enableExemplarStorage bool, logger *slog.Logger, noStepSuqueryInterval *safePromQLNoStepSubqueryInterval, callback func(bool), rls ...reloader) (err error) { +func reloadConfig(filename string, enableExemplarStorage bool, logger *slog.Logger, noStepSubqueryInterval *safePromQLNoStepSubqueryInterval, callback func(bool), rls ...reloader) (err error) { start := time.Now() timingsLogger := logger logger.Info("Loading configuration file", "filename", filename) @@ -1638,8 +1638,7 @@ func reloadConfig(filename string, enableExemplarStorage bool, logger *slog.Logg } updateGoGC(conf, logger) - - noStepSuqueryInterval.Set(conf.GlobalConfig.EvaluationInterval) + noStepSubqueryInterval.Set(conf.GlobalConfig.EvaluationInterval) timingsLogger.Info("Completed loading of configuration file", "filename", filename, "totalDuration", time.Since(start)) return nil } From 3bda9a3396325bed30d0818d9bd026946a82763c Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 2 Mar 2026 20:01:14 +0800 Subject: [PATCH 23/23] promql: allow timestamp() to be wrapped as a step invariant depending on arguments (#17313) Some timestamp functions can be safely wrapped as a step invariant. Then once we do that we never need to check or unwrap at evaluation time. For instance; `timestamp(metric @ 1)` is step invariant, whereas `timestamp(abs(metric @ 1))` is not. Currently all `timestamp` `*parser.Call` are excluded from being considered step invariant since it is listed in the `AtModifierUnsafeFunctions` map. This PR adds an extra check for timestamp functions which consider the arguments, and if the argument is a simple VectorSelector then the entire `*parser.Call` can be safely wrapped as a step invariant. Signed-off-by: Andrew Hall Co-authored-by: Bryan Boreham --- promql/engine.go | 22 ++++----- promql/engine_test.go | 53 +++++++++++++++++++++ promql/promqltest/testdata/at_modifier.test | 53 +++++++++++++++++++++ 3 files changed, 117 insertions(+), 11 deletions(-) diff --git a/promql/engine.go b/promql/engine.go index 524ad807d1..cbbc27a2de 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -1965,9 +1965,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value, // Matrix evaluation always returns the evaluation time, // so this function needs special handling when given // a vector selector. - arg := unwrapStepInvariantExpr(e.Args[0]) - vs, ok := arg.(*parser.VectorSelector) - if ok { + if vs, ok := e.Args[0].(*parser.VectorSelector); ok { return ev.rangeEvalTimestampFunctionOverVectorSelector(ctx, vs, call, e) } } @@ -4219,13 +4217,6 @@ func unwrapParenExpr(e *parser.Expr) { } } -func unwrapStepInvariantExpr(e parser.Expr) parser.Expr { - if p, ok := e.(*parser.StepInvariantExpr); ok { - return p.Expr - } - return e -} - // PreprocessExpr wraps all possible step invariant parts of the given expression with // StepInvariantExpr. It also resolves the preprocessors, evaluates duration expressions // into their numeric values and removes superfluous parenthesis on parameters to functions and aggregations. @@ -4284,15 +4275,24 @@ func preprocessExprHelper(expr parser.Expr, start, end time.Time) (isStepInvaria case *parser.Call: _, ok := AtModifierUnsafeFunctions[n.Func.Name] isStepInvariant := !ok + // A special case to allow timestamp() to be wrapped in a step invariant. + // timestamp() is considered AtModifierUnsafe, but it can be safe depending on its arguments. + // ie timestamp(metric @ 1) is step invariant, but timestamp(abs(metric @ 1)) is not. + isTimestampWithAllArgsStepInvariantSafe := n.Func.Name == "timestamp" shouldWrap := make([]bool, len(n.Args)) for i := range n.Args { unwrapParenExpr(&n.Args[i]) var argIsStepInvariant bool argIsStepInvariant, shouldWrap[i] = preprocessExprHelper(n.Args[i], start, end) isStepInvariant = isStepInvariant && argIsStepInvariant + + _, argIsVectorSelector := n.Args[i].(*parser.VectorSelector) + if !argIsStepInvariant || !argIsVectorSelector { + isTimestampWithAllArgsStepInvariantSafe = false + } } - if isStepInvariant { + if isStepInvariant || isTimestampWithAllArgsStepInvariantSafe { // The function and all arguments are step invariant. return true, true } diff --git a/promql/engine_test.go b/promql/engine_test.go index 5dfffd7cc7..739818d7c1 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -3252,6 +3252,59 @@ func TestPreprocessAndWrapWithStepInvariantExpr(t *testing.T) { }, }, }, + { + input: "timestamp(metric @ 10)", + expected: &parser.StepInvariantExpr{ + Expr: &parser.Call{ + Func: parser.MustGetFunction("timestamp"), + Args: parser.Expressions{ + &parser.VectorSelector{ + Name: "metric", + Timestamp: makeInt64Pointer(10000), + LabelMatchers: []*labels.Matcher{ + parser.MustLabelMatcher(labels.MatchEqual, "__name__", "metric"), + }, + PosRange: posrange.PositionRange{ + Start: 10, + End: 21, + }, + }, + }, + PosRange: posrange.PositionRange{Start: 0, End: 22}, + }, + }, + }, + { + input: "timestamp(abs(metric @ 10))", + expected: &parser.Call{ + Func: parser.MustGetFunction("timestamp"), + Args: parser.Expressions{ + &parser.StepInvariantExpr{ + Expr: &parser.Call{ + Func: parser.MustGetFunction("abs"), + Args: parser.Expressions{ + &parser.VectorSelector{ + Name: "metric", + Timestamp: makeInt64Pointer(10000), + LabelMatchers: []*labels.Matcher{ + parser.MustLabelMatcher(labels.MatchEqual, "__name__", "metric"), + }, + PosRange: posrange.PositionRange{ + Start: 14, + End: 25, + }, + }, + }, + PosRange: posrange.PositionRange{ + Start: 10, + End: 26, + }, + }, + }, + }, + PosRange: posrange.PositionRange{Start: 0, End: 27}, + }, + }, } for _, test := range testCases { diff --git a/promql/promqltest/testdata/at_modifier.test b/promql/promqltest/testdata/at_modifier.test index 194c877803..65f102d514 100644 --- a/promql/promqltest/testdata/at_modifier.test +++ b/promql/promqltest/testdata/at_modifier.test @@ -255,3 +255,56 @@ eval instant at 1111111s stdvar_over_time({__name__="up"}[1h:1m] @ 1111111) eval instant at 1111111s mad_over_time({__name__="up"}[1h:1m] @ 1111111) clear + +# Additional tests specific to timestamp() and @ modifier usage. +load 10s + metric 0+1x10 + metric_missing 0 _ 2 _ 4 _ 5 _ 6 + +# Return a vector where each sample is set to the metric value at T=11. +# Since T=11 falls within the [10s,20s) scrap window the sample value at T=10s is returned. +eval range from 0 to 60s step 10s metric @ 11 + {__name__="metric"} 1 1 1 1 1 1 1 + +eval range from 0 to 60s step 10s abs(metric @ 11) + {} 1 1 1 1 1 1 1 + +# Return a vector where each sample's value is set to the timestamp of each sample in the given metric series +eval range from 0 to 60s step 10s timestamp(metric) + {} 0 10 20 30 40 50 60 + +# Return a vector where each sample's value is set to the timestamp for the metric's sample used at T=11s. +# The result is 10 since the metric at T=11s falls within the [10s,20s) scrape window. +# The result is the timestamp of the sample at T=10s +eval range from 0 to 60s step 10s timestamp(metric @ 11) + {} 10 10 10 10 10 10 10 + +# As above - illustrating the sample used at the upper end of the [10s,20s) scrape window. +eval range from 0 to 60s step 10s timestamp(metric @ 19) + {} 10 10 10 10 10 10 10 + +# As above - illustrating the transition to the next scrap window. +eval range from 0 to 60s step 10s timestamp(metric @ 20) + {} 20 20 20 20 20 20 20 + +eval range from 0 to 60s step 10s timestamp(metric_missing @ 0) + {} 0 0 0 0 0 0 0 + +# The timestamp of 0 is returned since the sample is missing from the [10s,20s) scrape window. +# As such, the previous sample from T=0s is returned. +eval range from 0 to 60s step 10s timestamp(metric_missing @ 10) + {} 0 0 0 0 0 0 0 + +eval range from 0 to 60s step 10s timestamp(metric_missing @ 20) + {} 20 20 20 20 20 20 20 + +# The timestamps for each step are returned since abs() returns a new vector with new [T,V] samples. +# Each sample in this vector has its value set to the absolute value of the sample value at T=10s, and its timestamp aligned to the step interval. +# This is unlike the above tests where timestamp() is operating on a vector with the original series samples. +eval range from 0 to 60s step 10s timestamp(abs(metric @ 11)) + {} 0 10 20 30 40 50 60 + +eval range from 0 to 60s step 10s timestamp(abs(metric_missing @ 11)) + {} 0 10 20 30 40 50 60 + +clear \ No newline at end of file