Repository navigation
Conversation
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
tracing.NewTracer already short-circuits span creation behind an atomic flag, so creating a span costs nothing when no tracer provider is installed. The arguments of every tracer.Start call were still evaluated at the call site, though: formatting fingerprints with Sprintf, calling Name() and GroupKey(), building attribute.KeyValue structs and the variadic option slice. On the per-alert ingestion path (provider Put, routeAlert, groupAlert, aggregation group insert, inhibitor processAlert, both Mutes methods) that work ran for every alert with tracing disabled. Measured with tracing disabled on a single Start call carrying an alert name and a formatted fingerprint: attributes passed to Start: 314 ns/op, 5 allocs/op bare Start, SetAttributes behind span.IsRecording(): 5 ns/op, 0 allocs/op The guarded number needed one more fix in tracing itself: Start returned the package-level noopSpan variable, and boxing a copy of that 80-byte noop.Span struct into the trace.Span interface allocated on every call. The variable is now of interface type, so it is boxed once at package init. Move the attributes out of every tracer.Start call and set them with span.SetAttributes behind span.IsRecording(). On the hot paths, guard span.AddEvent calls that build attributes from computed values the same way. Constant span kinds stay in Start. In the in-memory provider, stop allocating the propagation header map and running the propagator for every alert and listener when tracing is disabled; leave provider.Alert.Header nil, which both consumers (the dispatcher and the inhibitor) already treat as "nothing to extract". tracing.Enabled is exported for that check. Add the convention to AGENTS.md and a go/ast guard test in tracing/ that fails when any tracer.Start call receives trace.WithAttributes, so the pattern does not creep back in with new spans. Signed-off-by: Guido Trotter <guido@hudson-trading.com>
Signed-off-by: Guido Trotter <guido@hudson-trading.com>
Use the default internal span kind and reuse complete option slices for client and producer spans. Guard the remaining attributes and error formatting with IsRecording. Add allocation tests and benchmarks for an empty provider batch, inhibitor Mutes, and successful and failed notifications. These paths now allocate nothing with tracing disabled; the regression tests fail against the previous implementation. Signed-off-by: Guido Trotter <guido@hudson-trading.com>
ac5b996 to
72cea22
Compare
Restrict attribute option construction to guarded event and error calls, independently of tracer names or option variables. Check guards for span attributes and require span-kind options to be built outside hot paths. Exercise accepted and rejected source patterns, skip vendored code, and document the sampling and propagation boundaries. Keep the silencer event attributes inline so the source guard can validate their use. Signed-off-by: Guido Trotter <guido@hudson-trading.com>
72cea22 to
edc5402
Compare
SoloJacobs
left a comment
There was a problem hiding this comment.
Can we cleanup the tracing callsite instead by using predefined functions? tracing should of course stay agnostic types in alertmanager, but we can do something like this:
// tracing/spans.go
package tracing
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
var (
producer = []trace.SpanStartOption{trace.WithSpanKind(trace.SpanKindProducer)}
// Boxed once, since converting noop.Span{} per call allocates.
noopSpan trace.Span = noop.Span{}
)
// Tracer starts Alertmanager's spans. The zero value starts none.
type Tracer struct {
t trace.Tracer
}
// StartSilencerMutes starts the span for silence.Silencer.Mutes.
func (tr Tracer) StartSilencerMutes(ctx context.Context, fingerprint func() string) (context.Context, trace.Span) {
if tr.t == nil {
return ctx, noopSpan
}
ctx, span := tr.t.Start(ctx, "silence.Silencer.Mutes")
span.SetAttributes(attribute.String("alerting.alert.fingerprint", fingerprint()))
return ctx, span
}
// StartInhibitorMutes starts the span for inhibit.Inhibitor.Mutes.
func (tr Tracer) StartInhibitorMutes(ctx context.Context, fingerprint func() st.Span) {
if tr.t == nil {
return ctx, noopSpan
}
ctx, span := tr.t.Start(ctx, "inhibit.Inhibitor.Mutes")
span.SetAttributes(attribute.String("alerting.alert.fingerprint", fingerprint()))
return ctx, span
}
// StartAlertFilter starts the span for the API alert filter.
func (tr Tracer) StartAlertFilter(ctx context.Context) (context.Context, trace.
if tr.t == nil {
return ctx, noopSpan
}
return tr.t.Start(ctx, "alertFilter")
}
// StartGroupAlert starts the span for dispatch.Dispatcher.groupAlert.
func (tr Tracer) StartGroupAlert(ctx context.Context, alertName string, fingerpstring) (context.Context, trace.Span) {
if tr.t == nil {
return ctx, noopSpan
}
ctx, span := tr.t.Start(ctx, "dispatch.Dispatcher.groupAlert")
span.SetAttributes(
attribute.String("alerting.alert.name", alertName),
attribute.String("alerting.alert.fingerprint", fingerprint()),
attribute.String("alerting.route.receiver.name", receiver),
)
return ctx, span
}
// StartAlertsPut starts the span for provider.mem.Put.
func (tr Tracer) StartAlertsPut(ctx context.Context, count int) (context.Contex
if tr.t == nil {
return ctx, noopSpan
}
ctx, span := tr.t.Start(ctx, "provider.mem.Put", producer...)
span.SetAttributes(attribute.Int("alerting.alerts.count", count))
return ctx, span
}
// tracing/tracing.go, in Manager
// 1. silence/silence.go
ctx, span := s.tracer.StartSilencerMutes(ctx, fp.String)
defer span.End()
// 2. inhibit/inhibit.go
_, span := ih.tracer.StartInhibitorMutes(ctx, fp.String)
defer span.End()
// 3. api/v2/api.go
ctx, span := api.tracer.StartAlertFilter(parent)
defer span.End()
// 4. dispatch/dispatch.go: a closure, since the method value
// alrt.Fingerprint().String would hash eagerly to bind its receiver.
_, span := d.tracer.StartGroupAlert(ctx, alrt.Name(),
func() string { return alrt.Fingerprint().String() },
route.RouteOpts.Receiver,
)
defer span.End()
// 5. provider/mem/mem.go
ctx, span := a.tracer.StartAlertsPut(ctx, len(alerts))
defer span.End()
tracing.NewTracer already short-circuits span creation behind an atomic flag, so creating a span costs nothing when no tracer provider is installed. The arguments of every tracer.Start call were still evaluated at the call site, though: formatting fingerprints with Sprintf, calling Name() and GroupKey(), building attribute.KeyValue structs and the variadic option slice. On the per-alert ingestion path (provider Put, routeAlert, groupAlert, aggregation group insert, inhibitor processAlert, both Mutes methods) that work ran for every alert with tracing disabled.
Measured with tracing disabled on a single Start call carrying an alert name and a formatted fingerprint:
attributes passed to Start: 314 ns/op, 5 allocs/op
bare Start, SetAttributes behind
span.IsRecording(): 5 ns/op, 0 allocs/op
The guarded number needed one more fix in tracing itself: Start returned the package-level noopSpan variable, and boxing a copy of that 80-byte noop.Span struct into the trace.Span interface allocated on every call. The variable is now of interface type, so it is boxed once at package init.
Move the attributes out of every tracer.Start call and set them with span.SetAttributes behind span.IsRecording(). On the hot paths, guard span.AddEvent calls that build attributes from computed values the same way. Constant span kinds stay in Start.
In the in-memory provider, stop allocating the propagation header map and running the propagator for every alert and listener when tracing is disabled; leave provider.Alert.Header nil, which both consumers (the dispatcher and the inhibitor) already treat as "nothing to extract". tracing.Enabled is exported for that check.
Add the convention to AGENTS.md and a go/ast guard test in tracing/ that fails when any tracer.Start call receives trace.WithAttributes, so the pattern does not creep back in with new spans.
Pull Request Checklist
Please check all the applicable boxes.
Which user-facing changes does this PR introduce?