trace

package
v5.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package trace is v5's cross-thread devtools timeline (plan item P4.3).

The criterion is that a single timeline correlates render-thread and domain-worker activity by correlation ID, and that a command's full lifecycle is traceable across the boundary.

The thing that makes this harder than appending two logs: THE TWO THREADS DO NOT SHARE A CLOCK. performance.now() is relative to a time origin that a worker sets when it starts, so a worker span timestamped 12.4 and a main thread span timestamped 12.4 did not happen at the same moment, and nothing in the numbers says so. Merging by timestamp produces a timeline that looks authoritative and is wrong — commands appearing to complete before they were sent, work appearing to overlap that could not have.

So ordering is CAUSAL, not chronological. A span records which thread it came from and what caused it, and the timeline reconstructs the order from that. Timestamps are kept, because within one thread they are meaningful and are the only source of durations, but they never order across threads. A caller who genuinely needs cross-thread wall-clock alignment must supply a measured ClockOffset, and the error bound comes with it.

Index

Constants

View Source
const DefaultCapacity = 4096

DefaultCapacity is how many spans a recorder keeps.

Bounded because R6 says diagnostics carry an allocation budget and because a tracing buffer that grows for the life of a session is a leak with a friendly name. Oldest spans are dropped first: a devtools panel is nearly always asked about something that just happened.

Variables

This section is empty.

Functions

This section is empty.

Types

type ClockOffset

type ClockOffset struct {
	// Thread whose clock is being converted.
	Thread Thread
	// OffsetNanos is added to that thread's timestamps to reach the render
	// thread's clock.
	OffsetNanos int64
	// ErrorNanos bounds the uncertainty. A gap smaller than this is noise.
	ErrorNanos int64
}

ClockOffset converts one thread's clock to another's.

Supplying one is optional and its accuracy is the caller's problem, which is why the error bound travels with it: a round-trip estimate is only as good as the round trip was symmetric, and on a busy worker it is not. The timeline never needs an offset to ORDER events — that is what causality is for — and uses it only to report cross-thread gaps, always with the bound attached.

type CorrelationID

type CorrelationID string

CorrelationID ties together every span belonging to one logical operation, across every thread it touches.

type LifecycleSummary

type LifecycleSummary struct {
	Correlation CorrelationID
	// Threads lists every thread the operation touched, in causal order.
	Threads []Thread
	// SpanCount is how many spans make it up.
	SpanCount int
	// CrossedBoundary reports whether the operation involved more than one
	// thread, which is the case P4.3 exists for.
	CrossedBoundary bool
	// Incomplete reports that some span never ended — a command still in flight,
	// or one whose worker died before closing it.
	Incomplete bool
	// Failed reports that some span ended with an error.
	Failed bool
	// ThreadDurations is time spent per thread, each on its own clock. NOT
	// summed into a total: adding two clocks' durations produces a number that
	// looks like a wall-clock elapsed time and is not one, since the threads
	// overlap by construction.
	ThreadDurations map[Thread]int64
}

LifecycleSummary describes one operation end to end.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder collects spans on one thread.

Not safe for concurrent use, which is not a limitation here: there is one recorder per thread and each thread is single-threaded in wasm.

func NewRecorder

func NewRecorder(parseThread Thread, parseCapacity int) (*Recorder, error)

NewRecorder creates a recorder for one thread.

func (*Recorder) Dropped

func (parseRecorder *Recorder) Dropped() int

Dropped reports how many spans were evicted.

Surfaced rather than silent: a timeline missing its beginning looks like an operation that started in the middle, and a reader has no other way to tell.

func (*Recorder) End

func (parseRecorder *Recorder) End(parseSpanID SpanID, parseEndNanos int64, parseErr string) error

End closes a span.

Ending an unknown span is an error rather than a no-op: it means the id was wrong, or the span was evicted, and both make the resulting timeline misleading in ways nothing downstream can detect.

func (*Recorder) Spans

func (parseRecorder *Recorder) Spans() []Span

Spans returns a copy of the recorded spans, oldest first.

func (*Recorder) Start

func (parseRecorder *Recorder) Start(parseName string, parseCorrelation CorrelationID, parseParent SpanID, parseStartNanos int64) (SpanID, error)

Start records the beginning of a span and returns its id.

The timestamp is the caller's, not read from a clock here: this package is compiled for both native tests and wasm, and the meaningful clock differs. Taking it as a parameter also lets a test produce a deterministic timeline.

type Span

type Span struct {
	ID     SpanID
	Parent SpanID
	// Correlation is the operation this span belongs to. Spans on different
	// threads sharing a correlation are the same logical work.
	Correlation CorrelationID
	Thread      Thread
	Name        string
	// StartNanos and EndNanos are on the RECORDING THREAD'S clock and are only
	// comparable to other spans from that same thread. See the package comment.
	StartNanos int64
	EndNanos   int64
	// Seq is a per-thread monotonic counter, assigned by the recorder.
	//
	// It exists because timestamps can tie — two spans in the same microsecond
	// are ordinary — and a timeline that reorders equal timestamps arbitrarily
	// is unreadable. Seq breaks the tie the way the thread actually ran.
	Seq uint64
	// Err is non-empty when the span ended in failure.
	Err string
}

Span is one interval of work on one thread.

func (Span) Duration

func (parseSpan Span) Duration() int64

Duration reports the span's length on its own thread's clock.

Zero for a span that never ended, which is a live span rather than an instantaneous one — the distinction matters when reading a timeline captured mid-operation.

func (Span) Open

func (parseSpan Span) Open() bool

Open reports whether the span has not ended.

type SpanID

type SpanID string

SpanID identifies one span.

type Thread

type Thread string

Thread names which side of the boundary a span came from.

const (
	// ThreadRender is the main thread that owns the DOM.
	ThreadRender Thread = "render"
	// ThreadDomain is the worker that owns application state.
	ThreadDomain Thread = "domain"
	// ThreadCompute is a compute-pool worker.
	ThreadCompute Thread = "compute"
)

type Timeline

type Timeline struct {
	// contains filtered or unexported fields
}

Timeline is a merged, causally-ordered view across threads.

func Merge

func Merge(parseRecorders ...*Recorder) *Timeline

Merge builds a timeline from several threads' recorders.

func (*Timeline) Correlations

func (parseTimeline *Timeline) Correlations() []CorrelationID

Correlations lists every correlation in the timeline, in first-seen order.

func (*Timeline) Dropped

func (parseTimeline *Timeline) Dropped() int

Dropped reports how many spans were evicted across all merged recorders.

func (*Timeline) Lifecycle

func (parseTimeline *Timeline) Lifecycle(parseCorrelation CorrelationID) []Span

Lifecycle returns every span for one correlation, in causal order.

This is P4.3's criterion: a command's full lifecycle traceable across the boundary. The order is derived from parentage first and per-thread sequence second — never from comparing timestamps across threads, which is the mistake that makes a merged timeline confidently wrong.

func (*Timeline) Summarize

func (parseTimeline *Timeline) Summarize(parseCorrelation CorrelationID) (LifecycleSummary, bool)

Summarize describes one operation's lifecycle.

func (*Timeline) WithClockOffset

func (parseTimeline *Timeline) WithClockOffset(parseOffset ClockOffset) *Timeline

WithClockOffset records a measured offset for one thread.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL