Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WithoutCounterSuffixes option in the prometheus exporter #4306

Merged
merged 1 commit into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add `PeriodicReader` struct in `go.opentelemetry.io/otel/sdk/metric`. (#4244)
- Add `Exporter` struct in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#4272)
- Add `Exporter` struct in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#4272)
- Add `WithoutCounterSuffixes` option in `go.opentelemetry.io/otel/exporters/prometheus` to disable addition of `_total` suffixes. (#TODO)
pellared marked this conversation as resolved.
Show resolved Hide resolved

### Changed

Expand Down
26 changes: 20 additions & 6 deletions exporters/prometheus/config.go
Expand Up @@ -24,12 +24,13 @@ import (

// config contains options for the exporter.
type config struct {
registerer prometheus.Registerer
disableTargetInfo bool
withoutUnits bool
aggregation metric.AggregationSelector
disableScopeInfo bool
namespace string
registerer prometheus.Registerer
disableTargetInfo bool
withoutUnits bool
withoutCounterSuffixes bool
aggregation metric.AggregationSelector
disableScopeInfo bool
namespace string
}

// newConfig creates a validated config configured with options.
Expand Down Expand Up @@ -110,6 +111,19 @@ func WithoutUnits() Option {
})
}

// WithoutUnits disables exporter's addition _total suffixes on counters.
dashpole marked this conversation as resolved.
Show resolved Hide resolved
//
// By default, metric names include a _total suffix to follow Prometheus naming
// conventions. For example, the counter metric happy.people would become
// happy_people_total. With this option set, the name would instead be
// happy_people.
func WithoutCounterSuffixes() Option {
return optionFunc(func(cfg config) config {
cfg.withoutCounterSuffixes = true
return cfg
})
}

// WithoutScopeInfo configures the Exporter to not export the otel_scope_info metric.
// If not specified, the Exporter will create a otel_scope_info metric containing
// the metrics' Instrumentation Scope, and also add labels about Instrumentation Scope to all metric points.
Expand Down
28 changes: 15 additions & 13 deletions exporters/prometheus/exporter.go
Expand Up @@ -59,9 +59,10 @@ var _ metric.Reader = &Exporter{}
type collector struct {
reader metric.Reader

withoutUnits bool
disableScopeInfo bool
namespace string
withoutUnits bool
withoutCounterSuffixes bool
disableScopeInfo bool
namespace string

mu sync.Mutex // mu protects all members below from the concurrent access.
disableTargetInfo bool
Expand All @@ -70,7 +71,7 @@ type collector struct {
metricFamilies map[string]*dto.MetricFamily
}

// prometheus counters MUST have a _total suffix:
// prometheus counters MUST have a _total suffix by default:
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/compatibility/prometheus_and_openmetrics.md
const counterSuffix = "_total"

Expand All @@ -84,13 +85,14 @@ func New(opts ...Option) (*Exporter, error) {
reader := metric.NewManualReader(cfg.manualReaderOptions()...)

collector := &collector{
reader: reader,
disableTargetInfo: cfg.disableTargetInfo,
withoutUnits: cfg.withoutUnits,
disableScopeInfo: cfg.disableScopeInfo,
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
metricFamilies: make(map[string]*dto.MetricFamily),
namespace: cfg.namespace,
reader: reader,
disableTargetInfo: cfg.disableTargetInfo,
withoutUnits: cfg.withoutUnits,
withoutCounterSuffixes: cfg.withoutCounterSuffixes,
disableScopeInfo: cfg.disableScopeInfo,
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
metricFamilies: make(map[string]*dto.MetricFamily),
namespace: cfg.namespace,
}

if err := cfg.registerer.Register(collector); err != nil {
Expand Down Expand Up @@ -387,12 +389,12 @@ func (c *collector) metricTypeAndName(m metricdata.Metrics) (*dto.MetricType, st
case metricdata.Histogram[int64], metricdata.Histogram[float64]:
return dto.MetricType_HISTOGRAM.Enum(), name
case metricdata.Sum[float64]:
if v.IsMonotonic {
if v.IsMonotonic && !c.withoutCounterSuffixes {
return dto.MetricType_COUNTER.Enum(), name + counterSuffix
}
return dto.MetricType_GAUGE.Enum(), name
case metricdata.Sum[int64]:
if v.IsMonotonic {
if v.IsMonotonic && !c.withoutCounterSuffixes {
return dto.MetricType_COUNTER.Enum(), name + counterSuffix
}
return dto.MetricType_GAUGE.Enum(), name
Expand Down
30 changes: 30 additions & 0 deletions exporters/prometheus/exporter_test.go
Expand Up @@ -71,6 +71,36 @@ func TestPrometheusExporter(t *testing.T) {
counter.Add(ctx, 5, otelmetric.WithAttributeSet(attrs2))
},
},
{
name: "counter with suffixes disabled",
expectedFile: "testdata/counter_disabled_suffix.txt",
options: []Option{WithoutCounterSuffixes()},
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",
otelmetric.WithDescription("a simple counter without a total suffix"),
otelmetric.WithUnit("ms"),
)
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: "gauge",
expectedFile: "testdata/gauge.txt",
Expand Down
10 changes: 10 additions & 0 deletions exporters/prometheus/testdata/counter_disabled_suffix.txt
@@ -0,0 +1,10 @@
# HELP foo_milliseconds a simple counter without a total suffix
# TYPE foo_milliseconds counter
foo_milliseconds{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3
foo_milliseconds{A="D",C="B",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 5
# HELP otel_scope_info Instrumentation Scope metadata
# TYPE otel_scope_info gauge
otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1
# HELP target_info Target metadata
# TYPE target_info gauge
target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1