Skip to content

Commit

Permalink
[mdatagen] generate utility code to test telemetry (open-telemetry#10212
Browse files Browse the repository at this point in the history
)

This will allow components to more easily test the telemetry they should
be emitting.

---------

Signed-off-by: Alex Boten <[email protected]>
  • Loading branch information
codeboten authored May 27, 2024
1 parent 12d0cde commit 2940226
Show file tree
Hide file tree
Showing 22 changed files with 641 additions and 24 deletions.
25 changes: 25 additions & 0 deletions .chloggen/codeboten_add-mdatagen-testutility.yaml
Original file line number Diff line number Diff line change
@@ -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: auto-generate utilities to test component telemetry

# One or more tracking issues or pull requests related to the change
issues: [19783]

# (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: []
29 changes: 15 additions & 14 deletions cmd/mdatagen/embeded_templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ func TestEnsureTemplatesLoaded(t *testing.T) {

var (
templateFiles = map[string]struct{}{
path.Join(rootDir, "component_test.go.tmpl"): {},
path.Join(rootDir, "documentation.md.tmpl"): {},
path.Join(rootDir, "metrics.go.tmpl"): {},
path.Join(rootDir, "metrics_test.go.tmpl"): {},
path.Join(rootDir, "resource.go.tmpl"): {},
path.Join(rootDir, "resource_test.go.tmpl"): {},
path.Join(rootDir, "config.go.tmpl"): {},
path.Join(rootDir, "config_test.go.tmpl"): {},
path.Join(rootDir, "package_test.go.tmpl"): {},
path.Join(rootDir, "readme.md.tmpl"): {},
path.Join(rootDir, "status.go.tmpl"): {},
path.Join(rootDir, "telemetry.go.tmpl"): {},
path.Join(rootDir, "telemetry_test.go.tmpl"): {},
path.Join(rootDir, "testdata", "config.yaml.tmpl"): {},
path.Join(rootDir, "component_test.go.tmpl"): {},
path.Join(rootDir, "component_telemetry_test.go.tmpl"): {},
path.Join(rootDir, "documentation.md.tmpl"): {},
path.Join(rootDir, "metrics.go.tmpl"): {},
path.Join(rootDir, "metrics_test.go.tmpl"): {},
path.Join(rootDir, "resource.go.tmpl"): {},
path.Join(rootDir, "resource_test.go.tmpl"): {},
path.Join(rootDir, "config.go.tmpl"): {},
path.Join(rootDir, "config_test.go.tmpl"): {},
path.Join(rootDir, "package_test.go.tmpl"): {},
path.Join(rootDir, "readme.md.tmpl"): {},
path.Join(rootDir, "status.go.tmpl"): {},
path.Join(rootDir, "telemetry.go.tmpl"): {},
path.Join(rootDir, "telemetry_test.go.tmpl"): {},
path.Join(rootDir, "testdata", "config.yaml.tmpl"): {},
}
count = 0
)
Expand Down
2 changes: 1 addition & 1 deletion cmd/mdatagen/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
go.opentelemetry.io/collector/receiver v0.101.0
go.opentelemetry.io/collector/semconv v0.101.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/zap v1.27.0
Expand Down Expand Up @@ -46,7 +47,6 @@ require (
go.opentelemetry.io/otel v1.27.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
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
7 changes: 6 additions & 1 deletion cmd/mdatagen/internal/samplereceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ func createTraces(context.Context, receiver.CreateSettings, component.Config, co
return nopInstance, nil
}

func createMetrics(context.Context, receiver.CreateSettings, component.Config, consumer.Metrics) (receiver.Metrics, error) {
func createMetrics(ctx context.Context, set receiver.CreateSettings, _ component.Config, _ consumer.Metrics) (receiver.Metrics, error) {
telemetryBuilder, err := metadata.NewTelemetryBuilder(set.TelemetrySettings, metadata.WithProcessRuntimeTotalAllocBytesCallback(func() int64 { return 2 }))
if err != nil {
return nil, err
}
telemetryBuilder.BatchSizeTriggerSend.Add(ctx, 1)
return nopInstance, nil
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions cmd/mdatagen/internal/samplereceiver/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
package samplereceiver

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"

"go.opentelemetry.io/collector/cmd/mdatagen/internal/samplereceiver/internal/metadata"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/receiver/receivertest"
)

Expand All @@ -18,3 +22,41 @@ func TestGeneratedMetrics(t *testing.T) {
m := mb.Emit()
require.Equal(t, 0, m.ResourceMetrics().Len())
}

func TestComponentTelemetry(t *testing.T) {
tt := setupTestTelemetry()
factory := NewFactory()
_, err := factory.CreateMetricsReceiver(context.Background(), tt.NewCreateSettings(), componenttest.NewNopHost(), new(consumertest.MetricsSink))
require.NoError(t, err)
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,
},
},
},
},
})
require.NoError(t, tt.Shutdown(context.Background()))
}
6 changes: 5 additions & 1 deletion cmd/mdatagen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func run(ymlPath string) error {
return fmt.Errorf("unable to create output directory %q: %w", codeDir, err)
}
if md.Status != nil {
if md.Status.Class != "cmd" && md.Status.Class != "pkg" {
if md.Status.Class != "cmd" && md.Status.Class != "pkg" && !md.Status.NotComponent {
if err = generateFile(filepath.Join(tmplDir, "status.go.tmpl"),
filepath.Join(codeDir, "generated_status.go"), md, "metadata"); err != nil {
return err
Expand Down Expand Up @@ -88,6 +88,10 @@ func run(ymlPath string) error {
toGenerate := map[string]string{}

if len(md.Telemetry.Metrics) != 0 { // if there are telemetry metrics, generate telemetry specific files
if err = generateFile(filepath.Join(tmplDir, "component_telemetry_test.go.tmpl"),
filepath.Join(ymlDir, "generated_component_telemetry_test.go"), md, packageName); err != nil {
return err
}
toGenerate[filepath.Join(tmplDir, "telemetry.go.tmpl")] = filepath.Join(codeDir, "generated_telemetry.go")
toGenerate[filepath.Join(tmplDir, "telemetry_test.go.tmpl")] = filepath.Join(codeDir, "generated_telemetry_test.go")
}
Expand Down
1 change: 1 addition & 0 deletions cmd/mdatagen/statusdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Status struct {
Warnings []string `mapstructure:"warnings"`
Codeowners *Codeowners `mapstructure:"codeowners"`
UnsupportedPlatforms []string `mapstructure:"unsupported_platforms"`
NotComponent bool `mapstructure:"not_component"`
}

func (s *Status) SortedDistributions() []string {
Expand Down
76 changes: 76 additions & 0 deletions cmd/mdatagen/templates/component_telemetry_test.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Code generated by mdatagen. DO NOT EDIT.

package {{ if isCommand -}}main{{ else }}{{ .Package }}{{- end }}

import (
"context"
"testing"

"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
"go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/{{ .Status.Class }}"
"go.opentelemetry.io/collector/{{ .Status.Class }}/{{ .Status.Class }}test"
)

type componentTestTelemetry struct {
reader *sdkmetric.ManualReader
meterProvider *sdkmetric.MeterProvider
}

func (tt *componentTestTelemetry) NewCreateSettings() {{ .Status.Class }}.CreateSettings {
settings := {{ .Status.Class }}test.NewNopCreateSettings()
settings.MeterProvider = tt.meterProvider
settings.ID = component.NewID(component.MustNewType("{{ .Type }}"))

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)
}
3 changes: 3 additions & 0 deletions cmd/mdatagen/templates/telemetry_test.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/metric"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/metricdata"
embeddedmetric "go.opentelemetry.io/otel/metric/embedded"
noopmetric "go.opentelemetry.io/otel/metric/noop"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -75,4 +77,5 @@ func TestNewTelemetryBuilder(t *testing.T) {
require.NoError(t, err)
require.True(t, applied)
}

{{- end }}
Loading

0 comments on commit 2940226

Please sign in to comment.