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 a MakeEvent constructor that takes a EventConfig struct. Below is an example for creating a goroutine state transition event:
details := MakeGoStateTransition(5, GoRunning, GoWaiting)
details.Stack = MakeStack([]StackFrame{
{PC: 1, Func: "time.Sleep", File: "runtime.go", Line: 10},
{PC: 2, Func: "main", File: "main.go", Line: 20},
})
details.Reason = "sleep"
ev, err := MakeEvent(EventConfig[StateTransition]{
Time: timestamp,
Goroutine: 1,
Proc: 2,
Thread: 3,
Details: details,
})
A partially completed prototype of this API is available in CL 691815.
API
// MakeEvent creates a new trace event with the given configuration.
func MakeEvent[T EventDetails](c EventConfig[T]) (Event, error)
// EventConfig holds the data for constructing a trace event.
type EventConfig[T EventDetails] struct {
Kind EventKind
Time Time
Goroutine GoID
Proc ProcID
Thread ThreadID
Stack Stack
Details T
}
// EventDetails is a union type of all event kind specific details.
type EventDetails interface {
StateTransition | Metric | Label | Range | Task | Region | Log
}
Functional options alternative
An earlier version of this proposal used functional options. But during the performance and diagnostics meeting on 2025-07-31 @mknyszek suggested that a preference towards a struct based API that reuses the existing struct types in this package.
Click to expand to see the previous version of the proposal that used functional options
Usage Example: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),
)
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
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.)
Comment From: felixge
@mknyszek I updated the proposal based on the discussions in the meeting yesterday. I've also updated the CL to make sure this would work (the code is still wrapping the old functional API I had before, but that was just the fastest way to get a struct based API working).
Also: During the call we discussed that this doesn't need to go through the official proposal process since it targets APIs that don't fall under the go1 promise for now (x/exp, internal/trace). Can somebody remove the label on the issue? Is there a preferred prefix for the title for this?
Comment From: mknyszek
@felixge I updated the issue title and labels to match where it's at right now. I think eventually it should be upgraded to a proposal, or just made part of the #62627.
Overall, this looks very nice to me. I have no major feedback, just a little bit of name bikeshedding and tiny nits.
- Can we replace
NewwithMakefor where we produce value types? (SoMakeEvent,MakeStack, etc.) My thinking is that since we're not returning a pointer to something, it aligns more withmakevs.newin the language itself, even if they use references under the hood. WDYT? - I worry that
EventTypeas a name overlaps up too much withEventKindand could be confusing. WDYT about calling itEventDetailsand calling the field inEventConfigDetails(really all the information is necessary to constitute an event)? - Inferring the
Kindfield is nice, but I'm wondering if we should makeKindcompulsory just to make the API a little more explicit. We can always relax that in the future, or we can make more bespokeRangeandStateTransitiontypes (as well as an emptyStackSampletype, maybe). We can do a little validation and panic if the details don't match.
Comment From: felixge
@mknyszek thanks. Your suggestions all make sense to me. I'll update the proposal and the CL ASAP.
Edit: The proposal has been updated.