From 74d6a1250ee5c9db6aa776510927e36fe5869681 Mon Sep 17 00:00:00 2001 From: Yuri Shkuro Date: Sun, 29 Jan 2023 15:16:44 -0500 Subject: [PATCH] [tracegen] Switch from Jaeger Client to OTEL SDK (#4189) ## Which problem is this PR solving? - Part of migrating away from deprecated Jaeger Go SDK ## Short description of the changes - switch tracegen from Jaeger Client to OTEL SDK - a breaking change in some respect: - default exporter changed to Jaeger HTTP - different environment variables are used to configure exporters - debug and firehose flags are no longer set in the trace context, but as span attributes - no more metrics from tracegen Signed-off-by: Yuri Shkuro --- cmd/tracegen/README.md | 14 ++--- cmd/tracegen/docker-compose.yml | 7 ++- cmd/tracegen/main.go | 79 +++++++++++++++------------ internal/tracegen/config.go | 23 ++++---- internal/tracegen/worker.go | 97 ++++++++++++++++----------------- 5 files changed, 116 insertions(+), 104 deletions(-) diff --git a/cmd/tracegen/README.md b/cmd/tracegen/README.md index b9e8af7466c..904a52c73ac 100644 --- a/cmd/tracegen/README.md +++ b/cmd/tracegen/README.md @@ -9,12 +9,12 @@ The binary is available from the Releases page, as well as a Docker image: $ docker run jaegertracing/jaeger-tracegen -service abcd -traces 10 ``` -Notice, however, that by default the generator uses the UDP exporter of `jaeger-client-go`, -which sends data to `localhost`, i.e. inside the networking namespace of the container itself, -which obviously doesn't go anywhere. You can use the environment variables supported by -[jaeger-client-go][env] to instruct the SDK where to send the data, for example to switch -to HTTP by setting `JAEGER_ENDPOINT`. +The generator can be configured to export traces in different formats, via `-exporter` flag. +By default, the exporters send data to `localhost`. If running in a container, this refers +to the networking namespace of the container itself, so to export to another container +(like Jaeger Collector), the exporters need to be provided with appropriate location. +Exporters accept configuration via environment variables: + * Jaeger exporter: see https://github.com/open-telemetry/opentelemetry-go/blob/main/exporters/jaeger/README.md + * OTLP exporter: see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md See example in the included [docker-compose](./docker-compose.yml) file. - -[env]: https://github.com/jaegertracing/jaeger-client-go#environment-variables diff --git a/cmd/tracegen/docker-compose.yml b/cmd/tracegen/docker-compose.yml index c43220e573b..e4105261726 100644 --- a/cmd/tracegen/docker-compose.yml +++ b/cmd/tracegen/docker-compose.yml @@ -3,14 +3,17 @@ version: '2' services: jaeger: image: jaegertracing/all-in-one:latest + environment: + - COLLECTOR_OTLP_ENABLED=true ports: - '16686:16686' + - '4318:4318' tracegen: image: jaegertracing/jaeger-tracegen:latest environment: - - JAEGER_AGENT_HOST=jaeger - - JAEGER_AGENT_PORT=6831 + - OTEL_EXPORTER_JAEGER_ENDPOINT=http://jaeger:14268/api/traces + - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=jaeger:4318 command: ["-duration", "10s", "-workers", "3", "-pause", "250ms"] depends_on: - jaeger diff --git a/cmd/tracegen/main.go b/cmd/tracegen/main.go index dea2740d416..a76e538dcca 100644 --- a/cmd/tracegen/main.go +++ b/cmd/tracegen/main.go @@ -15,15 +15,19 @@ package main import ( + "context" "flag" - "net/http" - "time" + "fmt" - "github.com/opentracing/opentracing-go" - "github.com/prometheus/client_golang/prometheus/promhttp" - jaegerConfig "github.com/uber/jaeger-client-go/config" - jaegerZap "github.com/uber/jaeger-client-go/log/zap" - "github.com/uber/jaeger-lib/metrics/prometheus" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.uber.org/zap" "github.com/jaegertracing/jaeger/internal/tracegen" @@ -37,38 +41,43 @@ func main() { cfg.Flags(fs) flag.Parse() - metricsFactory := prometheus.New() - traceCfg := &jaegerConfig.Configuration{ - ServiceName: cfg.Service, - Sampler: &jaegerConfig.SamplerConfig{ - Type: "const", - Param: 1, - }, - RPCMetrics: true, - } - traceCfg, err := traceCfg.FromEnv() - if err != nil { - logger.Fatal("failed to read tracer configuration", zap.Error(err)) - } + otel.SetTextMapPropagator(propagation.TraceContext{}) - tracer, tCloser, err := traceCfg.NewTracer( - jaegerConfig.Metrics(metricsFactory), - jaegerConfig.Logger(jaegerZap.NewLogger(logger)), - ) + exp, err := createOtelExporter(cfg.TraceExporter) if err != nil { - logger.Fatal("failed to create tracer", zap.Error(err)) + logger.Sugar().Fatalf("cannot create trace exporter %s: %w", cfg.TraceExporter, err) } - defer tCloser.Close() + logger.Sugar().Infof("using %s trace exporter", cfg.TraceExporter) - opentracing.InitGlobalTracer(tracer) - logger.Info("Initialized global tracer") - - http.Handle("/metrics", promhttp.Handler()) - go http.ListenAndServe(":2112", nil) - logger.Info("Initialized Prometheus endpoint at 2112") + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(cfg.Service), + )), + ) + defer tp.Shutdown(context.Background()) - tracegen.Run(cfg, logger) + tracegen.Run(cfg, tp.Tracer("tracegen"), logger) +} - logger.Info("Waiting 1.5sec for metrics to flush") - time.Sleep(3 * time.Second / 2) +func createOtelExporter(exporterType string) (sdktrace.SpanExporter, error) { + var exporter sdktrace.SpanExporter + var err error + switch exporterType { + case "jaeger": + exporter, err = jaeger.New( + jaeger.WithCollectorEndpoint(), + ) + case "otlp": + client := otlptracehttp.NewClient( + otlptracehttp.WithInsecure(), + ) + exporter, err = otlptrace.New(context.Background(), client) + case "stdout": + exporter, err = stdouttrace.New() + default: + return nil, fmt.Errorf("unrecognized exporter type %s", exporterType) + } + return exporter, err } diff --git a/internal/tracegen/config.go b/internal/tracegen/config.go index 8b906ff691b..3198967f9a5 100644 --- a/internal/tracegen/config.go +++ b/internal/tracegen/config.go @@ -21,35 +21,37 @@ import ( "sync/atomic" "time" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) // Config describes the test scenario. type Config struct { - Workers int - Traces int - Marshal bool - Debug bool - Firehose bool - Pause time.Duration - Duration time.Duration - Service string + Workers int + Traces int + Marshal bool + Debug bool + Firehose bool + Pause time.Duration + Duration time.Duration + Service string + TraceExporter string } // Flags registers config flags. func (c *Config) Flags(fs *flag.FlagSet) { fs.IntVar(&c.Workers, "workers", 1, "Number of workers (goroutines) to run") fs.IntVar(&c.Traces, "traces", 1, "Number of traces to generate in each worker (ignored if duration is provided") - fs.BoolVar(&c.Marshal, "marshal", false, "Whether to marshal trace context via HTTP headers") fs.BoolVar(&c.Debug, "debug", false, "Whether to set DEBUG flag on the spans to force sampling") fs.BoolVar(&c.Firehose, "firehose", false, "Whether to set FIREHOSE flag on the spans to skip indexing") fs.DurationVar(&c.Pause, "pause", time.Microsecond, "How long to pause before finishing trace") fs.DurationVar(&c.Duration, "duration", 0, "For how long to run the test") fs.StringVar(&c.Service, "service", "tracegen", "Service name to use") + fs.StringVar(&c.TraceExporter, "trace-exporter", "jaeger", "Trace exporter (jaeger|otlp|stdout). Exporters can be additionally configured via environment variables, see https://github.com/jaegertracing/jaeger/blob/main/cmd/tracegen/README.md") } // Run executes the test scenario. -func Run(c *Config, logger *zap.Logger) error { +func Run(c *Config, tracer trace.Tracer, logger *zap.Logger) error { if c.Duration > 0 { c.Traces = 0 } else if c.Traces <= 0 { @@ -62,6 +64,7 @@ func Run(c *Config, logger *zap.Logger) error { wg.Add(1) w := worker{ id: i, + tracer: tracer, traces: c.Traces, marshal: c.Marshal, debug: c.Debug, diff --git a/internal/tracegen/worker.go b/internal/tracegen/worker.go index 7c64a9986f7..6125ed5cb18 100644 --- a/internal/tracegen/worker.go +++ b/internal/tracegen/worker.go @@ -15,18 +15,19 @@ package tracegen import ( + "context" "fmt" "sync" "sync/atomic" "time" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - "github.com/uber/jaeger-client-go" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) type worker struct { + tracer trace.Tracer running *uint32 // pointer to shared flag that indicates it's time to stop the test id int // worker id traces int // how many traces the worker has to generate (only when duration==0) @@ -40,59 +41,13 @@ type worker struct { } const ( - fakeIP uint32 = 1<<24 | 2<<16 | 3<<8 | 4 - fakeSpanDuration = 123 * time.Microsecond ) func (w worker) simulateTraces() { - tracer := opentracing.GlobalTracer() var i int for atomic.LoadUint32(w.running) == 1 { - sp := tracer.StartSpan("lets-go") - ext.SpanKindRPCClient.Set(sp) - ext.PeerHostIPv4.Set(sp, fakeIP) - ext.PeerService.Set(sp, "tracegen-server") - if w.debug { - ext.SamplingPriority.Set(sp, 100) - } - - if w.firehose { - jaeger.EnableFirehose(sp.(*jaeger.Span)) - } - - childCtx := sp.Context() - if w.marshal { - m := make(map[string]string) - c := opentracing.TextMapCarrier(m) - if err := tracer.Inject(sp.Context(), opentracing.TextMap, c); err == nil { - c := opentracing.TextMapCarrier(m) - childCtx, err = tracer.Extract(opentracing.TextMap, c) - if err != nil { - w.logger.Error("cannot extract from TextMap", zap.Error(err)) - } - } else { - w.logger.Error("cannot inject span", zap.Error(err)) - } - } - child := opentracing.StartSpan( - "okey-dokey", - ext.RPCServerOption(childCtx), - ) - ext.PeerHostIPv4.Set(child, fakeIP) - ext.PeerService.Set(child, "tracegen-client") - - time.Sleep(w.pause) - - if w.pause == 0 { - child.Finish() - sp.Finish() - } else { - opt := opentracing.FinishOptions{FinishTime: time.Now().Add(fakeSpanDuration)} - child.FinishWithOptions(opt) - sp.FinishWithOptions(opt) - } - + w.simulateOneTrace() i++ if w.traces != 0 { if i >= w.traces { @@ -103,3 +58,45 @@ func (w worker) simulateTraces() { w.logger.Info(fmt.Sprintf("Worker %d generated %d traces", w.id, i)) w.wg.Done() } + +func (w worker) simulateOneTrace() { + ctx := context.Background() + attrs := []attribute.KeyValue{ + attribute.String("peer.service", "tracegen-server"), + attribute.String("peer.host.ipv4", "1.1.1.1"), + } + if w.debug { + attrs = append(attrs, attribute.Bool("jaeger.debug", true)) + } + if w.firehose { + attrs = append(attrs, attribute.Bool("jaeger.firehose", true)) + } + start := time.Now() + ctx, sp := w.tracer.Start( + ctx, + "lets-go", + trace.WithSpanKind(trace.SpanKindClient), + trace.WithAttributes(attrs...), + trace.WithTimestamp(start), + ) + + _, child := w.tracer.Start( + ctx, + "okey-dokey", + trace.WithSpanKind(trace.SpanKindServer), + ) + + time.Sleep(w.pause) + + if w.pause != 0 { + child.End() + sp.End() + } else { + child.End( + trace.WithTimestamp(start.Add(fakeSpanDuration)), + ) + sp.End( + trace.WithTimestamp(start.Add(fakeSpanDuration)), + ) + } +}