Skip to content

address counters and histograms context race and exemplars overhaul #128575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

rexagod
Copy link
Member

@rexagod rexagod commented Nov 5, 2024

What type of PR is this?

/kind bug
/kind failing-test

What this PR does / why we need it:

Which issue(s) this PR fixes:

Fixes #128548

Special notes for your reviewer:

/cc @pohly

While the patch addresses the k8s.io/component-base/metrics.(*Counter).WithContext() race, there's still some investigation that needs to be done (patch OTEL)?

WARNING: DATA RACE
Write at 0x00c016f881d0 by goroutine 835150:
  go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp.(*bodyWrapper).Read()
      /Users/rexagod/repositories/oss/kubernetes/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/wrap.go:31 +0x8c
  io.(*LimitedReader).Read()
      /Users/rexagod/repositories/oss/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.23.2.darwin-arm64/src/io/io.go:479 +0xbc
  io.ReadAll()
      /Users/rexagod/repositories/oss/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.23.2.darwin-arm64/src/io/io.go:712 +0x84
  io/ioutil.ReadAll()
      /Users/rexagod/repositories/oss/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.23.2.darwin-arm64/src/io/ioutil/ioutil.go:28 +0x1fc
  k8s.io/apiserver/pkg/endpoints/handlers.limitedReadBody()
      /Users/rexagod/repositories/oss/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go:382 +0x1ec
  k8s.io/apiserver/pkg/endpoints/handlers.limitedReadBodyWithRecordMetric()
      /Users/rexagod/repositories/oss/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go:393 +0x48
  k8s.io/apiextensions-apiserver/pkg/apiserver.(*crdHandler).serveResource.DeleteResource.func6()
      /Users/rexagod/repositories/oss/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go:80 +0x3f8
  k8s.io/apiextensions-apiserver/pkg/apiserver.(*crdHandler).ServeHTTP.InstrumentHandlerFunc.func1()
      /Users/rexagod/repositories/oss/kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/metrics/metrics.go:617 +0x184
  net/http.HandlerFunc.ServeHTTP()
      /Users/rexagod/repositories/oss/kubernetes/_output/local/go/cache/mod/golang.org/toolchain@v0.0.1-go1.23.2.darwin-arm64/src/net/http/server.go:2220 +0x48
  k8s.io/apiextensions-apiserver/pkg/apiserver.(*crdHandler).ServeHTTP.WithWaitGroup.withWaitGroup.func2()
      /Users/rexagod/repositories/oss/kubernetes/staging/src/k8s.io/apiserver/pkg/server/filters/waitgroup.go:86 +0x170
  net/http.HandlerFunc.ServeHTTP()

Does this PR introduce a user-facing change?

NONE

Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:


@k8s-ci-robot k8s-ci-robot requested a review from pohly November 5, 2024 13:46
@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. do-not-merge/invalid-commit-message Indicates that a PR should not merge because it has an invalid commit message. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. kind/bug Categorizes issue or PR as related to a bug. kind/failing-test Categorizes issue or PR as related to a consistently or frequently failing test. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. needs-priority Indicates a PR lacks a `priority/foo` label and requires one. labels Nov 5, 2024
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: rexagod

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/instrumentation Categorizes an issue or PR as relevant to SIG Instrumentation. and removed do-not-merge/needs-sig Indicates an issue or PR lacks a `sig/foo` label and requires one. labels Nov 5, 2024
@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Nov 6, 2024
@rexagod
Copy link
Member Author

rexagod commented Nov 6, 2024

/test pull-kubernetes-unit
/test pull-kubernetes-e2e-gce

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Nov 6, 2024
@rexagod rexagod marked this pull request as draft November 6, 2024 13:24
@k8s-ci-robot k8s-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 6, 2024
@rexagod rexagod force-pushed the address-counter-race branch from 571498f to 9390d0d Compare November 6, 2024 13:38
@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Nov 6, 2024
@rexagod rexagod marked this pull request as ready for review November 6, 2024 13:40
@k8s-ci-robot k8s-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Nov 6, 2024
@rexagod
Copy link
Member Author

rexagod commented Nov 6, 2024

/test pull-kubernetes-e2e-kind-ipv6

