| // Copyright 2025 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package trace | |
| import ( | |
| "fmt" | |
| "internal/trace/tracev2" | |
| "io" | |
| "runtime" | |
| "sync" | |
| "sync/atomic" | |
| _ "unsafe" | |
| ) | |
| var tracing traceMultiplexer | |
| type traceMultiplexer struct { | |
| sync.Mutex | |
| enabled atomic.Bool | |
| subscribers int | |
| subscribersMu sync.Mutex | |
| traceStartWriter io.Writer | |
| flightRecorder *recorder | |
| } | |
| func (t *traceMultiplexer) subscribeFlightRecorder(r *recorder) error { | |
| t.Lock() | |
| defer t.Unlock() | |
| t.subscribersMu.Lock() | |
| if t.flightRecorder != nil { | |
| t.subscribersMu.Unlock() | |
| return fmt.Errorf("flight recorder already enabled") | |
| } | |
| t.flightRecorder = r | |
| t.subscribersMu.Unlock() | |
| if err := t.addedSubscriber(); err != nil { | |
| t.subscribersMu.Lock() | |
| t.flightRecorder = nil | |
| t.subscribersMu.Unlock() | |
| return err | |
| } | |
| return nil | |
| } | |
| func (t *traceMultiplexer) unsubscribeFlightRecorder() error { | |
| t.Lock() | |
| defer t.Unlock() | |
| t.removingSubscriber() | |
| t.subscribersMu.Lock() | |
| if t.flightRecorder == nil { | |
| t.subscribersMu.Unlock() | |
| return fmt.Errorf("attempt to unsubscribe missing flight recorder") | |
| } | |
| t.flightRecorder = nil | |
| t.subscribersMu.Unlock() | |
| t.removedSubscriber() | |
| return nil | |
| } | |
| func (t *traceMultiplexer) subscribeTraceStartWriter(w io.Writer) error { | |
| t.Lock() | |
| defer t.Unlock() | |
| t.subscribersMu.Lock() | |
| if t.traceStartWriter != nil { | |
| t.subscribersMu.Unlock() | |
| return fmt.Errorf("execution tracer already enabled") | |
| } | |
| t.traceStartWriter = w | |
| t.subscribersMu.Unlock() | |
| if err := t.addedSubscriber(); err != nil { | |
| t.subscribersMu.Lock() | |
| t.traceStartWriter = nil | |
| t.subscribersMu.Unlock() | |
| return err | |
| } | |
| return nil | |
| } | |
| func (t *traceMultiplexer) unsubscribeTraceStartWriter() { | |
| t.Lock() | |
| defer t.Unlock() | |
| t.removingSubscriber() | |
| t.subscribersMu.Lock() | |
| if t.traceStartWriter == nil { | |
| t.subscribersMu.Unlock() | |
| return | |
| } | |
| t.traceStartWriter = nil | |
| t.subscribersMu.Unlock() | |
| t.removedSubscriber() | |
| return | |
| } | |
| func (t *traceMultiplexer) addedSubscriber() error { | |
| if t.enabled.Load() { | |
| // This is necessary for the trace reader goroutine to pick up on the new subscriber. | |
| runtime_traceAdvance(false) | |
| } else { | |
| if err := t.startLocked(); err != nil { | |
| return err | |
| } | |
| } | |
| t.subscribers++ | |
| return nil | |
| } | |
| func (t *traceMultiplexer) removingSubscriber() { | |
| if t.subscribers == 0 { | |
| return | |
| } | |
| t.subscribers-- | |
| if t.subscribers == 0 { | |
| runtime.StopTrace() | |
| t.enabled.Store(false) | |
| } else { | |
| // This is necessary to avoid missing trace data when the system is under high load. | |
| runtime_traceAdvance(false) | |
| } | |
| } | |
| func (t *traceMultiplexer) removedSubscriber() { | |
| if t.subscribers > 0 { | |
| // This is necessary for the trace reader goroutine to pick up on the new subscriber. | |
| runtime_traceAdvance(false) | |
| } | |
| } | |
| func (t *traceMultiplexer) startLocked() error { | |
| if err := runtime.StartTrace(); err != nil { | |
| return err | |
| } | |
| // Grab the trace reader goroutine's subscribers. | |
| // | |
| // We only update our subscribers if we see an end-of-generation | |
| // signal from the runtime after this, so any new subscriptions | |
| // or unsubscriptions must call traceAdvance to ensure the reader | |
| // goroutine sees an end-of-generation signal. | |
| t.subscribersMu.Lock() | |
| flightRecorder := t.flightRecorder | |
| traceStartWriter := t.traceStartWriter | |
| t.subscribersMu.Unlock() | |
| go func() { | |
| header := runtime_readTrace() | |
| if traceStartWriter != nil { | |
| traceStartWriter.Write(header) | |
| } | |
| if flightRecorder != nil { | |
| flightRecorder.Write(header) | |
| } | |
| for { | |
| data := runtime_readTrace() | |
| if data == nil { | |
| break | |
| } | |
| if len(data) == 1 && tracev2.EventType(data[0]) == tracev2.EvEndOfGeneration { | |
| if flightRecorder != nil { | |
| flightRecorder.endGeneration() | |
| } | |
| // Pick up any changes. | |
| t.subscribersMu.Lock() | |
| frIsNew := flightRecorder != t.flightRecorder && t.flightRecorder != nil | |
| trIsNew := traceStartWriter != t.traceStartWriter && t.traceStartWriter != nil | |
| flightRecorder = t.flightRecorder | |
| traceStartWriter = t.traceStartWriter | |
| t.subscribersMu.Unlock() | |
| if trIsNew { | |
| traceStartWriter.Write(header) | |
| } | |
| if frIsNew { | |
| flightRecorder.Write(header) | |
| } | |
| } else { | |
| if traceStartWriter != nil { | |
| traceStartWriter.Write(data) | |
| } | |
| if flightRecorder != nil { | |
| flightRecorder.Write(data) | |
| } | |
| } | |
| } | |
| }() | |
| t.enabled.Store(true) | |
| return nil | |
| } | |
| //go:linkname runtime_readTrace | |
| func runtime_readTrace() (buf []byte) |