Skip to content

Commit

Permalink
add bucket boundaries option to override defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
ajatprabha committed Apr 16, 2024
1 parent bab137f commit 4eef6f5
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 90 deletions.
12 changes: 12 additions & 0 deletions docs/docs/sdk/otelcourier.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Package otelcourier instruments the github.com/gojek/courier\-go package.
- [Constants](#constants)
- [Variables](#variables)
- [func DefaultTopicAttributeTransformer\(\_ context.Context, topic string\) string](#DefaultTopicAttributeTransformer)
- [type BucketBoundaries](#BucketBoundaries)
- [type OTel](#OTel)
- [func New\(service string, opts ...Option\) \*OTel](#New)
- [func \(t \*OTel\) ApplyMiddlewares\(c UseMiddleware\)](#OTel.ApplyMiddlewares)
Expand Down Expand Up @@ -83,6 +84,17 @@ func DefaultTopicAttributeTransformer(_ context.Context, topic string) string

DefaultTopicAttributeTransformer is the default transformer for topic attribute.

<a name="BucketBoundaries"></a>
## type [BucketBoundaries](https://github.com/gojek/courier-go/blob/main/otelcourier/options.go#L66-L68)

BucketBoundaries helps override default histogram bucket boundaries for metrics.

```go
type BucketBoundaries struct {
Publisher, Subscriber, Unsubscriber, Callback []float64
}
```

<a name="OTel"></a>
## type [OTel](https://github.com/gojek/courier-go/blob/main/otelcourier/otel.go#L29-L41)

Expand Down
29 changes: 18 additions & 11 deletions otelcourier/metric.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,30 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

var metricFlows = map[tracePath]struct {
name string
boundaries []float64
}{
tracePublisher: {name: "publish", boundaries: prom.ExponentialBucketsRange(0.0001, 1, 10)},
traceSubscriber: {name: "subscribe", boundaries: prom.ExponentialBucketsRange(0.001, 1, 7)},
traceUnsubscriber: {name: "unsubscribe", boundaries: prom.ExponentialBucketsRange(0.001, 1, 7)},
traceCallback: {name: "subscribe.callback", boundaries: prom.ExponentialBucketsRange(0.001, 10, 15)},
}
var (
defaultBoundaries = prom.ExponentialBucketsRange(0.001, 1, 7)
defBucketBoundaries = map[tracePath][]float64{
tracePublisher: prom.ExponentialBucketsRange(0.0001, 1, 10),
traceSubscriber: defaultBoundaries,
traceUnsubscriber: defaultBoundaries,
traceCallback: prom.ExponentialBucketsRange(0.001, 10, 15),
}

metricFlows = map[tracePath]string{
tracePublisher: "publish",
traceSubscriber: "subscribe",
traceUnsubscriber: "unsubscribe",
traceCallback: "subscribe.callback",
}
)

func (t *OTel) initRecorders() {
func (t *OTel) initRecorders(histogramBoundaries map[tracePath][]float64) {
for path, flow := range metricFlows {
if !t.tracePaths.match(path) {
continue
}

t.rc[path] = t.newRecorder(flow.name, flow.boundaries)
t.rc[path] = t.newRecorder(flow, histogramBoundaries[path])
}

if t.infoHandler != nil {
Expand Down
61 changes: 43 additions & 18 deletions otelcourier/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// Option helps configure trace options.
type Option interface{ apply(*traceOptions) }
type Option interface{ apply(*options) }

// TopicAttributeTransformer helps transform topic before making an attribute for it.
// It is used in metric recording only. Traces use the original topic.
Expand All @@ -20,31 +20,31 @@ type TopicAttributeTransformer func(context.Context, string) string
// WithTracerProvider specifies a tracer provider to use for creating a tracer.
// If none is specified, the global provider is used.
func WithTracerProvider(provider oteltrace.TracerProvider) Option {
return optFn(func(opts *traceOptions) { opts.tracerProvider = provider })
return optFn(func(opts *options) { opts.tracerProvider = provider })
}

// WithMeterProvider specifies a meter provider to use for creating a meter.
// If none is specified, the global provider is used.
func WithMeterProvider(provider metric.MeterProvider) Option {
return optFn(func(opts *traceOptions) { opts.meterProvider = provider })
return optFn(func(opts *options) { opts.meterProvider = provider })
}

// WithTextMapPropagator specifies the propagator to use for extracting/injecting key-value texts.
// If none is specified, the global provider is used.
func WithTextMapPropagator(propagator propagation.TextMapPropagator) Option {
return optFn(func(opts *traceOptions) { opts.propagator = propagator })
return optFn(func(opts *options) { opts.propagator = propagator })
}

// WithTextMapCarrierExtractFunc is used to specify the function which should be used to
// extract propagation.TextMapCarrier from the ongoing context.Context.
func WithTextMapCarrierExtractFunc(fn func(context.Context) propagation.TextMapCarrier) Option {
return optFn(func(opts *traceOptions) { opts.textMapCarrierExtractor = fn })
return optFn(func(opts *options) { opts.textMapCarrierExtractor = fn })
}

// WithInfoHandlerFrom is used to specify the handler which should be used to
// extract client information from the courier.Client instance.
func WithInfoHandlerFrom(c interface{ InfoHandler() http.Handler }) Option {
return optFn(func(opts *traceOptions) { opts.infoHandler = c.InfoHandler() })
return optFn(func(opts *options) { opts.infoHandler = c.InfoHandler() })
}

// DisableCallbackTracing disables implicit tracing on subscription callbacks.
Expand All @@ -62,19 +62,43 @@ var DisableUnsubscriberTracing = &disableTracePathOpt{traceUnsubscriber}
// DefaultTopicAttributeTransformer is the default transformer for topic attribute.
func DefaultTopicAttributeTransformer(_ context.Context, topic string) string { return topic }

// BucketBoundaries helps override default histogram bucket boundaries for metrics.
type BucketBoundaries struct {
Publisher, Subscriber, Unsubscriber, Callback []float64
}

func (b BucketBoundaries) apply(opts *options) {
if b.Publisher != nil {
opts.histogramBoundaries[tracePublisher] = b.Publisher
}

if b.Subscriber != nil {
opts.histogramBoundaries[traceSubscriber] = b.Subscriber
}

if b.Unsubscriber != nil {
opts.histogramBoundaries[traceUnsubscriber] = b.Unsubscriber
}

if b.Callback != nil {
opts.histogramBoundaries[traceCallback] = b.Callback
}
}

/// private types and functions

type optFn func(*traceOptions)
type optFn func(*options)

func (fn optFn) apply(opts *traceOptions) { fn(opts) }
func (fn optFn) apply(opts *options) { fn(opts) }

func defaultOptions() *traceOptions {
return &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber + traceCallback,
topicTransformer: DefaultTopicAttributeTransformer,
func defaultOptions() *options {
return &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber + traceCallback,
topicTransformer: DefaultTopicAttributeTransformer,
histogramBoundaries: defBucketBoundaries,
}
}

Expand All @@ -85,14 +109,15 @@ const (
traceCallback
)

type traceOptions struct {
type options struct {
tracerProvider oteltrace.TracerProvider
meterProvider metric.MeterProvider
propagator propagation.TextMapPropagator
textMapCarrierExtractor func(context.Context) propagation.TextMapCarrier
tracePaths tracePath
topicTransformer TopicAttributeTransformer
infoHandler http.Handler
histogramBoundaries map[tracePath][]float64
}

type tracePath uint
Expand All @@ -101,6 +126,6 @@ func (tp tracePath) match(o tracePath) bool { return tp&o != 0 }

type disableTracePathOpt struct{ tracePath }

func (o *disableTracePathOpt) apply(opts *traceOptions) { opts.tracePaths &^= o.tracePath }
func (o *disableTracePathOpt) apply(opts *options) { opts.tracePaths &^= o.tracePath }

func (t TopicAttributeTransformer) apply(opts *traceOptions) { opts.topicTransformer = t }
func (t TopicAttributeTransformer) apply(opts *options) { opts.topicTransformer = t }
128 changes: 77 additions & 51 deletions otelcourier/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,112 +14,120 @@ func TestOption(t *testing.T) {
testcases := []struct {
name string
options []Option
want *traceOptions
want *options
}{
{
name: "DefaultOptions",
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber + traceCallback,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber + traceCallback,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisablePublisher",
options: []Option{DisablePublisherTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: traceSubscriber + traceUnsubscriber + traceCallback,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: traceSubscriber + traceUnsubscriber + traceCallback,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisableSubscriber",
options: []Option{DisableSubscriberTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceUnsubscriber + traceCallback,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceUnsubscriber + traceCallback,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisableUnsubscriber",
options: []Option{DisableUnsubscriberTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceCallback,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceCallback,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisableCallback",
options: []Option{DisableCallbackTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisableCallbackTwice",
options: []Option{DisableCallbackTracing, DisableCallbackTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: tracePublisher + traceSubscriber + traceUnsubscriber,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisableAllButCallback",
options: []Option{DisablePublisherTracing, DisableSubscriberTracing, DisableUnsubscriberTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: traceCallback,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: traceCallback,
histogramBoundaries: defBucketBoundaries,
},
},
{
name: "DisableTwoTracers",
options: []Option{DisablePublisherTracing, DisableSubscriberTracing},
want: &traceOptions{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: traceUnsubscriber + traceCallback,
want: &options{
tracerProvider: otel.GetTracerProvider(),
meterProvider: otel.GetMeterProvider(),
propagator: otel.GetTextMapPropagator(),
tracePaths: traceUnsubscriber + traceCallback,
histogramBoundaries: defBucketBoundaries,
},
},
}

for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
to := defaultOptions()
o := defaultOptions()

for _, opt := range tt.options {
opt.apply(to)
opt.apply(o)
}

// Ignore topicTransformer
to.topicTransformer = nil
o.topicTransformer = nil

assert.Equal(t, tt.want, to)
assert.Equal(t, tt.want, o)
})
}

extractorFn := func(_ context.Context) propagation.TextMapCarrier { return &propagation.MapCarrier{} }

t.Run("TextMapCarrierExtractor", func(t *testing.T) {
to := defaultOptions()
o := defaultOptions()

WithTextMapCarrierExtractFunc(extractorFn).apply(to)
WithTextMapCarrierExtractFunc(extractorFn).apply(o)

assert.Equal(t, fmt.Sprintf("%p", extractorFn), fmt.Sprintf("%p", to.textMapCarrierExtractor))
assert.Equal(t, fmt.Sprintf("%p", extractorFn), fmt.Sprintf("%p", o.textMapCarrierExtractor))
})

t.Run("DefaultTopicAttributeTransformer", func(t *testing.T) {
Expand All @@ -129,10 +137,28 @@ func TestOption(t *testing.T) {
customTransformer := func(_ context.Context, topic string) string { return topic + "custom" }

t.Run("CustomTopicAttributeTransformer", func(t *testing.T) {
to := defaultOptions()
o := defaultOptions()

TopicAttributeTransformer(customTransformer).apply(to)
TopicAttributeTransformer(customTransformer).apply(o)

assert.Equal(t, "topiccustom", to.topicTransformer(context.Background(), "topic"))
assert.Equal(t, "topiccustom", o.topicTransformer(context.Background(), "topic"))
})

t.Run("BucketBoundaries", func(t *testing.T) {
o := defaultOptions()

bb := BucketBoundaries{
Publisher: []float64{1, 2, 3},
Subscriber: []float64{4, 5, 6},
Unsubscriber: []float64{7, 8, 9},
Callback: []float64{10, 11, 12},
}

bb.apply(o)

assert.Equal(t, []float64{1, 2, 3}, o.histogramBoundaries[tracePublisher])
assert.Equal(t, []float64{4, 5, 6}, o.histogramBoundaries[traceSubscriber])
assert.Equal(t, []float64{7, 8, 9}, o.histogramBoundaries[traceUnsubscriber])
assert.Equal(t, []float64{10, 11, 12}, o.histogramBoundaries[traceCallback])
})
}
Loading

0 comments on commit 4eef6f5

Please sign in to comment.