diff --git a/CHANGELOG.md b/CHANGELOG.md index e1a59392bcf..ccb9399c7e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Decouple `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal` from `go.opentelemetry.io/otel/exporters/otlp/internal` and `go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal` using gotmpl. (#4400, #3846) - Decouple `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal` from `go.opentelemetry.io/otel/exporters/otlp/internal` and `go.opentelemetry.io/otel/exporters/otlp/otlptrace/internal` using gotmpl. (#4401, #3846) - Do not block the metric SDK when OTLP metric exports are blocked in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#3925, #4395) +- Do not append _total if the counter already ends in total `go.opentelemetry.io/otel/exporter/prometheus`. (#4373) ## [1.16.0/0.39.0] 2023-05-18 diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index 4007f8915b2..81b12254f85 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -189,10 +189,11 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { } for _, m := range scopeMetrics.Metrics { - typ, name := c.metricTypeAndName(m) + typ := c.metricType(m) if typ == nil { continue } + name := c.getName(m, typ) drop, help := c.validateMetrics(name, m.Description, typ) if drop { @@ -366,17 +367,23 @@ var unitSuffixes = map[string]string{ } // getName returns the sanitized name, prefixed with the namespace and suffixed with unit. -func (c *collector) getName(m metricdata.Metrics) string { +func (c *collector) getName(m metricdata.Metrics, typ *dto.MetricType) string { name := sanitizeName(m.Name) + addCounterSuffix := !c.withoutCounterSuffixes && *typ == dto.MetricType_COUNTER + if addCounterSuffix { + // Remove the _total suffix here, as we will re-add the total suffix + // later, and it needs to come after the unit suffix. + name = strings.TrimSuffix(name, counterSuffix) + } if c.namespace != "" { name = c.namespace + name } - if c.withoutUnits { - return name - } - if suffix, ok := unitSuffixes[m.Unit]; ok && !strings.HasSuffix(name, suffix) { + if suffix, ok := unitSuffixes[m.Unit]; ok && !c.withoutUnits && !strings.HasSuffix(name, suffix) { name += suffix } + if addCounterSuffix { + name += counterSuffix + } return name } @@ -433,27 +440,24 @@ func sanitizeName(n string) string { return b.String() } -func (c *collector) metricTypeAndName(m metricdata.Metrics) (*dto.MetricType, string) { - name := c.getName(m) - +func (c *collector) metricType(m metricdata.Metrics) *dto.MetricType { switch v := m.Data.(type) { case metricdata.Histogram[int64], metricdata.Histogram[float64]: - return dto.MetricType_HISTOGRAM.Enum(), name + return dto.MetricType_HISTOGRAM.Enum() case metricdata.Sum[float64]: - if v.IsMonotonic && !c.withoutCounterSuffixes { - return dto.MetricType_COUNTER.Enum(), name + counterSuffix + if v.IsMonotonic { + return dto.MetricType_COUNTER.Enum() } - return dto.MetricType_GAUGE.Enum(), name + return dto.MetricType_GAUGE.Enum() case metricdata.Sum[int64]: - if v.IsMonotonic && !c.withoutCounterSuffixes { - return dto.MetricType_COUNTER.Enum(), name + counterSuffix + if v.IsMonotonic { + return dto.MetricType_COUNTER.Enum() } - return dto.MetricType_GAUGE.Enum(), name + return dto.MetricType_GAUGE.Enum() case metricdata.Gauge[int64], metricdata.Gauge[float64]: - return dto.MetricType_GAUGE.Enum(), name + return dto.MetricType_GAUGE.Enum() } - - return nil, "" + return nil } func (c *collector) scopeInfo(scope instrumentation.Scope) (prometheus.Metric, error) { diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index 05e03fd9621..3c96983a918 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -100,6 +100,35 @@ func TestPrometheusExporter(t *testing.T) { counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2)) }, }, + { + name: "counter that already has a total suffix", + expectedFile: "testdata/counter.txt", + recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { + opt := otelmetric.WithAttributes( + attribute.Key("A").String("B"), + attribute.Key("C").String("D"), + attribute.Key("E").Bool(true), + attribute.Key("F").Int(42), + ) + counter, err := meter.Float64Counter( + "foo.total", + otelmetric.WithDescription("a simple counter"), + otelmetric.WithUnit("s"), + ) + require.NoError(t, err) + counter.Add(ctx, 5, opt) + counter.Add(ctx, 10.3, opt) + counter.Add(ctx, 9, opt) + + attrs2 := attribute.NewSet( + attribute.Key("A").String("D"), + attribute.Key("C").String("B"), + attribute.Key("E").Bool(true), + attribute.Key("F").Int(42), + ) + counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2)) + }, + }, { name: "counter with suffixes disabled", expectedFile: "testdata/counter_disabled_suffix.txt",