As discussed in #62627, this issue proposed to extend the API of x/exp/trace to support programmatically creating trace events for testing and analysis purposes.

Note: Given that the code for this lives in internal/trace right now and is only exposed via x/exp/trace, I think this issue doesn't need to go through the official proposal process.

Overview

The proposal is to add an API using the functional options pattern. Below is an example for creating a goroutine state transition event:

stk := []StackFrame{
    {PC: 1, Func: "time.Sleep", File: "runtime.go", Line: 10},
    {PC: 2, Func: "main", File: "main.go", Line: 20},
}

event, err := NewEvent(timestamp,
    // goid, procid, threadid
    WithSchedulingContext(1, 2, 3),
    // goid, from, to, reason, stack
    WithGoroutineTransition(5, GoRunning, GoWaiting, "sleep", stk),
)

A partially completed prototype of this API is available in CL 691815.

API

// NewEvent creates a new trace event at the specified time with the given
// options. Options are applied in order and validate compatibility with each
// other. Returns an error if options are incompatible or invalid.
func NewEvent(time Time, opts ...eventOption) (Event, error)

// eventOption applies an option to an event.
type eventOption func(*Event) error

// WithSchedulingContext sets the scheduling context for the event.
func WithSchedulingContext(goroutine GoID, proc ProcID, thread ThreadID) eventOption

// WithGoroutineTransition specializes the event into a StateTransition event.
// May overwrite the scheduling context goroutine and event stack.
func WithGoroutineTransition(goroutine GoID, from, to GoState, reason string, stack []StackFrame) eventOption

// WithMetric specializes the event into a Metric event.
func WithMetric(name string, value Value) eventOption

// WithRange specializes the event into a Range event. The scope may overwrite
// the scheduling context.
func WithRange(kind EventKind, name string, scope ResourceID) eventOption

// WithLabel specializes the event into a Label event. Only ResourceGoroutine is
// supported right now and it overwrites the scheduling context goroutine.
func WithLabel(label string, resource ResourceID) eventOption

// WithProcTransition specializes the event into a ProcTransition event.
func WithProcTransition(proc ProcID, from, to ProcState) eventOption

// WithSync specializes the event into a Sync event.
func WithSync(n int, clockSnapshot *ClockSnapshot, experimentalBatches map[string][]ExperimentalBatch) eventOption

// WithTask specializes the event into a Task event.
func WithTask(id, parent TaskID, taskType string) eventOption

// WithRegion specializes the event into a Region event.
func WithRegion(task TaskID, regionType string) eventOption

// WithLog specializes the event into a Log event.
func WithLog(task TaskID, category, message string) eventOption

// WithStackSample specializes the event into a StackSample event.
func WithStackSample(stack []StackFrame) eventOption

Struct-based alternative

If functional options pattern is not preferred, we can also consider a struct based API. E.g.:

type EventType interface {
    GoroutineTransition | ProcTransition // ...
}

type EventConfig[T EventType] struct {
    Time      Time
    Goroutine GoID
    Proc      ProcID
    Thread    ThreadID
    Event     T
}

event, err := NewEvent(EventConfig{
    Time:      timestamp,
    Goroutine: 1,
    Proc:      2,
    Thread:    3,
    Event: GoroutineTransition{
        Goroutine: 5,
        From:      GoRunnable,
        To:        GoWaiting,
        Reason:    "sleep",
        Stack:     stk,
    },
})

The advantage of this approach is that it's easier to add new fields to the event type structs in the future. Otherwise I don't feel too strongly about one approach over the other.

Comment From: felixge

cc @mknyszek @prattmic @rhysh @nsrip-dd @dominikh

Comment From: gabyhelp

Related Code Changes

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)