diff --git a/exporter/prometheusremotewriteexporter/documentation.md b/exporter/prometheusremotewriteexporter/documentation.md new file mode 100644 index 000000000000..dc8d0cdf5998 --- /dev/null +++ b/exporter/prometheusremotewriteexporter/documentation.md @@ -0,0 +1,23 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# prometheusremotewrite + +## Internal Telemetry + +The following telemetry is emitted by this component. + +### exporter_prometheusremotewrite_failed_translations + +Number of translation operations that failed to translate metrics from Otel to Prometheus + +| Unit | Metric Type | Value Type | Monotonic | +| ---- | ----------- | ---------- | --------- | +| 1 | Sum | Int | true | + +### exporter_prometheusremotewrite_translated_time_series + +Number of Prometheus time series that were translated from OTel metrics + +| Unit | Metric Type | Value Type | Monotonic | +| ---- | ----------- | ---------- | --------- | +| 1 | Sum | Int | true | diff --git a/exporter/prometheusremotewriteexporter/exporter.go b/exporter/prometheusremotewriteexporter/exporter.go index 15118976624e..1e2c22908a24 100644 --- a/exporter/prometheusremotewriteexporter/exporter.go +++ b/exporter/prometheusremotewriteexporter/exporter.go @@ -41,17 +41,16 @@ type prwTelemetry interface { } type prwTelemetryOtel struct { - failedTranslations metric.Int64Counter - translatedTimeSeries metric.Int64Counter - otelAttrs []attribute.KeyValue + telemetryBuilder *metadata.TelemetryBuilder + otelAttrs []attribute.KeyValue } func (p *prwTelemetryOtel) recordTranslationFailure(ctx context.Context) { - p.failedTranslations.Add(ctx, 1, metric.WithAttributes(p.otelAttrs...)) + p.telemetryBuilder.ExporterPrometheusremotewriteFailedTranslations.Add(ctx, 1, metric.WithAttributes(p.otelAttrs...)) } func (p *prwTelemetryOtel) recordTranslatedTimeSeries(ctx context.Context, numTS int) { - p.translatedTimeSeries.Add(ctx, int64(numTS), metric.WithAttributes(p.otelAttrs...)) + p.telemetryBuilder.ExporterPrometheusremotewriteTranslatedTimeSeries.Add(ctx, int64(numTS), metric.WithAttributes(p.otelAttrs...)) } // prwExporter converts OTLP metrics to Prometheus remote write TimeSeries and sends them to a remote endpoint. @@ -73,27 +72,17 @@ type prwExporter struct { } func newPRWTelemetry(set exporter.CreateSettings) (prwTelemetry, error) { - - meter := metadata.Meter(set.TelemetrySettings) - // TODO: create helper functions similar to the processor helper: BuildCustomMetricName - prefix := "exporter/" + metadata.Type.String() + "/" - failedTranslations, errFailedTranslation := meter.Int64Counter(prefix+"failed_translations", - metric.WithDescription("Number of translation operations that failed to translate metrics from Otel to Prometheus"), - metric.WithUnit("1"), - ) - - translatedTimeSeries, errTranslatedMetrics := meter.Int64Counter(prefix+"translated_time_series", - metric.WithDescription("Number of Prometheus time series that were translated from OTel metrics"), - metric.WithUnit("1"), - ) + telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings) + if err != nil { + return nil, err + } return &prwTelemetryOtel{ - failedTranslations: failedTranslations, - translatedTimeSeries: translatedTimeSeries, + telemetryBuilder: telemetryBuilder, otelAttrs: []attribute.KeyValue{ attribute.String("exporter", set.ID.String()), }, - }, errors.Join(errFailedTranslation, errTranslatedMetrics) + }, nil } // newPRWExporter initializes a new prwExporter instance and sets fields accordingly. diff --git a/exporter/prometheusremotewriteexporter/exporter_test.go b/exporter/prometheusremotewriteexporter/exporter_test.go index c1dbaafe758e..f0db9029f75d 100644 --- a/exporter/prometheusremotewriteexporter/exporter_test.go +++ b/exporter/prometheusremotewriteexporter/exporter_test.go @@ -29,6 +29,8 @@ import ( "go.opentelemetry.io/collector/exporter/exporterhelper" "go.opentelemetry.io/collector/exporter/exportertest" "go.opentelemetry.io/collector/pdata/pmetric" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric/metricdata" "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/testdata" ) @@ -372,19 +374,6 @@ func runExportPipeline(ts *prompb.TimeSeries, endpoint *url.URL) error { return prwe.handleExport(context.Background(), testmap, nil) } -type mockPRWTelemetry struct { - failedTranslations int - translatedTimeSeries int -} - -func (m *mockPRWTelemetry) recordTranslationFailure(_ context.Context) { - m.failedTranslations++ -} - -func (m *mockPRWTelemetry) recordTranslatedTimeSeries(_ context.Context, numTs int) { - m.translatedTimeSeries += numTs -} - // Test_PushMetrics checks the number of TimeSeries received by server and the number of metrics dropped is the same as // expected func Test_PushMetrics(t *testing.T) { @@ -697,7 +686,6 @@ func Test_PushMetrics(t *testing.T) { } t.Run(tt.name, func(t *testing.T) { t.Parallel() - mockTelemetry := &mockPRWTelemetry{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if tt.reqTestFunc != nil { tt.reqTestFunc(t, r, tt.expectedTimeSeries, tt.isStaleMarker) @@ -744,11 +732,11 @@ func Test_PushMetrics(t *testing.T) { Description: "OpenTelemetry Collector", Version: "1.0", } - set := exportertest.NewNopCreateSettings() + tel := setupTestTelemetry() + set := tel.NewCreateSettings() set.BuildInfo = buildInfo prwe, nErr := newPRWExporter(cfg, set) - prwe.telemetry = mockTelemetry require.NoError(t, nErr) ctx, cancel := context.WithCancel(context.Background()) @@ -762,9 +750,41 @@ func Test_PushMetrics(t *testing.T) { assert.Error(t, err) return } + expectedMetrics := []metricdata.Metrics{} + if tt.expectedFailedTranslations > 0 { + expectedMetrics = append(expectedMetrics, metricdata.Metrics{ + Name: "exporter_prometheusremotewrite_failed_translations", + Description: "Number of translation operations that failed to translate metrics from Otel to Prometheus", + Unit: "1", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: int64(tt.expectedFailedTranslations), + Attributes: attribute.NewSet(attribute.String("exporter", "prometheusremotewrite")), + }, + }, + }, + }) + } - assert.Equal(t, tt.expectedFailedTranslations, mockTelemetry.failedTranslations) - assert.Equal(t, tt.expectedTimeSeries, mockTelemetry.translatedTimeSeries) + expectedMetrics = append(expectedMetrics, metricdata.Metrics{ + Name: "exporter_prometheusremotewrite_translated_time_series", + Description: "Number of Prometheus time series that were translated from OTel metrics", + Unit: "1", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: int64(tt.expectedTimeSeries), + Attributes: attribute.NewSet(attribute.String("exporter", "prometheusremotewrite")), + }, + }, + }, + }) + tel.assertMetrics(t, expectedMetrics) assert.NoError(t, err) }) } diff --git a/exporter/prometheusremotewriteexporter/generated_component_telemetry_test.go b/exporter/prometheusremotewriteexporter/generated_component_telemetry_test.go new file mode 100644 index 000000000000..78082bf04706 --- /dev/null +++ b/exporter/prometheusremotewriteexporter/generated_component_telemetry_test.go @@ -0,0 +1,76 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package prometheusremotewriteexporter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/exporter/exportertest" +) + +type componentTestTelemetry struct { + reader *sdkmetric.ManualReader + meterProvider *sdkmetric.MeterProvider +} + +func (tt *componentTestTelemetry) NewCreateSettings() exporter.CreateSettings { + settings := exportertest.NewNopCreateSettings() + settings.MeterProvider = tt.meterProvider + settings.ID = component.NewID(component.MustNewType("prometheusremotewrite")) + + return settings +} + +func setupTestTelemetry() componentTestTelemetry { + reader := sdkmetric.NewManualReader() + return componentTestTelemetry{ + reader: reader, + meterProvider: sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)), + } +} + +func (tt *componentTestTelemetry) assertMetrics(t *testing.T, expected []metricdata.Metrics) { + var md metricdata.ResourceMetrics + require.NoError(t, tt.reader.Collect(context.Background(), &md)) + // ensure all required metrics are present + for _, want := range expected { + got := tt.getMetric(want.Name, md) + metricdatatest.AssertEqual(t, want, got, metricdatatest.IgnoreTimestamp()) + } + + // ensure no additional metrics are emitted + require.Equal(t, len(expected), tt.len(md)) +} + +func (tt *componentTestTelemetry) getMetric(name string, got metricdata.ResourceMetrics) metricdata.Metrics { + for _, sm := range got.ScopeMetrics { + for _, m := range sm.Metrics { + if m.Name == name { + return m + } + } + } + + return metricdata.Metrics{} +} + +func (tt *componentTestTelemetry) len(got metricdata.ResourceMetrics) int { + metricsCount := 0 + for _, sm := range got.ScopeMetrics { + metricsCount += len(sm.Metrics) + } + + return metricsCount +} + +func (tt *componentTestTelemetry) Shutdown(ctx context.Context) error { + return tt.meterProvider.Shutdown(ctx) +} diff --git a/exporter/prometheusremotewriteexporter/go.mod b/exporter/prometheusremotewriteexporter/go.mod index 5793a3c2c0d9..c6c69abc2781 100644 --- a/exporter/prometheusremotewriteexporter/go.mod +++ b/exporter/prometheusremotewriteexporter/go.mod @@ -26,6 +26,7 @@ require ( go.opentelemetry.io/collector/pdata v1.8.1-0.20240529223953-eaab76e46d38 go.opentelemetry.io/otel v1.27.0 go.opentelemetry.io/otel/metric v1.27.0 + go.opentelemetry.io/otel/sdk/metric v1.27.0 go.opentelemetry.io/otel/trace v1.27.0 go.uber.org/goleak v1.3.0 go.uber.org/multierr v1.11.0 @@ -73,7 +74,6 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect go.opentelemetry.io/otel/exporters/prometheus v0.49.0 // indirect go.opentelemetry.io/otel/sdk v1.27.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.27.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry.go b/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry.go index aebe7b79d49b..bb4540e77242 100644 --- a/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry.go +++ b/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry.go @@ -3,6 +3,8 @@ package metadata import ( + "errors" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" @@ -15,3 +17,37 @@ func Meter(settings component.TelemetrySettings) metric.Meter { func Tracer(settings component.TelemetrySettings) trace.Tracer { return settings.TracerProvider.Tracer("otelcol/prometheusremotewrite") } + +// TelemetryBuilder provides an interface for components to report telemetry +// as defined in metadata and user config. +type TelemetryBuilder struct { + ExporterPrometheusremotewriteFailedTranslations metric.Int64Counter + ExporterPrometheusremotewriteTranslatedTimeSeries metric.Int64Counter +} + +// telemetryBuilderOption applies changes to default builder. +type telemetryBuilderOption func(*TelemetryBuilder) + +// NewTelemetryBuilder provides a struct with methods to update all internal telemetry +// for a component +func NewTelemetryBuilder(settings component.TelemetrySettings, options ...telemetryBuilderOption) (*TelemetryBuilder, error) { + builder := TelemetryBuilder{} + for _, op := range options { + op(&builder) + } + var err, errs error + meter := Meter(settings) + builder.ExporterPrometheusremotewriteFailedTranslations, err = meter.Int64Counter( + "exporter_prometheusremotewrite_failed_translations", + metric.WithDescription("Number of translation operations that failed to translate metrics from Otel to Prometheus"), + metric.WithUnit("1"), + ) + errs = errors.Join(errs, err) + builder.ExporterPrometheusremotewriteTranslatedTimeSeries, err = meter.Int64Counter( + "exporter_prometheusremotewrite_translated_time_series", + metric.WithDescription("Number of Prometheus time series that were translated from OTel metrics"), + metric.WithUnit("1"), + ) + errs = errors.Join(errs, err) + return &builder, errs +} diff --git a/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry_test.go b/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry_test.go index 379567277162..bfcc2f51f273 100644 --- a/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry_test.go +++ b/exporter/prometheusremotewriteexporter/internal/metadata/generated_telemetry_test.go @@ -61,3 +61,16 @@ func TestProviders(t *testing.T) { require.Fail(t, "returned Meter not mockTracer") } } + +func TestNewTelemetryBuilder(t *testing.T) { + set := component.TelemetrySettings{ + MeterProvider: mockMeterProvider{}, + TracerProvider: mockTracerProvider{}, + } + applied := false + _, err := NewTelemetryBuilder(set, func(b *TelemetryBuilder) { + applied = true + }) + require.NoError(t, err) + require.True(t, applied) +} diff --git a/exporter/prometheusremotewriteexporter/metadata.yaml b/exporter/prometheusremotewriteexporter/metadata.yaml index 2bd409f97907..492aaacc1d66 100644 --- a/exporter/prometheusremotewriteexporter/metadata.yaml +++ b/exporter/prometheusremotewriteexporter/metadata.yaml @@ -10,4 +10,21 @@ status: active: [Aneurysm9, rapphil] tests: - expect_consumer_error: true \ No newline at end of file + expect_consumer_error: true + +telemetry: + metrics: + exporter_prometheusremotewrite_failed_translations: + enabled: true + description: Number of translation operations that failed to translate metrics from Otel to Prometheus + unit: 1 + sum: + value_type: int + monotonic: true + exporter_prometheusremotewrite_translated_time_series: + enabled: true + description: Number of Prometheus time series that were translated from OTel metrics + unit: 1 + sum: + value_type: int + monotonic: true