From 9eb533a5a72261a3e802d9c3893bffedc651229d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraci=20Paix=C3=A3o=20Kr=C3=B6hling?= Date: Fri, 31 May 2024 11:42:55 +0200 Subject: [PATCH 1/2] [cmd/telemetrygen] Add exemplars support for telemetrygen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Juraci Paixão Kröhling --- cmd/telemetrygen/internal/common/validate.go | 38 +++++++++ .../internal/common/validate_test.go | 67 +++++++++++++++ cmd/telemetrygen/internal/logs/config.go | 19 +---- cmd/telemetrygen/internal/logs/config_test.go | 66 --------------- cmd/telemetrygen/internal/logs/logs.go | 7 -- cmd/telemetrygen/internal/metrics/config.go | 23 ++++++ cmd/telemetrygen/internal/metrics/metrics.go | 31 +++++++ .../internal/metrics/metrics_test.go | 81 +++++++++++++++++++ cmd/telemetrygen/internal/metrics/worker.go | 21 ++--- 9 files changed, 256 insertions(+), 97 deletions(-) create mode 100644 cmd/telemetrygen/internal/common/validate.go create mode 100644 cmd/telemetrygen/internal/common/validate_test.go delete mode 100644 cmd/telemetrygen/internal/logs/config_test.go create mode 100644 cmd/telemetrygen/internal/metrics/metrics_test.go diff --git a/cmd/telemetrygen/internal/common/validate.go b/cmd/telemetrygen/internal/common/validate.go new file mode 100644 index 000000000000..6440b6c6d65d --- /dev/null +++ b/cmd/telemetrygen/internal/common/validate.go @@ -0,0 +1,38 @@ +package common + +import ( + "encoding/hex" + "fmt" +) + +var ( + errInvalidTraceIDLenght = fmt.Errorf("TraceID must be a 32 character hex string, like: 'ae87dadd90e9935a4bc9660628efd569'") + errInvalidSpanIDLenght = fmt.Errorf("SpanID must be a 16 character hex string, like: '5828fa4960140870'") + errInvalidTraceID = fmt.Errorf("failed to create traceID byte array from the given traceID, make sure the traceID is a hex representation of a [16]byte, like: 'ae87dadd90e9935a4bc9660628efd569'") + errInvalidSpanID = fmt.Errorf("failed to create SpanID byte array from the given SpanID, make sure the SpanID is a hex representation of a [8]byte, like: '5828fa4960140870'") +) + +func ValidateTraceID(traceID string) error { + if len(traceID) != 32 { + return errInvalidTraceIDLenght + } + + _, err := hex.DecodeString(traceID) + if err != nil { + return errInvalidTraceID + } + + return nil +} + +func ValidateSpanID(spanID string) error { + if len(spanID) != 16 { + return errInvalidSpanIDLenght + } + _, err := hex.DecodeString(spanID) + if err != nil { + return errInvalidSpanID + } + + return nil +} diff --git a/cmd/telemetrygen/internal/common/validate_test.go b/cmd/telemetrygen/internal/common/validate_test.go new file mode 100644 index 000000000000..234c3db238b8 --- /dev/null +++ b/cmd/telemetrygen/internal/common/validate_test.go @@ -0,0 +1,67 @@ +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateTraceID(t *testing.T) { + tests := []struct { + name string + traceID string + expected error + }{ + { + name: "Valid", + traceID: "ae87dadd90e9935a4bc9660628efd569", + expected: nil, + }, + { + name: "InvalidLength", + traceID: "invalid-length", + expected: errInvalidTraceIDLenght, + }, + { + name: "InvalidTraceID", + traceID: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", + expected: errInvalidTraceID, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateTraceID(tt.traceID) + assert.ErrorIs(t, err, tt.expected) + }) + } +} + +func TestValidateSpanID(t *testing.T) { + tests := []struct { + name string + spanID string + expected error + }{ + { + name: "Valid", + spanID: "5828fa4960140870", + expected: nil, + }, + { + name: "InvalidLength", + spanID: "invalid-length", + expected: errInvalidSpanIDLenght, + }, + { + name: "InvalidTraceID", + spanID: "zzzzzzzzzzzzzzzz", + expected: errInvalidSpanID, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSpanID(tt.spanID) + assert.ErrorIs(t, err, tt.expected) + }) + } +} diff --git a/cmd/telemetrygen/internal/logs/config.go b/cmd/telemetrygen/internal/logs/config.go index 323026d573ec..6a619b51223b 100644 --- a/cmd/telemetrygen/internal/logs/config.go +++ b/cmd/telemetrygen/internal/logs/config.go @@ -4,8 +4,6 @@ package logs import ( - "encoding/hex" - "github.com/spf13/pflag" "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen/internal/common" @@ -39,23 +37,14 @@ func (c *Config) Flags(fs *pflag.FlagSet) { // Validate validates the test scenario parameters. func (c *Config) Validate() error { if c.TraceID != "" { - if len(c.TraceID) != 32 { - return errInvalidTraceIDLenght - } - - _, err := hex.DecodeString(c.TraceID) - if err != nil { - return errInvalidTraceID + if err := common.ValidateTraceID(c.TraceID); err != nil { + return err } } if c.SpanID != "" { - if len(c.SpanID) != 16 { - return errInvalidSpanIDLenght - } - _, err := hex.DecodeString(c.SpanID) - if err != nil { - return errInvalidSpanID + if err := common.ValidateSpanID(c.SpanID); err != nil { + return err } } diff --git a/cmd/telemetrygen/internal/logs/config_test.go b/cmd/telemetrygen/internal/logs/config_test.go deleted file mode 100644 index dc8e2361a1eb..000000000000 --- a/cmd/telemetrygen/internal/logs/config_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package logs - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestValidate(t *testing.T) { - tests := []struct { - name string - config *Config - expected error - }{ - { - name: "ValidConfig", - config: &Config{ - TraceID: "ae87dadd90e9935a4bc9660628efd569", - SpanID: "5828fa4960140870", - }, - expected: nil, - }, - { - name: "InvalidInvalidTraceIDLenght", - config: &Config{ - TraceID: "invalid-length", - SpanID: "5828fa4960140870", - }, - expected: errInvalidTraceIDLenght, - }, - { - name: "InvalidInvalidSpanIDLenght", - config: &Config{ - TraceID: "ae87dadd90e9935a4bc9660628efd569", - SpanID: "invalid-length", - }, - expected: errInvalidSpanIDLenght, - }, - { - name: "InvalidTraceID", - config: &Config{ - TraceID: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - SpanID: "5828fa4960140870", - }, - expected: errInvalidTraceID, - }, - { - name: "InvalidSpanID", - config: &Config{ - TraceID: "ae87dadd90e9935a4bc9660628efd569", - SpanID: "zzzzzzzzzzzzzzzz", - }, - expected: errInvalidSpanID, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.config.Validate() - assert.ErrorIs(t, err, tt.expected) - }) - } -} diff --git a/cmd/telemetrygen/internal/logs/logs.go b/cmd/telemetrygen/internal/logs/logs.go index c33f7c622529..2f0d1cd6b306 100644 --- a/cmd/telemetrygen/internal/logs/logs.go +++ b/cmd/telemetrygen/internal/logs/logs.go @@ -18,13 +18,6 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/cmd/telemetrygen/internal/common" ) -var ( - errInvalidTraceIDLenght = fmt.Errorf("TraceID must be a 32 character hex string, like: 'ae87dadd90e9935a4bc9660628efd569'") - errInvalidSpanIDLenght = fmt.Errorf("SpanID must be a 16 character hex string, like: '5828fa4960140870'") - errInvalidTraceID = fmt.Errorf("failed to create traceID byte array from the given traceID, make sure the traceID is a hex representation of a [16]byte, like: 'ae87dadd90e9935a4bc9660628efd569'") - errInvalidSpanID = fmt.Errorf("failed to create SpanID byte array from the given SpanID, make sure the SpanID is a hex representation of a [8]byte, like: '5828fa4960140870'") -) - // Start starts the log telemetry generator func Start(cfg *Config) error { logger, err := common.CreateLogger(cfg.SkipSettingGRPCLogger) diff --git a/cmd/telemetrygen/internal/metrics/config.go b/cmd/telemetrygen/internal/metrics/config.go index 9cb7f5c3816b..cab4633d28fb 100644 --- a/cmd/telemetrygen/internal/metrics/config.go +++ b/cmd/telemetrygen/internal/metrics/config.go @@ -15,6 +15,8 @@ type Config struct { NumMetrics int MetricName string MetricType metricType + SpanID string + TraceID string } // Flags registers config flags. @@ -29,4 +31,25 @@ func (c *Config) Flags(fs *pflag.FlagSet) { fs.Var(&c.MetricType, "metric-type", "Metric type enum. must be one of 'Gauge' or 'Sum'") fs.IntVar(&c.NumMetrics, "metrics", 1, "Number of metrics to generate in each worker (ignored if duration is provided)") + + fs.StringVar(&c.TraceID, "trace-id", "", "TraceID to use as exemplar") + fs.StringVar(&c.SpanID, "span-id", "", "SpanID to use as exemplar") + +} + +// Validate validates the test scenario parameters. +func (c *Config) Validate() error { + if c.TraceID != "" { + if err := common.ValidateTraceID(c.TraceID); err != nil { + return err + } + } + + if c.SpanID != "" { + if err := common.ValidateSpanID(c.SpanID); err != nil { + return err + } + } + + return nil } diff --git a/cmd/telemetrygen/internal/metrics/metrics.go b/cmd/telemetrygen/internal/metrics/metrics.go index 8fa2862103f7..837f8f6ee711 100644 --- a/cmd/telemetrygen/internal/metrics/metrics.go +++ b/cmd/telemetrygen/internal/metrics/metrics.go @@ -5,6 +5,7 @@ package metrics import ( "context" + "encoding/hex" "fmt" "sync" "sync/atomic" @@ -14,6 +15,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" "go.uber.org/zap" "golang.org/x/time/rate" @@ -95,6 +97,7 @@ func Run(c *Config, exp func() (sdkmetric.Exporter, error), logger *zap.Logger) numMetrics: c.NumMetrics, metricName: c.MetricName, metricType: c.MetricType, + exemplars: exemplarsFromConfig(c), limitPerSecond: limit, totalDuration: c.TotalDuration, running: running, @@ -112,3 +115,31 @@ func Run(c *Config, exp func() (sdkmetric.Exporter, error), logger *zap.Logger) wg.Wait() return nil } + +func exemplarsFromConfig(c *Config) []metricdata.Exemplar[int64] { + if c.TraceID != "" || c.SpanID != "" { + var exemplars []metricdata.Exemplar[int64] + + exemplar := metricdata.Exemplar[int64]{ + Value: 1, + Time: time.Now(), + } + + if c.TraceID != "" { + // we validated this already during the Validate() function for config + // nolint: errcheck + traceID, _ := hex.DecodeString(c.TraceID) + exemplar.TraceID = traceID + } + + if c.SpanID != "" { + // we validated this already during the Validate() function for config + // nolint: errcheck + spanID, _ := hex.DecodeString(c.SpanID) + exemplar.SpanID = spanID + } + + return append(exemplars, exemplar) + } + return nil +} diff --git a/cmd/telemetrygen/internal/metrics/metrics_test.go b/cmd/telemetrygen/internal/metrics/metrics_test.go new file mode 100644 index 000000000000..e779c7e5b4b0 --- /dev/null +++ b/cmd/telemetrygen/internal/metrics/metrics_test.go @@ -0,0 +1,81 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package metrics + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" +) + +func Test_exemplarsFromConfig(t *testing.T) { + traceID, err := hex.DecodeString("ae87dadd90e9935a4bc9660628efd569") + require.NoError(t, err) + + spanID, err := hex.DecodeString("5828fa4960140870") + require.NoError(t, err) + + tests := []struct { + name string + c *Config + validateFunc func(t *testing.T, got []metricdata.Exemplar[int64]) + }{ + { + name: "no exemplars", + c: &Config{}, + validateFunc: func(t *testing.T, got []metricdata.Exemplar[int64]) { + assert.Nil(t, got) + }, + }, + { + name: "both-traceID-and-spanID", + c: &Config{ + TraceID: "ae87dadd90e9935a4bc9660628efd569", + SpanID: "5828fa4960140870", + }, + validateFunc: func(t *testing.T, got []metricdata.Exemplar[int64]) { + require.Len(t, got, 1) + metricdatatest.AssertEqual[metricdata.Exemplar[int64]](t, got[0], metricdata.Exemplar[int64]{ + TraceID: traceID, + SpanID: spanID, + }, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + { + name: "only-traceID", + c: &Config{ + TraceID: "ae87dadd90e9935a4bc9660628efd569", + }, + validateFunc: func(t *testing.T, got []metricdata.Exemplar[int64]) { + require.Len(t, got, 1) + metricdatatest.AssertEqual[metricdata.Exemplar[int64]](t, got[0], metricdata.Exemplar[int64]{ + TraceID: traceID, + SpanID: nil, + }, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + { + name: "only-spanID", + c: &Config{ + SpanID: "5828fa4960140870", + }, + validateFunc: func(t *testing.T, got []metricdata.Exemplar[int64]) { + require.Len(t, got, 1) + metricdatatest.AssertEqual[metricdata.Exemplar[int64]](t, got[0], metricdata.Exemplar[int64]{ + TraceID: nil, + SpanID: spanID, + }, metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.validateFunc(t, exemplarsFromConfig(tt.c)) + }) + } +} diff --git a/cmd/telemetrygen/internal/metrics/worker.go b/cmd/telemetrygen/internal/metrics/worker.go index 30fb35787b8b..da3abf140aa1 100644 --- a/cmd/telemetrygen/internal/metrics/worker.go +++ b/cmd/telemetrygen/internal/metrics/worker.go @@ -18,15 +18,16 @@ import ( ) type worker struct { - running *atomic.Bool // pointer to shared flag that indicates it's time to stop the test - metricName string // name of metric to generate - metricType metricType // type of metric to generate - numMetrics int // how many metrics the worker has to generate (only when duration==0) - totalDuration time.Duration // how long to run the test for (overrides `numMetrics`) - limitPerSecond rate.Limit // how many metrics per second to generate - wg *sync.WaitGroup // notify when done - logger *zap.Logger // logger - index int // worker index + running *atomic.Bool // pointer to shared flag that indicates it's time to stop the test + metricName string // name of metric to generate + metricType metricType // type of metric to generate + exemplars []metricdata.Exemplar[int64] // exemplars to attach to the metric + numMetrics int // how many metrics the worker has to generate (only when duration==0) + totalDuration time.Duration // how long to run the test for (overrides `numMetrics`) + limitPerSecond rate.Limit // how many metrics per second to generate + wg *sync.WaitGroup // notify when done + logger *zap.Logger // logger + index int // worker index } func (w worker) simulateMetrics(res *resource.Resource, exporterFunc func() (sdkmetric.Exporter, error), signalAttrs []attribute.KeyValue) { @@ -59,6 +60,7 @@ func (w worker) simulateMetrics(res *resource.Resource, exporterFunc func() (sdk Time: time.Now(), Value: i, Attributes: attribute.NewSet(signalAttrs...), + Exemplars: w.exemplars, }, }, }, @@ -75,6 +77,7 @@ func (w worker) simulateMetrics(res *resource.Resource, exporterFunc func() (sdk Time: time.Now(), Value: i, Attributes: attribute.NewSet(signalAttrs...), + Exemplars: w.exemplars, }, }, }, From ece1ef7ddf34bec7ee1cb49885756df6e4f6f3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraci=20Paix=C3=A3o=20Kr=C3=B6hling?= Date: Fri, 31 May 2024 11:47:11 +0200 Subject: [PATCH 2/2] lint, licenses, changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Juraci Paixão Kröhling --- ...traceid-spanid-exemplars-telemetrygen.yaml | 27 +++++++++++++++++++ cmd/telemetrygen/internal/common/validate.go | 3 +++ .../internal/common/validate_test.go | 3 +++ 3 files changed, 33 insertions(+) create mode 100644 .chloggen/add-traceid-spanid-exemplars-telemetrygen.yaml diff --git a/.chloggen/add-traceid-spanid-exemplars-telemetrygen.yaml b/.chloggen/add-traceid-spanid-exemplars-telemetrygen.yaml new file mode 100644 index 000000000000..835ba8795fd9 --- /dev/null +++ b/.chloggen/add-traceid-spanid-exemplars-telemetrygen.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: cmd/telemetrygen + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add support for adding spanID and traceID as exemplars to datapoints generated by telemetrygen + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [33320] + +# (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/cmd/telemetrygen/internal/common/validate.go b/cmd/telemetrygen/internal/common/validate.go index 6440b6c6d65d..19a70ba230f2 100644 --- a/cmd/telemetrygen/internal/common/validate.go +++ b/cmd/telemetrygen/internal/common/validate.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package common import ( diff --git a/cmd/telemetrygen/internal/common/validate_test.go b/cmd/telemetrygen/internal/common/validate_test.go index 234c3db238b8..62e199725a00 100644 --- a/cmd/telemetrygen/internal/common/validate_test.go +++ b/cmd/telemetrygen/internal/common/validate_test.go @@ -1,3 +1,6 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + package common import (