Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions contrib/opentelemetry/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ var _ client.MetricsHandler = MetricsHandler{}
// MetricsHandler is an implementation of client.MetricsHandler
// for open telemetry.
type MetricsHandler struct {
meter metric.Meter
attributes attribute.Set
onError func(error)
meter metric.Meter
attributes attribute.Set
onError func(error)
useMonotonicCounters bool
}

// MetricsHandlerOptions are options provided to NewMetricsHandler.
Expand All @@ -34,6 +35,13 @@ type MetricsHandlerOptions struct {
//
// Optional: Defaults to panicking on any error.
OnError func(error)
// UseMonotonicCounters controls whether counters are created as monotonic
// Int64Counter (true) or non-monotonic Int64UpDownCounter (false).
// When true, Prometheus will correctly report counters with the _total
// suffix instead of treating them as gauges.
//
// Optional: Defaults to false for backward compatibility.
UseMonotonicCounters bool
}

// NewMetricsHandler returns a client.MetricsHandler that is backed by the given Meter
Expand All @@ -45,9 +53,10 @@ func NewMetricsHandler(options MetricsHandlerOptions) MetricsHandler {
options.OnError = func(err error) { panic(err) }
}
return MetricsHandler{
meter: options.Meter,
attributes: options.InitialAttributes,
onError: options.OnError,
meter: options.Meter,
attributes: options.InitialAttributes,
onError: options.OnError,
useMonotonicCounters: options.UseMonotonicCounters,
}
}

Expand Down Expand Up @@ -89,13 +98,24 @@ func (m MetricsHandler) WithTags(tags map[string]string) client.MetricsHandler {
attributes = append(attributes, attribute.String(k, v))
}
return MetricsHandler{
meter: m.meter,
attributes: attribute.NewSet(attributes...),
onError: m.onError,
meter: m.meter,
attributes: attribute.NewSet(attributes...),
onError: m.onError,
useMonotonicCounters: m.useMonotonicCounters,
}
}

func (m MetricsHandler) Counter(name string) client.MetricsCounter {
if m.useMonotonicCounters {
c, err := m.meter.Int64Counter(name)
if err != nil {
m.onError(err)
return client.MetricsNopHandler.Counter(name)
}
return metrics.CounterFunc(func(d int64) {
c.Add(context.Background(), d, metric.WithAttributeSet(m.attributes))
})
}
c, err := m.meter.Int64UpDownCounter(name)
if err != nil {
m.onError(err)
Expand Down
43 changes: 43 additions & 0 deletions contrib/opentelemetry/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,49 @@ func TestGaugeHandler(t *testing.T) {
metricdatatest.AssertEqual(t, want, metrics[0], metricdatatest.IgnoreTimestamp())
}

func TestMonotonicCounterHandler(t *testing.T) {
ctx := context.Background()
metricReader := metric.NewManualReader()
meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader))
handler := opentelemetry.NewMetricsHandler(
opentelemetry.MetricsHandlerOptions{
Meter: meterProvider.Meter("test"),
UseMonotonicCounters: true,
},
)
// Emit some values
testCounter := handler.WithTags(map[string]string{"tag1": "value1"}).Counter("testCounter")
testCounter.Inc(1)
testCounter.Inc(1)
// Emit some values with different tags
testCounter2 := handler.WithTags(map[string]string{"tag1": "value2"}).Counter("testCounter")
testCounter2.Inc(5)
// Assert result
var rm metricdata.ResourceMetrics
metricReader.Collect(ctx, &rm)
assert.Len(t, rm.ScopeMetrics, 1)
metrics := rm.ScopeMetrics[0].Metrics
assert.Len(t, metrics, 1)
want := metricdata.Metrics{
Name: "testCounter",
Data: metricdata.Sum[int64]{
Temporality: metricdata.CumulativeTemporality,
IsMonotonic: true,
DataPoints: []metricdata.DataPoint[int64]{
{
Value: 2,
Attributes: attribute.NewSet(attribute.String("tag1", "value1")),
},
{
Value: 5,
Attributes: attribute.NewSet(attribute.String("tag1", "value2")),
},
},
},
}
metricdatatest.AssertEqual(t, want, metrics[0], metricdatatest.IgnoreTimestamp())
}

func TestTimerHandler(t *testing.T) {
ctx := context.Background()
metricReader := metric.NewManualReader()
Expand Down