diff --git a/otmetricreader/find.go b/otmetricreader/find.go new file mode 100644 index 0000000..18df6b8 --- /dev/null +++ b/otmetricreader/find.go @@ -0,0 +1,55 @@ +package otmetricreader + +import ( + "context" + "fmt" + + "go.opentelemetry.io/otel/attribute" + otsdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +// FindVal is a testing utility to find the value of a specific metric +// collected by the sdk [metric.ManualReader]. +// +// It searches for a metric matching the provided metricName and [attribute.Set]. +// The type parameter V specifies the expected value type, which must be +// either int64 or float64. +// +// An error is returned if a metric matching the name and attributes is not found, +// or if the found metric's value type does not match V. +func FindVal[V int64 | float64](ctx context.Context, r *otsdkmetric.ManualReader, metricName string, kvs attribute.Set) (V, error) { + var zv V + res := metricdata.ResourceMetrics{} + if err := r.Collect(ctx, &res); err != nil { + return zv, err + } + + for _, sm := range res.ScopeMetrics { + for _, m := range sm.Metrics { + if m.Name != metricName { + continue + } + switch vs := m.Data.(type) { + case metricdata.Gauge[V]: + for _, d := range vs.DataPoints { + if !kvs.Equals(&d.Attributes) { + continue + } + return d.Value, nil + } + case metricdata.Sum[V]: + for _, d := range vs.DataPoints { + if !kvs.Equals(&d.Attributes) { + continue + } + return d.Value, nil + } + default: + // wrong metric-type (or histogram, which we'll have to implement later) + continue + } + } + } + return zv, fmt.Errorf("metric %s not found", metricName) +} diff --git a/otmetricreader/find_test.go b/otmetricreader/find_test.go new file mode 100644 index 0000000..a32e8d4 --- /dev/null +++ b/otmetricreader/find_test.go @@ -0,0 +1,50 @@ +package otmetricreader + +import ( + "context" + "testing" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + otsdkmetric "go.opentelemetry.io/otel/sdk/metric" +) + +func TestFindMetricVal(t *testing.T) { + ctx := context.Background() + + r := otsdkmetric.NewManualReader() + mp := otsdkmetric.NewMeterProvider(otsdkmetric.WithReader(r)) + + otM := mp.Meter("foobar") + attrSet := attribute.NewSet(attribute.String("fizzlebit", "foobar"), attribute.String("ouch", "icky")) + + // read an int64 counter + counter, countErr := otM.Int64Counter("a_counter_int") + if countErr != nil { + t.Fatalf("failed to register counter metric: %s", countErr) + } + counter.Add(ctx, 2, metric.WithAttributeSet(attrSet)) + + counterVal, counterValErr := FindVal[int64](ctx, r, "a_counter_int", attrSet) + if counterValErr != nil { + t.Errorf("failed to fetch counter metric value: %s", counterValErr) + } + if counterVal != 2 { + t.Errorf("unexpected counter metric value: want %d; got %d", 2, counterVal) + } + + // read an int64 gauge + gauge, gaugeErr := otM.Int64Gauge("a_gauge_int") + if gaugeErr != nil { + t.Fatalf("failed to register gauge metric: %s", gaugeErr) + } + gauge.Record(ctx, 3, metric.WithAttributeSet(attrSet)) + + gaugeVal, gaugeValErr := FindVal[int64](ctx, r, "a_gauge_int", attrSet) + if gaugeValErr != nil { + t.Errorf("failed to fetch gauge metric value: %s", gaugeValErr) + } + if gaugeVal != 3 { + t.Errorf("unexpected gauge metric value: want %d; got %d", 3, gaugeVal) + } +}