The golang.org/x/exp/trace package provides a mechanism to collect and retrieve the most recent execution data without keeping the complete execution tracing history. It offers a flight recorder pattern for runtime tracing, enabling efficient analysis of goroutine, processor, and thread execution behavior.
go get golang.org/x/exp/traceimport (
"golang.org/x/exp/trace"
"io"
"time"
"iter"
)package main
import (
"golang.org/x/exp/trace"
"log"
"os"
)
func main() {
// Create a new flight recorder
recorder := trace.NewFlightRecorder()
// Set the buffer size and period
recorder.SetSize(10 * 1024 * 1024) // 10MB
recorder.SetPeriod(30 * time.Second) // 30 seconds
// Start recording
if err := recorder.Start(); err != nil {
log.Fatal(err)
}
defer recorder.Stop()
// Perform some work...
// Write trace data to a file
f, _ := os.Create("trace.out")
defer f.Close()
recorder.WriteTo(f)
// Later, read and analyze the trace
f, _ = os.Open("trace.out")
defer f.Close()
reader, _ := trace.NewReader(f)
for {
event, err := reader.ReadEvent()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
// Process event
_ = event.Kind()
}
}The golang.org/x/exp/trace package provides a high-level API for analyzing Go runtime execution traces. The main components are:
The flight recorder captures runtime execution data in a circular buffer, storing only the most recent execution history. This enables continuous monitoring without the memory overhead of complete trace histories.
type FlightRecorder struct {
// Has unexported fields.
}
func NewFlightRecorder() *FlightRecorder
// NewFlightRecorder creates a new flight recording configuration.
// Flight recording holds execution trace data in a circular buffer
// representing the most recent execution data.
// Only one flight recording may be active at any given time.
func (r *FlightRecorder) Start() error
// Start begins flight recording. Only one flight recorder or one call to
// runtime/trace.Start may be active at any given time. Returns an error if
// starting the flight recorder would violate this rule.
func (r *FlightRecorder) Stop() error
// Stop ends flight recording. It waits until any concurrent
// FlightRecorder.WriteTo calls exit. Returns an error if the flight recorder
// is inactive.
func (r *FlightRecorder) Enabled() bool
// Enabled returns true if the flight recorder is active. Specifically,
// it will return true if Start did not return an error, and Stop has not yet
// been called. It is safe to call from multiple goroutines simultaneously.
func (r *FlightRecorder) SetSize(bytes int)
// SetSize sets the approximate size of the flight recorder's circular buffer.
// This generally takes precedence over the duration passed to SetPeriod.
// However, it does not make any guarantees on the size of the data WriteTo
// will write. This is just a hint to the runtime to enable some control over
// the memory overheads of tracing.
// The initial size is implementation defined.
// Adjustments to this value will not apply to an active flight recorder, and
// will not apply if tracing is already enabled via trace.Start. All tracing
// must be stopped and started again to change this value.
func (r *FlightRecorder) SetPeriod(d time.Duration)
// SetPeriod sets the approximate time duration that the flight recorder's
// circular buffer represents.
// Note that SetPeriod does not make any guarantees on the amount of time the
// trace produced by WriteTo will represent. This is just a hint to the runtime
// to enable some control the resulting trace.
// The initial period is implementation defined, but can be assumed to be on
// the order of seconds.
// Adjustments to this value will not apply to an active flight recorder, and
// will not apply if tracing is already enabled via trace.Start. All tracing
// must be stopped and started again to change this value.
func (r *FlightRecorder) WriteTo(w io.Writer) (total int, err error)
// WriteTo takes a snapshots of the circular buffer's contents and writes the
// execution data to w. Returns the number of bytes written and an error.
// An error is returned upon failure to write to w or if the flight recorder is
// inactive. Only one goroutine may execute WriteTo at a time, but it is safe
// to call from multiple goroutines. If a goroutine calls WriteTo while another
// goroutine is currently executing it, WriteTo will return ErrSnapshotActive
// to that goroutine.The event reader parses trace data streams and produces structured event objects that can be analyzed to understand runtime behavior.
type Reader struct {
// Has unexported fields.
}
func NewReader(r io.Reader) (*Reader, error)
// NewReader creates a new trace reader.
// Reader reads a byte stream, validates it, and produces trace events.
// Provided the trace is non-empty the Reader always produces a Sync event as
// the first event, and a Sync event as the last event. (There may also be any
// number of Sync events in the middle, too.)
func (r *Reader) ReadEvent() (e Event, err error)
// ReadEvent reads a single event from the stream.
// If the stream has been exhausted, it returns an invalid event and io.EOF.Events represent different categories of runtime activity, from goroutine state transitions to synchronization points.
type Event struct {
// Has unexported fields.
}
func (e Event) Kind() EventKind
// Kind returns the kind of event that this is.
func (e Event) String() string
// String returns the event as a human-readable string.
// The format of the string is intended for debugging and is subject to change.
func (e Event) Time() Time
// Time returns the timestamp of the event.
func (e Event) Goroutine() GoID
// Goroutine returns the ID of the goroutine that was executing when this event
// happened. It describes part of the execution context for this event.
// Note that for goroutine state transitions this always refers to the state
// before the transition. For example, if a goroutine is just starting
// to run on this thread and/or proc, then this will return NoGoroutine.
// In this case, the goroutine starting to run will be can be found at
// Event.StateTransition().Resource.
func (e Event) Proc() ProcID
// Proc returns the ID of the proc this event event pertains to.
// Note that for proc state transitions this always refers to the state before
// the transition. For example, if a proc is just starting to run on this
// thread, then this will return NoProc.
func (e Event) Thread() ThreadID
// Thread returns the ID of the thread this event pertains to.
// Note that for thread state transitions this always refers to the state
// before the transition. For example, if a thread is just starting to run,
// then this will return NoThread.
// Note: tracking thread state is not currently supported, so this will always
// return a valid thread ID. However thread state transitions may be tracked in
// the future, and callers must be robust to this possibility.
type EventKind uint16
// EventKind indicates the kind of event this is.
// Use this information to obtain a more specific event that allows access to
// more detailed information.
const (
EventBad EventKind = iota
// EventKindSync is an event that indicates a global synchronization
// point in the trace. At the point of a sync event, the
// trace reader can be certain that all resources (e.g. threads,
// goroutines) that have existed until that point have been enumerated.
EventSync
// EventMetric is an event that represents the value of a metric at
// a particular point in time.
EventMetric
// EventLabel attaches a label to a resource.
EventLabel
// EventStackSample represents an execution sample, indicating what a
// thread/proc/goroutine was doing at a particular point in time via
// its backtrace.
// Note: Samples should be considered a close approximation of
// what a thread/proc/goroutine was executing at a given point in time.
// These events may slightly contradict the situation StateTransitions
// describe, so they should only be treated as a best-effort annotation.
EventStackSample
// EventRangeBegin and EventRangeEnd are a pair of generic events representing
// a special range of time. Ranges are named and scoped to some resource
// (identified via ResourceKind). A range that has begun but has not ended
// is considered active.
// EvRangeBegin and EvRangeEnd will share the same name, and an End will always
// follow a Begin on the same instance of the resource. The associated
// resource ID can be obtained from the Event. ResourceNone indicates the
// range is globally scoped. That is, any goroutine/proc/thread can start or
// stop, but only one such range may be active at any given time.
// EventRangeActive is like EventRangeBegin, but indicates that the range was
// already active. In this case, the resource referenced may not be in the current
// context.
EventRangeBegin
EventRangeActive
EventRangeEnd
// EvTaskBegin and EvTaskEnd are a pair of events representing a runtime/trace.Task.
EventTaskBegin
EventTaskEnd
// EventRegionBegin and EventRegionEnd are a pair of events represent a runtime/trace.Region.
EventRegionBegin
EventRegionEnd
// EventLog represents a runtime/trace.Log call.
EventLog
// EventStateTransition represents a state change for some resource.
EventStateTransition
// EventExperimental is an experimental event that is unvalidated and exposed in a raw form.
// Users are expected to understand the format and perform their own validation. These events
// may always be safely ignored.
EventExperimental
)
func (e EventKind) String() string
// String returns a string form of the EventKind.Methods to extract detailed information from specific event types.
func (e Event) StateTransition() StateTransition
// StateTransition returns details about a StateTransition event.
// Panics if Kind != EventStateTransition.
func (e Event) Task() Task
// Task returns details about a TaskBegin or TaskEnd event.
// Panics if Kind != EventTaskBegin and Kind != EventTaskEnd.
func (e Event) Region() Region
// Region returns details about a RegionBegin or RegionEnd event.
// Panics if Kind != EventRegionBegin and Kind != EventRegionEnd.
func (e Event) Label() Label
// Label returns details about a Label event.
// Panics if Kind != EventLabel.
func (e Event) Range() Range
// Range returns details about an EventRangeBegin, EventRangeActive,
// or EventRangeEnd event.
// Panics if Kind != EventRangeBegin, Kind != EventRangeActive, and Kind !=
// EventRangeEnd.
func (e Event) RangeAttributes() []RangeAttribute
// RangeAttributes returns attributes for a completed range.
// Panics if Kind != EventRangeEnd.
func (e Event) Metric() Metric
// Metric returns details about a Metric event.
// Panics if Kind != EventMetric.
func (e Event) Log() Log
// Log returns details about a Log event.
// Panics if Kind != EventLog.
func (e Event) Sync() Sync
// Sync returns details that are relevant for the following events, up to but
// excluding the next EventSync event.
func (e Event) Experimental() ExperimentalEvent
// Experimental returns a view of the raw event for an experimental event.
// Panics if Kind != EventExperimental.
func (e Event) Stack() Stack
// Stack returns a handle to a stack associated with the event.
// This represents a stack trace at the current moment in time for the current
// execution context.Types and functions for tracking goroutine states and execution context.
type GoID int64
// GoID is the runtime-internal G structure's goid field. This is unique for
// each goroutine.
type GoState uint8
// GoState represents the state of a goroutine.
// New GoStates may be added in the future. Users of this type must be robust
// to that possibility.
const (
GoUndetermined GoState = iota
// No information is known about the goroutine.
GoNotExist
// Goroutine does not exist.
GoRunnable
// Goroutine is runnable but not running.
GoRunning
// Goroutine is running.
GoWaiting
// Goroutine is waiting on something to happen.
GoSyscall
// Goroutine is in a system call.
)
func (s GoState) Executing() bool
// Executing returns true if the state indicates that the goroutine is
// executing and bound to its thread.
func (s GoState) String() string
// String returns a human-readable representation of a GoState.
// The format of the returned string is for debugging purposes and is subject
// to change.
const NoGoroutine = GoID(-1)
// NoGoroutine indicates that the relevant events don't correspond to any
// goroutine in particular.Types and functions for tracking processor states in the Go runtime.
type ProcID int64
// ProcID is the runtime-internal G structure's id field. This is unique for
// each P.
type ProcState uint8
// ProcState represents the state of a proc.
// New ProcStates may be added in the future. Users of this type must be robust
// to that possibility.
const (
ProcUndetermined ProcState = iota
// No information is known about the proc.
ProcNotExist
// Proc does not exist.
ProcRunning
// Proc is running.
ProcIdle
// Proc is idle.
)
func (s ProcState) Executing() bool
// Executing returns true if the state indicates that the proc is executing and
// bound to its thread.
func (s ProcState) String() string
// String returns a human-readable representation of a ProcState.
// The format of the returned string is for debugging purposes and is subject
// to change.
const NoProc = ProcID(-1)
// NoProc indicates that the relevant events don't correspond to any P in
// particular.Types for identifying and working with OS threads in the runtime.
type ThreadID int64
// ThreadID is the runtime-internal M structure's ID. This is unique for each
// OS thread.
const NoThread = ThreadID(-1)
// NoThread indicates that the relevant events don't correspond to any thread
// in particular.Unified mechanisms for identifying and tracking state changes of runtime resources.
type ResourceID struct {
// Kind is the kind of resource this ID is for.
Kind ResourceKind
// Has unexported fields.
}
// ResourceID represents a generic resource ID.
func MakeResourceID[T interface{ GoID | ProcID | ThreadID }](id T) ResourceID
// MakeResourceID creates a general resource ID from a specific resource's ID.
func (r ResourceID) Goroutine() GoID
// Goroutine obtains a GoID from the resource ID.
// r.Kind must be ResourceGoroutine or this function will panic.
func (r ResourceID) Proc() ProcID
// Proc obtains a ProcID from the resource ID.
// r.Kind must be ResourceProc or this function will panic.
func (r ResourceID) Thread() ThreadID
// Thread obtains a ThreadID from the resource ID.
// r.Kind must be ResourceThread or this function will panic.
func (r ResourceID) String() string
// String returns a human-readable string representation of the ResourceID.
// This representation is subject to change and is intended primarily for
// debugging.
type ResourceKind uint8
// ResourceKind indicates a kind of resource that has a state machine.
// New ResourceKinds may be added in the future. Users of this type must be
// robust to that possibility.
const (
ResourceNone ResourceKind = iota
// No resource.
ResourceGoroutine
// Goroutine.
ResourceProc
// Proc.
ResourceThread
// Thread.
)
func (r ResourceKind) String() string
// String returns a human-readable representation of a ResourceKind.
// The format of the returned string is for debugging purposes and is subject
// to change.
type StateTransition struct {
// Resource is the resource this state transition is for.
Resource ResourceID
// Reason is a human-readable reason for the state transition.
Reason string
// Stack is the stack trace of the resource making the state transition.
// This is distinct from the result (Event).Stack because it pertains to
// the transitioning resource, not any of the ones executing the event
// this StateTransition came from.
// An example of this difference is the NotExist -> Runnable transition for
// goroutines, which indicates goroutine creation. In this particular case,
// a Stack here would refer to the starting stack of the new goroutine, and
// an (Event).Stack would refer to the stack trace of whoever created the
// goroutine.
Stack Stack
// Has unexported fields.
}
// StateTransition provides details about a StateTransition event.
func (d StateTransition) Goroutine() (from, to GoState)
// Goroutine returns the state transition for a goroutine.
// Transitions to and from states that are Executing are special in that they
// change the future execution context. In other words, future events on the
// same thread will feature the same goroutine until it stops running.
// Panics if d.Resource.Kind is not ResourceGoroutine.
func (d StateTransition) Proc() (from, to ProcState)
// Proc returns the state transition for a proc.
// Transitions to and from states that are Executing are special in that they
// change the future execution context. In other words, future events on the
// same thread will feature the same goroutine until it stops running.
// Panics if d.Resource.Kind is not ResourceProc.Types for tracking user-defined tasks and regions in the trace.
type TaskID uint64
// TaskID is the internal ID of a task used to disambiguate tasks (even if they
// are of the same type).
const (
NoTask = TaskID(^uint64(0))
// NoTask indicates the lack of a task.
BackgroundTask = TaskID(0)
// BackgroundTask is the global task that events are attached to if there was
// no other task in the context at the point the event was emitted.
)
type Task struct {
// ID is a unique identifier for the task.
// This can be used to associate the beginning of a task with its end.
ID TaskID
// ParentID is the ID of the parent task.
Parent TaskID
// Type is the taskType that was passed to runtime/trace.NewTask.
// May be "" if a task's TaskBegin event isn't present in the trace.
Type string
}
// Task provides details about a Task event.
type Region struct {
// Task is the ID of the task this region is associated with.
Task TaskID
// Type is the regionType that was passed to runtime/trace.StartRegion or runtime/trace.WithRegion.
Type string
}
// Region provides details about a Region event.
type Range struct {
// Name is a human-readable name for the range.
// This name can be used to identify the end of the range for the resource
// its scoped to, because only one of each type of range may be active on
// a particular resource. The relevant resource should be obtained from the
// Event that produced these details. The corresponding RangeEnd will have
// an identical name.
Name string
// Scope is the resource that the range is scoped to.
// For example, a ResourceGoroutine scope means that the same goroutine
// must have a start and end for the range, and that goroutine can only
// have one range of a particular name active at any given time. The
// ID that this range is scoped to may be obtained via Event.Goroutine.
// The ResourceNone scope means that the range is globally scoped. As a
// result, any goroutine/proc/thread may start or end the range, and only
// one such named range may be active globally at any given time.
// For RangeBegin and RangeEnd events, this will always reference some
// resource ID in the current execution context. For RangeActive events,
// this may reference a resource not in the current context. Prefer Scope
// over the current execution context.
Scope ResourceID
}
// Range provides details about a Range event.
type RangeAttribute struct {
// Name is the human-readable name for the range.
Name string
// Value is the value of the attribute.
Value Value
}
// RangeAttributes provides attributes about a completed Range.Types for tracking logging calls and resource labels in the trace.
type Label struct {
// Label is the label applied to some resource.
Label string
// Resource is the resource to which this label should be applied.
Resource ResourceID
}
// Label provides details about a Label event.
type Log struct {
// Task is the ID of the task this region is associated with.
Task TaskID
// Category is the category that was passed to runtime/trace.Log or runtime/trace.Logf.
Category string
// Message is the message that was passed to runtime/trace.Log or runtime/trace.Logf.
Message string
}
// Log provides details about a Log event.Types for tracking metrics and performance data in the trace.
type Metric struct {
// Name is the name of the sampled metric.
// Names follow the same convention as metric names in the
// runtime/metrics package, meaning they include the unit.
// Names that match with the runtime/metrics package represent
// the same quantity. Note that this corresponds to the
// runtime/metrics package for the Go version this trace was
// collected for.
Name string
// Value is the sampled value of the metric.
// The Value's Kind is tied to the name of the metric, and so is
// guaranteed to be the same for metric samples for the same metric.
Value Value
}
// Metric provides details about a Metric event.
type Value struct {
// Has unexported fields.
}
// Value is a dynamically-typed value obtained from a trace.
func (v Value) Kind() ValueKind
// Kind returns the ValueKind of the value.
// It represents the underlying structure of the value.
// New ValueKinds may be added in the future. Users of this type must be robust
// to that possibility.
func (v Value) String() string
// String returns the string value for a ValueString, and otherwise a string
// representation of the value for other kinds of values.
func (v Value) Uint64() uint64
// Uint64 returns the uint64 value for a ValueUint64.
// Panics if this Value's Kind is not ValueUint64.
type ValueKind uint8
// ValueKind is the type of a dynamically-typed value from a trace.
const (
ValueBad ValueKind = iota
ValueUint64
ValueString
)Types for accessing and inspecting stack frames and traces.
type Stack struct {
// Has unexported fields.
}
// Stack represents a stack. It's really a handle to a stack and it's trivially
// comparable.
// If two Stacks are equal then their Frames are guaranteed to be identical.
// If they are not equal, however, their Frames may still be equal.
func (s Stack) Frames() iter.Seq[StackFrame]
// Frames is an iterator over the frames in a Stack.
type StackFrame struct {
// PC is the program counter of the function call if this
// is not a leaf frame. If it's a leaf frame, it's the point
// at which the stack trace was taken.
PC uint64
// Func is the name of the function this frame maps to.
Func string
// File is the file which contains the source code of Func.
File string
// Line is the line number within File which maps to PC.
Line uint64
}
// StackFrame represents a single frame of a stack.
var NoStack = Stack{}
// NoStack is a sentinel value that can be compared against any Stack value,
// indicating a lack of a stack trace.Types for clock synchronization and coordination in traces.
type Sync struct {
// N indicates that this is the Nth sync event in the trace.
N int
// ClockSnapshot represents a near-simultaneous clock reading of several
// different system clocks. The snapshot can be used as a reference to
// convert timestamps to different clocks, which is helpful for correlating
// timestamps with data captured by other tools. The value is nil for traces
// before go1.25.
ClockSnapshot *ClockSnapshot
// ExperimentalBatches contain all the unparsed batches of data for a given experiment.
ExperimentalBatches map[string][]ExperimentalBatch
}
// Sync contains details potentially relevant to all the following events,
// up to but excluding the next EventSync event.
type ClockSnapshot struct {
// Trace is a snapshot of the trace clock.
Trace Time
// Wall is a snapshot of the system's wall clock.
Wall time.Time
// Mono is a snapshot of the system's monotonic clock.
Mono uint64
}
// ClockSnapshot represents a near-simultaneous clock reading of several
// different system clocks. The snapshot can be used as a reference to convert
// timestamps to different clocks, which is helpful for correlating timestamps
// with data captured by other tools.
type Time int64
// Time is a timestamp in nanoseconds.
// It corresponds to the monotonic clock on the platform that the trace was
// taken, and so is possible to correlate with timestamps for other traces
// taken on the same machine using the same clock (i.e. no reboots in between).
// The actual absolute value of the timestamp is only meaningful in relation to
// other timestamps from the same clock.
// BUG: Timestamps coming from traces on Windows platforms are only comparable
// with timestamps from the same trace. Timestamps across traces cannot be
// compared, because the system clock is not used as of Go 1.22.
// BUG: Traces produced by Go versions 1.21 and earlier cannot be compared with
// timestamps from other traces taken on the same machine. This is because the
// system clock was not used at all to collect those timestamps.
func (t Time) Sub(t0 Time) time.Duration
// Sub subtracts t0 from t, returning the duration in nanoseconds.Types for handling raw experimental trace events.
type ExperimentalEvent struct {
// Name is the name of the event.
Name string
// Experiment is the name of the experiment this event is a part of.
Experiment string
// Args lists the names of the event's arguments in order.
Args []string
// Has unexported fields.
}
// ExperimentalEvent presents a raw view of an experimental event's arguments
// and their names.
func (e ExperimentalEvent) ArgValue(i int) Value
// ArgValue returns a typed Value for the i'th argument in the experimental
// event.
type ExperimentalBatch struct {
// Thread is the ID of the thread that produced a packet of data.
Thread ThreadID
// Data is a packet of unparsed data all produced by one thread.
Data []byte
}
// ExperimentalBatch represents a packet of unparsed data along with metadata
// about that packet.Error constants and conditions specific to trace operations.
var ErrSnapshotActive = fmt.Errorf("call to WriteTo for trace.FlightRecorder already in progress")
// ErrSnapshotActive indicates that a call to WriteTo was made while one was
// already in progress. If the caller of WriteTo sees this error, they should
// use the result from the other call to WriteTo.package main
import (
"golang.org/x/exp/trace"
"io"
"log"
"os"
"time"
)
func main() {
// Create and configure the flight recorder
recorder := trace.NewFlightRecorder()
recorder.SetSize(5 * 1024 * 1024) // 5MB buffer
recorder.SetPeriod(10 * time.Second)
// Start recording
if err := recorder.Start(); err != nil {
log.Fatal("Failed to start recorder:", err)
}
// Simulate some work
time.Sleep(2 * time.Second)
// Write trace to file
f, err := os.Create("trace.out")
if err != nil {
log.Fatal(err)
}
defer f.Close()
_, err = recorder.WriteTo(f)
if err != nil {
log.Fatal("Failed to write trace:", err)
}
if err := recorder.Stop(); err != nil {
log.Fatal("Failed to stop recorder:", err)
}
}package main
import (
"golang.org/x/exp/trace"
"io"
"log"
"os"
)
func main() {
// Open trace file
f, err := os.Open("trace.out")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// Create reader
reader, err := trace.NewReader(f)
if err != nil {
log.Fatal("Failed to create reader:", err)
}
eventCount := 0
stateTransitionCount := 0
// Process all events
for {
event, err := reader.ReadEvent()
if err == io.EOF {
break
}
if err != nil {
log.Fatal("Failed to read event:", err)
}
eventCount++
// Handle different event types
switch event.Kind() {
case trace.EventStateTransition:
stateTransitionCount++
st := event.StateTransition()
log.Printf("State transition: %s (resource: %v)\n", st.Reason, st.Resource)
case trace.EventSync:
sync := event.Sync()
log.Printf("Sync point %d\n", sync.N)
case trace.EventMetric:
metric := event.Metric()
log.Printf("Metric: %s = %v\n", metric.Name, metric.Value)
case trace.EventLog:
logEvent := event.Log()
log.Printf("[%s] %s\n", logEvent.Category, logEvent.Message)
case trace.EventStackSample:
stack := event.Stack()
log.Printf("Stack sample from goroutine %d\n", event.Goroutine())
for frame := range stack.Frames() {
log.Printf(" %s at %s:%d\n", frame.Func, frame.File, frame.Line)
}
}
}
log.Printf("Total events: %d, State transitions: %d\n", eventCount, stateTransitionCount)
}package main
import (
"golang.org/x/exp/trace"
"io"
"log"
"os"
)
func main() {
f, _ := os.Open("trace.out")
defer f.Close()
reader, _ := trace.NewReader(f)
goroutineStates := make(map[trace.GoID]trace.GoState)
for {
event, err := reader.ReadEvent()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
// Track goroutine state transitions
if event.Kind() == trace.EventStateTransition {
st := event.StateTransition()
if st.Resource.Kind == trace.ResourceGoroutine {
goID := st.Resource.Goroutine()
fromState, toState := st.Goroutine()
log.Printf("Goroutine %d: %s -> %s\n", goID, fromState, toState)
goroutineStates[goID] = toState
}
}
}
log.Printf("Final goroutine states:\n")
for goID, state := range goroutineStates {
log.Printf(" G%d: %s\n", goID, state)
}
}package main
import (
"golang.org/x/exp/trace"
"io"
"log"
"os"
"sync"
)
func main() {
recorder := trace.NewFlightRecorder()
recorder.Start()
defer recorder.Stop()
var wg sync.WaitGroup
// Multiple goroutines trying to write trace
for i := 0; i < 3; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
filename := os.Sprintf("trace_%d.out", index)
f, _ := os.Create(filename)
defer f.Close()
_, err := recorder.WriteTo(f)
if err == trace.ErrSnapshotActive {
log.Printf("Snapshot %d: already in progress, skipping\n", index)
} else if err != nil {
log.Printf("Snapshot %d: error %v\n", index, err)
} else {
log.Printf("Snapshot %d: success\n", index)
}
}(i)
}
wg.Wait()
}runtime/trace.Start may be active at any given time.