From 98d0d63ba77d40d4d8af1543f9c41020d1571294 Mon Sep 17 00:00:00 2001 From: Edwin Date: Wed, 16 Aug 2023 20:34:59 +0100 Subject: [PATCH] [cmd/telemetrygen] Add status code to cli argument (#24673) **Description:** Adding command line argument `--status-code` to `telemetrygen traces`, which accepts `(Unset,Error,Ok)` (case sensitive) or the enum equivalent of `(0,1,2)`. Running ```shell telemetrygen traces --otlp-insecure --traces 1 --status-code 1 ``` against a minimal local collector yields ```txt 2023-07-29T21:27:57.862+0100 info ResourceSpans #0 Resource SchemaURL: https://opentelemetry.io/schemas/1.4.0 Resource attributes: -> service.name: Str(telemetrygen) ScopeSpans #0 ScopeSpans SchemaURL: InstrumentationScope telemetrygen Span #0 Trace ID : f6dc4be32c78b9999c69d504a79e68c1 Parent ID : 4e2cd6e0e90cf2ea ID : 20835413e32d26a5 Name : okey-dokey Kind : Server Start time : 2023-07-29 20:27:57.861602 +0000 UTC End time : 2023-07-29 20:27:57.861726 +0000 UTC Status code : Error Status message : Attributes: -> net.peer.ip: Str(1.2.3.4) -> peer.service: Str(telemetrygen-client) Span #1 Trace ID : f6dc4be32c78b9999c69d504a79e68c1 Parent ID : ID : 4e2cd6e0e90cf2ea Name : lets-go Kind : Client Start time : 2023-07-29 20:27:57.861584 +0000 UTC End time : 2023-07-29 20:27:57.861726 +0000 UTC Status code : Error Status message : Attributes: -> net.peer.ip: Str(1.2.3.4) -> peer.service: Str(telemetrygen-server) ``` and similarly (the string version) ```shell telemetrygen traces --otlp-insecure --traces 1 --status-code '"Ok"' ``` produces ```txt Resource SchemaURL: https://opentelemetry.io/schemas/1.4.0 Resource attributes: -> service.name: Str(telemetrygen) ScopeSpans #0 ScopeSpans SchemaURL: InstrumentationScope telemetrygen Span #0 Trace ID : dfd830da170acfe567b12f87685d7917 Parent ID : 8e15b390dc6a1ccc ID : 165c300130532072 Name : okey-dokey Kind : Server Start time : 2023-07-29 20:29:16.026965 +0000 UTC End time : 2023-07-29 20:29:16.027089 +0000 UTC Status code : Ok Status message : Attributes: -> net.peer.ip: Str(1.2.3.4) -> peer.service: Str(telemetrygen-client) Span #1 Trace ID : dfd830da170acfe567b12f87685d7917 Parent ID : ID : 8e15b390dc6a1ccc Name : lets-go Kind : Client Start time : 2023-07-29 20:29:16.026956 +0000 UTC End time : 2023-07-29 20:29:16.027089 +0000 UTC Status code : Ok Status message : Attributes: -> net.peer.ip: Str(1.2.3.4) -> peer.service: Str(telemetrygen-server) ``` The default is `Unset` which is the current behaviour. **Link to tracking Issue:** 24286 **Testing:** Added unit tests which covers both valid and invalid inputs. **Documentation:** Command line arguments are self documenting via the usage info in the flag. Co-authored-by: Pablo Baeyens --- ...-24286-add-flag-for-trace-status-code.yaml | 20 ++++++++ cmd/telemetrygen/internal/traces/config.go | 2 + cmd/telemetrygen/internal/traces/traces.go | 10 ++++ cmd/telemetrygen/internal/traces/worker.go | 4 ++ .../internal/traces/worker_test.go | 50 +++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 .chloggen/issue-24286-add-flag-for-trace-status-code.yaml diff --git a/.chloggen/issue-24286-add-flag-for-trace-status-code.yaml b/.chloggen/issue-24286-add-flag-for-trace-status-code.yaml new file mode 100644 index 000000000000..3b64eb09eaa3 --- /dev/null +++ b/.chloggen/issue-24286-add-flag-for-trace-status-code.yaml @@ -0,0 +1,20 @@ +# Use this changelog template to create an entry for release notes. +# If your change doesn't affect end users, such as a test fix or a tooling change, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. + +# 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 cli flag --status-code for trace generation + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [24286] + +# (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: diff --git a/cmd/telemetrygen/internal/traces/config.go b/cmd/telemetrygen/internal/traces/config.go index 0579175e1ebf..2a8dbc77b53b 100644 --- a/cmd/telemetrygen/internal/traces/config.go +++ b/cmd/telemetrygen/internal/traces/config.go @@ -15,6 +15,7 @@ type Config struct { NumTraces int PropagateContext bool ServiceName string + StatusCode string Batch bool LoadSize int } @@ -25,6 +26,7 @@ func (c *Config) Flags(fs *pflag.FlagSet) { fs.IntVar(&c.NumTraces, "traces", 1, "Number of traces to generate in each worker (ignored if duration is provided)") fs.BoolVar(&c.PropagateContext, "marshal", false, "Whether to marshal trace context via HTTP headers") fs.StringVar(&c.ServiceName, "service", "telemetrygen", "Service name to use") + fs.StringVar(&c.StatusCode, "status-code", "Unset", "Status code to use for the spans, one of (Unset, Error, Ok) or the equivalent integer (0,1,2)") fs.BoolVar(&c.Batch, "batch", true, "Whether to batch traces") fs.IntVar(&c.LoadSize, "size", 0, "Desired minimum size in MB of string data for each trace generated. This can be used to test traces with large payloads, i.e. when testing the OTLP receiver endpoint max receive size.") } diff --git a/cmd/telemetrygen/internal/traces/traces.go b/cmd/telemetrygen/internal/traces/traces.go index 5b6011fd6457..a7e6cb8ce866 100644 --- a/cmd/telemetrygen/internal/traces/traces.go +++ b/cmd/telemetrygen/internal/traces/traces.go @@ -12,6 +12,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" @@ -121,6 +122,14 @@ func Run(c *Config, logger *zap.Logger) error { logger.Info("generation of traces is limited", zap.Float64("per-second", float64(limit))) } + var statusCode codes.Code + if c.StatusCode == "" { + statusCode = codes.Unset + } else { + if err := statusCode.UnmarshalJSON([]byte(c.StatusCode)); err != nil { + return fmt.Errorf("expected `status-code` to be one of (Unset, Error, Ok) or (0, 1, 2), got %q instead", c.StatusCode) + } + } wg := sync.WaitGroup{} running := &atomic.Bool{} @@ -131,6 +140,7 @@ func Run(c *Config, logger *zap.Logger) error { w := worker{ numTraces: c.NumTraces, propagateContext: c.PropagateContext, + statusCode: statusCode, limitPerSecond: limit, totalDuration: c.TotalDuration, running: running, diff --git a/cmd/telemetrygen/internal/traces/worker.go b/cmd/telemetrygen/internal/traces/worker.go index 4921c8ffd24f..b5c9794608bb 100644 --- a/cmd/telemetrygen/internal/traces/worker.go +++ b/cmd/telemetrygen/internal/traces/worker.go @@ -12,6 +12,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.opentelemetry.io/otel/trace" @@ -23,6 +24,7 @@ type worker struct { running *atomic.Bool // pointer to shared flag that indicates it's time to stop the test numTraces int // how many traces the worker has to generate (only when duration==0) propagateContext bool // whether the worker needs to propagate the trace context via HTTP headers + statusCode codes.Code // the status code set for the child and parent spans totalDuration time.Duration // how long to run the test for (overrides `numTraces`) limitPerSecond rate.Limit // how many spans per second to generate wg *sync.WaitGroup // notify when done @@ -75,7 +77,9 @@ func (w worker) simulateTraces() { } opt := trace.WithTimestamp(time.Now().Add(fakeSpanDuration)) + child.SetStatus(w.statusCode, "") child.End(opt) + sp.SetStatus(w.statusCode, "") sp.End(opt) i++ diff --git a/cmd/telemetrygen/internal/traces/worker_test.go b/cmd/telemetrygen/internal/traces/worker_test.go index e8b8aa18c136..d6f2ffed0c0d 100644 --- a/cmd/telemetrygen/internal/traces/worker_test.go +++ b/cmd/telemetrygen/internal/traces/worker_test.go @@ -5,12 +5,14 @@ package traces import ( "context" + "fmt" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" @@ -123,6 +125,54 @@ func TestSpanKind(t *testing.T) { } } +func TestSpanStatuses(t *testing.T) { + tests := []struct { + inputStatus string + spanStatus codes.Code + validInput bool + }{ + {inputStatus: `"Unset"`, spanStatus: codes.Unset, validInput: true}, + {inputStatus: `"Error"`, spanStatus: codes.Error, validInput: true}, + {inputStatus: `"Ok"`, spanStatus: codes.Ok, validInput: true}, + {inputStatus: `0`, spanStatus: codes.Unset, validInput: true}, + {inputStatus: `1`, spanStatus: codes.Error, validInput: true}, + {inputStatus: `2`, spanStatus: codes.Ok, validInput: true}, + {inputStatus: `"Foo"`, spanStatus: codes.Unset, validInput: false}, + {inputStatus: `"UNSET"`, spanStatus: codes.Unset, validInput: false}, + {inputStatus: `-1`, spanStatus: codes.Unset, validInput: false}, + {inputStatus: `3`, spanStatus: codes.Unset, validInput: false}, + } + + for _, tt := range tests { + syncer := &mockSyncer{} + + tracerProvider := sdktrace.NewTracerProvider() + sp := sdktrace.NewSimpleSpanProcessor(syncer) + tracerProvider.RegisterSpanProcessor(sp) + otel.SetTracerProvider(tracerProvider) + + cfg := &Config{ + Config: common.Config{ + WorkerCount: 1, + }, + NumTraces: 1, + StatusCode: tt.inputStatus, + } + + // test the program given input, including erroneous inputs + if tt.validInput { + require.NoError(t, Run(cfg, zap.NewNop())) + // verify that the default the span status is set as expected + for _, span := range syncer.spans { + assert.Equal(t, span.Status().Code, tt.spanStatus, fmt.Sprintf("span status: %v and expected status %v", span.Status().Code, tt.spanStatus)) + } + } else { + require.Error(t, Run(cfg, zap.NewNop())) + } + } + +} + var _ sdktrace.SpanExporter = (*mockSyncer)(nil) type mockSyncer struct {