@aojea
Copy link
Member

aojea commented Nov 6, 2024

/assign @richabanker @dgrisonnet

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Mar 5, 2025
@rexagod rexagod force-pushed the address-counter-race branch from c6d7404 to fd3747f Compare March 5, 2025 22:11
@rexagod rexagod requested a review from dgrisonnet March 5, 2025 22:11
@rexagod rexagod force-pushed the address-counter-race branch from fd3747f to 55b1dbf Compare March 5, 2025 22:24
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 5, 2025
@rexagod rexagod changed the title address metrics context race and implicit AddWithExemplar address metrics context race Mar 5, 2025
@rexagod rexagod changed the title address metrics context race address counters and histograms context race Mar 5, 2025
@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Mar 9, 2025
Comment on lines 298 to 308
// WithContext is a no-op.
func (v *CounterVec) WithContext(_ context.Context) *CounterVec {
return v
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Upstream divergence started from func (v *CounterVec) WithContext onwards.

@rexagod rexagod changed the title address counters and histograms context race address counters and histograms context race and exemplars overhaul Mar 25, 2025
// Verify exemplar with different span context.
exemplarGatherAndVerify(t, registry, altTraceID, altSpanID, 1, 2*toAdd+4)

// Verify that all contextual counter calls are exclusive.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this almost feels like it should be a separate test case since its following the table-test format.

}

