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 {