From b7508da6fa3f7c76f51c65de9db47123aafd1876 Mon Sep 17 00:00:00 2001 From: Tawhid Hannan Date: Mon, 9 Nov 2020 21:52:05 +0000 Subject: [PATCH] Export non monotonic counters as gauge values from the prometheus exporter (#1269) * Export non monotonic counters as gauge values from the prometheus exporter * Add a test to ensure the pre-existing LastValue pathway on the collector exports the last value * Add a TODO around refactoring the prometheus test to verify metadata as well Co-authored-by: Anthony Mirabella Co-authored-by: Tyler Yahn --- CHANGELOG.md | 1 + exporters/metric/prometheus/prometheus.go | 28 ++++++++++++++++--- .../metric/prometheus/prometheus_test.go | 17 ++++++++++- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb0f4994..4bfbbcb81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Fix `Code.UnmarshalJSON` to work with valid json only. (#1276) - The `resource.New()` method changes signature to support builtin attributes and functional options, including `telemetry.sdk.*` and `host.name` semantic conventions; the former method is renamed `resource.NewWithAttributes`. (#1235) +- The prometheus exporter now exports non-monotonic counters (i.e. `UpDownCounter`s) as gauges. (#1210) - Correct the `Span.End` method documentation in the `otel` API to state updates are not allowed on a span after it has ended. (#1310) ### Removed diff --git a/exporters/metric/prometheus/prometheus.go b/exporters/metric/prometheus/prometheus.go index b319f4e4a..5730e7146 100644 --- a/exporters/metric/prometheus/prometheus.go +++ b/exporters/metric/prometheus/prometheus.go @@ -214,6 +214,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { err := ctrl.ForEach(c.exp, func(record export.Record) error { agg := record.Aggregation() numberKind := record.Descriptor().NumberKind() + instrumentKind := record.Descriptor().InstrumentKind() var labelKeys, labels []string mergeLabels(record, &labelKeys, &labels) @@ -236,9 +237,13 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { if err := c.exportSummary(ch, dist, numberKind, desc, labels); err != nil { return fmt.Errorf("exporting summary: %w", err) } - } else if sum, ok := agg.(aggregation.Sum); ok { - if err := c.exportCounter(ch, sum, numberKind, desc, labels); err != nil { - return fmt.Errorf("exporting counter: %w", err) + } else if sum, ok := agg.(aggregation.Sum); ok && instrumentKind.Monotonic() { + if err := c.exportMonotonicCounter(ch, sum, numberKind, desc, labels); err != nil { + return fmt.Errorf("exporting monotonic counter: %w", err) + } + } else if sum, ok := agg.(aggregation.Sum); ok && !instrumentKind.Monotonic() { + if err := c.exportNonMonotonicCounter(ch, sum, numberKind, desc, labels); err != nil { + return fmt.Errorf("exporting non monotonic counter: %w", err) } } else if lastValue, ok := agg.(aggregation.LastValue); ok { if err := c.exportLastValue(ch, lastValue, numberKind, desc, labels); err != nil { @@ -267,7 +272,22 @@ func (c *collector) exportLastValue(ch chan<- prometheus.Metric, lvagg aggregati return nil } -func (c *collector) exportCounter(ch chan<- prometheus.Metric, sum aggregation.Sum, kind otel.NumberKind, desc *prometheus.Desc, labels []string) error { +func (c *collector) exportNonMonotonicCounter(ch chan<- prometheus.Metric, sum aggregation.Sum, kind otel.NumberKind, desc *prometheus.Desc, labels []string) error { + v, err := sum.Sum() + if err != nil { + return fmt.Errorf("error retrieving counter: %w", err) + } + + m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, v.CoerceToFloat64(kind), labels...) + if err != nil { + return fmt.Errorf("error creating constant metric: %w", err) + } + + ch <- m + return nil +} + +func (c *collector) exportMonotonicCounter(ch chan<- prometheus.Metric, sum aggregation.Sum, kind otel.NumberKind, desc *prometheus.Desc, labels []string) error { v, err := sum.Sum() if err != nil { return fmt.Errorf("error retrieving counter: %w", err) diff --git a/exporters/metric/prometheus/prometheus_test.go b/exporters/metric/prometheus/prometheus_test.go index 6ec49eeeb..768962076 100644 --- a/exporters/metric/prometheus/prometheus_test.go +++ b/exporters/metric/prometheus/prometheus_test.go @@ -34,6 +34,10 @@ import ( ) func TestPrometheusExporter(t *testing.T) { + // #TODO: This test does not adequately verify the type of + // prometheus metric exported for all types - for example, + // it does not verify that an UpDown- counter is exported + // as a gauge. To be improved. exporter, err := prometheus.NewExportPipeline( prometheus.Config{ DefaultHistogramBoundaries: []float64{-0.5, 1}, @@ -44,7 +48,7 @@ func TestPrometheusExporter(t *testing.T) { require.NoError(t, err) meter := exporter.MeterProvider().Meter("test") - + upDownCounter := otel.Must(meter).NewFloat64UpDownCounter("updowncounter") counter := otel.Must(meter).NewFloat64Counter("counter") valuerecorder := otel.Must(meter).NewFloat64ValueRecorder("valuerecorder") @@ -61,6 +65,12 @@ func TestPrometheusExporter(t *testing.T) { expected = append(expected, `counter{A="B",C="D",R="V"} 15.3`) + _ = otel.Must(meter).NewInt64ValueObserver("intobserver", func(_ context.Context, result otel.Int64ObserverResult) { + result.Observe(1, labels...) + }) + + expected = append(expected, `intobserver{A="B",C="D",R="V"} 1`) + valuerecorder.Record(ctx, -0.6, labels...) valuerecorder.Record(ctx, -0.4, labels...) valuerecorder.Record(ctx, 0.6, labels...) @@ -72,6 +82,11 @@ func TestPrometheusExporter(t *testing.T) { expected = append(expected, `valuerecorder_count{A="B",C="D",R="V"} 4`) expected = append(expected, `valuerecorder_sum{A="B",C="D",R="V"} 19.6`) + upDownCounter.Add(ctx, 10, labels...) + upDownCounter.Add(ctx, -3.2, labels...) + + expected = append(expected, `updowncounter{A="B",C="D",R="V"} 6.8`) + compareExport(t, exporter, expected) compareExport(t, exporter, expected) }