Skip to content

Commit d73235f

Browse files
authored
[mdatagen] use mdatagen to produce component internal telemetry (open-telemetry#10054)
#### Description This updates mdatagen to generate internal telemetry for components based on metadata.yaml configuration. #### Testing Added tests to mdatagen and updated the batch processor to use this as well for synchronous counters and histogram --------- Signed-off-by: Alex Boten <[email protected]>
1 parent 6c2a34e commit d73235f

File tree

11 files changed

+311
-44
lines changed

11 files changed

+311
-44
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
7+
component: mdatagen
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: add ability to use metadata.yaml to automatically generate instruments for components
11+
12+
# One or more tracking issues or pull requests related to the change
13+
issues: [10054]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
The `telemetry` section in metadata.yaml is used to generate
20+
instruments for components to measure telemetry about themselves.
21+
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

cmd/mdatagen/internal/samplereceiver/internal/metadata/generated_telemetry.go

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/mdatagen/internal/samplereceiver/metadata.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,19 @@ metrics:
149149
monotonic: true
150150
aggregation_temporality: cumulative
151151
attributes: [ string_attr, overridden_int_attr, enum_attr, slice_attr, map_attr ]
152+
153+
telemetry:
154+
metrics:
155+
batch_size_trigger_send:
156+
enabled: true
157+
description: Number of times the batch was sent due to a size trigger
158+
unit: 1
159+
sum:
160+
value_type: int
161+
monotonic: true
162+
request_duration:
163+
enabled: true
164+
description: Duration of request
165+
unit: s
166+
histogram:
167+
value_type: double

cmd/mdatagen/loader.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ type metric struct {
117117
Sum *sum `mapstructure:"sum,omitempty"`
118118
// Gauge stores metadata for gauge metric type
119119
Gauge *gauge `mapstructure:"gauge,omitempty"`
120+
// Gauge stores metadata for gauge metric type
121+
Histogram *histogram `mapstructure:"histogram,omitempty"`
120122

121123
// Attributes is the list of attributes that the metric emits.
122124
Attributes []attributeName `mapstructure:"attributes"`
@@ -135,6 +137,9 @@ func (m metric) Data() MetricData {
135137
if m.Gauge != nil {
136138
return m.Gauge
137139
}
140+
if m.Histogram != nil {
141+
return m.Histogram
142+
}
138143
return nil
139144
}
140145

@@ -221,13 +226,19 @@ type tests struct {
221226
ExpectConsumerError bool `mapstructure:"expect_consumer_error"`
222227
}
223228

229+
type telemetry struct {
230+
Metrics map[metricName]metric `mapstructure:"metrics"`
231+
}
232+
224233
type metadata struct {
225234
// Type of the component.
226235
Type string `mapstructure:"type"`
227236
// Type of the parent component (applicable to subcomponents).
228237
Parent string `mapstructure:"parent"`
229238
// Status information for the component.
230239
Status *Status `mapstructure:"status"`
240+
// Telemetry information for the component.
241+
Telemetry telemetry `mapstructure:"telemetry"`
231242
// SemConvVersion is a version number of OpenTelemetry semantic conventions applied to the scraped metrics.
232243
SemConvVersion string `mapstructure:"sem_conv_version"`
233244
// ResourceAttributes that can be emitted by the component.

cmd/mdatagen/loader_test.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,27 @@ func TestLoadMetadata(t *testing.T) {
231231
Attributes: []attributeName{"string_attr", "overridden_int_attr", "enum_attr", "slice_attr", "map_attr"},
232232
},
233233
},
234+
Telemetry: telemetry{
235+
Metrics: map[metricName]metric{
236+
"batch_size_trigger_send": {
237+
Enabled: true,
238+
Description: "Number of times the batch was sent due to a size trigger",
239+
Unit: strPtr("1"),
240+
Sum: &sum{
241+
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeInt},
242+
Mono: Mono{Monotonic: true},
243+
},
244+
},
245+
"request_duration": {
246+
Enabled: true,
247+
Description: "Duration of request",
248+
Unit: strPtr("s"),
249+
Histogram: &histogram{
250+
MetricValueType: MetricValueType{pmetric.NumberDataPointValueTypeDouble},
251+
},
252+
},
253+
},
254+
},
234255
ScopeName: "go.opentelemetry.io/collector/internal/receiver/samplereceiver",
235256
ShortFolderName: "sample",
236257
},
@@ -264,11 +285,6 @@ func TestLoadMetadata(t *testing.T) {
264285
name: "testdata/unknown_value_type.yaml",
265286
wantErr: "1 error(s) decoding:\n\n* error decoding 'metrics[system.cpu.time]': 1 error(s) decoding:\n\n* error decoding 'sum': 1 error(s) decoding:\n\n* error decoding 'value_type': invalid value_type: \"unknown\"",
266287
},
267-
{
268-
name: "testdata/no_aggregation.yaml",
269-
want: metadata{},
270-
wantErr: "1 error(s) decoding:\n\n* error decoding 'metrics[default.metric]': 1 error(s) decoding:\n\n* error decoding 'sum': missing required field: `aggregation_temporality`",
271-
},
272288
{
273289
name: "testdata/invalid_aggregation.yaml",
274290
want: metadata{},

cmd/mdatagen/metadata-schema.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,36 @@ tests:
125125
ignore:
126126
top: [string] # Optional: array of strings representing functions that should be ignore via IgnoreTopFunction
127127
any: [string] # Optional: array of strings representing functions that should be ignore via IgnoreAnyFunction
128+
129+
130+
# Optional: map of metric names with the key being the metric name and value
131+
# being described below.
132+
telemetry:
133+
<metric.name>:
134+
# Required: whether the metric is collected by default.
135+
enabled: bool
136+
# Required: metric description.
137+
description:
138+
# Optional: extended documentation of the metric.
139+
extended_documentation:
140+
# Optional: warnings that will be shown to user under specified conditions.
141+
warnings:
142+
# A warning that will be displayed if the metric is enabled in user config.
143+
# Should be used for deprecated default metrics that will be removed soon.
144+
if_enabled:
145+
# A warning that will be displayed if `enabled` field is not set explicitly in user config.
146+
# Should be used for metrics that will be turned from default to optional or vice versa.
147+
if_enabled_not_set:
148+
# A warning that will be displayed if the metrics is configured by user in any way.
149+
# Should be used for deprecated optional metrics that will be removed soon.
150+
if_configured:
151+
# Required: metric unit as defined by https://ucum.org/ucum.html.
152+
unit:
153+
# Required: metric type with its settings.
154+
<sum|gauge|histogram>:
155+
# Required for sum and gauge metrics: type of number data point values.
156+
value_type: <int|double>
157+
# Required for sum metric: whether the metric is monotonic (no negative delta values).
158+
monotonic: bool
159+
# Optional: array of attributes that were defined in the attributes section that are emitted by this metric.
160+
attributes: [string]

cmd/mdatagen/metricdata.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import (
77
"errors"
88
"fmt"
99

10+
"golang.org/x/text/cases"
11+
"golang.org/x/text/language"
12+
1013
"go.opentelemetry.io/collector/confmap"
1114
"go.opentelemetry.io/collector/pdata/pmetric"
1215
)
1316

1417
var (
1518
_ MetricData = &gauge{}
1619
_ MetricData = &sum{}
20+
_ MetricData = &histogram{}
1721
)
1822

1923
// MetricData is generic interface for all metric datatypes.
@@ -22,6 +26,7 @@ type MetricData interface {
2226
HasMonotonic() bool
2327
HasAggregated() bool
2428
HasMetricInputType() bool
29+
Instrument() string
2530
}
2631

2732
// AggregationTemporality defines a metric aggregation type.
@@ -140,6 +145,10 @@ func (d gauge) HasAggregated() bool {
140145
return false
141146
}
142147

148+
func (d gauge) Instrument() string {
149+
return ""
150+
}
151+
143152
type sum struct {
144153
AggregationTemporality `mapstructure:"aggregation_temporality"`
145154
Mono `mapstructure:",squash"`
@@ -149,9 +158,6 @@ type sum struct {
149158

150159
// Unmarshal is a custom unmarshaler for sum. Needed mostly to avoid MetricValueType.Unmarshal inheritance.
151160
func (d *sum) Unmarshal(parser *confmap.Conf) error {
152-
if !parser.IsSet("aggregation_temporality") {
153-
return errors.New("missing required field: `aggregation_temporality`")
154-
}
155161
if err := d.MetricValueType.Unmarshal(parser); err != nil {
156162
return err
157163
}
@@ -180,3 +186,45 @@ func (d sum) HasMonotonic() bool {
180186
func (d sum) HasAggregated() bool {
181187
return true
182188
}
189+
190+
func (d sum) Instrument() string {
191+
instrumentName := cases.Title(language.English).String(d.MetricValueType.BasicType())
192+
193+
if !d.Monotonic {
194+
instrumentName += "UpDown"
195+
}
196+
instrumentName += "Counter"
197+
return instrumentName
198+
}
199+
200+
type histogram struct {
201+
AggregationTemporality `mapstructure:"aggregation_temporality"`
202+
Mono `mapstructure:",squash"`
203+
MetricValueType `mapstructure:"value_type"`
204+
MetricInputType `mapstructure:",squash"`
205+
}
206+
207+
func (d histogram) Type() string {
208+
return "Histogram"
209+
}
210+
211+
func (d histogram) HasMonotonic() bool {
212+
return true
213+
}
214+
215+
func (d histogram) HasAggregated() bool {
216+
return true
217+
}
218+
219+
func (d histogram) Instrument() string {
220+
instrumentName := cases.Title(language.English).String(d.MetricValueType.BasicType())
221+
return instrumentName + d.Type()
222+
}
223+
224+
// Unmarshal is a custom unmarshaler for histogram. Needed mostly to avoid MetricValueType.Unmarshal inheritance.
225+
func (d *histogram) Unmarshal(parser *confmap.Conf) error {
226+
if err := d.MetricValueType.Unmarshal(parser); err != nil {
227+
return err
228+
}
229+
return parser.Unmarshal(d, confmap.WithIgnoreUnused())
230+
}

cmd/mdatagen/templates/telemetry.go.tmpl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
package {{ .Package }}
44

55
import (
6+
{{- if .Telemetry.Metrics }}
7+
"errors"
8+
{{- end }}
9+
610
"go.opentelemetry.io/collector/component"
711
"go.opentelemetry.io/otel/metric"
812
"go.opentelemetry.io/otel/trace"
@@ -15,3 +19,34 @@ func Meter(settings component.TelemetrySettings) metric.Meter {
1519
func Tracer(settings component.TelemetrySettings) trace.Tracer {
1620
return settings.TracerProvider.Tracer("{{ .ScopeName }}")
1721
}
22+
{{- if .Telemetry.Metrics }}
23+
24+
// TelemetryBuilder provides an interface for components to report telemetry
25+
// as defined in metadata and user config.
26+
type TelemetryBuilder struct {
27+
{{- range $name, $metric := .Telemetry.Metrics }}
28+
{{ $name.Render }} metric.{{ $metric.Data.Instrument }}
29+
{{- end }}
30+
}
31+
32+
// telemetryBuilderOption applies changes to default builder.
33+
type telemetryBuilderOption func(*TelemetryBuilder)
34+
35+
// NewTelemetryBuilder provides a struct with methods to update all internal telemetry
36+
// for a component
37+
func NewTelemetryBuilder(settings component.TelemetrySettings, options ...telemetryBuilderOption) (*TelemetryBuilder, error) {
38+
builder := TelemetryBuilder{}
39+
var err, errs error
40+
meter := Meter(settings)
41+
{{- range $name, $metric := .Telemetry.Metrics }}
42+
builder.{{ $name.Render }}, err = meter.{{ $metric.Data.Instrument }}(
43+
"{{ $name }}",
44+
metric.WithDescription("{{ $metric.Description }}"),
45+
metric.WithUnit("{{ $metric.Unit }}"),
46+
)
47+
errs = errors.Join(errs, err)
48+
{{- end }}
49+
return &builder, errs
50+
}
51+
52+
{{- end }}

0 commit comments

Comments
 (0)