blob: 8bc0e2ec3be5edb3bf5b93072590da9cf4bd4079 [file] [log] [blame]
From 580612cea9e4eaec1c25821d872e1551335f8c95 Mon Sep 17 00:00:00 2001
From: Pranshu Srivastava <rexagod@gmail.com>
Date: Tue, 5 Nov 2024 18:37:40 +0530
Subject: [PATCH] address `metrics.(*Counter)` context data-race
PR: https://github.com/kubernetes/kubernetes/pull/128575
Fixes: https://github.com/kubernetes/kubernetes/issues/128548
Signed-off-by: Pranshu Srivastava <rexagod@gmail.com>
---
.../k8s.io/component-base/metrics/counter.go | 105 +++++---
.../component-base/metrics/counter_test.go | 254 +++++++++++++++++-
.../component-base/metrics/histogram.go | 44 +--
.../component-base/metrics/histogram_test.go | 207 ++++++++++++--
4 files changed, 537 insertions(+), 73 deletions(-)
diff --git a/metrics/counter.go b/metrics/counter.go
index 8a7dd71541c23..f92c8d59f00e5 100644
--- a/metrics/counter.go
+++ b/metrics/counter.go
@@ -30,23 +30,22 @@ import (
// Counter is our internal representation for our wrapping struct around prometheus
// counters. Counter implements both kubeCollector and CounterMetric.
type Counter struct {
- ctx context.Context
CounterMetric
*CounterOpts
lazyMetric
selfCollector
}
+type CounterWithContext struct {
+ ctx context.Context
+ *Counter
+}
+
// The implementation of the Metric interface is expected by testutil.GetCounterMetricValue.
var _ Metric = &Counter{}
// All supported exemplar metric types implement the metricWithExemplar interface.
-var _ metricWithExemplar = &Counter{}
-
-// exemplarCounterMetric holds a context to extract exemplar labels from, and a counter metric to attach them to. It implements the metricWithExemplar interface.
-type exemplarCounterMetric struct {
- *Counter
-}
+var _ metricWithExemplar = &CounterWithContext{}
// NewCounter returns an object which satisfies the kubeCollector and CounterMetric interfaces.
// However, the object returned will not measure anything unless the collector is first
@@ -106,28 +105,17 @@ func (c *Counter) initializeDeprecatedMetric() {
}
// WithContext allows the normal Counter metric to pass in context.
-func (c *Counter) WithContext(ctx context.Context) CounterMetric {
- c.ctx = ctx
- return c.CounterMetric
-}
-
-// withExemplar initializes the exemplarMetric object and sets the exemplar value.
-func (c *Counter) withExemplar(v float64) {
- (&exemplarCounterMetric{c}).withExemplar(v)
-}
-
-func (c *Counter) Add(v float64) {
- c.withExemplar(v)
-}
-
-func (c *Counter) Inc() {
- c.withExemplar(1)
+func (c *Counter) WithContext(ctx context.Context) *CounterWithContext {
+ return &CounterWithContext{
+ ctx: ctx,
+ Counter: c,
+ }
}
// withExemplar attaches an exemplar to the metric.
-func (e *exemplarCounterMetric) withExemplar(v float64) {
- if m, ok := e.CounterMetric.(prometheus.ExemplarAdder); ok {
- maybeSpanCtx := trace.SpanContextFromContext(e.ctx)
+func (c *CounterWithContext) withExemplar(v float64) {
+ if m, ok := c.CounterMetric.(prometheus.ExemplarAdder); ok {
+ maybeSpanCtx := trace.SpanContextFromContext(c.ctx)
if maybeSpanCtx.IsValid() && maybeSpanCtx.IsSampled() {
exemplarLabels := prometheus.Labels{
"trace_id": maybeSpanCtx.TraceID().String(),
@@ -138,7 +126,23 @@ func (e *exemplarCounterMetric) withExemplar(v float64) {
}
}
- e.CounterMetric.Add(v)
+ c.CounterMetric.Add(v)
+}
+
+func (c *Counter) Add(v float64) {
+ c.CounterMetric.Add(v)
+}
+
+func (c *Counter) Inc() {
+ c.CounterMetric.Inc()
+}
+
+func (c *CounterWithContext) Add(v float64) {
+ c.withExemplar(v)
+}
+
+func (c *CounterWithContext) Inc() {
+ c.withExemplar(1)
}
// CounterVec is the internal representation of our wrapping struct around prometheus
@@ -295,12 +299,51 @@ type CounterVecWithContext struct {
ctx context.Context
}
+// CounterVecWithContextWithCounter is the wrapper of CounterVecWithContext with counter.
+type CounterVecWithContextWithCounter struct {
+ *CounterVecWithContext
+ counter CounterMetric
+}
+
+// withExemplar attaches an exemplar to the metric.
+func (vcc *CounterVecWithContextWithCounter) withExemplar(v float64) {
+ if m, ok := vcc.counter.(prometheus.ExemplarAdder); ok {
+ maybeSpanCtx := trace.SpanContextFromContext(vcc.ctx)
+ if maybeSpanCtx.IsValid() && maybeSpanCtx.IsSampled() {
+ exemplarLabels := prometheus.Labels{
+ "trace_id": maybeSpanCtx.TraceID().String(),
+ "span_id": maybeSpanCtx.SpanID().String(),
+ }
+ m.AddWithExemplar(v, exemplarLabels)
+ return
+ }
+ }
+
+ vcc.counter.Add(v)
+}
+
+// Add adds the given value to the counter with the provided labels.
+func (vcc *CounterVecWithContextWithCounter) Add(v float64) {
+ vcc.withExemplar(v)
+}
+
+// Inc increments the counter with the provided labels.
+func (vcc *CounterVecWithContextWithCounter) Inc() {
+ vcc.withExemplar(1)
+}
+
// WithLabelValues is the wrapper of CounterVec.WithLabelValues.
-func (vc *CounterVecWithContext) WithLabelValues(lvs ...string) CounterMetric {
- return vc.CounterVec.WithLabelValues(lvs...)
+func (vc *CounterVecWithContext) WithLabelValues(lvs ...string) *CounterVecWithContextWithCounter {
+ return &CounterVecWithContextWithCounter{
+ CounterVecWithContext: vc,
+ counter: vc.CounterVec.WithLabelValues(lvs...),
+ }
}
// With is the wrapper of CounterVec.With.
-func (vc *CounterVecWithContext) With(labels map[string]string) CounterMetric {
- return vc.CounterVec.With(labels)
+func (vc *CounterVecWithContext) With(labels map[string]string) *CounterVecWithContextWithCounter {
+ return &CounterVecWithContextWithCounter{
+ CounterVecWithContext: vc,
+ counter: vc.CounterVec.With(labels),
+ }
}
diff --git a/metrics/counter_test.go b/metrics/counter_test.go
index 2afcc4d63044c..ea47cab282985 100644
--- a/metrics/counter_test.go
+++ b/metrics/counter_test.go
@@ -292,7 +292,7 @@ func TestCounterWithLabelValueAllowList(t *testing.T) {
}
func TestCounterWithExemplar(t *testing.T) {
- // Set exemplar.
+ // Create context.
fn := func(offset int) []byte {
arr := make([]byte, 16)
for i := 0; i < 16; i++ {
@@ -313,8 +313,7 @@ func TestCounterWithExemplar(t *testing.T) {
counter := NewCounter(&CounterOpts{
Name: "metric_exemplar_test",
Help: "helpless",
- })
- _ = counter.WithContext(ctxForSpanCtx)
+ }).WithContext(ctxForSpanCtx)
// Register counter.
registry := newKubeRegistry(apimachineryversion.Info{
@@ -381,4 +380,253 @@ func TestCounterWithExemplar(t *testing.T) {
t.Fatalf("Got unexpected label %s", *l.Name)
}
}
+
+ // Verify that all contextual counter calls are exclusive.
+ contextualCounter := NewCounter(&CounterOpts{
+ Name: "contextual_counter",
+ Help: "helpless",
+ })
+ spanIDa := trace.SpanID(fn(3))
+ traceIDa := trace.TraceID(fn(4))
+ contextualCounterA := contextualCounter.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ SpanID: spanIDa,
+ TraceID: traceIDa,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+ spanIDb := trace.SpanID(fn(5))
+ traceIDb := trace.TraceID(fn(6))
+ contextualCounterB := contextualCounter.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ SpanID: spanIDb,
+ TraceID: traceIDb,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+
+ runs := []struct {
+ spanID trace.SpanID
+ traceID trace.TraceID
+ contextualCounter *CounterWithContext
+ }{
+ {
+ spanID: spanIDa,
+ traceID: traceIDa,
+ contextualCounter: contextualCounterA,
+ },
+ {
+ spanID: spanIDb,
+ traceID: traceIDb,
+ contextualCounter: contextualCounterB,
+ },
+ }
+ for _, run := range runs {
+ registry.MustRegister(run.contextualCounter)
+ run.contextualCounter.Inc()
+
+ mfs, err = registry.Gather()
+ if err != nil {
+ t.Fatalf("Gather failed %v", err)
+ }
+ if len(mfs) != 2 {
+ t.Fatalf("Got %v metric families, Want: 2 metric families", len(mfs))
+ }
+
+ dtoMetric := mfs[0].GetMetric()[0]
+ if dtoMetric.GetCounter().GetExemplar() == nil {
+ t.Fatalf("Got nil exemplar, wanted an exemplar")
+ }
+ dtoMetricLabels := dtoMetric.GetCounter().GetExemplar().GetLabel()
+ if len(dtoMetricLabels) != 2 {
+ t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(dtoMetricLabels))
+ }
+ for _, l := range dtoMetricLabels {
+ switch *l.Name {
+ case "trace_id":
+ if *l.Value != run.traceID.String() {
+ t.Fatalf("Got %s as traceID, wanted %s", *l.Value, run.traceID.String())
+ }
+ case "span_id":
+ if *l.Value != run.spanID.String() {
+ t.Fatalf("Got %s as spanID, wanted %s", *l.Value, run.spanID.String())
+ }
+ default:
+ t.Fatalf("Got unexpected label %s", *l.Name)
+ }
+ }
+
+ registry.Unregister(run.contextualCounter)
+ }
+}
+
+func TestCounterVecWithExemplar(t *testing.T) {
+ // Create context.
+ fn := func(offset int) []byte {
+ arr := make([]byte, 16)
+ for i := 0; i < 16; i++ {
+ arr[i] = byte(2<<7 - i - offset)
+ }
+ return arr
+ }
+ traceID := trace.TraceID(fn(1))
+ spanID := trace.SpanID(fn(2))
+ ctxForSpanCtx := trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ SpanID: spanID,
+ TraceID: traceID,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ )
+ toAdd := float64(40)
+
+ // Create contextual counter.
+ counter := NewCounterVec(&CounterOpts{
+ Name: "metricvec_exemplar_test",
+ Help: "helpless",
+ }, []string{"label_a"}).WithContext(ctxForSpanCtx)
+
+ // Register counter.
+ registry := newKubeRegistry(apimachineryversion.Info{
+ Major: "1",
+ Minor: "15",
+ GitVersion: "v1.15.0-alpha-1.12345",
+ })
+ registry.MustRegister(counter)
+
+ // Call underlying exemplar methods.
+ counter.WithLabelValues("a").Add(toAdd)
+ counter.WithLabelValues("a").Inc()
+ counter.WithLabelValues("a").Inc()
+
+ // Gather.
+ mfs, err := registry.Gather()
+ if err != nil {
+ t.Fatalf("Gather failed %v", err)
+ }
+ if len(mfs) != 1 {
+ t.Fatalf("Got %v metric families, Want: 1 metric family", len(mfs))
+ }
+
+ // Verify metric type.
+ mf := mfs[0]
+ var m *dto.Metric
+ switch mf.GetType() {
+ case dto.MetricType_COUNTER:
+ m = mfs[0].GetMetric()[0]
+ default:
+ t.Fatalf("Got %v metric type, Want: %v metric type", mf.GetType(), dto.MetricType_COUNTER)
+ }
+
+ // Verify value.
+ want := toAdd + 2
+ got := m.GetCounter().GetValue()
+ if got != want {
+ t.Fatalf("Got %f, wanted %f as the count", got, want)
+ }
+
+ // Verify exemplars.
+ e := m.GetCounter().GetExemplar()
+ if e == nil {
+ t.Fatalf("Got nil exemplar, wanted an exemplar")
+ }
+ eLabels := e.GetLabel()
+ if eLabels == nil {
+ t.Fatalf("Got nil exemplar label, wanted an exemplar label")
+ }
+ if len(eLabels) != 2 {
+ t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(eLabels))
+ }
+ for _, l := range eLabels {
+ switch *l.Name {
+ case "trace_id":
+ if *l.Value != traceID.String() {
+ t.Fatalf("Got %s as traceID, wanted %s", *l.Value, traceID.String())
+ }
+ case "span_id":
+ if *l.Value != spanID.String() {
+ t.Fatalf("Got %s as spanID, wanted %s", *l.Value, spanID.String())
+ }
+ default:
+ t.Fatalf("Got unexpected label %s", *l.Name)
+ }
+ }
+
+ // Verify that all contextual counter calls are exclusive.
+ contextualCounterVec := NewCounterVec(&CounterOpts{
+ Name: "contextual_counter",
+ Help: "helpless",
+ }, []string{"label_a"})
+ spanIDa := trace.SpanID(fn(3))
+ traceIDa := trace.TraceID(fn(4))
+ contextualCounterVecA := contextualCounterVec.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ SpanID: spanIDa,
+ TraceID: traceIDa,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+ spanIDb := trace.SpanID(fn(5))
+ traceIDb := trace.TraceID(fn(6))
+ contextualCounterVecB := contextualCounterVec.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ SpanID: spanIDb,
+ TraceID: traceIDb,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+
+ runs := []struct {
+ spanID trace.SpanID
+ traceID trace.TraceID
+ contextualCounter *CounterVecWithContext
+ }{
+ {
+ spanID: spanIDa,
+ traceID: traceIDa,
+ contextualCounter: contextualCounterVecA,
+ },
+ {
+ spanID: spanIDb,
+ traceID: traceIDb,
+ contextualCounter: contextualCounterVecB,
+ },
+ }
+ for _, run := range runs {
+ registry.MustRegister(run.contextualCounter)
+ run.contextualCounter.WithLabelValues("a").Inc()
+
+ mfs, err = registry.Gather()
+ if err != nil {
+ t.Fatalf("Gather failed %v", err)
+ }
+ if len(mfs) != 2 {
+ t.Fatalf("Got %v metric families, Want: 2 metric families", len(mfs))
+ }
+
+ dtoMetric := mfs[0].GetMetric()[0]
+ if dtoMetric.GetCounter().GetExemplar() == nil {
+ t.Fatalf("Got nil exemplar, wanted an exemplar")
+ }
+ dtoMetricLabels := dtoMetric.GetCounter().GetExemplar().GetLabel()
+ if len(dtoMetricLabels) != 2 {
+ t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(dtoMetricLabels))
+ }
+ for _, l := range dtoMetricLabels {
+ switch *l.Name {
+ case "trace_id":
+ if *l.Value != run.traceID.String() {
+ t.Fatalf("Got %s as traceID, wanted %s", *l.Value, run.traceID.String())
+ }
+ case "span_id":
+ if *l.Value != run.spanID.String() {
+ t.Fatalf("Got %s as spanID, wanted %s", *l.Value, run.spanID.String())
+ }
+ default:
+ t.Fatalf("Got unexpected label %s", *l.Name)
+ }
+ }
+
+ registry.Unregister(run.contextualCounter)
+ }
}
diff --git a/metrics/histogram.go b/metrics/histogram.go
index 3065486ab41d1..de9ed858fcfaf 100644
--- a/metrics/histogram.go
+++ b/metrics/histogram.go
@@ -28,34 +28,38 @@ import (
// Histogram is our internal representation for our wrapping struct around prometheus
// histograms. Summary implements both kubeCollector and ObserverMetric
type Histogram struct {
- ctx context.Context
ObserverMetric
*HistogramOpts
lazyMetric
selfCollector
}
-// exemplarHistogramMetric holds a context to extract exemplar labels from, and a historgram metric to attach them to. It implements the metricWithExemplar interface.
-type exemplarHistogramMetric struct {
+// TODO: make this true: var _ Metric = &Histogram{}
+
+// HistogramWithContext implements the metricWithExemplar interface.
+var _ metricWithExemplar = &HistogramWithContext{}
+
+// HistogramWithContext holds a context to extract exemplar labels from, and a historgram metric to attach them to. It implements the metricWithExemplar interface.
+type HistogramWithContext struct {
+ ctx context.Context
*Histogram
}
-type exemplarHistogramVec struct {
+type HistogramVecWithContextWithObserver struct {
*HistogramVecWithContext
- observer prometheus.Observer
+ observer ObserverMetric
}
func (h *Histogram) Observe(v float64) {
- h.withExemplar(v)
+ h.ObserverMetric.Observe(v)
}
-// withExemplar initializes the exemplarMetric object and sets the exemplar value.
-func (h *Histogram) withExemplar(v float64) {
- (&exemplarHistogramMetric{h}).withExemplar(v)
+func (e *HistogramWithContext) Observe(v float64) {
+ e.withExemplar(v)
}
// withExemplar attaches an exemplar to the metric.
-func (e *exemplarHistogramMetric) withExemplar(v float64) {
+func (e *HistogramWithContext) withExemplar(v float64) {
if m, ok := e.Histogram.ObserverMetric.(prometheus.ExemplarObserver); ok {
maybeSpanCtx := trace.SpanContextFromContext(e.ctx)
if maybeSpanCtx.IsValid() && maybeSpanCtx.IsSampled() {
@@ -112,9 +116,11 @@ func (h *Histogram) initializeDeprecatedMetric() {
}
// WithContext allows the normal Histogram metric to pass in context. The context is no-op now.
-func (h *Histogram) WithContext(ctx context.Context) ObserverMetric {
- h.ctx = ctx
- return h.ObserverMetric
+func (h *Histogram) WithContext(ctx context.Context) *HistogramWithContext {
+ return &HistogramWithContext{
+ ctx: ctx,
+ Histogram: h,
+ }
}
// HistogramVec is the internal representation of our wrapping struct around prometheus
@@ -263,11 +269,11 @@ type HistogramVecWithContext struct {
ctx context.Context
}
-func (h *exemplarHistogramVec) Observe(v float64) {
+func (h *HistogramVecWithContextWithObserver) Observe(v float64) {
h.withExemplar(v)
}
-func (h *exemplarHistogramVec) withExemplar(v float64) {
+func (h *HistogramVecWithContextWithObserver) withExemplar(v float64) {
if m, ok := h.observer.(prometheus.ExemplarObserver); ok {
maybeSpanCtx := trace.SpanContextFromContext(h.HistogramVecWithContext.ctx)
if maybeSpanCtx.IsValid() && maybeSpanCtx.IsSampled() {
@@ -283,16 +289,16 @@ func (h *exemplarHistogramVec) withExemplar(v float64) {
}
// WithLabelValues is the wrapper of HistogramVec.WithLabelValues.
-func (vc *HistogramVecWithContext) WithLabelValues(lvs ...string) *exemplarHistogramVec {
- return &exemplarHistogramVec{
+func (vc *HistogramVecWithContext) WithLabelValues(lvs ...string) *HistogramVecWithContextWithObserver {
+ return &HistogramVecWithContextWithObserver{
HistogramVecWithContext: vc,
observer: vc.HistogramVec.WithLabelValues(lvs...),
}
}
// With is the wrapper of HistogramVec.With.
-func (vc *HistogramVecWithContext) With(labels map[string]string) *exemplarHistogramVec {
- return &exemplarHistogramVec{
+func (vc *HistogramVecWithContext) With(labels map[string]string) *HistogramVecWithContextWithObserver {
+ return &HistogramVecWithContextWithObserver{
HistogramVecWithContext: vc,
observer: vc.HistogramVec.With(labels),
}
diff --git a/metrics/histogram_test.go b/metrics/histogram_test.go
index 5efbfb6eeae2d..3cf4e703912ea 100644
--- a/metrics/histogram_test.go
+++ b/metrics/histogram_test.go
@@ -318,7 +318,7 @@ func TestHistogramWithLabelValueAllowList(t *testing.T) {
}
func TestHistogramWithExemplar(t *testing.T) {
- // Arrange.
+ // Create context.
traceID := trace.TraceID([]byte("trace-0000-xxxxx"))
spanID := trace.SpanID([]byte("span-0000-xxxxx"))
ctxForSpanCtx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
@@ -328,13 +328,14 @@ func TestHistogramWithExemplar(t *testing.T) {
}))
value := float64(10)
+ // Create contextual histogram.
histogram := NewHistogram(&HistogramOpts{
Name: "histogram_exemplar_test",
Help: "helpless",
Buckets: []float64{100},
- })
- _ = histogram.WithContext(ctxForSpanCtx)
+ }).WithContext(ctxForSpanCtx)
+ // Register histogram.
registry := newKubeRegistry(apimachineryversion.Info{
Major: "1",
Minor: "15",
@@ -342,53 +343,51 @@ func TestHistogramWithExemplar(t *testing.T) {
})
registry.MustRegister(histogram)
- // Act.
+ // Call underlying exemplar methods.
histogram.Observe(value)
- // Assert.
+ // Gather.
mfs, err := registry.Gather()
if err != nil {
t.Fatalf("Gather failed %v", err)
}
-
if len(mfs) != 1 {
t.Fatalf("Got %v metric families, Want: 1 metric family", len(mfs))
}
+ // Verify metric type.
mf := mfs[0]
var m *dto.Metric
switch mf.GetType() {
case dto.MetricType_HISTOGRAM:
m = mfs[0].GetMetric()[0]
default:
- t.Fatalf("Got %v metric type, Want: %v metric type", mf.GetType(), dto.MetricType_COUNTER)
+ t.Fatalf("Got %v metric type, Want: %v metric type", mf.GetType(), dto.MetricType_HISTOGRAM)
}
+ // Verify value.
want := value
got := m.GetHistogram().GetSampleSum()
if got != want {
t.Fatalf("Got %f, wanted %f as the count", got, want)
}
+ // Verify exemplars.
buckets := m.GetHistogram().GetBucket()
if len(buckets) == 0 {
t.Fatalf("Got 0 buckets, wanted 1")
}
-
e := buckets[0].GetExemplar()
if e == nil {
t.Fatalf("Got nil exemplar, wanted an exemplar")
}
-
eLabels := e.GetLabel()
if eLabels == nil {
t.Fatalf("Got nil exemplar label, wanted an exemplar label")
}
-
if len(eLabels) != 2 {
t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(eLabels))
}
-
for _, l := range eLabels {
switch *l.Name {
case "trace_id":
@@ -403,10 +402,95 @@ func TestHistogramWithExemplar(t *testing.T) {
t.Fatalf("Got unexpected label %s", *l.Name)
}
}
+
+ // Verify that all contextual histogram calls are exclusive.
+ contextualHistogram := NewHistogram(&HistogramOpts{
+ Name: "contextual_histogram",
+ Help: "helpless",
+ Buckets: []float64{100},
+ })
+ traceIDa := trace.TraceID([]byte("trace-0000-aaaaa"))
+ spanIDa := trace.SpanID([]byte("span-0000-aaaaa"))
+ contextualHistogramA := contextualHistogram.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ TraceID: traceIDa,
+ SpanID: spanIDa,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+ traceIDb := trace.TraceID([]byte("trace-0000-bbbbb"))
+ spanIDb := trace.SpanID([]byte("span-0000-bbbbb"))
+ contextualHistogramB := contextualHistogram.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ TraceID: traceIDb,
+ SpanID: spanIDb,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+
+ runs := []struct {
+ spanID trace.SpanID
+ traceID trace.TraceID
+ contextualHistogram *HistogramWithContext
+ }{
+ {
+ spanID: spanIDa,
+ traceID: traceIDa,
+ contextualHistogram: contextualHistogramA,
+ },
+ {
+ spanID: spanIDb,
+ traceID: traceIDb,
+ contextualHistogram: contextualHistogramB,
+ },
+ }
+ for _, run := range runs {
+ registry.MustRegister(run.contextualHistogram)
+ run.contextualHistogram.Observe(value)
+
+ mfs, err = registry.Gather()
+ if err != nil {
+ t.Fatalf("Gather failed %v", err)
+ }
+ if len(mfs) != 2 {
+ t.Fatalf("Got %v metric families, Want: 2 metric families", len(mfs))
+ }
+
+ dtoMetric := mfs[0].GetMetric()[0]
+ dtoMetricBuckets := dtoMetric.GetHistogram().GetBucket()
+ if len(dtoMetricBuckets) == 0 {
+ t.Fatalf("Got nil buckets")
+ }
+ dtoMetricBucketsExemplar := dtoMetricBuckets[0].GetExemplar()
+ if dtoMetricBucketsExemplar == nil {
+ t.Fatalf("Got nil exemplar")
+ }
+
+ dtoMetricLabels := dtoMetricBucketsExemplar.GetLabel()
+ if len(dtoMetricLabels) != 2 {
+ t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(dtoMetricLabels))
+ }
+ for _, l := range dtoMetricLabels {
+ switch *l.Name {
+ case "trace_id":
+ if *l.Value != run.traceID.String() {
+ t.Fatalf("Got %s as traceID, wanted %s", *l.Value, run.traceID.String())
+ }
+ case "span_id":
+ if *l.Value != run.spanID.String() {
+ t.Fatalf("Got %s as spanID, wanted %s", *l.Value, run.spanID.String())
+ }
+ default:
+ t.Fatalf("Got unexpected label %s", *l.Name)
+ }
+ }
+
+ registry.Unregister(run.contextualHistogram)
+ }
}
func TestHistogramVecWithExemplar(t *testing.T) {
- // Arrange.
+ // Create context.
traceID := trace.TraceID([]byte("trace-0000-xxxxx"))
spanID := trace.SpanID([]byte("span-0000-xxxxx"))
ctxForSpanCtx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{
@@ -416,6 +500,7 @@ func TestHistogramVecWithExemplar(t *testing.T) {
}))
value := float64(10)
+ // Create contextual histogram.
histogramVec := NewHistogramVec(&HistogramOpts{
Name: "histogram_exemplar_test",
Help: "helpless",
@@ -423,6 +508,7 @@ func TestHistogramVecWithExemplar(t *testing.T) {
}, []string{"group"})
h := histogramVec.WithContext(ctxForSpanCtx)
+ // Register histogram.
registry := newKubeRegistry(apimachineryversion.Info{
Major: "1",
Minor: "15",
@@ -430,53 +516,51 @@ func TestHistogramVecWithExemplar(t *testing.T) {
})
registry.MustRegister(histogramVec)
- // Act.
+ // Call underlying exemplar methods.
h.WithLabelValues("foo").Observe(value)
- // Assert.
+ // Gather.
mfs, err := registry.Gather()
if err != nil {
t.Fatalf("Gather failed %v", err)
}
-
if len(mfs) != 1 {
t.Fatalf("Got %v metric families, Want: 1 metric family", len(mfs))
}
+ // Verify metric type.
mf := mfs[0]
var m *dto.Metric
switch mf.GetType() {
case dto.MetricType_HISTOGRAM:
m = mfs[0].GetMetric()[0]
default:
- t.Fatalf("Got %v metric type, Want: %v metric type", mf.GetType(), dto.MetricType_COUNTER)
+ t.Fatalf("Got %v metric type, Want: %v metric type", mf.GetType(), dto.MetricType_HISTOGRAM)
}
+ // Verify value.
want := value
got := m.GetHistogram().GetSampleSum()
if got != want {
t.Fatalf("Got %f, wanted %f as the count", got, want)
}
+ // Verify exemplars.
buckets := m.GetHistogram().GetBucket()
if len(buckets) == 0 {
t.Fatalf("Got 0 buckets, wanted 1")
}
-
e := buckets[0].GetExemplar()
if e == nil {
t.Fatalf("Got nil exemplar, wanted an exemplar")
}
-
eLabels := e.GetLabel()
if eLabels == nil {
t.Fatalf("Got nil exemplar label, wanted an exemplar label")
}
-
if len(eLabels) != 2 {
t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(eLabels))
}
-
for _, l := range eLabels {
switch *l.Name {
case "trace_id":
@@ -491,4 +575,87 @@ func TestHistogramVecWithExemplar(t *testing.T) {
t.Fatalf("Got unexpected label %s", *l.Name)
}
}
+
+ // Verify that all contextual histogram calls are exclusive.
+ contextualHistogramVec := NewHistogramVec(&HistogramOpts{
+ Name: "contextual_histogram_vec",
+ Help: "helpless",
+ Buckets: []float64{100},
+ }, []string{"group"})
+ traceIDa := trace.TraceID([]byte("trace-0000-aaaaa"))
+ spanIDa := trace.SpanID([]byte("span-0000-aaaaa"))
+ contextualHistogramVecA := contextualHistogramVec.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ TraceID: traceIDa,
+ SpanID: spanIDa,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+ traceIDb := trace.TraceID([]byte("trace-0000-bbbbb"))
+ spanIDb := trace.SpanID([]byte("span-0000-bbbbb"))
+ contextualHistogramVecB := contextualHistogramVec.WithContext(trace.ContextWithSpanContext(context.Background(),
+ trace.NewSpanContext(trace.SpanContextConfig{
+ TraceID: traceIDb,
+ SpanID: spanIDb,
+ TraceFlags: trace.FlagsSampled,
+ }),
+ ))
+ runs := []struct {
+ spanID trace.SpanID
+ traceID trace.TraceID
+ contextualHistogramVec *HistogramVecWithContext
+ }{
+ {
+ spanID: spanIDa,
+ traceID: traceIDa,
+ contextualHistogramVec: contextualHistogramVecA,
+ }, {
+ spanID: spanIDb,
+ traceID: traceIDb,
+ contextualHistogramVec: contextualHistogramVecB,
+ },
+ }
+ for _, run := range runs {
+ registry.MustRegister(run.contextualHistogramVec)
+ run.contextualHistogramVec.WithLabelValues("foo").Observe(value)
+
+ mfs, err = registry.Gather()
+ if err != nil {
+ t.Fatalf("Gather failed %v", err)
+ }
+ if len(mfs) != 2 {
+ t.Fatalf("Got %v metric families, Want: 2 metric families", len(mfs))
+ }
+
+ dtoMetric := mfs[0].GetMetric()[0]
+ dtoMetricBuckets := dtoMetric.GetHistogram().GetBucket()
+ if len(dtoMetricBuckets) == 0 {
+ t.Fatalf("Got nil buckets")
+ }
+ dtoMetricBucketsExemplar := dtoMetricBuckets[0].GetExemplar()
+ if dtoMetricBucketsExemplar == nil {
+ t.Fatalf("Got nil exemplar")
+ }
+
+ dtoMetricLabels := dtoMetricBucketsExemplar.GetLabel()
+ if len(dtoMetricLabels) != 2 {
+ t.Fatalf("Got %v exemplar labels, wanted 2 exemplar labels", len(dtoMetricLabels))
+ }
+ for _, l := range dtoMetricLabels {
+ switch *l.Name {
+ case "trace_id":
+ if *l.Value != run.traceID.String() {
+ t.Fatalf("Got %s as traceID, wanted %s", *l.Value, run.traceID.String())
+ }
+ case "span_id":
+ if *l.Value != run.spanID.String() {
+ t.Fatalf("Got %s as spanID, wanted %s", *l.Value, run.spanID.String())
+ }
+ default:
+ t.Fatalf("Got unexpected label %s", *l.Name)
+ }
+ }
+
+ registry.Unregister(run.contextualHistogramVec)
+ }
}