diff --git a/.chloggen/codeboten_mdatagen-statsd.yaml b/.chloggen/codeboten_mdatagen-statsd.yaml new file mode 100644 index 000000000000..dde0f5b50c02 --- /dev/null +++ b/.chloggen/codeboten_mdatagen-statsd.yaml @@ -0,0 +1,27 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: statsdreceiver + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: update statsd receiver to use mdatagen + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [33524] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# If your change doesn't affect end users or the exported elements of any package, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/receiver/statsdreceiver/documentation.md b/receiver/statsdreceiver/documentation.md new file mode 100644 index 000000000000..6980e07b7f60 --- /dev/null +++ b/receiver/statsdreceiver/documentation.md @@ -0,0 +1,15 @@ +[comment]: <> (Code generated by mdatagen. DO NOT EDIT.) + +# statsd + +## Internal Telemetry + +The following telemetry is emitted by this component. + +### receiver_received_statsd_metrics + +Number of statsd metrics received. + +| Unit | Metric Type | Value Type | Monotonic | +| ---- | ----------- | ---------- | --------- | +| 1 | Sum | Int | true | diff --git a/receiver/statsdreceiver/generated_component_telemetry_test.go b/receiver/statsdreceiver/generated_component_telemetry_test.go new file mode 100644 index 000000000000..5b9d5acc2fef --- /dev/null +++ b/receiver/statsdreceiver/generated_component_telemetry_test.go @@ -0,0 +1,76 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package statsdreceiver + +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/receiver" + "go.opentelemetry.io/collector/receiver/receivertest" +) + +type componentTestTelemetry struct { + reader *sdkmetric.ManualReader + meterProvider *sdkmetric.MeterProvider +} + +func (tt *componentTestTelemetry) NewSettings() receiver.Settings { + settings := receivertest.NewNopSettings() + settings.MeterProvider = tt.meterProvider + settings.ID = component.NewID(component.MustNewType("statsd")) + + 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/receiver/statsdreceiver/go.mod b/receiver/statsdreceiver/go.mod index 79ea74340508..bad39eb3a64c 100644 --- a/receiver/statsdreceiver/go.mod +++ b/receiver/statsdreceiver/go.mod @@ -10,6 +10,7 @@ require ( go.opentelemetry.io/collector v0.102.2-0.20240611143128-7dfb57b9ad1c go.opentelemetry.io/collector/component v0.102.2-0.20240611143128-7dfb57b9ad1c go.opentelemetry.io/collector/config/confignet v0.102.2-0.20240611143128-7dfb57b9ad1c + go.opentelemetry.io/collector/config/configtelemetry v0.102.2-0.20240611143128-7dfb57b9ad1c go.opentelemetry.io/collector/confmap v0.102.2-0.20240611143128-7dfb57b9ad1c go.opentelemetry.io/collector/consumer v0.102.2-0.20240611143128-7dfb57b9ad1c go.opentelemetry.io/collector/pdata v1.9.1-0.20240611143128-7dfb57b9ad1c @@ -17,6 +18,7 @@ require ( go.opentelemetry.io/collector/semconv v0.102.2-0.20240611143128-7dfb57b9ad1c 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 @@ -47,11 +49,9 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.54.0 // indirect github.com/prometheus/procfs v0.15.0 // indirect - go.opentelemetry.io/collector/config/configtelemetry v0.102.2-0.20240611143128-7dfb57b9ad1c // indirect go.opentelemetry.io/collector/featuregate v1.9.1-0.20240611143128-7dfb57b9ad1c // 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.16.0 // indirect diff --git a/receiver/statsdreceiver/internal/metadata/generated_telemetry.go b/receiver/statsdreceiver/internal/metadata/generated_telemetry.go index 1721283e63d1..9db5f2d91b29 100644 --- a/receiver/statsdreceiver/internal/metadata/generated_telemetry.go +++ b/receiver/statsdreceiver/internal/metadata/generated_telemetry.go @@ -3,9 +3,14 @@ package metadata import ( - "go.opentelemetry.io/collector/component" + "errors" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/noop" "go.opentelemetry.io/otel/trace" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configtelemetry" ) func Meter(settings component.TelemetrySettings) metric.Meter { @@ -15,3 +20,43 @@ func Meter(settings component.TelemetrySettings) metric.Meter { func Tracer(settings component.TelemetrySettings) trace.Tracer { return settings.TracerProvider.Tracer("otelcol/statsdreceiver") } + +// TelemetryBuilder provides an interface for components to report telemetry +// as defined in metadata and user config. +type TelemetryBuilder struct { + meter metric.Meter + ReceiverReceivedStatsdMetrics metric.Int64Counter + level configtelemetry.Level +} + +// telemetryBuilderOption applies changes to default builder. +type telemetryBuilderOption func(*TelemetryBuilder) + +// WithLevel sets the current telemetry level for the component. +func WithLevel(lvl configtelemetry.Level) telemetryBuilderOption { + return func(builder *TelemetryBuilder) { + builder.level = lvl + } +} + +// 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{level: configtelemetry.LevelBasic} + for _, op := range options { + op(&builder) + } + var err, errs error + if builder.level >= configtelemetry.LevelBasic { + builder.meter = Meter(settings) + } else { + builder.meter = noop.Meter{} + } + builder.ReceiverReceivedStatsdMetrics, err = builder.meter.Int64Counter( + "receiver_received_statsd_metrics", + metric.WithDescription("Number of statsd metrics received."), + metric.WithUnit("1"), + ) + errs = errors.Join(errs, err) + return &builder, errs +} diff --git a/receiver/statsdreceiver/internal/metadata/generated_telemetry_test.go b/receiver/statsdreceiver/internal/metadata/generated_telemetry_test.go index bdbef08eeb99..4084b48cf4a0 100644 --- a/receiver/statsdreceiver/internal/metadata/generated_telemetry_test.go +++ b/receiver/statsdreceiver/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/receiver/statsdreceiver/metadata.yaml b/receiver/statsdreceiver/metadata.yaml index e24c0521dc83..63011408a9f8 100644 --- a/receiver/statsdreceiver/metadata.yaml +++ b/receiver/statsdreceiver/metadata.yaml @@ -8,3 +8,13 @@ status: distributions: [contrib] codeowners: active: [jmacd, dmitryax] + +telemetry: + metrics: + receiver_received_statsd_metrics: + enabled: true + description: Number of statsd metrics received. + unit: "1" + sum: + value_type: int + monotonic: true diff --git a/receiver/statsdreceiver/reporter.go b/receiver/statsdreceiver/reporter.go index 28d4ec836d27..3583ed91f899 100644 --- a/receiver/statsdreceiver/reporter.go +++ b/receiver/statsdreceiver/reporter.go @@ -17,10 +17,10 @@ import ( // reporter struct implements the transport.Reporter interface to give consistent // observability per Collector metric observability package. type reporter struct { - logger *zap.Logger - sugaredLogger *zap.SugaredLogger // Used for generic debug logging - receiverAttr attribute.KeyValue - receivedCount metric.Int64Counter + logger *zap.Logger + sugaredLogger *zap.SugaredLogger // Used for generic debug logging + receiverAttr attribute.KeyValue + telemetryBuilder *metadata.TelemetryBuilder } var ( @@ -29,19 +29,15 @@ var ( ) func newReporter(set receiver.Settings) (*reporter, error) { - receivedCount, err := metadata.Meter(set.TelemetrySettings).Int64Counter( - "receiver/received_statsd_metrics", - metric.WithDescription("Number of statsd metrics received."), - metric.WithUnit("1"), - ) + telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings) if err != nil { return nil, err } return &reporter{ - logger: set.Logger, - sugaredLogger: set.Logger.Sugar(), - receiverAttr: attribute.String("receiver", set.ID.String()), - receivedCount: receivedCount, + logger: set.Logger, + sugaredLogger: set.Logger.Sugar(), + receiverAttr: attribute.String("receiver", set.ID.String()), + telemetryBuilder: telemetryBuilder, }, nil } @@ -52,7 +48,7 @@ func (r *reporter) OnDebugf(template string, args ...any) { } func (r *reporter) RecordParseFailure() { - r.receivedCount.Add( + r.telemetryBuilder.ReceiverReceivedStatsdMetrics.Add( context.Background(), 1, metric.WithAttributes( @@ -62,7 +58,7 @@ func (r *reporter) RecordParseFailure() { } func (r *reporter) RecordParseSuccess(count int64) { - r.receivedCount.Add( + r.telemetryBuilder.ReceiverReceivedStatsdMetrics.Add( context.Background(), count, metric.WithAttributes(