diff --git a/Makefile b/Makefile index 070e88b486f05392d4c5264d76fc6dd31580ca23..0f697f27d0181b20ab8d786c0c2b9691ef346f40 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ GOLANGCI_LINT_CONFIG ?= ${SOURCE_DIR}/.golangci.yml GITALY_PACKAGE := $(shell go list -m 2>/dev/null || echo unknown) GITALY_VERSION := $(shell ${GIT} describe --match v* 2>/dev/null | sed 's/^v//' || cat ${SOURCE_DIR}/VERSION 2>/dev/null || echo unknown) GO_LDFLAGS := -X ${GITALY_PACKAGE}/internal/version.version=${GITALY_VERSION} -SERVER_BUILD_TAGS := tracer_static,tracer_static_jaeger,tracer_static_stackdriver,continuous_profiler_stackdriver +SERVER_BUILD_TAGS := continuous_profiler_stackdriver ## FIPS_MODE controls whether to build Gitaly and dependencies in FIPS mode. ## Set this to a non-empty value to enable it. diff --git a/client/dial_test.go b/client/dial_test.go index c82503efa167c94b8d54bfa45ee8c519db9de15b..bedfe0281c0d48e1be58451325bff609c52ce16b 100644 --- a/client/dial_test.go +++ b/client/dial_test.go @@ -3,6 +3,7 @@ package client import ( "context" "crypto/tls" + "errors" "fmt" "io" "net" @@ -12,20 +13,20 @@ import ( "testing" "github.com/miekg/dns" - "github.com/opentracing/opentracing-go" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" gitalyauth "gitlab.com/gitlab-org/gitaly/v18/auth" internalclient "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/client" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" - "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" gitalyx509 "gitlab.com/gitlab-org/gitaly/v18/internal/x509" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" "gitlab.com/gitlab-org/labkit/correlation" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/propagation" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" @@ -359,59 +360,74 @@ func TestDial_Correlation(t *testing.T) { } func TestDial_Tracing(t *testing.T) { - serverSocketPath := testhelper.GetTemporaryGitalySocketFileName(t) + const serviceBaggageKey = "service" - listener, err := net.Listen("unix", serverSocketPath) - require.NoError(t, err) + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) - clientSendClosed := make(chan struct{}) + getSvc := func() *testSvc { + return &testSvc{ + unaryCall: func(ctx context.Context, r *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) { + spanCtx, span := reporter.TracerProvider().Tracer("server").Start(ctx, "nested-span-unary") + defer span.End() - // This is our test service. All it does is to create additional spans - // which should in the end be visible when collecting all registered - // spans. - grpcServer := grpc.NewServer( - grpc.UnaryInterceptor(grpctracing.UnaryServerTracingInterceptor()), - grpc.StreamInterceptor(grpctracing.StreamServerTracingInterceptor()), - ) - svc := &testSvc{ - unaryCall: func(ctx context.Context, r *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) { - span, _ := tracing.StartSpan(ctx, "nested-span", nil) - defer span.Finish() - span.LogKV("was", "called") - return &grpc_testing.SimpleResponse{}, nil - }, - fullDuplexCall: func(stream grpc_testing.TestService_FullDuplexCallServer) error { - // synchronize the client has returned from CloseSend as the client span finishing - // races with sending the stream close to the server - select { - case <-clientSendClosed: - case <-stream.Context().Done(): - return stream.Context().Err() - } + b := baggage.FromContext(spanCtx) + serviceFromBaggage := b.Member(serviceBaggageKey) + attributeFromBaggage := attribute.String(serviceBaggageKey, serviceFromBaggage.Value()) + span.SetAttributes(attributeFromBaggage) + return &grpc_testing.SimpleResponse{}, nil + }, + fullDuplexCall: func(stream grpc_testing.TestService_FullDuplexCallServer) error { + spanCtx, span := reporter.TracerProvider().Tracer("server").Start(stream.Context(), "nested-span-full-duplex") + defer span.End() + + // set attributes to span + b := baggage.FromContext(spanCtx) + serviceFromBaggage := b.Member(serviceBaggageKey) + attributeFromBaggage := attribute.String(serviceBaggageKey, serviceFromBaggage.Value()) + span.SetAttributes(attributeFromBaggage) + + // process message + for { + _, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + } - span, _ := tracing.StartSpan(stream.Context(), "nested-span", nil) - defer span.Finish() - span.LogKV("was", "called") - return nil - }, + return nil + }, + } } - grpc_testing.RegisterTestServiceServer(grpcServer, svc) - go testhelper.MustServe(t, grpcServer, listener) - defer grpcServer.Stop() - ctx := testhelper.Context(t) + propagator := propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) t.Run("unary", func(t *testing.T) { - reporter := jaeger.NewInMemoryReporter() - tracer, tracerCloser := jaeger.NewTracer("", jaeger.NewConstSampler(true), reporter) - defer testhelper.MustClose(t, tracerCloser) + reporter.Reset() - defer func(old opentracing.Tracer) { opentracing.SetGlobalTracer(old) }(opentracing.GlobalTracer()) - opentracing.SetGlobalTracer(tracer) + grpcServer := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler( + otelgrpc.WithPropagators(propagator), + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + )), + ) + grpc_testing.RegisterTestServiceServer(grpcServer, getSvc()) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName(t) + + listener, err := net.Listen("unix", serverSocketPath) + require.NoError(t, err) + go testhelper.MustServe(t, grpcServer, listener) + defer grpcServer.Stop() + ctx := testhelper.Context(t) - // This needs to be run after setting up the global tracer as it will cause us to - // create the span when executing the RPC call further down below. cc, err := DialContext(ctx, "unix://"+serverSocketPath, []grpc.DialOption{ + grpc.WithStatsHandler(otelgrpc.NewClientHandler( + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + otelgrpc.WithPropagators(propagator))), internalclient.UnaryInterceptor(), internalclient.StreamInterceptor(), WithGitalyDNSResolver(DefaultDNSResolverBuilderConfig()), @@ -422,57 +438,84 @@ func TestDial_Tracing(t *testing.T) { // We set up a "main" span here, which is going to be what the // other spans inherit from. In order to check whether baggage // works correctly, we also set up a "stub" baggage item which - // should be inherited to child contexts. - span := tracer.StartSpan("unary-check") - span = span.SetBaggageItem("service", "stub") - ctx := opentracing.ContextWithSpan(ctx, span) - - // We're now invoking the unary RPC with the span injected into - // the context. This should create a span that's nested into - // the "stream-check" span. - _, err = grpc_testing.NewTestServiceClient(cc).UnaryCall(ctx, &grpc_testing.SimpleRequest{}) + // should be inherited to child contexts. It would be the + // responsibility of the other processes to inspect the baggage + // and add it as attribute to a child span. + globalTracer := reporter.TracerProvider().Tracer("test") + rootSpanCtx, rootSpan := globalTracer.Start(ctx, "root-span") + + m0, err := baggage.NewMember(serviceBaggageKey, "stub") + require.NoError(t, err) + + b, err := baggage.New(m0) + require.NoError(t, err) + + rootSpanCtx = baggage.ContextWithBaggage(rootSpanCtx, b) + + // We're now invoking the unary RPC with the span injected into the context. + _, err = grpc_testing.NewTestServiceClient(cc).UnaryCall(rootSpanCtx, &grpc_testing.SimpleRequest{}) require.NoError(t, err) - span.Finish() + rootSpan.End() - spans := reporter.GetSpans() - require.Len(t, spans, 3) + recorderSpans := reporter.GetSpans() - for i, expectedSpan := range []struct { - baggage string - operation string + require.Len(t, recorderSpans, 4) + + for i, expectedSpanSpecs := range []struct { + operation string + attributeValue string }{ // This is the first span we expect, which is the - // "health" span which we've manually created inside of - // PingMethod. - {baggage: "", operation: "nested-span"}, - // This span is the RPC call to TestService/Ping. It - // inherits the "unary-check" we set up and thus has - // baggage. - {baggage: "stub", operation: "/grpc.testing.TestService/UnaryCall"}, - // And this finally is the outermost span which we - // manually set up before the RPC call. - {baggage: "stub", operation: "unary-check"}, + // _operation_ span created inside the gRPC handler + {operation: "nested-span-unary", attributeValue: "stub"}, + // The next two spans are the client span and the server span + // added automatically by otel instrumentation + {operation: "grpc.testing.TestService/UnaryCall", attributeValue: ""}, + {operation: "grpc.testing.TestService/UnaryCall", attributeValue: ""}, + // This is the root span, the first one we created at the beginning + // of the test. + {operation: "root-span", attributeValue: ""}, } { - assert.IsType(t, spans[i], &jaeger.Span{}) - span := spans[i].(*jaeger.Span) + span := recorderSpans[i] + + serviceAttribute := attribute.KeyValue{} + for _, attr := range span.Attributes { + if string(attr.Key) == serviceBaggageKey { + serviceAttribute = attr + } + } - assert.Equal(t, expectedSpan.baggage, span.BaggageItem("service"), "wrong baggage item for span %d", i) - assert.Equal(t, expectedSpan.operation, span.OperationName(), "wrong operation name for span %d", i) + require.Equal(t, serviceAttribute.Value.AsString(), expectedSpanSpecs.attributeValue) + assert.Equal(t, expectedSpanSpecs.operation, span.Name, "wrong operation name for span %d", i) } }) t.Run("stream", func(t *testing.T) { - reporter := jaeger.NewInMemoryReporter() - tracer, tracerCloser := jaeger.NewTracer("", jaeger.NewConstSampler(true), reporter) - defer testhelper.MustClose(t, tracerCloser) + reporter.Reset() - defer func(old opentracing.Tracer) { opentracing.SetGlobalTracer(old) }(opentracing.GlobalTracer()) - opentracing.SetGlobalTracer(tracer) + grpcServer := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler( + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + otelgrpc.WithPropagators(propagator), + )), + ) + grpc_testing.RegisterTestServiceServer(grpcServer, getSvc()) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName(t) + + listener, err := net.Listen("unix", serverSocketPath) + require.NoError(t, err) + go testhelper.MustServe(t, grpcServer, listener) + defer grpcServer.Stop() + ctx := testhelper.Context(t) // This needs to be run after setting up the global tracer as it will cause us to // create the span when executing the RPC call further down below. cc, err := DialContext(ctx, "unix://"+serverSocketPath, []grpc.DialOption{ + grpc.WithStatsHandler(otelgrpc.NewClientHandler( + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + otelgrpc.WithPropagators(propagator))), internalclient.UnaryInterceptor(), internalclient.StreamInterceptor(), WithGitalyDNSResolver(DefaultDNSResolverBuilderConfig()), @@ -480,53 +523,65 @@ func TestDial_Tracing(t *testing.T) { require.NoError(t, err) defer testhelper.MustClose(t, cc) - // We set up a "main" span here, which is going to be what the other spans inherit - // from. In order to check whether baggage works correctly, we also set up a "stub" - // baggage item which should be inherited to child contexts. - span := tracer.StartSpan("stream-check") - span = span.SetBaggageItem("service", "stub") - ctx := opentracing.ContextWithSpan(ctx, span) + // We set up a "main" span here, which is going to be what the + // other spans inherit from. In order to check whether baggage + // works correctly, we also set up a "stub" baggage item which + // should be inherited to child contexts. It would be the + // responsibility of the other processes to inspect the baggage + // and add it as attribute to a child span. + globalTracer := reporter.TracerProvider().Tracer("test") + rootSpanCtx, rootSpan := globalTracer.Start(ctx, "root-span") + + m0, err := baggage.NewMember(serviceBaggageKey, "stub") + require.NoError(t, err) + + b, err := baggage.New(m0) + require.NoError(t, err) + + rootSpanCtx = baggage.ContextWithBaggage(rootSpanCtx, b) // We're now invoking the streaming RPC with the span injected into the context. // This should create a span that's nested into the "stream-check" span. - stream, err := grpc_testing.NewTestServiceClient(cc).FullDuplexCall(ctx) + stream, err := grpc_testing.NewTestServiceClient(cc).FullDuplexCall(rootSpanCtx) require.NoError(t, err) require.NoError(t, stream.CloseSend()) - close(clientSendClosed) // wait for the server to finish its spans and close the stream - resp, err := stream.Recv() + _, err = stream.Recv() require.Equal(t, err, io.EOF) - require.Nil(t, resp) - span.Finish() + rootSpan.End() + + recorderSpans := reporter.GetSpans() - spans := reporter.GetSpans() - require.Len(t, spans, 3) + require.Len(t, recorderSpans, 4) - for i, expectedSpan := range []struct { - baggage string - operation string + for i, expectedSpanSpecs := range []struct { + operation string + attributeValue string }{ - // This span is the RPC call to TestService/Ping. - {baggage: "stub", operation: "/grpc.testing.TestService/FullDuplexCall"}, - // This is the second span we expect, which is the "nested-span" span which - // we've manually created inside of PingMethod. This is different than for - // unary RPCs: given that one can send multiple messages to the RPC, we may - // see multiple such "nested-span"s being created. And the PingStream span - // will only be finalized last. - {baggage: "", operation: "nested-span"}, - // And this finally is the outermost span which we - // manually set up before the RPC call. - {baggage: "stub", operation: "stream-check"}, + // This is the first span we expect, which is the + // _operation_ span created inside the gRPC handler + {operation: "nested-span-full-duplex", attributeValue: "stub"}, + // The next two spans are the client span and the server span + // added automatically by otel instrumentation + {operation: "grpc.testing.TestService/FullDuplexCall", attributeValue: ""}, + {operation: "grpc.testing.TestService/FullDuplexCall", attributeValue: ""}, + // This is the root span, the first one we created at the beginning + // of the test. + {operation: "root-span", attributeValue: ""}, } { - if !assert.IsType(t, spans[i], &jaeger.Span{}) { - continue + span := recorderSpans[i] + + serviceAttribute := attribute.KeyValue{} + for _, attr := range span.Attributes { + if string(attr.Key) == serviceBaggageKey { + serviceAttribute = attr + } } - span := spans[i].(*jaeger.Span) - assert.Equal(t, expectedSpan.baggage, span.BaggageItem("service"), "wrong baggage item for span %d", i) - assert.Equal(t, expectedSpan.operation, span.OperationName(), "wrong operation name for span %d", i) + require.Equal(t, serviceAttribute.Value.AsString(), expectedSpanSpecs.attributeValue) + assert.Equal(t, expectedSpanSpecs.operation, span.Name, "wrong operation name for span %d", i) } }) } diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index d67ec8d951ccf09d8b37c62df0c466a3bd16a853..8e77500b059e1bcdb5ade976384fd3bee7fb8efb 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -159,6 +159,9 @@ func executeHook(ctx context.Context, cmd hookCommand, args []string) error { ctx = injectMetadataIntoOutgoingCtx(ctx, payload) + tracerCloser := initializeTracing() + defer func() { _ = tracerCloser.Close() }() + conn, err := dialGitaly(ctx, payload) if err != nil { return fmt.Errorf("error when connecting to gitaly: %w", err) @@ -209,15 +212,15 @@ func dialGitaly(ctx context.Context, payload gitcmd.HooksPayload) (*grpc.ClientC ), } - // Setup tracing is possible - initializeTracing() - if spanContext, err := tracing.ExtractSpanContextFromEnv(os.Environ()); err == nil { + if spanContext, err := tracing.PropagateFromEnv(os.Environ()); err == nil { unaryInterceptors = append(unaryInterceptors, tracing.UnaryPassthroughInterceptor(spanContext)) streamInterceptors = append(streamInterceptors, tracing.StreamPassthroughInterceptor(spanContext)) } - dialOpts = append(dialOpts, grpc.WithChainUnaryInterceptor(unaryInterceptors...)) - dialOpts = append(dialOpts, grpc.WithChainStreamInterceptor(streamInterceptors...)) + dialOpts = append(dialOpts, + grpc.WithChainUnaryInterceptor(unaryInterceptors...), + grpc.WithChainStreamInterceptor(streamInterceptors...), + ) conn, err := client.New(ctx, "unix://"+payload.InternalSocket, client.WithGrpcOptions(dialOpts)) if err != nil { return nil, fmt.Errorf("error when dialing: %w", err) @@ -226,13 +229,12 @@ func dialGitaly(ctx context.Context, payload gitcmd.HooksPayload) (*grpc.ClientC return conn, nil } -func initializeTracing() { +func initializeTracing() io.Closer { // All stdout and stderr are captured by Gitaly process. They may be sent back to users. // We don't want to bother them with these redundant logs. As a result, all logs should be - // suppressed while labkit is in initialization phase. + // suppressed while tracing is in initialization phase. // - //nolint:forbidigo // LabKit does not allow us to supply our own logger, so we must modify the standard logger - // instead. + //nolint:forbidigo output := logrus.StandardLogger().Out logrus.SetOutput(io.Discard) defer logrus.SetOutput(output) @@ -242,8 +244,9 @@ func initializeTracing() { // or starting new span. This technique connects the parent span in parent Gitaly process // and the remote spans when Gitaly handle subsequent gRPC calls issued by this hook. // As tracing is a nice-to-have feature, it should not interrupt the main functionality - // of Gitaly hook. As a result, errors, if any, are ignored. - labkittracing.Initialize() + // of Gitaly hook. As a result, errors, if any, are logged but not returned. + _, closer, _ := tracing.InitializeTracerProvider(context.Background(), "gitaly-hooks") + return closer } func gitPushOptions() []string { diff --git a/go.mod b/go.mod index ff74ff321b967035823b91177e02f66b48e89e94..6740c43f1a17ab9ceaa7a6bcedaa9794e8772f8f 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,9 @@ go 1.24.0 replace github.com/go-enry/go-license-detector/v4 => github.com/gl-gitaly/go-license-detector/v4 v4.0.0-20230524080836-4cc9a3796917 require ( - cloud.google.com/go/storage v1.44.0 + cloud.google.com/go/storage v1.50.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 github.com/ProtonMail/go-crypto v1.1.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.63.1 github.com/cloudflare/tableflip v1.2.3 @@ -31,7 +32,6 @@ require ( github.com/miekg/dns v1.1.68 github.com/olekukonko/tablewriter v1.0.7 github.com/opencontainers/runtime-spec v1.2.1 - github.com/opentracing/opentracing-go v1.2.0 github.com/pelletier/go-toml/v2 v2.2.4 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 @@ -39,21 +39,30 @@ require ( github.com/rubenv/sql-migrate v1.8.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.11.1 - github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/urfave/cli/v3 v3.3.3 gitlab.com/gitlab-org/labkit v1.31.2 go.etcd.io/raft/v3 v3.6.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 + go.opentelemetry.io/contrib/propagators/ot v1.38.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/automaxprocs v1.6.0 go.uber.org/goleak v1.3.0 gocloud.dev v0.40.1-0.20241107185025-56954848c3aa golang.org/x/crypto v0.42.0 - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f - golang.org/x/net v0.44.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/sync v0.17.0 golang.org/x/sys v0.37.0 golang.org/x/text v0.30.0 golang.org/x/time v0.11.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c + // +gitaly pinVersion google.golang.org/grpc v1.71.1 // Please upgrade grpc-go with caution. Newer grpc-go versions contain some known issues: // - https://gitlab.com/gitlab-com/request-for-help/-/issues/2127 @@ -63,15 +72,15 @@ require ( ) require ( - cel.dev/expr v0.23.1 // indirect - cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.3 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/monitoring v1.21.0 // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.120.0 // indirect + cloud.google.com/go/auth v0.16.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/profiler v0.1.0 // indirect - cloud.google.com/go/trace v1.11.0 // indirect + cloud.google.com/go/trace v1.11.6 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect @@ -81,39 +90,40 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/DataDog/datadog-go v4.4.0+incompatible // indirect - github.com/DataDog/sketches-go v1.0.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/DataDog/sketches-go v1.4.7 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect - github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect + github.com/aws/aws-sdk-go v1.55.6 // indirect + github.com/aws/aws-sdk-go-v2 v1.37.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.21.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect + github.com/aws/smithy-go v1.22.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cilium/ebpf v0.16.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.5 // indirect @@ -137,19 +147,21 @@ require ( github.com/go-git/go-billy/v5 v5.6.0 // indirect github.com/go-git/go-git/v5 v5.13.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da // indirect - github.com/google/s2a-go v0.1.8 // indirect + github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/wire v0.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -177,55 +189,59 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/montanaflynn/stats v0.7.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/ulid/v2 v2.0.2 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect github.com/olekukonko/ll v0.0.8 // indirect github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect - github.com/philhofer/fwd v1.1.1 // indirect + github.com/onsi/gomega v1.35.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/prometheus v0.54.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/shirou/gopsutil/v3 v3.21.12 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shogo82148/go-shuffle v1.0.1 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/ssgelm/cookiejarparser v1.0.1 // indirect - github.com/tinylib/msgp v1.1.2 // indirect - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.3.0 // indirect + github.com/tinylib/msgp v1.3.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect gitlab.com/gitlab-org/go/reopen v1.0.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - gonum.org/v1/gonum v0.11.0 // indirect - google.golang.org/api v0.197.0 // indirect - google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + gonum.org/v1/gonum v0.16.0 // indirect + google.golang.org/api v0.229.0 // indirect + google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect gopkg.in/neurosnap/sentences.v1 v1.0.7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index c6ea71dbae6593be1eda65494ce97c24360da2c6..1a771add8ac486721f5cde6df99d949ee2974084 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= -cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -25,30 +25,30 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= -cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= -cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= -cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= -cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro= -cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk= cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -60,10 +60,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.44.0 h1:abBzXf4UJKMmQ04xxJf9dYM/fNl24KHoTuBjyJDX2AI= -cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= -cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI= -cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= +cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -96,21 +96,24 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk= github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= -github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4= github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/DataDog/sketches-go v1.4.7 h1:eHs5/0i2Sdf20Zkj0udVFWuCrXGRFig2Dcfm5rtcTxc= +github.com/DataDog/sketches-go v1.4.7/go.mod h1:eAmQ/EBmtSO+nQp7IZMZVRPT4BQTmIc5RZQ+deGlTPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0/go.mod h1:E05RN++yLx9W4fXPtX978OLo9P0+fBacauUdET1BckA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -124,50 +127,52 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.37.0 h1:YtCOESR/pN4j5oA7cVHSfOwIcuh/KwHC4DOSXFbv5F0= +github.com/aws/aws-sdk-go-v2 v1.37.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5/go.mod h1:wYSv6iDS621sEFLfKvpPE2ugjTuGlAG7iROg0hLOkfc= -github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= -github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/config v1.30.1 h1:sHL8g/+9tcZATeV2tEkEfxZeaNokDtKsSjGMGHD49qA= +github.com/aws/aws-sdk-go-v2/config v1.30.1/go.mod h1:wkibEyFfxXRyTSzRU4bbF5IUsSXyE4xQ4ZjkGmi5tFo= +github.com/aws/aws-sdk-go-v2/credentials v1.18.1 h1:E55xvOqlX7CvB66Z7rSM9usCrFU1ryUIUHqiXsEzVoE= +github.com/aws/aws-sdk-go-v2/credentials v1.18.1/go.mod h1:iobSQfR5MkvILxssGOvi/P1jjOhrRzfTiCPCzku0vx4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 h1:9sBTeKQwAvmJUWKIACIoiFSnxxl+sS++YDfr17/ngq0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0/go.mod h1:LW9/PxQD1SYFC7pnWcgqPhoyZprhjEdg5hBK6qYPLW8= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 h1:H2iZoqW/v2Jnrh1FnU725Bq6KJ0k2uP63yH+DcY+HUI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0/go.mod h1:L0FqLbwMXHvNC/7crWV1iIxUlOKYZUE8KuTIA+TozAI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 h1:EDped/rNzAhFPhVY0sDGbtD16OKqksfA8OjF/kLEgw8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0/go.mod h1:uUI335jvzpZRPpjYx6ODc/wg1qH+NnoSTK/FwVeK0C0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 h1:OWYvKL53l1rbsUmW7bQyJVsYU/Ii3bbAAQIIFNbM0Tk= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18/go.mod h1:CUx0G1v3wG6l01tUB+j7Y8kclA8NSqK4ef0YG79a4cg= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 h1:rTWjG6AvWekO2B1LHeM3ktU7MqyX9rzWQ7hgzneZW7E= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20/go.mod h1:RGW2DDpVc8hu6Y6yG8G5CHVmVOAn1oV8rNKOHRJyswg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 h1:eRhU3Sh8dGbaniI6B+I48XJMrTPRkK4DKo+vqIxziOU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0/go.mod h1:paNLV18DZ6FnWE/bd06RIKPDIFpjuvCkGKWTG/GDBeM= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 h1:eb+tFOIl9ZsUe2259/BKPeniKuz4/02zZFH/i4Nf8Rg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18/go.mod h1:GVCC2IJNJTmdlyEsSmofEy7EfJncP7DNnXDzRjJ5Keg= github.com/aws/aws-sdk-go-v2/service/s3 v1.63.1 h1:TR96r56VwELV0qguNFCuz+/bEpRfnR3ZsS9/IG05C7Q= github.com/aws/aws-sdk-go-v2/service/s3 v1.63.1/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 h1:cuFWHH87GP1NBGXXfMicUbE7Oty5KpPxN6w4JpmuxYc= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.0/go.mod h1:aJBemdlbCKyOXEXdXBqS7E+8S9XTDcOTaoOjtng54hA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 h1:t2va+wewPOYIqC6XyJ4MGjiGKkczMAPsgq5W4FtL9ME= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0/go.mod h1:ExCTcqYqN0hYYRsDlBVU8+68grqlWdgX9/nZJwQW4aY= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 h1:FD9agdG4CeOGS3ORLByJk56YIXDS7mxFpmZyCtpqExc= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.0/go.mod h1:NDzDPbBF1xtSTZUMuZx0w3hIfWzcL7X2AQ0Tr9becIQ= +github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= +github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -188,8 +193,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= @@ -292,13 +297,14 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= @@ -326,8 +332,9 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -395,23 +402,23 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g= -github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= +github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= @@ -423,6 +430,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -528,8 +537,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/neurosnap/sentences v1.0.6 h1:iBVUivNtlwGkYsJblWV8GGVFmXzZzak907Ci8aA0VTE= github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= -github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc= @@ -543,8 +552,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -554,8 +563,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= -github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -570,8 +579,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= @@ -593,8 +602,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 h1:mncRSDOqYCng7jOD+Y6+IivdRI6Kzv2BLWYkWkdQfu0= @@ -606,8 +615,12 @@ github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBa github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= -github.com/shirou/gopsutil/v3 v3.21.12 h1:VoGxEW2hpmz0Vt3wUvHIl9fquzYLNpVpgNNB7pGJimA= -github.com/shirou/gopsutil/v3 v3.21.12/go.mod h1:BToYZVTlSVlfazpDDYFnsVZLaoRG+g8ufT6fPQLdJzA= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc= github.com/shogo82148/go-shuffle v1.0.1 h1:4swIpHXLMAz14DE4YTgakgadpRN0n1wE1dieGnOTVFU= github.com/shogo82148/go-shuffle v1.0.1/go.mod h1:HQPjVgUUZ9TNgm4/K/iXRuAdhPsQrXnAGgtk/9kqbBY= @@ -636,14 +649,15 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= @@ -663,9 +677,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/gitlab-org/go/reopen v1.0.0 h1:6BujZ0lkkjGIejTUJdNO1w56mN1SI10qcVQyQlOPM+8= gitlab.com/gitlab-org/go/reopen v1.0.0/go.mod h1:D6OID8YJDzEVZNYW02R/Pkj0v8gYFSIhXFTArAsBQw8= gitlab.com/gitlab-org/labkit v1.31.2 h1:uot1m2vh7DT+JCq2LFUYIP6hk0qw9Ebw2eZKLk8BYqI= @@ -683,23 +698,37 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= +go.opentelemetry.io/contrib/propagators/ot v1.38.0 h1:k4gSyyohaDXI8F9BDXYC3uO2vr5sRNeQFMsN9Zn0EoI= +go.opentelemetry.io/contrib/propagators/ot v1.38.0/go.mod h1:2hDsuiHRO39SRUMhYGqmj64z/IuMRoxE4bBSFR82Lo8= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -709,6 +738,8 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= @@ -741,8 +772,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -769,6 +800,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -812,6 +844,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -906,8 +939,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1012,6 +1044,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= @@ -1026,8 +1059,8 @@ golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUO golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1056,8 +1089,8 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= -google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= +google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8= +google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1117,12 +1150,12 @@ google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/internal/cli/gitaly/serve.go b/internal/cli/gitaly/serve.go index 194d80ec56b3e88dd9efadd41d64db89c637d43c..eb7bc2a5b9ad45ce9eae9d3ff96026476d81a230 100644 --- a/internal/cli/gitaly/serve.go +++ b/internal/cli/gitaly/serve.go @@ -61,7 +61,6 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/version" "gitlab.com/gitlab-org/labkit/fips" "gitlab.com/gitlab-org/labkit/monitoring" - labkittracing "gitlab.com/gitlab-org/labkit/tracing" "go.uber.org/automaxprocs/maxprocs" "gocloud.dev/blob" "google.golang.org/grpc" @@ -111,6 +110,12 @@ func serveAction(ctx context.Context, cmd *cli.Command) error { return cli.Exit(err, 1) } + _, tracingCloser, err := tracing.InitializeTracerProvider(context.Background(), "gitaly") + if err != nil { + logger.WithError(err).Error("error initializing distributed tracing") + } + defer func() { _ = tracingCloser.Close() }() + if cfg.Auth.Transitioning && len(cfg.Auth.Token) > 0 { logger.Warn("Authentication is enabled but not enforced because transitioning=true. Gitaly will accept unauthenticated requests.") } @@ -163,7 +168,6 @@ func configure(configPath string) (config.Cfg, log.Logger, error) { sentry.ConfigureSentry(logger, version.GetVersion(), sentry.Config(cfg.Logging.Sentry)) cfg.Prometheus.Configure(logger) - labkittracing.Initialize(labkittracing.WithServiceName("gitaly")) preloadLicenseDatabase(logger) return cfg, logger, nil @@ -189,7 +193,7 @@ func run(appCtx *cli.Command, cfg config.Cfg, logger log.Logger) error { beganRun := time.Now() bootstrapSpan, ctx := tracing.StartSpan(ctx, "gitaly-bootstrap", nil) - defer bootstrapSpan.Finish() + defer bootstrapSpan.End() if cfg.RuntimeDir != "" { if err := config.PruneOldGitalyProcessDirectories(logger, cfg.RuntimeDir); err != nil { @@ -773,7 +777,7 @@ func run(appCtx *cli.Command, cfg config.Cfg, logger log.Logger) error { if err := b.Start(); err != nil { return fmt.Errorf("unable to start the bootstrap: %w", err) } - bootstrapSpan.Finish() + bootstrapSpan.End() // There are a few goroutines running async tasks that may still be in progress (i.e. preloading the license // database), but this is a close enough indication of startup latency. logger.WithField("duration_ms", time.Since(beganRun).Milliseconds()).Info("Started Gitaly") diff --git a/internal/command/command.go b/internal/command/command.go index af9a9e118663541abd69ce165918be9343c153a2..798ffd1423ba4730bb918987b5c560c288e72849 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -15,7 +15,6 @@ import ( "syscall" "time" - "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "gitlab.com/gitlab-org/gitaly/v18/internal/command/commandcounter" @@ -24,6 +23,8 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/log" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" labkittracing "gitlab.com/gitlab-org/labkit/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) var ( @@ -153,7 +154,7 @@ type Command struct { finalizers []func(context.Context, *Command) - span opentracing.Span + span trace.Span metricsCmd string metricsSubCmd string @@ -230,9 +231,9 @@ func New(ctx context.Context, logger log.Logger, nameAndArgs []string, opts ...O span, ctx := tracing.StartSpanIfHasParent( ctx, spanName, - tracing.Tags{ - "path": nameAndArgs[0], - "args": strings.Join(nameAndArgs[1:], " "), + []attribute.KeyValue{ + attribute.String("path", nameAndArgs[0]), + attribute.String("args", strings.Join(nameAndArgs[1:], " ")), }, ) cmd := exec.Command(nameAndArgs[0], nameAndArgs[1:]...) @@ -701,22 +702,28 @@ func (c *Command) logProcessComplete() { contextSwitchesTotal.WithLabelValues(service, method, cmdName, c.metricsSubCmd, "nonvoluntary", c.cmdGitVersion, c.refBackend).Add(float64(rusage.Nivcsw)) } - c.span.SetTag("pid", cmd.ProcessState.Pid()) - c.span.SetTag("exit_code", exitCode) - c.span.SetTag("system_time_ms", systemTime.Milliseconds()) - c.span.SetTag("user_time_ms", userTime.Milliseconds()) - c.span.SetTag("real_time_ms", realTime.Milliseconds()) + attributes := []attribute.KeyValue{ + attribute.Int("pid", cmd.ProcessState.Pid()), + attribute.Int("exit_code", exitCode), + attribute.Int64("system_time_ms", systemTime.Milliseconds()), + attribute.Int64("user_time_ms", userTime.Milliseconds()), + attribute.Int64("real_time_ms", realTime.Milliseconds()), + } + if ok { - c.span.SetTag("maxrss", rusage.Maxrss) - c.span.SetTag("inblock", rusage.Inblock) - c.span.SetTag("oublock", rusage.Oublock) - c.span.SetTag("minflt", rusage.Minflt) - c.span.SetTag("majflt", rusage.Majflt) + attributes = append(attributes, + attribute.Int64("maxrss", rusage.Maxrss), + attribute.Int64("inblock", rusage.Inblock), + attribute.Int64("oublock", rusage.Oublock), + attribute.Int64("minflt", rusage.Minflt), + attribute.Int64("majflt", rusage.Majflt), + ) } if c.cgroupPath != "" { - c.span.SetTag("cgroup_path", c.cgroupPath) + attributes = append(attributes, attribute.String("cgroup_path", c.cgroupPath)) } - c.span.Finish() + c.span.SetAttributes(attributes...) + c.span.End() } // Args is an accessor for the command arguments diff --git a/internal/git/catfile/cache.go b/internal/git/catfile/cache.go index f256beaf18708220d56386502a32b169a97eca58..5ebd5543e41ac5f87c51cc4eecf460d2dd8cce3b 100644 --- a/internal/git/catfile/cache.go +++ b/internal/git/catfile/cache.go @@ -15,6 +15,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/helper" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/labkit/correlation" + "go.opentelemetry.io/otel/attribute" ) const ( @@ -239,7 +240,7 @@ func (c *ProcessCache) getOrCreateProcess( defer c.reportCacheMembers() span, ctx := tracing.StartSpanIfHasParent(ctx, spanName, nil) - defer span.Finish() + defer span.End() cacheKey, isCacheable := newCacheKey(fmt.Sprintf("%d", roundToNearestFiveMinute(time.Now())), repo) @@ -252,14 +253,14 @@ func (c *ProcessCache) getOrCreateProcess( if entry, ok := processes.Checkout(cacheKey); ok { c.catfileCacheCounter.WithLabelValues("hit").Inc() - span.SetTag("hit", true) + span.SetAttributes(attribute.Bool("hit", true)) return entry.value, func() { c.returnToCache(processes, cacheKey, entry.value, entry.cancel) }, nil } c.catfileCacheCounter.WithLabelValues("miss").Inc() - span.SetTag("hit", false) + span.SetAttributes(attribute.Bool("hit", false)) // When cache misses, a new process is created. This process may be re-used later. // In that case, the lifecycle of the process is stretched across multiple diff --git a/internal/git/catfile/tag.go b/internal/git/catfile/tag.go index db86ba4c9394009a7d089fbfb5833d7d9b0d12db..19aef60bd5fdfa830d7dd1396e45d02991fd72e5 100644 --- a/internal/git/catfile/tag.go +++ b/internal/git/catfile/tag.go @@ -10,14 +10,19 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/helper" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + "go.opentelemetry.io/otel/attribute" ) // GetTag looks up a commit by tagID using an existing catfile.Batch instance. Note: we pass // in the tagName because the tag name from refs/tags may be different than the name found in the // actual tag object. We want to use the tagName found in refs/tags func GetTag(ctx context.Context, objectReader ObjectContentReader, tagID git.Revision, tagName string) (*gitalypb.Tag, error) { - span, ctx := tracing.StartSpanIfHasParent(ctx, "catfile.GetTag", tracing.Tags{"tagName": tagName}) - defer span.Finish() + span, ctx := tracing.StartSpanIfHasParent(ctx, + "catfile.GetTag", + []attribute.KeyValue{ + attribute.String("tagName", tagName), + }) + defer span.End() object, err := objectReader.Object(ctx, tagID) if err != nil { diff --git a/internal/git/catfile/tracing.go b/internal/git/catfile/tracing.go index f668b922d3327c44115a6ddf4172ab18f70b6034..d5c43fe26ae38928926a342b98b5b33842c3fa02 100644 --- a/internal/git/catfile/tracing.go +++ b/internal/git/catfile/tracing.go @@ -4,13 +4,15 @@ import ( "context" "sync" - "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel/attribute" + oteltrace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) type trace struct { - span opentracing.Span + span oteltrace.Span counter *prometheus.CounterVec requestsLock sync.Mutex @@ -25,9 +27,9 @@ func startTrace( counter *prometheus.CounterVec, methodName string, ) *trace { - var span opentracing.Span + var span oteltrace.Span if methodName == "" { - span = &tracing.NoopSpan{} + span = noop.Span{} } else { span, _ = tracing.StartSpanIfHasParent(ctx, methodName, nil) } @@ -62,11 +64,11 @@ func (t *trace) finish() { continue } - t.span.SetTag(requestType, requestCount) + t.span.SetAttributes(attribute.Int(requestType, requestCount)) if t.counter != nil { t.counter.WithLabelValues(requestType).Add(float64(requestCount)) } } - t.span.Finish() + t.span.End() } diff --git a/internal/git/catfile/tree_entries.go b/internal/git/catfile/tree_entries.go index 8a538043dd6789794a61a66fba54282a42c18809..49e6a7571931b9fcf394b3c3dc182d2dbe6db842 100644 --- a/internal/git/catfile/tree_entries.go +++ b/internal/git/catfile/tree_entries.go @@ -14,6 +14,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/git" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + "go.opentelemetry.io/otel/attribute" ) type revisionPath struct{ revision, path string } @@ -37,9 +38,12 @@ func (tef *TreeEntryFinder) FindByRevisionAndPath(ctx context.Context, revision, span, ctx := tracing.StartSpanIfHasParent( ctx, "catfile.FindByRevisionAndPatch", - tracing.Tags{"revision": revision, "path": path}, + []attribute.KeyValue{ + attribute.String("revision", revision), + attribute.String("path", path), + }, ) - defer span.Finish() + defer span.End() dir := pathPkg.Dir(path) cacheKey := revisionPath{revision: revision, path: dir} @@ -121,9 +125,12 @@ func TreeEntries( span, ctx := tracing.StartSpanIfHasParent( ctx, "catfile.TreeEntries", - tracing.Tags{"revision": revision, "path": path}, + []attribute.KeyValue{ + attribute.String("revision", revision), + attribute.String("path", path), + }, ) - defer span.Finish() + defer span.End() if path == "." { path = "" diff --git a/internal/git/gitcmd/command_factory_test.go b/internal/git/gitcmd/command_factory_test.go index 22334d500a39c7569683eb3eb0eb3b166aab639c..12ccc62b0bdb1757879da9c7317ecae3fc2b89a7 100644 --- a/internal/git/gitcmd/command_factory_test.go +++ b/internal/git/gitcmd/command_factory_test.go @@ -25,6 +25,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel" "golang.org/x/time/rate" ) @@ -949,8 +950,10 @@ func TestTrace2TracingExporter(t *testing.T) { { desc: "active span is sampled", setup: func(t *testing.T, ctx context.Context) context.Context { - _, ctx = tracing.StartSpan(ctx, "root", nil) - return ctx + tr := otel.GetTracerProvider().Tracer(t.Name()) + spanCtx, span := tr.Start(ctx, "root") + span.End() + return spanCtx }, assert: func(t *testing.T, spans []string, statFields map[string]any) { require.Equal(t, statFields["trace2.activated"], "true") @@ -968,8 +971,10 @@ func TestTrace2TracingExporter(t *testing.T) { desc: "active span is not sampled", tracerOptions: []testhelper.StubTracingReporterOption{testhelper.NeverSampled()}, setup: func(t *testing.T, ctx context.Context) context.Context { - _, ctx = tracing.StartSpan(ctx, "root", nil) - return ctx + tr := otel.GetTracerProvider().Tracer(t.Name()) + spanCtx, span := tr.Start(ctx, "root") + span.End() + return spanCtx }, assert: func(t *testing.T, spans []string, statFields map[string]any) { require.NotContains(t, statFields, "trace2.activated") @@ -981,8 +986,8 @@ func TestTrace2TracingExporter(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t, tc.tracerOptions...) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t, tc.tracerOptions...) + defer func() { _ = reporter.Close() }() ctx := tc.setup(t, log.InitContextCustomFields(testhelper.Context(t))) performRevList(t, ctx) @@ -992,8 +997,8 @@ func TestTrace2TracingExporter(t *testing.T) { statFields := customFields.Fields() var spans []string - for _, span := range testhelper.ReportedSpans(t, reporter) { - spans = append(spans, span.Operation) + for _, span := range reporter.GetSpans() { + spans = append(spans, span.Name) } tc.assert(t, spans, statFields) @@ -1085,7 +1090,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { desc: "active span is sampled", subCmd: "status", setup: func(t *testing.T) (context.Context, []trace2.Hook) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + defer span.End() return ctx, []trace2.Hook{ trace2hooks.NewTracingExporter(), } @@ -1095,7 +1101,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { desc: "active span is not sampled", subCmd: "status", setup: func(t *testing.T) (context.Context, []trace2.Hook) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + defer span.End() return ctx, []trace2.Hook{} }, tracerOptions: []testhelper.StubTracingReporterOption{testhelper.NeverSampled()}, @@ -1113,7 +1120,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { desc: "subcmd is pack-objects and active span is sampled", subCmd: "pack-objects", setup: func(t *testing.T) (context.Context, []trace2.Hook) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + defer span.End() hooks := []trace2.Hook{ trace2hooks.NewTracingExporter(), trace2hooks.NewPackObjectsMetrics(), @@ -1128,8 +1136,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { setup: func(t *testing.T) (context.Context, []trace2.Hook) { ctx := testhelper.Context(t) ctx = featureflag.ContextWithFeatureFlag(ctx, featureflag.LogGitTraces, true) - _, ctx = tracing.StartSpan(ctx, "root", nil) - + span, ctx := tracing.StartSpan(ctx, "root", nil) + defer span.End() hooks := []trace2.Hook{ trace2hooks.NewTracingExporter(), trace2hooks.NewPackObjectsMetrics(), @@ -1141,8 +1149,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - _, cleanup := testhelper.StubTracingReporter(t, tc.tracerOptions...) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t, tc.tracerOptions...) + defer func() { _ = reporter.Close() }() ctx, expectedHooks := tc.setup(t) hooks := gitcmd.DefaultTrace2HooksFor(ctx, tc.subCmd, testhelper.SharedLogger(t), rate.NewLimiter(1, 1)) diff --git a/internal/git/housekeeping/manager/optimize_repository.go b/internal/git/housekeeping/manager/optimize_repository.go index 7b68cb3f6dcfdd8578e96ba68074f5392691a44e..c33040162ed99cfecf1d63c5c6c6a6591b2a4a43 100644 --- a/internal/git/housekeeping/manager/optimize_repository.go +++ b/internal/git/housekeeping/manager/optimize_repository.go @@ -57,7 +57,7 @@ func (m *RepositoryManager) OptimizeRepository( } span, ctx := tracing.StartSpanIfHasParent(ctx, "housekeeping.OptimizeRepository", nil) - defer span.Finish() + defer span.End() if err := m.maybeStartTransaction(ctx, repo, func(ctx context.Context, tx storage.Transaction, repo *localrepo.Repo) error { originalRepo := &gitalypb.Repository{ @@ -465,7 +465,7 @@ func (m *RepositoryManager) packRefsIfNeeded(ctx context.Context, repo *localrep // CleanStaleData removes any stale data in the repository as per the provided configuration. func (m *RepositoryManager) CleanStaleData(ctx context.Context, repo *localrepo.Repo, cfg housekeeping.CleanStaleDataConfig) error { span, ctx := tracing.StartSpanIfHasParent(ctx, "housekeeping.CleanStaleData", nil) - defer span.Finish() + defer span.End() repoPath, err := repo.Path(ctx) if err != nil { diff --git a/internal/git/trace2/parser.go b/internal/git/trace2/parser.go index b999bc9f7a914b31a2568920459b26c473d0245c..c72ebed00e976b23f57bd78f479ba1a3bffcce1d 100644 --- a/internal/git/trace2/parser.go +++ b/internal/git/trace2/parser.go @@ -37,7 +37,7 @@ import ( // For more information, please visit Trace2 API: https://git-scm.com/docs/api-trace2 func Parse(ctx context.Context, reader io.Reader) (*Trace, error) { span, _ := tracing.StartSpanIfHasParent(ctx, "trace2.parse", nil) - defer span.Finish() + defer span.End() decoder := json.NewDecoder(reader) p := &parser{decoder: decoder} diff --git a/internal/git/trace2hooks/tracingexporter.go b/internal/git/trace2hooks/tracingexporter.go index a2bf5de8a0dd83339f8c82642dc38b59b121f16f..f14f43817cc98cbac018bd9c2164169a5ad06d8d 100644 --- a/internal/git/trace2hooks/tracingexporter.go +++ b/internal/git/trace2hooks/tracingexporter.go @@ -4,9 +4,10 @@ import ( "context" "fmt" - "github.com/opentracing/opentracing-go" "gitlab.com/gitlab-org/gitaly/v18/internal/git/trace2" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel/attribute" + oteltrace "go.opentelemetry.io/otel/trace" ) // NewTracingExporter initializes TracingExporter, which is a hook to convert Trace2 events to @@ -35,13 +36,13 @@ func (t *TracingExporter) Handle(rootCtx context.Context, trace *trace2.Trace) e } spanName := fmt.Sprintf("git:%s", trace.Name) - span, ctx := tracing.StartSpanIfHasParent(ctx, spanName, nil, opentracing.StartTime(trace.StartTime)) - span.SetTag("thread", trace.Thread) - span.SetTag("childID", trace.ChildID) + span, ctx := tracing.StartSpanIfHasParent(ctx, spanName, nil, oteltrace.WithTimestamp(trace.StartTime)) + span.SetAttributes(attribute.String("thread", trace.Thread)) + span.SetAttributes(attribute.String("childID", trace.ChildID)) for key, value := range trace.Metadata { - span.SetTag(key, value) + span.SetAttributes(attribute.String(key, value)) } - span.FinishWithOptions(opentracing.FinishOptions{FinishTime: trace.FinishTime}) + span.End(oteltrace.WithTimestamp(trace.FinishTime)) return ctx }) diff --git a/internal/git/trace2hooks/tracingexporter_test.go b/internal/git/trace2hooks/tracingexporter_test.go index d25662ff1f3e3bdd2cd7ba74c91baa1e2e8510aa..c74c4346d989bd2c43185c0a04468ca0a4c3db9d 100644 --- a/internal/git/trace2hooks/tracingexporter_test.go +++ b/internal/git/trace2hooks/tracingexporter_test.go @@ -9,11 +9,15 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/git/trace2" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + oteltrace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) func TestTracingExporter_Handle(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) // Pin a timestamp for trace tree generation below. This way enables asserting the time // frames of spans correctly. @@ -24,135 +28,151 @@ func TestTracingExporter_Handle(t *testing.T) { for _, tc := range []struct { desc string - setup func(*testing.T) (context.Context, *trace2.Trace) - expectedSpans []*testhelper.Span + setup func(*testing.T) (context.Context, *trace2.Trace, oteltrace.Span) + expectedSpans tracetest.SpanStubs }{ { desc: "empty trace", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) - return ctx, nil + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + return ctx, nil, span }, - expectedSpans: nil, + expectedSpans: tracetest.SpanStubs{}, }, { desc: "receives trace consisting of root only", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) return ctx, &trace2.Trace{ Thread: "main", Name: "root", StartTime: current, FinishTime: time.Time{}, - } + }, span }, - expectedSpans: nil, + expectedSpans: tracetest.SpanStubs{}, }, { desc: "receives a complete trace", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) - - return ctx, exampleTrace + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + return ctx, exampleTrace, span }, - expectedSpans: []*testhelper.Span{ + expectedSpans: []tracetest.SpanStub{ { - Operation: "git:version", + Name: "git:version", StartTime: current, - Duration: 1 * time.Second, - Tags: map[string]string{ - "childID": "", - "thread": "main", - "exe": "2.42.0", + EndTime: current.Add(1 * time.Second), + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), + attribute.String("exe", "2.42.0"), }, }, { - Operation: "git:start", + Name: "git:start", StartTime: current.Add(1 * time.Second), - Duration: 1 * time.Second, - Tags: map[string]string{ - "argv": "git fetch origin master", - "childID": "", - "thread": "main", + EndTime: current.Add(2 * time.Second), + Attributes: []attribute.KeyValue{ + attribute.String("argv", "git fetch origin master"), + attribute.String("childID", ""), + attribute.String("thread", "main"), }, }, { - Operation: "git:def_repo", + Name: "git:def_repo", StartTime: current.Add(2 * time.Second), - Duration: 1 * time.Second, - Tags: map[string]string{ - "childID": "", - "thread": "main", - "worktree": "/Users/userx123/Documents/gitlab-development-kit", + EndTime: current.Add(3 * time.Second), + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), + attribute.String("worktree", "/Users/userx123/Documents/gitlab-development-kit"), }, }, { - Operation: "git:index:do_read_index", + Name: "git:index:do_read_index", StartTime: current.Add(3 * time.Second), - Duration: 3 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "", - "thread": "main", + EndTime: current.Add(6 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), }, }, { - Operation: "git:cache_tree:read", + Name: "git:cache_tree:read", StartTime: current.Add(3 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "0", - "thread": "main", + EndTime: current.Add(4 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", "0"), + attribute.String("thread", "main"), }, }, { - Operation: "git:data:index:read/version", + Name: "git:data:index:read/version", StartTime: current.Add(4 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "0", - "thread": "main", - "data": "2", + EndTime: current.Add(5 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", "0"), + attribute.String("thread", "main"), + attribute.String("data", "2"), }, }, { - Operation: "git:data:index:read/cache_nr", + Name: "git:data:index:read/cache_nr", StartTime: current.Add(5 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "0", - "thread": "main", - "data": "1500", + EndTime: current.Add(6 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", "0"), + attribute.String("thread", "main"), + attribute.String("data", "1500"), }, }, { - Operation: "git:submodule:parallel/fetch", + Name: "git:submodule:parallel/fetch", StartTime: current.Add(6 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "", - "thread": "main", + EndTime: current.Add(7 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), }, }, }, }, { desc: "receives a complete trace but tracing is not initialized", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - return testhelper.Context(t), exampleTrace + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + return testhelper.Context(t), exampleTrace, noop.Span{} }, expectedSpans: nil, }, } { t.Run(tc.desc, func(t *testing.T) { reporter.Reset() - ctx, trace := tc.setup(t) + ctx, trace, span := tc.setup(t) + defer span.End() exporter := NewTracingExporter() err := exporter.Handle(ctx, trace) require.NoError(t, err) - spans := testhelper.ReportedSpans(t, reporter) - require.Equal(t, tc.expectedSpans, spans) + recordedSpans := reporter.GetSpans() + + for idx, expectedSpan := range tc.expectedSpans { + recordedSpan := recordedSpans[idx] + require.Equal(t, expectedSpan.Name, recordedSpan.Name) + + recordedAttributes := attributesToMap(recordedSpan.Attributes) + expectedAttributes := attributesToMap(expectedSpan.Attributes) + require.Equal(t, recordedAttributes, expectedAttributes) + } }) } } + +func attributesToMap(attributes []attribute.KeyValue) map[string]string { + res := make(map[string]string) + for _, attr := range attributes { + res[string(attr.Key)] = attr.Value.AsString() + } + return res +} diff --git a/internal/gitaly/server/server.go b/internal/gitaly/server/server.go index 64b5c953b6f213da0792ba9978586e36f214b61b..2a388e33704751fe219f07ad83faef33b4c1062a 100644 --- a/internal/gitaly/server/server.go +++ b/internal/gitaly/server/server.go @@ -25,8 +25,10 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/protoregistry" gitalylog "gitlab.com/gitlab-org/gitaly/v18/internal/log" "gitlab.com/gitlab-org/gitaly/v18/internal/structerr" + "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" @@ -162,7 +164,6 @@ func (s *GitalyServerFactory) New(external, secure bool, opts ...Option) (*grpc. } streamServerInterceptors = append(streamServerInterceptors, - grpctracing.StreamServerTracingInterceptor(), cache.StreamInvalidator(s.cacheInvalidator, protoregistry.GitalyProtoPreregistered, s.logger), // Panic handler should remain last so that application panics will be // converted to errors and logged @@ -170,7 +171,6 @@ func (s *GitalyServerFactory) New(external, secure bool, opts ...Option) (*grpc. ) unaryServerInterceptors = append(unaryServerInterceptors, - grpctracing.UnaryServerTracingInterceptor(), cache.UnaryInvalidator(s.cacheInvalidator, protoregistry.GitalyProtoPreregistered, s.logger), // Panic handler should remain last so that application panics will be // converted to errors and logged @@ -204,6 +204,9 @@ func (s *GitalyServerFactory) New(external, secure bool, opts ...Option) (*grpc. } serverOptions := []grpc.ServerOption{ + grpc.StatsHandler(tracing.NewGRPCServerStatsHandler( + otelgrpc.WithTracerProvider(otel.GetTracerProvider()), + )), grpc.StatsHandler(loghandler.PerRPCLogHandler{ Underlying: &grpcstats.PayloadBytes{}, FieldProducers: []loghandler.FieldsProducer{grpcstats.FieldsProducer}, diff --git a/internal/gitaly/service/repository/license.go b/internal/gitaly/service/repository/license.go index 39c1524076b2fa6eb60f0eae6532d63525a69032..b65a12f9059f856a206b102e2aefc9fcf3d18cfc 100644 --- a/internal/gitaly/service/repository/license.go +++ b/internal/gitaly/service/repository/license.go @@ -72,7 +72,7 @@ func (s *server) FindLicense(ctx context.Context, req *gitalypb.FindLicenseReque func findLicense(ctx context.Context, repo *localrepo.Repo, commitID git.ObjectID) (*gitalypb.FindLicenseResponse, error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "repository.findLicense", nil) - defer span.Finish() + defer span.End() repoFiler := &gitFiler{ctx: ctx, repo: repo, treeishID: commitID} detectedLicenses, err := licensedb.Detect(repoFiler) diff --git a/internal/gitaly/storage/storagemgr/middleware.go b/internal/gitaly/storage/storagemgr/middleware.go index 88417e7b668bdd4e0d3171e873dde953dcbdcab7..f9e382367d45a62fa37761d7ef5422e8c962bc6f 100644 --- a/internal/gitaly/storage/storagemgr/middleware.go +++ b/internal/gitaly/storage/storagemgr/middleware.go @@ -209,7 +209,7 @@ func nonTransactionalRequest(ctx context.Context, firstMessage proto.Message) tr // transaction. The returned values are valid even if the request should not run transactionally. func transactionalizeRequest(ctx context.Context, logger log.Logger, txRegistry *TransactionRegistry, node storage.Node, locator storage.Locator, methodInfo protoregistry.MethodInfo, req proto.Message) (_ transactionalizedRequest, returnedErr error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.transactionalizeRequest", nil) - defer span.Finish() + defer span.End() switch methodInfo.Scope { case protoregistry.ScopeRepository: diff --git a/internal/gitaly/storage/storagemgr/partition/transaction_manager.go b/internal/gitaly/storage/storagemgr/partition/transaction_manager.go index 8056d30a85b3ad095e1b12ff6636601d2a8a33af..bcf406407813c2839c92d0adf4aa1d369c262c45 100644 --- a/internal/gitaly/storage/storagemgr/partition/transaction_manager.go +++ b/internal/gitaly/storage/storagemgr/partition/transaction_manager.go @@ -42,6 +42,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" "gitlab.com/gitlab-org/labkit/correlation" + "go.opentelemetry.io/otel/attribute" "golang.org/x/sync/errgroup" "google.golang.org/protobuf/proto" ) @@ -248,9 +249,9 @@ func (mgr *TransactionManager) Begin(ctx context.Context, opts storage.BeginOpti } span, _ := tracing.StartSpanIfHasParent(ctx, "transaction.Begin", nil) - span.SetTag("write", opts.Write) - span.SetTag("relativePath", relativePath) - defer span.Finish() + span.SetAttributes(attribute.Bool("write", opts.Write)) + span.SetAttributes(attribute.String("relativePath", relativePath)) + defer span.End() mgr.mutex.Lock() @@ -275,7 +276,7 @@ func (mgr *TransactionManager) Begin(ctx context.Context, opts storage.BeginOpti mgr.mutex.Unlock() - span.SetTag("snapshotLSN", txn.snapshotLSN) + span.SetAttributes(attribute.String("snapshotLSN", txn.snapshotLSN.String())) txn.finish = func(admitted bool) error { defer trace.StartRegion(ctx, "finish transaction").End() @@ -1078,7 +1079,7 @@ type resultChannel chan commitResult // commit queues the transaction for processing and returns once the result has been determined. func (mgr *TransactionManager) commit(ctx context.Context, transaction *Transaction) (storage.LSN, error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.Commit", nil) - defer span.Finish() + defer span.End() transaction.result = make(resultChannel, 1) @@ -1229,7 +1230,7 @@ func (txn *Transaction) referenceUpdatesToProto() []*gitalypb.LogEntry_Reference // complete state and stages it into the transaction for committing. func (mgr *TransactionManager) stageRepositoryCreation(ctx context.Context, transaction *Transaction) error { span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.stageRepositoryCreation", nil) - defer span.Finish() + defer span.End() objectHash, err := transaction.snapshotRepository.ObjectHash(ctx) if err != nil { @@ -1268,7 +1269,7 @@ func (mgr *TransactionManager) setupStagingRepository(ctx context.Context, trans defer trace.StartRegion(ctx, "setupStagingRepository").End() span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.setupStagingRepository", nil) - defer span.Finish() + defer span.End() if transaction.stagingSnapshot != nil { return nil, errors.New("staging snapshot already setup") @@ -1318,7 +1319,7 @@ func (mgr *TransactionManager) packObjects(ctx context.Context, transaction *Tra } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.packObjects", nil) - defer span.Finish() + defer span.End() // We want to only pack the objects that are present in the quarantine as they are potentially // new. Disable the alternate, which is the repository's original object directory, so that we'll @@ -1759,7 +1760,7 @@ func (mgr *TransactionManager) processTransaction(ctx context.Context) (returned } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.processTransaction", nil) - defer span.Finish() + defer span.End() transaction.result <- func() commitResult { var zeroOID git.ObjectID diff --git a/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go b/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go index e1d130b8d4d3384f27f3164c7e54f28c5dab9773..94a7706fc37348cab73aa989ab3128b809f86bc1 100644 --- a/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go +++ b/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go @@ -122,7 +122,7 @@ func (mgr *TransactionManager) prepareHousekeeping(ctx context.Context, transact } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareHousekeeping", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("total", "prepare") defer finishTimer() @@ -155,7 +155,7 @@ func (mgr *TransactionManager) preparePackRefs(ctx context.Context, transaction } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.preparePackRefs", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("pack-refs", "prepare") defer finishTimer() @@ -204,7 +204,7 @@ func (mgr *TransactionManager) prepareRepacking(ctx context.Context, transaction } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareRepacking", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("repack", "prepare") defer finishTimer() @@ -392,7 +392,7 @@ func (mgr *TransactionManager) prepareCommitGraphs(ctx context.Context, transact } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareCommitGraphs", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("commit-graph", "prepare") defer finishTimer() @@ -471,7 +471,7 @@ func (mgr *TransactionManager) verifyHousekeeping(ctx context.Context, transacti defer trace.StartRegion(ctx, "verifyHousekeeping").End() span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.verifyHousekeeping", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("total", "verify") defer finishTimer() @@ -529,7 +529,7 @@ func (mgr *TransactionManager) verifyPackRefs(ctx context.Context, transaction * } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.verifyPackRefs", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("pack-refs", "verify") defer finishTimer() @@ -576,7 +576,7 @@ func (mgr *TransactionManager) verifyRepacking(ctx context.Context, transaction } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.verifyRepacking", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("repack", "verify") defer finishTimer() @@ -838,7 +838,7 @@ func (mgr *TransactionManager) prepareOffloading(ctx context.Context, transactio } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareOffloading", nil) - defer span.Finish() + defer span.End() // Loading configurations for offloading cfg := transaction.runHousekeeping.runOffloading.config @@ -977,7 +977,7 @@ func (mgr *TransactionManager) prepareRehydrating(ctx context.Context, transacti } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareRehydrating", nil) - defer span.Finish() + defer span.End() workingRepository := mgr.repositoryFactory.Build(transaction.snapshot.RelativePath(transaction.relativePath)) // workingRepoPath is the current repository path which we are performing operations on. diff --git a/internal/grpc/client/dial.go b/internal/grpc/client/dial.go index adb258b947827f00ea09586506443b819029adf7..583ee013292627874cf711a6953bc8b6913568da 100644 --- a/internal/grpc/client/dial.go +++ b/internal/grpc/client/dial.go @@ -9,10 +9,11 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/dnsresolver" "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/protoregistry" + "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" gitalyx509 "gitlab.com/gitlab-org/gitaly/v18/internal/x509" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" @@ -193,6 +194,9 @@ func New(ctx context.Context, rawAddress string, opts ...DialOption) (*grpc.Clie // For more information: // https://gitlab.com/groups/gitlab-org/-/epics/8971#note_1207008162 grpc.WithDefaultServiceConfig(defaultServiceConfig()), + + // Add distributed tracing instrumentation + grpc.WithStatsHandler(tracing.NewGRPCClientStatsHandler()), ) // https://github.com/grpc/grpc-go/issues/8207 prevents Unix connections from working @@ -213,7 +217,6 @@ func New(ctx context.Context, rawAddress string, opts ...DialOption) (*grpc.Clie // StreamInterceptor returns the stream interceptors that should be configured for a client. func StreamInterceptor() grpc.DialOption { return grpc.WithChainStreamInterceptor( - grpctracing.StreamClientTracingInterceptor(), grpccorrelation.StreamClientCorrelationInterceptor(), ) } @@ -221,7 +224,6 @@ func StreamInterceptor() grpc.DialOption { // UnaryInterceptor returns the unary interceptors that should be configured for a client. func UnaryInterceptor() grpc.DialOption { return grpc.WithChainUnaryInterceptor( - grpctracing.UnaryClientTracingInterceptor(), grpccorrelation.UnaryClientCorrelationInterceptor(), ) } diff --git a/internal/grpc/sidechannel/sidechannel.go b/internal/grpc/sidechannel/sidechannel.go index a9b69dcfd97bfe1d69606d505677d9777a67f18a..0ef683e971717ec0c948170da35bf9773365891a 100644 --- a/internal/grpc/sidechannel/sidechannel.go +++ b/internal/grpc/sidechannel/sidechannel.go @@ -39,7 +39,7 @@ type RetryableError struct{ error } // extracted from the current peer connection. func OpenSidechannel(ctx context.Context) (_ *ServerConn, err error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "sidechannel.OpenSidechannel", nil) - defer span.Finish() + defer span.End() md, ok := metadata.FromIncomingContext(ctx) if !ok { diff --git a/internal/limiter/concurrency_limiter.go b/internal/limiter/concurrency_limiter.go index 4f52a50d6e2a5995be6f556f2846e4b5776eda0b..28b78e4074b49f0593ff5f7d926061b2ae5b71ba 100644 --- a/internal/limiter/concurrency_limiter.go +++ b/internal/limiter/concurrency_limiter.go @@ -11,6 +11,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/structerr" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + "go.opentelemetry.io/otel/attribute" "google.golang.org/protobuf/types/known/durationpb" ) @@ -197,9 +198,11 @@ func (c *ConcurrencyLimiter) Limit(ctx context.Context, limitingKey string, f Li span, ctx := tracing.StartSpanIfHasParent( ctx, "limiter.ConcurrencyLimiter.Limit", - tracing.Tags{"key": limitingKey}, + []attribute.KeyValue{ + attribute.String("key", limitingKey), + }, ) - defer span.Finish() + defer span.End() if c.currentLimit() <= 0 { return f() diff --git a/internal/testhelper/tracing.go b/internal/testhelper/tracing.go index ea5c279a4e959d02cf32b78a7d8eb3097336728e..0e739a591d7f79accb85347316c1e52d82ea6d2e 100644 --- a/internal/testhelper/tracing.go +++ b/internal/testhelper/tracing.go @@ -1,17 +1,21 @@ package testhelper import ( - "fmt" + "context" "testing" - "time" - "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + oteltracingsdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + "go.opentelemetry.io/otel/trace" ) type stubTracingReporterConfig struct { - sampler jaeger.Sampler + sampler oteltracingsdk.Sampler } // StubTracingReporterOption is a function that modifies the config of stubbed tracing reporter @@ -20,67 +24,94 @@ type StubTracingReporterOption func(*stubTracingReporterConfig) // NeverSampled is an option that makes the stubbed tracer never sample spans func NeverSampled() StubTracingReporterOption { return func(conf *stubTracingReporterConfig) { - conf.sampler = jaeger.NewConstSampler(false) + conf.sampler = oteltracingsdk.NeverSample() } } -// StubTracingReporter stubs the distributed tracing's global tracer. It returns a reporter that -// records all generated spans along the way. The data is cleaned up afterward after the test is -// done. As there is only one global tracer, this stub is not safe to run in parallel. -func StubTracingReporter(t *testing.T, opts ...StubTracingReporterOption) (*jaeger.InMemoryReporter, func()) { +// StubTracingReporter is a tracing reporter to be sued in tests. +// It uses a memory exporter to save all spans in memory, which +// allows for inspection during tests. +type StubTracingReporter struct { + exporter *tracetest.InMemoryExporter + tp *oteltracingsdk.TracerProvider + old trace.TracerProvider +} + +// NewStubTracingReporter stubs the distributed tracing's global tracer. It returns a reporter that +// records all generated spans along the way. The data is cleaned up afterward after the test is done. +// The `StubTracingReporter` has a `TracerProvider()` method to get access to the tracer provider. +// This allows tests using this stub to run in parallel. +func NewStubTracingReporter(t *testing.T, opts ...StubTracingReporterOption) *StubTracingReporter { conf := &stubTracingReporterConfig{ - jaeger.NewConstSampler(true), + oteltracingsdk.AlwaysSample(), } for _, opt := range opts { opt(conf) } - reporter := jaeger.NewInMemoryReporter() - tracer, tracerCloser := jaeger.NewTracer("", conf.sampler, reporter) + res, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(t.Name()), + semconv.ServiceVersion("1.0.0"), + ), + ) + require.NoError(t, err) + + exporter := tracetest.NewInMemoryExporter() - old := opentracing.GlobalTracer() - opentracing.SetGlobalTracer(tracer) + tp := oteltracingsdk.NewTracerProvider( + oteltracingsdk.WithSyncer(exporter), + oteltracingsdk.WithSampler(conf.sampler), + oteltracingsdk.WithResource(res), + ) - return reporter, func() { - MustClose(t, tracerCloser) - opentracing.SetGlobalTracer(old) + old := otel.GetTracerProvider() + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}), + ) + + return &StubTracingReporter{ + exporter: exporter, + tp: tp, + old: old, } } -// Span is a struct that provides a more test-friendly way to assert distributed tracing spans. -type Span struct { - // Operation is the name of the operation being traced. - Operation string - // StartTime is the point in time when the span started. - StartTime time.Time - // Duration captures the elapsed time of the operation traced by the span. - Duration time.Duration - // Tags is a map that contains key-value pairs of stringified tags associated with the span. - Tags map[string]string +// TracerProvider returns the tracer provider associated with this reporter +func (e *StubTracingReporter) TracerProvider() trace.TracerProvider { + return e.tp } -// ReportedSpans function converts the spans that were captured by the stubbed reporter into an -// assertable data structure. Initially, the collected traces are represented using opentracing's -// Span interface. However, this interface is not suitable for testing purposes. Therefore, we must -// cast them to a Jaeger Span, which contains a lot of information that is not relevant to testing. -// The new span struct that is created contains only essential and safe-for-testing fields -func ReportedSpans(t *testing.T, reporter *jaeger.InMemoryReporter) []*Span { - var reportedSpans []*Span - for _, span := range reporter.GetSpans() { - jaegerSpan, ok := span.(*jaeger.Span) - require.Truef(t, ok, "stubbed span must be a Jaeger span") - - reportedSpan := &Span{ - Operation: jaegerSpan.OperationName(), - StartTime: jaegerSpan.StartTime(), - Duration: jaegerSpan.Duration(), - Tags: map[string]string{}, - } +// GetSpans returns all spans saved in memory +func (e *StubTracingReporter) GetSpans() tracetest.SpanStubs { + return e.exporter.GetSpans() +} + +// Reset empties the memory buffer holding spans. +// This is useful to reuse the same Reporter. +func (e *StubTracingReporter) Reset() { + e.exporter.Reset() +} + +// Close closes the underlying tracer provider +func (e *StubTracingReporter) Close() error { + defer func() { + otel.SetTracerProvider(e.old) + }() + return e.tp.Shutdown(context.Background()) +} - for key, value := range jaegerSpan.Tags() { - reportedSpan.Tags[key] = fmt.Sprintf("%s", value) +// GetSpanByName returns a span by its name +func (e *StubTracingReporter) GetSpanByName(name string) tracetest.SpanStub { + for _, recordedSpan := range e.GetSpans() { + if recordedSpan.Name == name { + return recordedSpan } - reportedSpans = append(reportedSpans, reportedSpan) } - return reportedSpans + return tracetest.SpanStub{} } diff --git a/internal/tracing/configuration.go b/internal/tracing/configuration.go new file mode 100644 index 0000000000000000000000000000000000000000..08a94e832e8450dc563687878fd12605171a8e10 --- /dev/null +++ b/internal/tracing/configuration.go @@ -0,0 +1,226 @@ +package tracing + +import ( + "errors" + "fmt" + "net" + "net/url" + "strconv" + "strings" +) + +const ( + // gitlabTracingEnvKey is the environment variable that contains the + // tracing configuration in the form of a connection string. + // The value of that variable is in one of those two format: + // 1. opentracing://?option1=value1?option2=value2 + // 2. http://host:port?option1=value1?option2=value2 + // This format is a legacy of the LabKit library used previously. + // The original parsing logic can be found here: + // https://gitlab.com/gitlab-org/labkit/-/blob/v1.31.2/tracing/connstr/connection_string_parser.go?ref_type=tags#L17 + gitlabTracingEnvKey = "GITLAB_TRACING" + + // optHeaderPrefix is the prefix used to provide HTTP headers into the + // GITLAB_TRACING connection string. + // When using OpenTelemetry, authentication to tracing backends is done by + // providing tokens or API keys through HTTP headers. + // Example: opentracing://datadog?option1=value1?header^DD_API_KEY=apikey + // In the example above, DD_API_KEY=apikey will be parsed as a header and + // included in each request to the Datadog backend. + optHeaderPrefix = "header^" + + // optSamplerParameter is the expected key in the connection string to provide the sampler parameter + optSamplerParameter = "sampler_param" + + otelGrpcProtocol = "otlp-grpc" + otelHTTPProtocol = "otlp-http" + + otlpGrpcDefaultEndpoint = "localhost:4317" + otlpHTTPDefaultEndpoint = "localhost:4318" + + vendorStackDriver = "stackdriver" + + defaultSamplerParamValue = 0.001 +) + +var ErrInvalidConfiguration = errors.New("invalid configuration") + +// tracingConfig holds the configuration extracted from GITLAB_TRACING +type tracingConfig struct { + // protocol to use when collector endpoint is accessible locally: otlp-grpc or otlp-http + protocol string + + // endpoint of the OTEL collector + endpoint string + + // vendor is the vendor that provides the tracing backend. + // This field is set when the collector is managed by a cloud provider such as stackdriver. + // If the collector is running locally or can be accessed by a URL directly + // (without the need of an SDK such as Google Cloud SDK), this field must remain empty + // and `protocol` and `endpoint` must be set. + vendor string + + // insecure relates to TLS/SSL. + // If true, HTTP will be used. + // If false, HTTPs will be used. + // Default is true (HTTP). + insecure bool + + // serviceName is he name of the service being instrumented. + serviceName string + + // samplingParam is a float value indicating the ratio to use for sampling. + // 1.0 = 100% + // 0.0 = 0% + samplingParam float64 + + // headers are included in each request sent to the collector endpoint. + // This is useful to pass along information such as API keys or tokens for authentication. + headers map[string]string +} + +// parseConnectionString parses a connection string and extracts tracing configuration. +// It returns an error if the configuration is invalid. +func parseConnectionString(connStr string) (cfg tracingConfig, err error) { + // Handle empty connection string + if connStr == "" { + return tracingConfig{}, + fmt.Errorf("%w: empty connection string", ErrInvalidConfiguration) + } + + // Parse URL + u, err := url.Parse(connStr) + if err != nil { + return tracingConfig{}, + fmt.Errorf("%w: failed to parse connection string: %w", ErrInvalidConfiguration, err) + } + + options := make(map[string]string) + headers := make(map[string]string) + + // Extract options from query parameters + for k, v := range u.Query() { + if len(v) == 0 { + continue + } + // if the query param is a header, add it to the header map + // else it is an option + if h, ok := strings.CutPrefix(k, optHeaderPrefix); ok { + headers[h] = v[0] + } else { + options[k] = v[0] + } + } + + var endpoint string + + // Extract protocol + protocol := u.Scheme + if protocol == "" { + return tracingConfig{}, + fmt.Errorf("%w: no protocol specified in connection string", ErrInvalidConfiguration) + } + + vendor := "" + switch protocol { + case otelGrpcProtocol, otelHTTPProtocol: + // For OpenTelemetry, the format is: + // otlp-grpc://localhost:4317?opt1=val1&opt2=val2 + // otlp-http://localhost:4318?opt1=val1&opt2=val2 + endpoint = u.Host + if endpoint == "" { + return tracingConfig{}, + fmt.Errorf("%w: no endpoint specified in connection string", ErrInvalidConfiguration) + } + if _, _, err := net.SplitHostPort(endpoint); err != nil { + return tracingConfig{}, + fmt.Errorf("%w: invalid endpoint format %q: %w", ErrInvalidConfiguration, endpoint, err) + } + + case "opentracing": + // When `opentracing` is used as the protocol, the format of the connection string is: + // opentracing://?opt1=val1&opt2=val2 + // OpenTracing is the legacy distributed tracing protocol, and is supported here for backward + // compatibility reasons. + // When using `opentracing`, only Stackdriver and Jaeger are supported. + // If the provider is not `jaeger` or `stackdriver`, it defaults to using OpenTelemetry + // protocol on the default gRPC endpoint. + provider := u.Host + if provider == "" { + return tracingConfig{}, + fmt.Errorf("%w: no provider specified in opentracing connection string", ErrInvalidConfiguration) + } + + // Map OpenTracing providers to OpenTelemetry protocols + switch provider { + case vendorStackDriver: + vendor = provider + protocol = "" + endpoint = "" + default: + // Jaeger is a special case because it is not a vendor + protocol = otelGrpcProtocol + endpoint = otlpGrpcDefaultEndpoint + if udpEndpoint, ok := options["udp_endpoint"]; ok { + // Parse the UDP endpoint to extract host:port + host, _, err := net.SplitHostPort(udpEndpoint) + if err == nil && host != "" { + // Use the host from UDP endpoint but with the OTLP port + endpoint = net.JoinHostPort(host, "4317") + } + + } + } + default: + return tracingConfig{}, + fmt.Errorf("%w: unsupported protocol (%s)", ErrInvalidConfiguration, protocol) + } + + // Check for insecure options + insecure := true + if val, ok := options["insecure"]; ok { + pval, err := strconv.ParseBool(val) + if err != nil { + return tracingConfig{}, + fmt.Errorf("%w: insecure option must be a boolean value: %w", ErrInvalidConfiguration, err) + } + insecure = pval + } + + samplerFloatValue := defaultSamplerParamValue + + // Check for sampler value in options + // If the option is not defined, we assume default value + if !strings.EqualFold(options[optSamplerParameter], "") { + f, err := strconv.ParseFloat(options[optSamplerParameter], 64) + if err != nil { + return tracingConfig{}, + fmt.Errorf("%w: %s must be a float value: %w", ErrInvalidConfiguration, optSamplerParameter, err) + } + + if f > 1.0 || f < 0.0 { + return tracingConfig{}, + fmt.Errorf("%s is '%v' but must be a float value between 0.0 and 1.0", optSamplerParameter, f) + } + + samplerFloatValue = clampValue(f, 0.0, 1.0) + } + + return tracingConfig{ + protocol: protocol, + endpoint: endpoint, + vendor: vendor, + insecure: insecure, + samplingParam: samplerFloatValue, + headers: headers, + }, nil +} + +func clampValue(val, min, max float64) float64 { + if val > max { + return 1.0 + } else if val < min { + return 0.0 + } + return val +} diff --git a/internal/tracing/configuration_test.go b/internal/tracing/configuration_test.go new file mode 100644 index 0000000000000000000000000000000000000000..68c3a82c8b03b71dd8b416692d7b517c48974e8b --- /dev/null +++ b/internal/tracing/configuration_test.go @@ -0,0 +1,137 @@ +package tracing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_parseConnectionString(t *testing.T) { + tests := []struct { + name string + connStr string + wantCfg tracingConfig + wantErr error + }{ + { + name: "otlp-grpc: when host is not specified it should return an error", + connStr: "otlp-grpc://", + wantCfg: tracingConfig{}, + wantErr: ErrInvalidConfiguration, + }, + { + name: "otlp-http: when host is not specified it should return an error", + connStr: "otlp-http://", + wantCfg: tracingConfig{}, + wantErr: ErrInvalidConfiguration, + }, + { + name: "otlp-grpc protocol: with headers", + connStr: "otlp-grpc://localhost:1234?header^API-KEY=my-key", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with headers", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key", + wantCfg: tracingConfig{ + protocol: otelHTTPProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-grpc protocol: with insecure false", + connStr: "otlp-grpc://localhost:1234?header^API-KEY=my-key&insecure=false", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: "localhost:1234", + insecure: false, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with insecure false", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key&insecure=true", + wantCfg: tracingConfig{ + protocol: otelHTTPProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-grpc protocol: with insecure true", + connStr: "otlp-grpc://localhost:1234?header^API-KEY=my-key&insecure=true", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with insecure true", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key&insecure=true", + wantCfg: tracingConfig{ + protocol: otelHTTPProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with typo in insecure value", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key&insecure=flase", + wantCfg: tracingConfig{}, + }, + { + name: "invalid provider: it should return an error", + connStr: "something://", + wantCfg: tracingConfig{}, + wantErr: ErrInvalidConfiguration, + }, + { + name: "jaeger provider", + connStr: "opentracing://jaeger?http_endpoint=127.0.0.1:1234&sampler_param=0.76", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: otlpGrpcDefaultEndpoint, + insecure: true, + samplingParam: 0.76, + headers: map[string]string{}, + }, + }, + { + name: "stackdriver provider", + connStr: "opentracing://stackdriver?http_endpoint=127.0.0.1:1234&sampler_param=1.0", + wantCfg: tracingConfig{ + insecure: true, + vendor: vendorStackDriver, + samplingParam: 1.0, + headers: map[string]string{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCfg, err := parseConnectionString(tt.connStr) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + return + } + assert.Equalf(t, tt.wantCfg, gotCfg, "parseConnectionString(%v)", tt.connStr) + }) + } +} diff --git a/internal/tracing/middlewares.go b/internal/tracing/middlewares.go new file mode 100644 index 0000000000000000000000000000000000000000..ba050a4ceebde6a6dcc58962970750637a830074 --- /dev/null +++ b/internal/tracing/middlewares.go @@ -0,0 +1,26 @@ +package tracing + +import ( + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc/stats" +) + +// NewGRPCServerStatsHandler is an OTEL instrumented middleware for gRPC servers. It returns a stats.Handler as +// recommended by the OTEL project after deprecating the UnaryServerInterceptor. +// See: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@v0.54.0#UnaryServerInterceptor +func NewGRPCServerStatsHandler(opts ...otelgrpc.Option) stats.Handler { + defaultOpts := []otelgrpc.Option{ + otelgrpc.WithPropagators(defaultPropagator), + } + return otelgrpc.NewServerHandler(append(defaultOpts, opts...)...) +} + +// NewGRPCClientStatsHandler is an OTEL instrumented middleware for gRPC client. It returns a stats.Handler as +// recommended by the OTEL project after deprecating the UnaryClientInterceptor. +// See: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@v0.54.0#UnaryServerInterceptor +func NewGRPCClientStatsHandler(opts ...otelgrpc.Option) stats.Handler { + defaultOpts := []otelgrpc.Option{ + otelgrpc.WithPropagators(defaultPropagator), + } + return otelgrpc.NewClientHandler(append(defaultOpts, opts...)...) +} diff --git a/internal/tracing/noop.go b/internal/tracing/noop.go deleted file mode 100644 index 38d96c76e426fc1dd6a40f785840058fef509f30..0000000000000000000000000000000000000000 --- a/internal/tracing/noop.go +++ /dev/null @@ -1,74 +0,0 @@ -package tracing - -import ( - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/log" -) - -// NoopSpan is a dummy span implementing opentracing.Span interface. All data setting functions do -// nothing. Data getting functions return other dummy objects. Spans of this kind are not recorded -// later. -type NoopSpan struct{} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Finish() {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) FinishWithOptions(_ opentracing.FinishOptions) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Context() opentracing.SpanContext { return NoopSpanContext{} } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogFields(...log.Field) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) SetOperationName(string) opentracing.Span { return s } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Log(opentracing.LogData) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) SetTag(string, interface{}) opentracing.Span { return s } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogKV(...interface{}) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) SetBaggageItem(string, string) opentracing.Span { return s } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) BaggageItem(string) string { return "" } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Tracer() opentracing.Tracer { return &NoopTracer{} } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogEvent(string) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogEventWithPayload(string, interface{}) {} - -// NoopSpanContext is a dummy context returned by NoopSpan -type NoopSpanContext struct{} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopSpanContext) ForeachBaggageItem(func(k string, v string) bool) {} - -// NoopTracer is a dummy tracer returned by NoopSpan -type NoopTracer struct{} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopTracer) StartSpan(string, ...opentracing.StartSpanOption) opentracing.Span { - return &NoopSpan{} -} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopTracer) Inject(opentracing.SpanContext, interface{}, interface{}) error { - return nil -} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopTracer) Extract(interface{}, interface{}) (opentracing.SpanContext, error) { - return &NoopSpanContext{}, nil -} diff --git a/internal/tracing/passthrough.go b/internal/tracing/passthrough.go index 9cbc61e566ca218c38d6e9475e5f00d98737f9aa..dab3b5359e51fbc865448c922caae3783f26eda9 100644 --- a/internal/tracing/passthrough.go +++ b/internal/tracing/passthrough.go @@ -2,53 +2,77 @@ package tracing import ( "context" + "errors" "strings" - grpcmwmetadata "github.com/grpc-ecosystem/go-grpc-middleware/v2/metadata" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) -// ExtractSpanContextFromEnv extracts a SpanContext from the environment variable list. The caller -// usually passes the result of os.Environ() into this method. -func ExtractSpanContextFromEnv(envs []string) (opentracing.SpanContext, error) { - envMap := environAsMap(envs) - return opentracing.GlobalTracer().Extract( - opentracing.TextMap, - opentracing.TextMapCarrier(envMap), - ) +var ErrNoSpanContextInEnv = errors.New("no span context found in environment variables") + +// PropagateFromEnv propagates the SpanContext extracted from environment variables +// into a new context that can later be used to create child spans. +func PropagateFromEnv(envs []string) (context.Context, error) { + // Create carrier from environment variables. A carrier is an object that holds data + // and to which data can be written, and from which data can be read. + carrier := propagation.MapCarrier(environAsMap(envs)) + + // Create a TextMap propagator, that can read from and write to any TextMap carrier such as a MapCarrier. + // This propagator is created with the TraceContext and Baggage propagator, which means that it is + // configured to either read trace context and baggage from a context and write it into a carrier, or read + // baggage and trace context from a carrier to inject it into a new span context. + propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) + + // Start with an empty context + emptyCtx := context.Background() + + // Using the empty context as parent, create a new context in which to inject the span context from the + // carrier (if any). + spanCtx := propagator.Extract(emptyCtx, carrier) + + // If a span context has been successfully injected into `spanCtx`, return it. + if trace.SpanContextFromContext(spanCtx).IsValid() { + return spanCtx, nil + } + + // Else, return the empty context with no span context whatsoever + return emptyCtx, ErrNoSpanContextInEnv } -// UnaryPassthroughInterceptor is a client gRPC unary interceptor that rewrites a span context into -// the outgoing metadata of the call. It is useful for intermediate systems who don't want to -// start new spans. -func UnaryPassthroughInterceptor(spanContext opentracing.SpanContext) grpc.UnaryClientInterceptor { - return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - ctxWithMetadata := injectSpanContext(parentCtx, spanContext) +// UnaryPassthroughInterceptor is a client gRPC unary interceptor that injects a span context into +// the outgoing context of the call. It is useful to propagate span context between processes that do +// not have automatic span propagation between them. +func UnaryPassthroughInterceptor(contextToInject context.Context) grpc.UnaryClientInterceptor { + return func(requestCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + ctxWithMetadata := injectSpanContextIntoContext(requestCtx, contextToInject) return invoker(ctxWithMetadata, method, req, reply, cc, opts...) } } -// StreamPassthroughInterceptor is equivalent to UnaryPassthroughInterceptor, but for streaming -// gRPC calls. -func StreamPassthroughInterceptor(spanContext opentracing.SpanContext) grpc.StreamClientInterceptor { - return func(parentCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { - ctxWithMetadata := injectSpanContext(parentCtx, spanContext) +// StreamPassthroughInterceptor is equivalent to UnaryPassthroughInterceptor, but for streaming gRPC calls. +func StreamPassthroughInterceptor(contextToInject context.Context) grpc.StreamClientInterceptor { + return func(requestCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + ctxWithMetadata := injectSpanContextIntoContext(requestCtx, contextToInject) return streamer(ctxWithMetadata, desc, cc, method, opts...) } } -func injectSpanContext(parentCtx context.Context, spanContext opentracing.SpanContext) context.Context { - tracer := opentracing.GlobalTracer() - md := grpcmwmetadata.ExtractOutgoing(parentCtx).Clone() - if err := tracer.Inject(spanContext, opentracing.HTTPHeaders, metadataTextMap(md)); err != nil { - return parentCtx - } - ctxWithMetadata := md.ToOutgoing(parentCtx) - return ctxWithMetadata +// injectSpanContextIntoContext inject the span context included in `contextToInject` into `ctx`. +// Because context are immutable objects, the returned context is a new context with `ctx` as its parent +// and includes the span context extracted from `contextToInject`. +func injectSpanContextIntoContext(ctx context.Context, contextToInject context.Context) context.Context { + grpcMetaData, _ := metadata.FromOutgoingContext(ctx) + otel.GetTextMapPropagator().Inject(contextToInject, grpcMetadataCarrier(grpcMetaData)) + return metadata.NewOutgoingContext(ctx, grpcMetaData) } +// environAsMap takes a list of environment variable in the format `key=value` +// and splits each value using the `=` sign such as to return a map with each +// key associated with its value. func environAsMap(env []string) map[string]string { envMap := make(map[string]string, len(env)) for _, v := range env { @@ -58,14 +82,34 @@ func environAsMap(env []string) map[string]string { return envMap } -// metadataTextMap is a wrapper for gRPC's metadata.MD. It implements opentracing.TextMapWriter, -// which is to set opentracing-related fields. In this use case, the passthrough interceptors touch -// span identify and maybe some luggage or tag fields. This implementation is good-enough for such -// fields. gRPC header name format is: -// > Header-Name → 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - . -// > Source: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md -type metadataTextMap metadata.MD +// grpcMetadataCarrier implements propagation.TextMapCarrier. The purpose of this +// custom implementation is to convert all metadata keys into lowercase. The default +// OTEL implementation of HeaderCarrier converts all keys into HTTP Headers using the +// canonical form. See: +// * https://github.com/open-telemetry/opentelemetry-go/blob/main/propagation/propagation.go#L92 +// * https://github.com/golang/go/blob/master/src/net/http/header.go#L40 +// +// However, in gRPC, only lowerkey metadata keys are accepted. The character +// set is `[0-9-a-z-_.]`. See: +// * https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md +type grpcMetadataCarrier metadata.MD + +func (g grpcMetadataCarrier) Get(key string) string { + values := metadata.MD(g).Get(key) + if len(values) == 0 { + return "" + } + return values[0] +} -func (m metadataTextMap) Set(key, val string) { - m[strings.ToLower(key)] = []string{val} +func (g grpcMetadataCarrier) Set(key string, value string) { + metadata.MD(g).Set(strings.ToLower(key), value) +} + +func (g grpcMetadataCarrier) Keys() []string { + var keys []string + for k := range g { + keys = append(keys, k) + } + return keys } diff --git a/internal/tracing/passthrough_test.go b/internal/tracing/passthrough_test.go index 08db259688e253897ec726644d89a9aeae692317..086cdc1628d9e700e860dfac0d3e42066166e818 100644 --- a/internal/tracing/passthrough_test.go +++ b/internal/tracing/passthrough_test.go @@ -1,4 +1,4 @@ -package tracing +package tracing_test import ( "context" @@ -7,120 +7,154 @@ import ( "net" "testing" - "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/client" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" + "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/interop/grpc_testing" ) func TestExtractSpanContextFromEnv(t *testing.T) { - _, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) - injectedSpan := opentracing.StartSpan("test", opentracing.Tag{Key: "do-not-carry", Value: "value"}) - injectedSpan.SetBaggageItem("hi", "hello") + ctx := context.Background() - jaegerInjectedSpan := injectedSpan.(*jaeger.Span) - jaegerInjectedSpanContext := jaegerInjectedSpan.SpanContext() + tracer := reporter.TracerProvider().Tracer(t.Name()) + initSpanCtx, span := tracer.Start(ctx, "test", + trace.WithAttributes(attribute.String("do-not-carry", "value")), + ) + defer span.End() + + testBaggage, err := baggage.NewMember("hi", "hello") + require.NoError(t, err) + + bg, err := baggage.New(testBaggage) + require.NoError(t, err) - createSpanContext := func() []string { - env := envMap{} - err := opentracing.GlobalTracer().Inject(injectedSpan.Context(), opentracing.TextMap, env) - require.NoError(t, err) - return env.toSlice() + // Set baggage into a new immutable context + initSpanCtx = baggage.ContextWithBaggage(initSpanCtx, bg) + + createSpanContextAsEnv := func() []string { + envs := map[string]string{} + carrier := propagation.MapCarrier(envs) + otel.GetTextMapPropagator().Inject(initSpanCtx, carrier) + return envMapToSlice(envs) } tests := []struct { - desc string - envs []string - expectedContext opentracing.SpanContext - expectedError string + desc string + envs []string + expectedError error }{ { desc: "empty environment map", envs: []string{}, - expectedError: "opentracing: SpanContext not found in Extract carrier", + expectedError: tracing.ErrNoSpanContextInEnv, }, { desc: "irrelevant environment map", envs: []string{"SOME_THING=A", "SOMETHING_ELSE=B"}, - expectedError: "opentracing: SpanContext not found in Extract carrier", + expectedError: tracing.ErrNoSpanContextInEnv, }, { - desc: "environment variable includes span context", - envs: createSpanContext(), + desc: "environment variable includes span context", + envs: createSpanContextAsEnv(), + expectedError: nil, }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - spanContext, err := ExtractSpanContextFromEnv(tc.envs) - if tc.expectedError != "" { - require.Equal(t, tc.expectedError, err.Error()) + rootSpanCtx, err := tracing.PropagateFromEnv(tc.envs) + if err != nil { + require.Equal(t, tc.expectedError, err) } else { require.NoError(t, err) - require.NotNil(t, spanContext) + require.NotNil(t, rootSpanCtx) + + rootSpan := trace.SpanFromContext(rootSpanCtx) + require.True(t, rootSpan.SpanContext().IsValid()) + + // We need a span to verify the propagation and parent-child relationship + // so it is safe to close it just after starting it. + childSpanCtx, childSpan := tracer.Start(rootSpanCtx, tc.desc) + childSpan.End() - span := opentracing.StartSpan("test", opentracing.ChildOf(spanContext)) - jaegerSpan := span.(*jaeger.Span) - jaegerSpanContext := jaegerSpan.SpanContext() + require.Equal(t, rootSpan.SpanContext().TraceID().String(), childSpan.SpanContext().TraceID().String()) - require.Equal(t, jaegerInjectedSpanContext.TraceID(), jaegerSpanContext.TraceID()) - require.Equal(t, jaegerInjectedSpanContext.SpanID(), jaegerSpanContext.ParentID()) - require.Equal(t, opentracing.Tags{}, jaegerSpan.Tags()) - require.Equal(t, "hello", jaegerSpan.BaggageItem("hi")) + // Baggage should have been propagated + childBaggage := baggage.FromContext(childSpanCtx) + require.Equal(t, childBaggage.Member(testBaggage.Key()).Value(), testBaggage.Value()) + + // Attributes on a span are not propagated since they are + // not a span context. + recordedSpan := reporter.GetSpanByName(tc.desc) + require.True(t, recordedSpan.SpanContext.IsValid()) + require.Nil(t, recordedSpan.Attributes) } }) } } func TestUnaryPassthroughInterceptor(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) + + tracer := reporter.TracerProvider().Tracer(t.Name()) tests := []struct { desc string - setup func(*testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) + setup func(*testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) expectedSpans []string }{ { desc: "empty span context", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - return 0, nil, func() {} + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + emptyCtx := context.Background() + nullTraceID := trace.TraceID{} + return nullTraceID, emptyCtx, func() {} }, expectedSpans: []string{ - "/grpc.testing.TestService/UnaryCall", + "grpc.testing.TestService/UnaryCall", }, }, { - desc: "span context with a simple span", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - span := opentracing.GlobalTracer().StartSpan("root") - return span.(*jaeger.Span).SpanContext().SpanID(), span.Context(), span.Finish + desc: "span context with a single span", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + spanCtx, span := tracer.Start(initCtx, "init") + spanTraceID := span.SpanContext().TraceID() + return spanTraceID, spanCtx, func() { span.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/UnaryCall", - "root", + "grpc.testing.TestService/UnaryCall", + "init", }, }, { - desc: "span context with a trace chain", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - root := opentracing.GlobalTracer().StartSpan("root") - child := opentracing.GlobalTracer().StartSpan("child", opentracing.ChildOf(root.Context())) - grandChild := opentracing.GlobalTracer().StartSpan("grandChild", opentracing.ChildOf(child.Context())) - - return grandChild.(*jaeger.Span).SpanContext().SpanID(), grandChild.Context(), func() { - grandChild.Finish() - child.Finish() - root.Finish() + desc: "span context with a multi-span trace chain", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + rootCtx, rootSpan := tracer.Start(initCtx, "root") + childCtx, childSpan := tracer.Start(rootCtx, "child") + grandchildCtx, grandchildSpan := tracer.Start(childCtx, "grandChild") + + spanTraceID := grandchildSpan.SpanContext().TraceID() + return spanTraceID, grandchildCtx, func() { + grandchildSpan.End() + childSpan.End() + rootSpan.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/UnaryCall", + "grpc.testing.TestService/UnaryCall", "grandChild", "child", "root", @@ -131,72 +165,92 @@ func TestUnaryPassthroughInterceptor(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { reporter.Reset() - var parentID jaeger.SpanID + ctx := testhelper.Context(t) + + var traceID trace.TraceID service := &testSvc{ unaryCall: func(ctx context.Context, request *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) { - if span := opentracing.SpanFromContext(ctx); span != nil { - parentID = span.(*jaeger.Span).SpanContext().ParentID() + if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { + traceID = span.SpanContext().TraceID() } return &grpc_testing.SimpleResponse{}, nil }, } - expectedParentID, spanContext, finishFunc := tc.setup(t) - client := startFakeGitalyServer(t, service, spanContext) - _, err := client.UnaryCall(testhelper.Context(t), &grpc_testing.SimpleRequest{}) + expectedTraceID, contextToInject, finishFunc := tc.setup(t) + + grpcClient := startFakeGitalyServer(t, contextToInject, reporter.TracerProvider(), service) + _, err := grpcClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{}) require.NoError(t, err) + // In the case where there is no root span to inject into + // the incoming rRPC calls, we cannot know in advance what the + // traceID of the span created by the gRPC middleware will be. + // So in that special case, when we cannot know in advance what + // traceID to expect, we return the empty one. That's why this check + // is needed. + if expectedTraceID.IsValid() { + require.Equal(t, expectedTraceID.String(), traceID.String()) + } + finishFunc() - require.Equal(t, expectedParentID, parentID) - require.Equal(t, tc.expectedSpans, reportedSpans(t, reporter)) + require.Equal(t, tc.expectedSpans, reportedSpanNames(t, reporter)) }) } } func TestStreamPassthroughInterceptor(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() + + tracer := reporter.TracerProvider().Tracer(t.Name()) tests := []struct { desc string - setup func(*testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) + setup func(*testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) expectedSpans []string }{ { desc: "empty span context", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - return 0, nil, func() {} + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + emptyCtx := context.Background() + nullTraceID := trace.TraceID{} + return nullTraceID, emptyCtx, func() {} }, expectedSpans: []string{ - "/grpc.testing.TestService/FullDuplexCall", + "grpc.testing.TestService/FullDuplexCall", }, }, { - desc: "span context with a simple span", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - span := opentracing.GlobalTracer().StartSpan("root") - return span.(*jaeger.Span).SpanContext().SpanID(), span.Context(), span.Finish + desc: "span context with a single span", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + spanCtx, span := tracer.Start(initCtx, "init") + spanTraceID := span.SpanContext().TraceID() + return spanTraceID, spanCtx, func() { span.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/FullDuplexCall", - "root", + "grpc.testing.TestService/FullDuplexCall", + "init", }, }, { - desc: "span context with a trace chain", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - root := opentracing.GlobalTracer().StartSpan("root") - child := opentracing.GlobalTracer().StartSpan("child", opentracing.ChildOf(root.Context())) - grandChild := opentracing.GlobalTracer().StartSpan("grandChild", opentracing.ChildOf(child.Context())) - - return grandChild.(*jaeger.Span).SpanContext().SpanID(), grandChild.Context(), func() { - grandChild.Finish() - child.Finish() - root.Finish() + desc: "span context with a multi-span trace chain", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + rootCtx, rootSpan := tracer.Start(initCtx, "root") + childCtx, childSpan := tracer.Start(rootCtx, "child") + grandchildCtx, grandchildSpan := tracer.Start(childCtx, "grandChild") + + spanTraceID := grandchildSpan.SpanContext().TraceID() + return spanTraceID, grandchildCtx, func() { + grandchildSpan.End() + childSpan.End() + rootSpan.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/FullDuplexCall", + "grpc.testing.TestService/FullDuplexCall", "grandChild", "child", "root", @@ -207,42 +261,48 @@ func TestStreamPassthroughInterceptor(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { reporter.Reset() - var parentID jaeger.SpanID + var traceID trace.TraceID service := &testSvc{ fullDuplexCall: func(stream grpc_testing.TestService_FullDuplexCallServer) error { _, err := stream.Recv() - require.NoError(t, err) - if span := opentracing.SpanFromContext(stream.Context()); span != nil { - parentID = span.(*jaeger.Span).SpanContext().ParentID() + require.Equal(t, err, io.EOF) + if span := trace.SpanFromContext(stream.Context()); span.SpanContext().IsValid() { + traceID = span.SpanContext().TraceID() } require.NoError(t, stream.Send(&grpc_testing.StreamingOutputCallResponse{})) return nil }, } - expectedParentID, spanContext, finishFunc := tc.setup(t) - client := startFakeGitalyServer(t, service, spanContext) - stream, err := client.FullDuplexCall(testhelper.Context(t)) - require.NoError(t, err) + expectedTraceID, contextToInject, finishFunc := tc.setup(t) - require.NoError(t, stream.Send(&grpc_testing.StreamingOutputCallRequest{})) + grpcClient := startFakeGitalyServer(t, contextToInject, reporter.TracerProvider(), service) + stream, err := grpcClient.FullDuplexCall(testhelper.Context(t)) + require.NoError(t, err) + require.NoError(t, stream.CloseSend()) resp, err := stream.Recv() require.NoError(t, err) testhelper.ProtoEqual(t, &grpc_testing.StreamingOutputCallResponse{}, resp) - resp, err = stream.Recv() - require.Equal(t, io.EOF, err) - require.Nil(t, resp) - finishFunc() - require.Equal(t, expectedParentID, parentID) - require.Equal(t, tc.expectedSpans, reportedSpans(t, reporter)) + // In the case where there is no root span to inject into + // the incoming rRPC calls, we cannot know in advance what the + // traceID of the span created by the gRPC middleware will be. + // So in that special case, when we cannot know in advance what + // traceID to expect, we return the empty one. That's why this check + // is needed. + if expectedTraceID.IsValid() { + require.Equal(t, expectedTraceID.String(), traceID.String()) + } + + require.Equal(t, tc.expectedSpans, reportedSpanNames(t, reporter)) }) } } +// testSvc is the gRPC server implementation for the fake server initialized below type testSvc struct { grpc_testing.UnimplementedTestServiceServer unaryCall func(context.Context, *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) @@ -257,16 +317,23 @@ func (ts *testSvc) FullDuplexCall(stream grpc_testing.TestService_FullDuplexCall return ts.fullDuplexCall(stream) } -func startFakeGitalyServer(t *testing.T, svc *testSvc, spanContext opentracing.SpanContext) grpc_testing.TestServiceClient { +// startFakeGitalyServer starts a test Gitaly server and returns a client already configured to +// communicate with the server. +func startFakeGitalyServer(t *testing.T, spanContext context.Context, tp trace.TracerProvider, svc *testSvc) grpc_testing.TestServiceClient { t.Helper() listener, err := net.Listen("tcp", "localhost:0") require.NoError(t, err) - srv := grpc.NewServer( - grpc.StreamInterceptor(grpctracing.StreamServerTracingInterceptor()), - grpc.UnaryInterceptor(grpctracing.UnaryServerTracingInterceptor()), + // Set global propagators + otel.SetTextMapPropagator( + propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ), ) + + srv := grpc.NewServer(grpc.StatsHandler(otelgrpc.NewServerHandler(otelgrpc.WithTracerProvider(tp)))) grpc_testing.RegisterTestServiceServer(srv, svc) go testhelper.MustServe(t, srv, listener) @@ -276,8 +343,8 @@ func startFakeGitalyServer(t *testing.T, svc *testSvc, spanContext opentracing.S testhelper.Context(t), fmt.Sprintf("tcp://%s", listener.Addr().String()), client.WithGrpcOptions([]grpc.DialOption{ - grpc.WithUnaryInterceptor(UnaryPassthroughInterceptor(spanContext)), - grpc.WithStreamInterceptor(StreamPassthroughInterceptor(spanContext)), + grpc.WithUnaryInterceptor(tracing.UnaryPassthroughInterceptor(spanContext)), + grpc.WithStreamInterceptor(tracing.StreamPassthroughInterceptor(spanContext)), })) require.NoError(t, err) t.Cleanup(func() { testhelper.MustClose(t, conn) }) @@ -285,27 +352,21 @@ func startFakeGitalyServer(t *testing.T, svc *testSvc, spanContext opentracing.S return grpc_testing.NewTestServiceClient(conn) } -// envMap implements opentracing.TextMapReader and opentracing.TextMapWriter. It is used to create -// testing environment maps used in below tests -type envMap map[string]string - -func (e envMap) Set(key, val string) { - e[key] = val -} - -func (e envMap) ForeachKey(handler func(key string, val string) error) error { - for key, val := range e { - if err := handler(key, val); err != nil { - return err - } - } - return nil -} - -func (e envMap) toSlice() []string { +// envMapToSlice takes a map of key/value string pair and converts them into +// a slice where each key. and value are delimited with a `=` sign, the same +// way environment variables are defined in a .env file. +func envMapToSlice(envs map[string]string) []string { var envSlice []string - for key, value := range e { + for key, value := range envs { envSlice = append(envSlice, fmt.Sprintf("%s=%s", key, value)) } return envSlice } + +func reportedSpanNames(_ *testing.T, reporter *testhelper.StubTracingReporter) []string { + var names []string + for _, span := range reporter.GetSpans() { + names = append(names, span.Name) + } + return names +} diff --git a/internal/tracing/tracer.go b/internal/tracing/tracer.go new file mode 100644 index 0000000000000000000000000000000000000000..4cf1da4a97ce33313462bfe374ebc7fbed838969 --- /dev/null +++ b/internal/tracing/tracer.go @@ -0,0 +1,173 @@ +package tracing + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + stackdriver "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" + "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/contrib/propagators/ot" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + oteltracingsdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +var ErrTracingDisabled = errors.New("tracing is disabled") + +// defaultPropagator is the default TextMapPropagator +// Propagators are objects responsible to propagate tracing data. +// Note: contrary to OpenTracing, OpenTelemetry propagates baggage within contexts, not within spans. +var defaultPropagator = propagation.NewCompositeTextMapPropagator( + // TraceContext propagator propagates trace IDs across process boundaries + propagation.TraceContext{}, + + // Baggage propagator propagates baggage across process boundaries. + propagation.Baggage{}, + + // OpenTracing propagator is needed because other components at GitLab still use it. + ot.OT{}, + + // Jaeger/Uber propagator is needed because other components at GitLab still use it. + jaeger.Jaeger{}, +) + +// InitializeTracerProvider creates an OpenTelemetry tracer provider. +// The configuration is coming from the GITLAB_TRACING environment variable +// in the form of a connection string. +// It has two format: +// 1. opentracing://jaeger?host=localhost:1234&opt1=val1&opt2=val2 +// 2. otlp-:host:port?opt1=val2&opt2=val2 +// The function returns a TracerProvider, an io.Closer to close the TracerProvider +// and an error. +// NOTE: If an error occurs, the returned tracer provider is a Noop Tracer Provider. +// Thus, it is always safe to register the returned tracer, even in case of an error. +// This allows to return non-critical errors that can be logged by the caller to debug +// tracer misconfiguration. +func InitializeTracerProvider(ctx context.Context, serviceName string) (trace.TracerProvider, io.Closer, error) { + // We start by registering a noop tracer. + // Since tracing is not required to run Gitaly, we don't want to abort + // Gitaly when tracing cannot be initialized. Thus, if an error occurs + // we will return a NoopTracer. Only if initialization succeed will we + // register a configured tracer provider. + noopTracer := noop.NewTracerProvider() + otel.SetTracerProvider(noopTracer) + + connStr := os.Getenv(gitlabTracingEnvKey) + if connStr == "" { + return noopTracer, io.NopCloser(nil), ErrTracingDisabled + } + + cfg, err := parseConnectionString(connStr) + if err != nil { + return noopTracer, io.NopCloser(nil), err + } + + cfg.serviceName = serviceName + + var exporter oteltracingsdk.SpanExporter + switch cfg.vendor { + case vendorStackDriver: + exporter, err = stackdriver.New(stackdriver.WithContext(ctx)) + default: + switch cfg.protocol { + case otelHTTPProtocol: + exporter, err = otlptrace.New(ctx, newHTTPOtelClient(cfg)) + case otelGrpcProtocol: + exporter, err = otlptrace.New(ctx, newGrpcOtelClient(cfg)) + default: + err = fmt.Errorf("unsupported protocol: %s", cfg.protocol) + } + } + + if err != nil { + return noopTracer, io.NopCloser(nil), err + } + + // warningError wraps all errors that should be reported for logging but + // that are not critical in initializing the tracer provider. + var warningError error = nil + + // Create a resource to provide additional context to spans + // When creating a new resource, OTEL SDK used `Detectors` to detect + // certain attributes such as PID, Owner ID, etc. + // Some Detector's might fail for various reason, and when that is the + // case, a PartialResource is returned. + // The worst that can happen is a PartialResource returned and some attributes + // empty or missing on spans. In our case, a PartialResource + // is better than nothing, so we accept it and ignore the error. + // One example of a Detector failing is when running unit tests. The user ID + // is often a dummy one, such as 9999. The OwnerDetector cannot find this + // user ID on the system, and thus returns an error. + serviceResource, resErr := resource.New( + ctx, + resource.WithAttributes(attribute.String("service.name", cfg.serviceName)), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + resource.WithProcess(), + resource.WithOS(), + resource.WithContainer(), + resource.WithHost(), + ) + if resErr != nil { + // wrap the error but do not return, as this error is not critical + // for creating the tracer provider + warningError = fmt.Errorf("error creating resources: %w", resErr) + } + + // Create a new tracer provider with the exporter configured above + tp := oteltracingsdk.NewTracerProvider( + oteltracingsdk.WithBatcher(exporter), + oteltracingsdk.WithResource(serviceResource), + oteltracingsdk.WithSampler(oteltracingsdk.TraceIDRatioBased(cfg.samplingParam)), + ) + + otel.SetTextMapPropagator(defaultPropagator) + otel.SetTracerProvider(tp) + return tp, newOtelTracerCloser(tp), warningError +} + +func newHTTPOtelClient(cfg tracingConfig, opts ...otlptracehttp.Option) otlptrace.Client { + defaultOps := []otlptracehttp.Option{ + otlptracehttp.WithEndpoint(cfg.endpoint), + otlptracehttp.WithHeaders(cfg.headers), + } + if cfg.insecure { + defaultOps = append(defaultOps, otlptracehttp.WithInsecure()) + } + + return otlptracehttp.NewClient(append(defaultOps, opts...)...) +} + +func newGrpcOtelClient(cfg tracingConfig, opts ...otlptracegrpc.Option) otlptrace.Client { + defaultOps := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint(cfg.endpoint), + otlptracegrpc.WithHeaders(cfg.headers), + } + if cfg.insecure { + defaultOps = append(defaultOps, otlptracegrpc.WithInsecure()) + } + + return otlptracegrpc.NewClient(append(defaultOps, opts...)...) +} + +type otelTracerCloser struct { + tp *oteltracingsdk.TracerProvider +} + +func newOtelTracerCloser(tp *oteltracingsdk.TracerProvider) *otelTracerCloser { + return &otelTracerCloser{tp: tp} +} + +func (c *otelTracerCloser) Close() error { + return c.tp.Shutdown(context.Background()) +} diff --git a/internal/tracing/tracer_test.go b/internal/tracing/tracer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..8f4b1f653e380857d5f174189db40c6b97aa10e6 --- /dev/null +++ b/internal/tracing/tracer_test.go @@ -0,0 +1,80 @@ +package tracing + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" +) + +// TestInitializeTracerProvider tests the initialization of a tracer +// Because the configuration is already tested in configuration_test.go +// this test does not attempt to test all possible combination of configuration. +func TestInitializeTracerProvider(t *testing.T) { + tests := []struct { + name string + setup func() + wantErr error + }{ + { + name: "tracing disabled", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "") + }, + wantErr: ErrTracingDisabled, + }, + { + name: "otel-grpc tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "otlp-grpc://localhost:1234") + }, + wantErr: nil, + }, + { + name: "otel-http tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "otlp-http://localhost:1234") + }, + wantErr: nil, + }, + { + name: "opentracing tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "opentracing://jaeger:localhost:1234") + }, + wantErr: nil, + }, + { + name: "invalid tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "opentrac://jaeger:localhost:1234") + }, + wantErr: ErrInvalidConfiguration, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup() + ctx := context.Background() + + tp, closer, err := InitializeTracerProvider(ctx, "gitaly") + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + assert.IsType(t, tp, noop.TracerProvider{}) + return + } + + assert.NotNil(t, tp) + // For some cases, such as when resource creation fails, an error will be returned + // but this error should not prevent a tracer provider to be created. + // As such, we validate here that the tracer returned is indeed a working tracer provider + // and not a NoopTracer. + assert.IsNotType(t, tp, noop.TracerProvider{}) + assert.NotNil(t, closer) + assert.Nil(t, closer.Close()) + }) + } +} diff --git a/internal/tracing/tracing.go b/internal/tracing/tracing.go index bae918fd0705c6bb397d20d69fb63bec4b2806c4..27930c346314efe89c57d55d825aafdfefec1a71 100644 --- a/internal/tracing/tracing.go +++ b/internal/tracing/tracing.go @@ -3,52 +3,53 @@ package tracing import ( "context" - "github.com/opentracing/opentracing-go" - "gitlab.com/gitlab-org/labkit/tracing" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) -// Tags is a key-value map. It is used to set tags for a span -type Tags map[string]any +var tracerName = "gitaly" -// StartSpan creates a new span with name and options (mostly tags). This function is a wrapper for +var defaultNoopSpan = &noop.Span{} + +// StartSpan creates a new span with name, attributes and options. This function is a wrapper for // underlying tracing libraries. This method should only be used at the entrypoint of the program. -func StartSpan(ctx context.Context, spanName string, tags Tags, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { - return opentracing.StartSpanFromContext(ctx, spanName, tagsToOpentracingTags(opts, tags)...) +// +//nolint:spancheck +func StartSpan(ctx context.Context, spanName string, attrs []attribute.KeyValue, opts ...trace.SpanStartOption) (trace.Span, context.Context) { + cctx, span := otel.GetTracerProvider().Tracer(tracerName).Start(ctx, spanName, opts...) + span.SetAttributes(attrs...) + + return span, cctx } // StartSpanIfHasParent creates a new span if the context already has an existing span. This function // adds a simple validation to prevent orphan spans outside interested code paths. It returns a dummy // span, which acts as normal span, but does absolutely nothing and is not recorded later. -func StartSpanIfHasParent(ctx context.Context, spanName string, tags Tags, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { - parent := opentracing.SpanFromContext(ctx) - if parent == nil { - return &NoopSpan{}, ctx +func StartSpanIfHasParent(ctx context.Context, spanName string, attrs []attribute.KeyValue, opts ...trace.SpanStartOption) (trace.Span, context.Context) { + if !trace.SpanFromContext(ctx).SpanContext().IsValid() { + return defaultNoopSpan, ctx } - return opentracing.StartSpanFromContext(ctx, spanName, tagsToOpentracingTags(opts, tags)...) + return StartSpan(ctx, spanName, attrs, opts...) } -// DiscardSpanInContext discards the current active span from the context. This function is helpful -// when the current code path enters an area shared by other code paths. Git catfile cache is a -// good example of this type of span. +// DiscardSpanInContext discards the current active span in the context by replacing it with a +// non-recording span. If the context does not contain any active span, it is left intact. +// This function is helpful when the current code path enters an area shared by other code +// paths. Git catfile cache is a good example of this type of span. func DiscardSpanInContext(ctx context.Context) context.Context { - if opentracing.SpanFromContext(ctx) == nil { + if !trace.SpanFromContext(ctx).SpanContext().IsValid() { return ctx } - return opentracing.ContextWithSpan(ctx, nil) + return trace.ContextWithSpan(ctx, nil) } // IsSampled tells whether a span belongs to a sampled trace func IsSampled(ctx context.Context) bool { - span := opentracing.SpanFromContext(ctx) + span := trace.SpanFromContext(ctx) if span != nil { - return tracing.IsSampled(span) + return span.SpanContext().IsSampled() } return false } - -func tagsToOpentracingTags(opts []opentracing.StartSpanOption, tags Tags) []opentracing.StartSpanOption { - for key, value := range tags { - opts = append(opts, opentracing.Tag{Key: key, Value: value}) - } - return opts -} diff --git a/internal/tracing/tracing_test.go b/internal/tracing/tracing_test.go index abd42871e2f07d352de98160e640ccd8bddb7964..73a7f05773410fc3b58771f156facf3573110749 100644 --- a/internal/tracing/tracing_test.go +++ b/internal/tracing/tracing_test.go @@ -3,146 +3,131 @@ package tracing import ( "testing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/log" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) func TestCreateSpan(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() - - var span opentracing.Span - span, _ = StartSpan(testhelper.Context(t), "root", Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }) - span.Finish() - - require.Equal(t, []string{"root"}, reportedSpans(t, reporter)) - require.Equal(t, Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }, spanTags(span)) + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() + + ctx := testhelper.Context(t) + + spanAttributes := []attribute.KeyValue{ + attribute.String("tagRoot1", "value1"), + attribute.String("tagRoot2", "value2"), + attribute.String("tagRoot3", "value3"), + } + + span, _ := StartSpan(ctx, "root", spanAttributes) + span.End() + + generatedSpans := reporter.GetSpans() + require.Len(t, generatedSpans, 1) + + require.Equal(t, "root", generatedSpans[0].Name) + require.Equal(t, spanAttributes, generatedSpans[0].Attributes) } func TestCreateSpanIfHasParent_emptyContext(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span, span2 opentracing.Span + var span, span2 trace.Span span, ctx = StartSpanIfHasParent(ctx, "should-not-report-root", nil) - span.SetBaggageItem("baggage", "baggageValue") - span.SetTag("tag", "tagValue") - span.LogFields(log.String("log", "logValue")) - span.LogKV("log2", "logValue") - span.Finish() + span.SetAttributes(attribute.String("tag", "tagValue")) + span.End() span2, _ = StartSpanIfHasParent(ctx, "should-not-report-child", nil) - span2.Finish() + span2.End() - require.Empty(t, reportedSpans(t, reporter)) + require.Empty(t, reporter.GetSpans()) } func TestCreateSpanIfHasParent_hasParent(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span1, span2 opentracing.Span + var span1, span2 trace.Span span1, ctx = StartSpan(ctx, "root", nil) span2, _ = StartSpanIfHasParent(ctx, "child", nil) - span2.Finish() - span1.Finish() + span2.End() + span1.End() - spans := reportedSpans(t, reporter) + spans := reportedSpanNames(t, reporter) require.Equal(t, []string{"child", "root"}, spans) + require.Len(t, reporter.GetSpans(), 2) } func TestCreateSpanIfHasParent_hasParentWithTags(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span1, span2 opentracing.Span - span1, ctx = StartSpan(ctx, "root", Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }) - span2, _ = StartSpanIfHasParent(ctx, "child", Tags{ - "tagChild1": "value1", - "tagChild2": "value2", - "tagChild3": "value3", - }) - span2.Finish() - span1.Finish() - - spans := reportedSpans(t, reporter) - require.Equal(t, []string{"child", "root"}, spans) - require.Equal(t, Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }, spanTags(span1)) - require.Equal(t, Tags{ - "tagChild1": "value1", - "tagChild2": "value2", - "tagChild3": "value3", - }, spanTags(span2)) + var span1, span2 trace.Span + span1Attributes := []attribute.KeyValue{ + attribute.String("tagRoot1", "value1"), + attribute.String("tagRoot2", "value2"), + attribute.String("tagRoot3", "value3"), + } + span1, ctx = StartSpan(ctx, "root", span1Attributes) + + span2Attributes := []attribute.KeyValue{ + attribute.String("tagChild1", "value1"), + attribute.String("tagChild2", "value2"), + attribute.String("tagChild3", "value3"), + } + span2, _ = StartSpanIfHasParent(ctx, "child", span2Attributes) + + span2.End() + span1.End() + + require.Equal(t, []string{"child", "root"}, reportedSpanNames(t, reporter)) + + recordedSpans := reporter.GetSpans() + require.Len(t, recordedSpans, 2) + + require.Equal(t, span1Attributes, recordedSpans[1].Attributes) + require.Equal(t, span2Attributes, recordedSpans[0].Attributes) } func TestDiscardSpanInContext_emptyContext(t *testing.T) { ctx := DiscardSpanInContext(testhelper.Context(t)) - require.Nil(t, opentracing.SpanFromContext(ctx)) + span := trace.SpanFromContext(ctx) + require.False(t, span.IsRecording()) } func TestDiscardSpanInContext_hasParent(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span1, span2, span3 opentracing.Span + var span1, span2, span3 trace.Span span1, ctx = StartSpan(ctx, "root", nil) span2, ctx = StartSpanIfHasParent(ctx, "child", nil) ctx = DiscardSpanInContext(ctx) span3, _ = StartSpanIfHasParent(ctx, "discarded", nil) - span3.Finish() - span2.Finish() - span1.Finish() + span3.End() + span2.End() + span1.End() - spans := reportedSpans(t, reporter) + spans := reportedSpanNames(t, reporter) require.Equal(t, []string{"child", "root"}, spans) } -func reportedSpans(t *testing.T, reporter *jaeger.InMemoryReporter) []string { +func reportedSpanNames(_ *testing.T, reporter *testhelper.StubTracingReporter) []string { var names []string for _, span := range reporter.GetSpans() { - if !assert.IsType(t, span, &jaeger.Span{}) { - continue - } - jaegerSpan := span.(*jaeger.Span) - names = append(names, jaegerSpan.OperationName()) + names = append(names, span.Name) } return names } - -func spanTags(span opentracing.Span) Tags { - tags := Tags{} - jaegerSpan := span.(*jaeger.Span) - for key, value := range jaegerSpan.Tags() { - tags[key] = value - } - return tags -}