Files
llmgw/internal/obs/obstest/obstest.go
T
Ilker ManapandClaude Opus 5.5 e725adc2f0 Tracing beyond the router: reconcile passes, placement and a replica's start traced down to the node
A reconcile pass that does something is a trace, with placement (where it
went, or every candidate turned down), scale-up/down, drain, stop, eviction,
preemption, reaping and restarts beneath it. A replica's start is a span open
from placement until it serves; the start command carries its context in a
new ControlMessage.trace field, and the agent continues the trace through
the weights fetch, engine launch, rendezvous and readiness wait. Stops,
drains, adoption and engine installs are traced on the node, model jobs are
a trace with a span per step linked to the replica's start, and agent.yaml
takes the same tracing block as server.yaml. Off, every span is the no-op one
and commands carry nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:03:17 +02:00

99 lines
3.0 KiB
Go

// Package obstest records spans for tests that assert what was traced.
package obstest
import (
"testing"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
)
// Recorder is every span ended while a test runs.
type Recorder struct{ *tracetest.SpanRecorder }
// Record installs a provider that keeps every span, and the W3C propagator,
// for the length of the test, putting back what was there after.
//
// Global, as the code under test finds it, so no test that records may run
// in parallel with another that traces.
func Record(t *testing.T) *Recorder {
t.Helper()
rec := tracetest.NewSpanRecorder()
prevTP, prevProp := otel.GetTracerProvider(), otel.GetTextMapPropagator()
otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(rec)))
otel.SetTextMapPropagator(propagation.TraceContext{})
t.Cleanup(func() { otel.SetTracerProvider(prevTP); otel.SetTextMapPropagator(prevProp) })
return &Recorder{rec}
}
// Named is the ended spans called name, oldest first.
func (r *Recorder) Named(name string) []sdktrace.ReadOnlySpan {
var out []sdktrace.ReadOnlySpan
for _, s := range r.Ended() {
if s.Name() == name {
out = append(out, s)
}
}
return out
}
// One is the single ended span called name in trace tid, failing the test
// when there is not exactly one. A zero tid is any trace.
func (r *Recorder) One(t *testing.T, tid trace.TraceID, name string) sdktrace.ReadOnlySpan {
t.Helper()
var found []sdktrace.ReadOnlySpan
for _, s := range r.Named(name) {
if !tid.IsValid() || s.SpanContext().TraceID() == tid {
found = append(found, s)
}
}
if len(found) != 1 {
t.Fatalf("%d spans called %q in the trace; ended: %v", len(found), name, r.names())
}
return found[0]
}
// Await is One, waiting up to limit for the span to end: the code that
// ends it may run after whatever the test waited on.
func (r *Recorder) Await(t *testing.T, tid trace.TraceID, name string, limit time.Duration) sdktrace.ReadOnlySpan {
t.Helper()
deadline := time.Now().Add(limit)
for time.Now().Before(deadline) {
for _, s := range r.Named(name) {
if s.SpanContext().TraceID() == tid {
return r.One(t, tid, name)
}
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("no span called %q ended within %v; ended: %v", name, limit, r.names())
return nil
}
func (r *Recorder) names() []string {
var out []string
for _, s := range r.Ended() {
out = append(out, s.Name())
}
return out
}
// Attrs is a span's attributes, as strings.
func Attrs(s sdktrace.ReadOnlySpan) map[string]string {
out := map[string]string{}
for _, a := range s.Attributes() {
out[string(a.Key)] = a.Value.Emit()
}
return out
}
// ChildOf reports whether child's parent is parent.
func ChildOf(child, parent sdktrace.ReadOnlySpan) bool {
return child.Parent().SpanID() == parent.SpanContext().SpanID() &&
child.SpanContext().TraceID() == parent.SpanContext().TraceID()
}