Skip to content

Commit e1d521a

Browse files
committed
Fix tracing.NewRandomRatioBased panic (#5044)
* fix NewRandomRatioBased panic Signed-off-by: Alan Protasio <[email protected]> * changelog Signed-off-by: Alan Protasio <[email protected]> * make lint happy Signed-off-by: Alan Protasio <[email protected]> * adding back xray propagator Signed-off-by: Alan Protasio <[email protected]> * rename the sampler Signed-off-by: Alan Protasio <[email protected]> Signed-off-by: Alan Protasio <[email protected]> Signed-off-by: Alan Protasio <[email protected]>
1 parent 06d7313 commit e1d521a

File tree

4 files changed

+57
-58
lines changed

4 files changed

+57
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Changelog
22

33
## master / unreleased
4+
## 1.14.1 2022-12-18
5+
* [BUGFIX] Fix panic when otel and xray tracing is enabled. #5044
46

57
## 1.14.0 2022-12-02
68

pkg/tracing/sampler/sampling.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
11
package sampler
22

33
import (
4+
"encoding/binary"
45
"fmt"
6+
"math"
57

68
sdktrace "go.opentelemetry.io/otel/sdk/trace"
79
"go.opentelemetry.io/otel/trace"
810
)
911

10-
type randGenerator interface {
11-
Float64() float64
12-
}
13-
14-
type RandomRatioBased struct {
12+
type xrayTraceIDRatioBased struct {
1513
sdktrace.Sampler
16-
rnd randGenerator
17-
fraction float64
14+
max uint64
1815
}
1916

20-
// NewRandomRatioBased creates a sampler based on random number.
17+
// NewXrayTraceIDRatioBased creates a sampler based on random number.
2118
// fraction parameter should be between 0 and 1 where:
2219
// fraction >= 1 it will always sample
2320
// fraction <= 0 it will never sample
24-
func NewRandomRatioBased(fraction float64, rnd randGenerator) sdktrace.Sampler {
21+
func NewXrayTraceIDRatioBased(fraction float64) sdktrace.Sampler {
2522
if fraction >= 1 {
2623
return sdktrace.AlwaysSample()
2724
} else if fraction <= 0 {
2825
return sdktrace.NeverSample()
2926
}
3027

31-
return &RandomRatioBased{rnd: rnd, fraction: fraction}
28+
return &xrayTraceIDRatioBased{max: uint64(fraction * math.MaxUint64)}
3229
}
3330

34-
func (s *RandomRatioBased) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
31+
func (s *xrayTraceIDRatioBased) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult {
32+
// The default otel sampler pick the first 8 bytes to make the sampling decision and this is a problem to
33+
// xray case as the first 4 bytes on the xray traceId is the time of the original request and the random part are
34+
// the 12 last bytes, and so, this sampler pick the last 8 bytes to make the sampling decision.
35+
// Xray Trace format: https://docs.aws.amazon.com/xray/latest/devguide/xray-api-sendingdata.html
36+
// Xray Id Generator: https://github.com/open-telemetry/opentelemetry-go-contrib/blob/54f0bc5c0fd347cd6db9b7bc14c9f0c00dfcb36b/propagators/aws/xray/idgenerator.go#L58-L63
37+
// Ref: https://github.com/open-telemetry/opentelemetry-go/blob/7a60bc785d669fa6ad26ba70e88151d4df631d90/sdk/trace/sampling.go#L82-L95
38+
val := binary.BigEndian.Uint64(p.TraceID[8:16])
3539
psc := trace.SpanContextFromContext(p.ParentContext)
36-
shouldSample := s.rnd.Float64() < s.fraction
40+
shouldSample := val < s.max
3741
if shouldSample {
3842
return sdktrace.SamplingResult{
3943
Decision: sdktrace.RecordAndSample,
@@ -46,6 +50,6 @@ func (s *RandomRatioBased) ShouldSample(p sdktrace.SamplingParameters) sdktrace.
4650
}
4751
}
4852

49-
func (s *RandomRatioBased) Description() string {
50-
return fmt.Sprintf("RandomRatioBased{%g}", s.fraction)
53+
func (s *xrayTraceIDRatioBased) Description() string {
54+
return fmt.Sprintf("xrayTraceIDRatioBased{%v}", s.max)
5155
}

pkg/tracing/sampler/sampling_test.go

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,72 @@ package sampler
22

33
import (
44
"context"
5-
"math/rand"
65
"testing"
76

87
"github.com/stretchr/testify/require"
8+
"go.opentelemetry.io/contrib/propagators/aws/xray"
99
sdktrace "go.opentelemetry.io/otel/sdk/trace"
1010
"go.opentelemetry.io/otel/trace"
1111
)
1212

13-
type mockGenerator struct {
14-
mockedValue float64
15-
}
16-
17-
func (g *mockGenerator) Float64() float64 {
18-
return g.mockedValue
19-
}
20-
2113
func Test_ShouldSample(t *testing.T) {
22-
traceID, _ := trace.TraceIDFromHex("4bf92f3577b34da6a3ce929d0e0e4736")
2314
parentCtx := trace.ContextWithSpanContext(
2415
context.Background(),
2516
trace.NewSpanContext(trace.SpanContextConfig{
2617
TraceState: trace.TraceState{},
2718
}),
2819
)
2920

21+
generator := xray.NewIDGenerator()
22+
3023
tests := []struct {
31-
name string
32-
samplingDecision sdktrace.SamplingDecision
33-
fraction float64
34-
generator randGenerator
24+
name string
25+
fraction float64
3526
}{
3627
{
37-
name: "should always sample",
38-
samplingDecision: sdktrace.RecordAndSample,
39-
fraction: 1,
40-
generator: rand.New(rand.NewSource(rand.Int63())),
28+
name: "should always sample",
29+
fraction: 1,
4130
},
4231
{
43-
name: "should nerver sample",
44-
samplingDecision: sdktrace.Drop,
45-
fraction: 0,
46-
generator: rand.New(rand.NewSource(rand.Int63())),
32+
name: "should nerver sample",
33+
fraction: 0,
4734
},
4835
{
49-
name: "should sample when fraction is above generated",
50-
samplingDecision: sdktrace.RecordAndSample,
51-
fraction: 0.5,
52-
generator: &mockGenerator{0.2},
36+
name: "should sample 50%",
37+
fraction: 0.5,
5338
},
5439
{
55-
name: "should not sample when fraction is not above generated",
56-
samplingDecision: sdktrace.Drop,
57-
fraction: 0.5,
58-
generator: &mockGenerator{0.8},
40+
name: "should sample 10%",
41+
fraction: 0.1,
42+
},
43+
{
44+
name: "should sample 1%",
45+
fraction: 0.01,
5946
},
6047
}
6148

49+
totalIterations := 10000
6250
for _, tt := range tests {
6351
t.Run(tt.name, func(t *testing.T) {
64-
s := NewRandomRatioBased(tt.fraction, tt.generator)
65-
for i := 0; i < 100; i++ {
66-
52+
totalSampled := 0
53+
s := NewXrayTraceIDRatioBased(tt.fraction)
54+
for i := 0; i < totalIterations; i++ {
55+
traceID, _ := generator.NewIDs(context.Background())
6756
r := s.ShouldSample(
6857
sdktrace.SamplingParameters{
6958
ParentContext: parentCtx,
7059
TraceID: traceID,
7160
Name: "test",
7261
Kind: trace.SpanKindServer,
7362
})
74-
75-
require.Equal(t, tt.samplingDecision, r.Decision)
63+
if r.Decision == sdktrace.RecordAndSample {
64+
totalSampled++
65+
}
7666
}
67+
68+
tolerance := 0.1
69+
expected := tt.fraction * float64(totalIterations)
70+
require.InDelta(t, expected, totalSampled, expected*tolerance)
7771
})
7872
}
7973
}

pkg/tracing/tracing.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ import (
44
"context"
55
"flag"
66
"fmt"
7-
"math/rand"
87
"strings"
9-
"time"
108

119
"github.com/go-kit/log/level"
1210
"github.com/pkg/errors"
1311
"github.com/weaveworks/common/tracing"
12+
"go.opentelemetry.io/contrib/propagators/aws/xray"
1413
"google.golang.org/grpc/credentials"
1514

1615
"github.com/opentracing/opentracing-go"
17-
"go.opentelemetry.io/contrib/propagators/aws/xray"
1816
"go.opentelemetry.io/otel"
1917
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
2018
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
@@ -110,10 +108,10 @@ func SetupTracing(ctx context.Context, name string, c Config) (func(context.Cont
110108
return nil, fmt.Errorf("creating tracing resource: %w", err)
111109
}
112110

113-
tracerProvider := newTraceProvider(r, c, exporter)
111+
propagator, tracerProvider := newTraceProvider(r, c, exporter)
114112

115113
bridge, wrappedProvider := migration.NewCortexBridgeTracerWrapper(tracerProvider.Tracer("github.com/cortexproject/cortex/cmd/cortex"))
116-
bridge.SetTextMapPropagator(propagation.TraceContext{})
114+
bridge.SetTextMapPropagator(propagator)
117115
opentracing.SetGlobalTracer(bridge)
118116
otel.SetTracerProvider(wrappedProvider)
119117

@@ -125,21 +123,22 @@ func SetupTracing(ctx context.Context, name string, c Config) (func(context.Cont
125123
}, nil
126124
}
127125

128-
func newTraceProvider(r *resource.Resource, c Config, exporter *otlptrace.Exporter) *sdktrace.TracerProvider {
126+
func newTraceProvider(r *resource.Resource, c Config, exporter *otlptrace.Exporter) (propagation.TextMapPropagator, *sdktrace.TracerProvider) {
129127
options := []sdktrace.TracerProviderOption{
130128
sdktrace.WithBatcher(exporter),
131129
sdktrace.WithResource(r),
132130
}
133-
131+
var propagator propagation.TextMapPropagator = propagation.TraceContext{}
134132
switch strings.ToLower(c.Otel.ExporterType) {
135133
case "awsxray":
136134
options = append(options, sdktrace.WithIDGenerator(xray.NewIDGenerator()))
137-
options = append(options, sdktrace.WithSampler(sdktrace.ParentBased(sampler.NewRandomRatioBased(c.Otel.SampleRatio, rand.New(rand.NewSource(time.Now().Unix()))))))
135+
options = append(options, sdktrace.WithSampler(sdktrace.ParentBased(sampler.NewXrayTraceIDRatioBased(c.Otel.SampleRatio))))
136+
propagator = xray.Propagator{}
138137
default:
139138
options = append(options, sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(c.Otel.SampleRatio))))
140139
}
141140

142-
return sdktrace.NewTracerProvider(options...)
141+
return propagator, sdktrace.NewTracerProvider(options...)
143142
}
144143

145144
func newResource(ctx context.Context, target string, detectors []resource.Detector) (*resource.Resource, error) {

0 commit comments

Comments
 (0)