Documentation
¶
Overview ¶
Package perf collects OpenTelemetry spans into an in-process tree so `ox` commands and the daemon can render per-phase timing to a local sink (stderr, daemon log) without needing the OTLP backend.
The package wraps OTel — it does NOT replace it. Spans produced via perf.Start are real OTel spans that still flow to the configured exporter. perf adds a SpanProcessor that buffers each trace's spans in memory and, on root-span end, hands the assembled tree to a sink.
Index ¶
- Constants
- func RecordError(span trace.Span, err error)
- func RenderTree(w io.Writer, root *Node)
- func Start(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, trace.Span)
- func TraceEnabled() bool
- type Node
- type Options
- type SpanSink
- type TreeCollectorProcessor
- type TreeSink
Constants ¶
const PerSpanSlogThreshold = 100 * time.Millisecond
PerSpanSlogThreshold is the per-span duration at or above which a single-line slog record is emitted for that span when OX_TRACE is unset. Below this, only OX_TRACE=1 forces emission.
const SlowThreshold = 3 * time.Second
SlowThreshold is the root-trace duration at or above which the tree is rendered to stderr even when OX_TRACE is unset. Tuned for "user notices ox felt slow."
Variables ¶
This section is empty.
Functions ¶
func RecordError ¶
RecordError marks span as failed and attaches err. Convenience over repeating two OTel calls at every error site. No-op when err is nil or span is nil.
func RenderTree ¶
RenderTree writes the node tree to w using dotted-leader alignment so durations line up in a single column.
pre-push total ........................ 4.2s ├─ resolve_push_settings ............. 0.1s ├─ git_push .......................... 1.9s └─ cleanup ........................... 0.1s
The dot column width is computed once over the whole tree so deeply nested children stay aligned with shallow ones.
func Start ¶
func Start(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, trace.Span)
Start creates a child span under the active span in ctx. The returned context carries the new span; the returned trace.Span has the usual OTel API (.End, .SetAttributes, .RecordError, .SetStatus).
Use perf.Start instead of observability.Tracer().Start so that:
- Callers don't have to import both observability and otel/trace.
- Failed spans can be marked via perf.RecordError without callers pulling in the codes package.
Safe to call when tracing is disabled: returns a no-op span whose End is a cheap no-op.
func TraceEnabled ¶
func TraceEnabled() bool
TraceEnabled returns true if OX_TRACE is set to a truthy value. Used by sinks to gate full-detail rendering and per-span slog emission.
Types ¶
type Node ¶
type Node struct {
Name string
SpanID trace.SpanID
Start time.Time
End time.Time
Duration time.Duration
Status sdktrace.Status
Attrs []attribute.KeyValue
Children []*Node
// Detached is true when the span's parent ended before it did and the
// parent's tree was already emitted, or when its parent SpanID was not
// present in the trace's pending set. Detached spans render under a
// synthetic "<detached>" node so the user sees the timing rather than
// silently dropping it.
Detached bool
}
Node is a span captured by the processor with its children attached. Trees are rooted at the trace's root span (no valid parent SpanID).
type SpanSink ¶
type SpanSink func(s sdktrace.ReadOnlySpan)
SpanSink is called for every closed span (one call per OnEnd). Used to emit per-span slog records when OX_TRACE=1 or duration exceeds a threshold. Nil is a no-op.
func NoopSpanSink ¶
func NoopSpanSink() SpanSink
NoopSpanSink returns a span sink that does nothing. Useful in tests or when only tree-level emission is desired.
func PerSpanSlog ¶
PerSpanSlog returns a SpanSink that emits one slog record per closed span when either:
- OX_TRACE is truthy, OR
- the span's duration meets PerSpanSlogThreshold.
Without this gate a 5-second daemon sync loop would produce thousands of info-level lines per hour from no-op pull dedup spans, which would dominate `ox daemon logs` and worsen disk pressure.
The emitted record is single-line key=value (project convention) so it stays greppable and aggregator-friendly.
type TreeCollectorProcessor ¶
type TreeCollectorProcessor struct {
// contains filtered or unexported fields
}
TreeCollectorProcessor is an OTel sdktrace.SpanProcessor that buffers spans per trace and, when each trace's root span ends, builds a Node tree and calls Options.OnTree with it.
Concurrency: the OTel SDK may call OnEnd from multiple goroutines for spans in the same or different traces. All map mutations are guarded by mu.
func NewTreeProcessor ¶
func NewTreeProcessor(opts Options) *TreeCollectorProcessor
NewTreeProcessor returns a processor ready to register with an OTel TracerProvider via sdktrace.WithSpanProcessor.
func (*TreeCollectorProcessor) ForceFlush ¶
func (p *TreeCollectorProcessor) ForceFlush(_ context.Context) error
ForceFlush is a no-op. Trees emit synchronously on root OnEnd.
func (*TreeCollectorProcessor) OnEnd ¶
func (p *TreeCollectorProcessor) OnEnd(s sdktrace.ReadOnlySpan)
OnEnd buffers the span. If the span is the root of its trace, we assemble the tree and hand it to OnTree.
func (*TreeCollectorProcessor) OnStart ¶
func (p *TreeCollectorProcessor) OnStart(_ context.Context, _ sdktrace.ReadWriteSpan)
OnStart is part of the SpanProcessor interface. We don't need it.
type TreeSink ¶
type TreeSink func(root *Node)
TreeSink is called once per closed trace, with the assembled root Node. Used to render the tree to stderr or to the daemon log. Nil is a no-op.
func CLITreeSink ¶
CLITreeSink renders the root tree to w when:
- OX_TRACE is truthy, OR
- verbose is true (-v / --verbose), OR
- the root trace took at least SlowThreshold (auto-surface).
In the auto-surface case, a footer hint about OX_TRACE is appended so users discover the env var the next time they want detail.
w is typically os.Stderr; pass a buffer in tests.
func DaemonTreeSink ¶
DaemonTreeSink renders the root tree to the daemon logger at debug level. Tree blocks land in the daemon log only when the operator opts in via OX_TRACE=1 (env var inherited by the daemon child process) — otherwise per-span slog records (from PerSpanSlog below) are sufficient for grep + jq workflows without inflating the log.
func NoopTreeSink ¶
func NoopTreeSink() TreeSink
NoopTreeSink returns a tree sink that does nothing. Useful in tests or when only per-span emission is desired.