diff --git a/.chloggen/codeboten_optional-internal-metric.yaml b/.chloggen/codeboten_optional-internal-metric.yaml new file mode 100644 index 00000000000..ed776cd9519 --- /dev/null +++ b/.chloggen/codeboten_optional-internal-metric.yaml @@ -0,0 +1,25 @@ +# 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. otlpreceiver) +component: mdatagen + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: add support for optional internal metrics + +# One or more tracking issues or pull requests related to the change +issues: [10316] + +# (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: + +# 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/cmd/mdatagen/internal/samplereceiver/documentation.md b/cmd/mdatagen/internal/samplereceiver/documentation.md index bfd994dd1b5..696344bb1cc 100644 --- a/cmd/mdatagen/internal/samplereceiver/documentation.md +++ b/cmd/mdatagen/internal/samplereceiver/documentation.md @@ -133,6 +133,16 @@ Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalA | ---- | ----------- | ---------- | --------- | | By | Sum | Int | true | +### queue_length + +This metric is optional and therefore not initialized in NewTelemetryBuilder. + +For example this metric only exists if feature A is enabled. + +| Unit | Metric Type | Value Type | +| ---- | ----------- | ---------- | +| 1 | Gauge | Int | + ### request_duration Duration of request diff --git a/cmd/mdatagen/internal/samplereceiver/factory.go b/cmd/mdatagen/internal/samplereceiver/factory.go index f4e425c4c5e..7a3c9bb170d 100644 --- a/cmd/mdatagen/internal/samplereceiver/factory.go +++ b/cmd/mdatagen/internal/samplereceiver/factory.go @@ -32,7 +32,7 @@ func createMetrics(ctx context.Context, set receiver.Settings, _ component.Confi return nil, err } telemetryBuilder.BatchSizeTriggerSend.Add(ctx, 1) - return nopInstance, nil + return nopReceiver{telemetryBuilder: telemetryBuilder}, nil } func createLogs(context.Context, receiver.Settings, component.Config, consumer.Logs) (receiver.Logs, error) { @@ -44,4 +44,9 @@ var nopInstance = &nopReceiver{} type nopReceiver struct { component.StartFunc component.ShutdownFunc + telemetryBuilder *metadata.TelemetryBuilder +} + +func (r nopReceiver) initOptionalMetric() { + _ = r.telemetryBuilder.InitQueueLength(func() int64 { return 1 }) } diff --git a/cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry.go b/cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry.go index 8b34cf0c164..f56e18ba5dc 100644 --- a/cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry.go +++ b/cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry.go @@ -30,6 +30,7 @@ type TelemetryBuilder struct { BatchSizeTriggerSend metric.Int64Counter ProcessRuntimeTotalAllocBytes metric.Int64ObservableCounter observeProcessRuntimeTotalAllocBytes func() int64 + QueueLength metric.Int64ObservableGauge RequestDuration metric.Float64Histogram level configtelemetry.Level attributeSet attribute.Set @@ -59,6 +60,21 @@ func WithProcessRuntimeTotalAllocBytesCallback(cb func() int64) telemetryBuilder } } +// InitQueueLength configures the QueueLength metric. +func (builder *TelemetryBuilder) InitQueueLength(cb func() int64) error { + var err error + builder.QueueLength, err = builder.meter.Int64ObservableGauge( + "queue_length", + metric.WithDescription("This metric is optional and therefore not initialized in NewTelemetryBuilder."), + metric.WithUnit("1"), + metric.WithInt64Callback(func(_ context.Context, o metric.Int64Observer) error { + o.Observe(cb(), metric.WithAttributeSet(builder.attributeSet)) + return nil + }), + ) + return err +} + // NewTelemetryBuilder provides a struct with methods to update all internal telemetry // for a component func NewTelemetryBuilder(settings component.TelemetrySettings, options ...telemetryBuilderOption) (*TelemetryBuilder, error) { diff --git a/cmd/mdatagen/internal/samplereceiver/metadata.yaml b/cmd/mdatagen/internal/samplereceiver/metadata.yaml index 03955d62c0a..1ccfa58b2b4 100644 --- a/cmd/mdatagen/internal/samplereceiver/metadata.yaml +++ b/cmd/mdatagen/internal/samplereceiver/metadata.yaml @@ -174,3 +174,12 @@ telemetry: async: true value_type: int monotonic: true + queue_length: + enabled: true + description: This metric is optional and therefore not initialized in NewTelemetryBuilder. + extended_documentation: For example this metric only exists if feature A is enabled. + unit: 1 + optional: true + gauge: + async: true + value_type: int diff --git a/cmd/mdatagen/internal/samplereceiver/metrics_test.go b/cmd/mdatagen/internal/samplereceiver/metrics_test.go index 3055b1dea31..de8146c12db 100644 --- a/cmd/mdatagen/internal/samplereceiver/metrics_test.go +++ b/cmd/mdatagen/internal/samplereceiver/metrics_test.go @@ -26,7 +26,7 @@ func TestGeneratedMetrics(t *testing.T) { func TestComponentTelemetry(t *testing.T) { tt := setupTestTelemetry() factory := NewFactory() - _, err := factory.CreateMetricsReceiver(context.Background(), tt.NewSettings(), componenttest.NewNopHost(), new(consumertest.MetricsSink)) + receiver, err := factory.CreateMetricsReceiver(context.Background(), tt.NewSettings(), componenttest.NewNopHost(), new(consumertest.MetricsSink)) require.NoError(t, err) tt.assertMetrics(t, []metricdata.Metrics{ { @@ -58,5 +58,51 @@ func TestComponentTelemetry(t *testing.T) { }, }, }) + rcv, ok := receiver.(nopReceiver) + require.True(t, ok) + rcv.initOptionalMetric() + tt.assertMetrics(t, []metricdata.Metrics{ + { + Name: "batch_size_trigger_send", + Description: "Number of times the batch was sent due to a size trigger", + Unit: "1", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + }, + }, + }, + }, + { + Name: "process_runtime_total_alloc_bytes", + Description: "Cumulative bytes allocated for heap objects (see 'go doc runtime.MemStats.TotalAlloc')", + Unit: "By", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: true, + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 2, + }, + }, + }, + }, + { + Name: "queue_length", + Description: "This metric is optional and therefore not initialized in NewTelemetryBuilder.", + Unit: "1", + Data: metricdata.Gauge[int64]{ + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + }, + }, + }, + }, + }) require.NoError(t, tt.Shutdown(context.Background())) + } diff --git a/cmd/mdatagen/loader.go b/cmd/mdatagen/loader.go index a2f1de68419..70a316981c7 100644 --- a/cmd/mdatagen/loader.go +++ b/cmd/mdatagen/loader.go @@ -111,6 +111,10 @@ type metric struct { // be appended to the description used in generated documentation. ExtendedDocumentation string `mapstructure:"extended_documentation"` + // Optional can be used to specify metrics that may + // or may not be present in all cases, depending on configuration. + Optional bool `mapstructure:"optional"` + // Unit of the metric. Unit *string `mapstructure:"unit"` diff --git a/cmd/mdatagen/loader_test.go b/cmd/mdatagen/loader_test.go index bdd8ffeeb37..de7fdc4d37a 100644 --- a/cmd/mdatagen/loader_test.go +++ b/cmd/mdatagen/loader_test.go @@ -263,6 +263,19 @@ func TestLoadMetadata(t *testing.T) { Async: true, }, }, + "queue_length": { + Enabled: true, + Description: "This metric is optional and therefore not initialized in NewTelemetryBuilder.", + ExtendedDocumentation: "For example this metric only exists if feature A is enabled.", + Unit: strPtr("1"), + Optional: true, + Gauge: &gauge{ + MetricValueType: MetricValueType{ + ValueType: pmetric.NumberDataPointValueTypeInt, + }, + Async: true, + }, + }, }, }, ScopeName: "go.opentelemetry.io/collector/internal/receiver/samplereceiver", diff --git a/cmd/mdatagen/metadata-schema.yaml b/cmd/mdatagen/metadata-schema.yaml index 24b5d038d00..999397a45c6 100644 --- a/cmd/mdatagen/metadata-schema.yaml +++ b/cmd/mdatagen/metadata-schema.yaml @@ -140,6 +140,9 @@ telemetry: description: # Optional: extended documentation of the metric. extended_documentation: + # Optional: whether or not this metric is optional. Optional metrics may only be initialized + # if certain features are enabled or configured. + optional: bool # Optional: warnings that will be shown to user under specified conditions. warnings: # A warning that will be displayed if the metric is enabled in user config. diff --git a/cmd/mdatagen/templates/telemetry.go.tmpl b/cmd/mdatagen/templates/telemetry.go.tmpl index 21ed853be04..992df6bf328 100644 --- a/cmd/mdatagen/templates/telemetry.go.tmpl +++ b/cmd/mdatagen/templates/telemetry.go.tmpl @@ -32,7 +32,7 @@ type TelemetryBuilder struct { meter metric.Meter {{- range $name, $metric := .Telemetry.Metrics }} {{ $name.Render }} metric.{{ $metric.Data.Instrument }} - {{- if $metric.Data.Async }} + {{- if and ($metric.Data.Async) (not $metric.Optional) }} observe{{ $name.Render }} func() {{ $metric.Data.BasicType }} {{- end }} {{- end }} @@ -60,6 +60,28 @@ func WithAttributeSet(set attribute.Set) telemetryBuilderOption { {{- end }} {{- range $name, $metric := .Telemetry.Metrics }} + {{- if $metric.Optional }} +// Init{{ $name.Render }} configures the {{ $name.Render }} metric. +func (builder *TelemetryBuilder) Init{{ $name.Render }}({{ if $metric.Data.Async -}}cb func() {{ $metric.Data.BasicType }}{{- end }}) error { + var err error + builder.{{ $name.Render }}, err = builder.meter.{{ $metric.Data.Instrument }}( + "{{ $name }}", + metric.WithDescription("{{ $metric.Description }}"), + metric.WithUnit("{{ $metric.Unit }}"), + {{- if eq $metric.Data.Type "Histogram" -}} + {{ if $metric.Data.Boundaries -}}metric.WithExplicitBucketBoundaries([]float64{ {{- range $metric.Data.Boundaries }} {{.}}, {{- end }} }...),{{- end }} + {{- end }} + {{ if $metric.Data.Async -}} + metric.With{{ casesTitle $metric.Data.BasicType }}Callback(func(_ context.Context, o metric.{{ casesTitle $metric.Data.BasicType }}Observer) error { + o.Observe(cb(), metric.WithAttributeSet(builder.attributeSet)) + return nil + }), + {{- end }} + ) + return err +} + + {{- else }} {{ if $metric.Data.Async -}} // With{{ $name.Render }}Callback sets callback for observable {{ $name.Render }} metric. func With{{ $name.Render }}Callback(cb func() {{ $metric.Data.BasicType }}) telemetryBuilderOption { @@ -68,6 +90,7 @@ func With{{ $name.Render }}Callback(cb func() {{ $metric.Data.BasicType }}) tele } } {{- end }} + {{- end }} {{- end }} @@ -86,6 +109,7 @@ func NewTelemetryBuilder(settings component.TelemetrySettings, options ...teleme } {{- range $name, $metric := .Telemetry.Metrics }} + {{- if not $metric.Optional }} builder.{{ $name.Render }}, err = builder.meter.{{ $metric.Data.Instrument }}( "{{ $name }}", metric.WithDescription("{{ $metric.Description }}"), @@ -102,6 +126,7 @@ func NewTelemetryBuilder(settings component.TelemetrySettings, options ...teleme ) errs = errors.Join(errs, err) {{- end }} + {{- end }} return &builder, errs }