type exemplarHistogramVec struct {
type HistogramVecWithContextWithObserver struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this type now since we removed the CounterVecWithContext type ?

Comment on lines 484 to 487
counter := NewCounterVec(&CounterOpts{
Name: "metricvec_exemplar_test",
Help: "helpless",
}, []string{"label_a"}).WithContext(ctxForSpanCtx)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am ok with having the option to specify the context only at the Counter / Histogram level (especially since a spanID/traceID is applicable to a single function call as pointed out) as long as both are consistent. But looks like currently in this PR we are supporting specifying context for HistogramVec but not for CounterVec ?

Also, we can add the support for specifying counterVec / HistogramVec level contexts if the need arises in future?

@richabanker
Copy link
Contributor

@rexagod Any plans of merging this for 1.34? (not urgent but just bumping this up)

@rexagod rexagod force-pushed the address-counter-race branch from 32ec8d4 to cb91901 Compare July 24, 2025 15:31
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jul 24, 2025
@rexagod rexagod force-pushed the address-counter-race branch from cb91901 to efe5a81 Compare July 24, 2025 15:33
// WithContext is a no-op.
// WithContext is a no-op for now, users should still attach this to vectors for seamless future API upgrades (which rely on the context).
// This is done to keep extensions (such as exemplars) on the counter and not its derivatives.
// Note that there are no actual uses for this in the codebase except for chaining it with `WithLabelValues`, which makes no use of the context.
Copy link
Member Author

@rexagod rexagod Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could still be breaking for downstream users that rely on the earlier WithContext(...).ctx in some way or the other, albeit no such use case was found in Kubernetes itself.

I'm assuming general API stability cycles apply for that API, so we may want to push this out gradually, although I really doubt anyone will read value from this context rather than doing so just one step earlier in the chain (before passing the context).

@rexagod rexagod force-pushed the address-counter-race branch from efe5a81 to 1b5b50b Compare July 24, 2025 15:45
@k8s-ci-robot
Copy link
Contributor

@rexagod: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
pull-kubernetes-linter-hints 1b5b50b link false /test pull-kubernetes-linter-hints
pull-kubernetes-verify 1b5b50b link true /test pull-kubernetes-verify

Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

c := counterVec.WithContext(nil)

c.WithLabelValues("1", "2").Inc()
c.WithLabelValues("1", "2").(prometheus.ExemplarAdder).AddWithExemplar(100, prometheus.Labels{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just came across #89267, which essentially reinforces my concern that component owners, if needed, could extend vectors with exemplars like so. But, in doing this, they'll be directly interacting with the Prometheus client, which is discouraged, and makes sense, as it goes against the direction of the SIG post-Metrics Stability effort.

Do we want to diverge from upstream and allow component owners to extend vectors with exemplars (making context extensions ambiguous for us in the future as this would serve as the base for future additions on contextual vectors)? Not to mention, diverging from upstream may lead to similar divergence in the future, stacking up more maintenance debt for us?

This was talked about briefly on this PR a while ago, but IIRC we never reached a consensus, so this should make for a worthy agenda bullet point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is, even if we lean in the other direction, and try to add existing Add, Inc, and Observe methods over contextual metrics, we'll not be able to utilise those since there's already a portion of Prometheus API being surfaced through component-base, which goes against its design. Users will still need to rely on working with Prometheus' client directly, such as:

c.WithLabelValues("1", "2").(prometheus.ExemplarAdder).AddWithExemplar(100, prometheus.Labels{
	"exemplar_label": "42",
})

which will, for good reason, get flagged by the script linked above, and add to technical debt.

On the other hand, I'm not entirely certain we can completely address things without breaking a couple of APIs. Of course, I'll send out a PR to refactor all uses within K8s to follow the established conventions to make us future-proof, but I'm not sure if and how much we take into account out-of-tree breakages for component-base?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it is right now, we don't break any usages within the tree itself. Here's a quick rundown around what I deem to be the only breaking change (for out-of-tree users):

taking this as an example, all WithContext applications in the tree as used the same way:

levelCounter.WithContext(ctx).WithLabelValues(string(level)).Inc()

however, WithLabelValues makes no use of the wrapped contextual (counter) vector that was passed to it.

Since we do away with contexts at the vector level in this patch, WithContext(ctx) will now essentially be a no-op, as we no use-case for it, and pass down a (counter) vector, instead of a contextual counter vector, and thus call it's WithLabelValues, instead of the contextual vectors', which has been dropped in this patch.

Copy link
Contributor

@richabanker richabanker Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to diverge from upstream and allow component owners to extend vectors with exemplars

No, we dont want to do that. If the need arises to support contextual vectors, it should be provided via component-base. The script that you linked should help bring attention to any usages of the prometheus client library.

but IIRC we never reached a consensus

I thought the consensus was to not support contexts for vectors. Only support attaching contexts for Counter / Histogram ?

My understanding so far is that we are agreeing to have the following as a part of this PR

  1. We apply AddWithExemplar() implicitly for a contextualized Counter's / Histogram's Add() | Inc() | Observe() methods. And we ensure that we are creating the contextualized Counter/Histogram object in a race-free manner
  2. Noop-ing the CounterVec.WithContext() & HistogramVec.WithContext() since we agree that it doesn't make sense to apply a context to an entire vector. Instead, each individual counter or histogram within the vector should attach its own context and record exemplars. Further, I dont like a bunch of WithContext() usages across the code for vectors, which can continue to increase if people just copy this pattern for new metrics too. Eventually we should consider getting rid of the CounterVec.WithContext() | HistogramVec.WithContext() entirely

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @dgrisonnet to confirm if there's any misunderstanding in the above comment, or if there's any other concerns that need to be addressed here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the consensus was to not support contexts for vectors. Only support attaching contexts for Counter / Histogram?

We talked about it earlier, yes, but we never got Damien's ACK to move forward with this. It'd be nice to have every stakeholder within the SIG be okay with this change.

Noop-ing the CounterVec.WithContext() & HistogramVec.WithContext() since we agree that it doesn't make sense to apply a context to an entire vector. Instead, each individual counter or histogram within the vector should attach its own context and record exemplars. Further, I dont like a bunch of WithContext() usages across the code for vectors, which can continue to increase if people just copy this pattern for new metrics too. Eventually we should consider getting rid of the CounterVec.WithContext() | HistogramVec.WithContext() entirely.

My only concern with doing this is how this will effect downstream users of WithContext, as it's fairly plausible to assume that they may be creating a contextual vector using that, working with that a bit (say, asserting if it's a CounterVectorWithContext, injecting custom KVs into that context, etc.), and then adding labels to it. This breaks with this patch, and essentially what all of my comments above resolved around.

Of course, being able to do that allows us to reposition the library with better Prometheus API abstraction, while following the established design pattern and reducing the technical debt that would otherwise arise in the future.

As is, we could either merge this PR if it looks good, or issue a deprecation notice for one release (since nothing breaks in the k/k tree itself, but potentially, and in a rare circumstance as I mentioned above, downstream) and then merge it.

Copy link
Member

@dgrisonnet dgrisonnet Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @dgrisonnet to confirm if there's any misunderstanding in the above comment, or if there's any other concerns that need to be addressed here.

Your comment is for the most part in line with my previous comments on the topic.

We talked about it earlier, yes, but we never got Damien's ACK to move forward with this. It'd be nice to have every stakeholder within the SIG be okay with this change.

This work is going on for quite some time now, I'd be better to finish it once and for all if all three of us are in agreement with what needs to be done.

My only concern with doing this is how this will effect downstream users of WithContext

I don't see how this change is breaking users of the library. Sure we are changing where the context needs to be attached, but functionality-wise, this is still the same as it was before. They will just have to specify label values before setting the context in order to migrate to the new version. That should for the most part be a pretty easy change to do when upgrading the library.

it's fairly plausible to assume that they may be creating a contextual vector using that, working with that a bit (say, asserting if it's a CounterVectorWithContext, injecting custom KVs into that context, etc.), and then adding labels to it.

If they abuse context to do that, it is wrong. They should use MustCurryWith which is the supported way to do that with the Prometheus client. If we "break" that "functionnality" for them they have option to still be able to do what they were doing but using the supported method.

Copy link
Member

@dgrisonnet dgrisonnet Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noop-ing the CounterVec.WithContext() & HistogramVec.WithContext() since we agree that it doesn't make sense to apply a context to an entire vector. Instead, each individual counter or histogram within the vector should attach its own context and record exemplars. Further, I dont like a bunch of WithContext() usages across the code for vectors, which can continue to increase if people just copy this pattern for new metrics too. Eventually we should consider getting rid of the CounterVec.WithContext() | HistogramVec.WithContext() entirely

I think we should just remove these methods entirely instead of noop-ing them to make sure that users are aware that they need to make some code change before proceeding.
Again we are not breaking any functionality, we are just changing how to use it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they abuse context to do that, it is wrong. They should use MustCurryWith which is the supported way to do that with the Prometheus client.

I see. Just want to clarify that while MustCurryWith will attach LVs to the MetricVec, what I meant there was a WithValue usage: ctx := <...>.WithContext(ctx).ctx; ctx = context.WithValue(ctx, "k", "v"). But I'm fairly certain that will also fall into the similar category of abusing the context, so we don't need to respect any implicit guarantees for it.

This is essentially the stability guarantee for component-base that I was unsure about, and just assumed that since we export that (WithContext) API, we'll need to cautiously deprecate it, same as others. But I understand that this is done on a case-by case basis (depending on factors such as the ease of migration, the degree of inclusion upstream, etc.).

I think we should just remove these methods entirely instead of noop-ing them to make sure that users are aware that they need to make some code change before proceeding. Again we are not breaking any functionality, we are just changing how to use it.

CMIIW but dropping the API over the span of the same release as this patch may not be the best practise or well received. I propose a godoc deprecation update for the same (WithContext) API, as well as a CHANGELOG highlight, over at-least one more release before we drop it. Note that this will need a (albeit, straightforward) k/k-wide refactor to drop this usage as it would otherwise break instrumentation for them.

We can still make them aware next release by dropping the API, but by then we'll also have followed a deprecation cycle for it.

Copy link
Member Author

@rexagod rexagod Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, apparently MustCurryWith is only wrapped-over for timing and weighted histograms, which means users, as you mentioned earlier, will need to directly rely on the Prometheus client in order to curry all other metrics constructs exposed by us, which, IIUC, seems like a discrepancy.

We could drop it for these histograms, but since that will be breaking, we could make sure GaugeVecOps and WeightedObserverVec is generalised and implemented across all MetricVec implementations.

Something we might want to pick up in the future; mentioning here for posterity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/bug Categorizes issue or PR as related to a bug. kind/failing-test Categorizes issue or PR as related to a consistently or frequently failing test. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. release-note-none Denotes a PR that doesn't merit a release note. sig/architecture Categorizes an issue or PR as relevant to SIG Architecture. sig/instrumentation Categorizes an issue or PR as relevant to SIG Instrumentation. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Counter.WithContext: data race
9 participants