| // 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" | |
| "io" | |
| "sync" | |
| "time" | |
| _ "unsafe" // added for go linkname usage | |
| ) | |
| // FlightRecorder represents a single consumer of a Go execution | |
| // trace. | |
| // It tracks a moving window over the execution trace produced by | |
| // the runtime, always containing the most recent trace data. | |
| // | |
| // At most one flight recorder may be active at any given time, | |
| // though flight recording is allowed to be concurrently active | |
| // with a trace consumer using trace.Start. | |
| // This restriction of only a single flight recorder may be removed | |
| // in the future. | |
| type FlightRecorder struct { | |
| err error | |
| // State specific to the recorder. | |
| header [16]byte | |
| active rawGeneration | |
| ringMu sync.Mutex | |
| ring []rawGeneration | |
| freq frequency // timestamp conversion factor, from the runtime | |
| // Externally-set options. | |
| targetSize uint64 | |
| targetPeriod time.Duration | |
| enabled bool // whether the flight recorder is enabled. | |
| writing sync.Mutex // protects concurrent calls to WriteTo | |
| // The values of targetSize and targetPeriod we've committed to since the last Start. | |
| wantSize uint64 | |
| wantDur time.Duration | |
| } | |
| // NewFlightRecorder creates a new flight recorder from the provided configuration. | |
| func NewFlightRecorder(cfg FlightRecorderConfig) *FlightRecorder { | |
| fr := new(FlightRecorder) | |
| if cfg.MaxBytes != 0 { | |
| fr.targetSize = cfg.MaxBytes | |
| } else { | |
| fr.targetSize = 10 << 20 // 10 MiB. | |
| } | |
| if cfg.MinAge != 0 { | |
| fr.targetPeriod = cfg.MinAge | |
| } else { | |
| fr.targetPeriod = 10 * time.Second | |
| } | |
| return fr | |
| } | |
| // Start activates the flight recorder and begins recording trace data. | |
| // Only one call to trace.Start may be active at any given time. | |
| // In addition, currently only one flight recorder may be active in the program. | |
| // Returns an error if the flight recorder cannot be started or is already started. | |
| func (fr *FlightRecorder) Start() error { | |
| if fr.enabled { | |
| return fmt.Errorf("cannot enable a enabled flight recorder") | |
| } | |
| fr.wantSize = fr.targetSize | |
| fr.wantDur = fr.targetPeriod | |
| fr.err = nil | |
| fr.freq = frequency(1.0 / (float64(runtime_traceClockUnitsPerSecond()) / 1e9)) | |
| // Start tracing, data is sent to a recorder which forwards it to our own | |
| // storage. | |
| if err := tracing.subscribeFlightRecorder(&recorder{r: fr}); err != nil { | |
| return err | |
| } | |
| fr.enabled = true | |
| return nil | |
| } | |
| // Stop ends recording of trace data. It blocks until any concurrent WriteTo calls complete. | |
| func (fr *FlightRecorder) Stop() { | |
| if !fr.enabled { | |
| return | |
| } | |
| fr.enabled = false | |
| tracing.unsubscribeFlightRecorder() | |
| // Reset all state. No need to lock because the reader has already exited. | |
| fr.active = rawGeneration{} | |
| fr.ring = nil | |
| } | |
| // 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 (fr *FlightRecorder) Enabled() bool { return fr.enabled } | |
| // WriteTo snapshots the moving window tracked by the flight recorder. | |
| // The snapshot is expected to contain data that is up-to-date as of when WriteTo is called, | |
| // though this is not a hard guarantee. | |
| // Only one goroutine may execute WriteTo at a time. | |
| // An error is returned upon failure to write to w, if another WriteTo call is already in-progress, | |
| // or if the flight recorder is inactive. | |
| func (fr *FlightRecorder) WriteTo(w io.Writer) (n int64, err error) { | |
| if !fr.enabled { | |
| return 0, fmt.Errorf("cannot snapshot a disabled flight recorder") | |
| } | |
| if !fr.writing.TryLock() { | |
| // 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. | |
| return 0, fmt.Errorf("call to WriteTo for trace.FlightRecorder already in progress") | |
| } | |
| defer fr.writing.Unlock() | |
| // Force a global buffer flush. | |
| runtime_traceAdvance(false) | |
| // Now that everything has been flushed and written, grab whatever we have. | |
| // | |
| // N.B. traceAdvance blocks until the tracer goroutine has actually written everything | |
| // out, which means the generation we just flushed must have been already been observed | |
| // by the recorder goroutine. Because we flushed twice, the first flush is guaranteed to | |
| // have been both completed *and* processed by the recorder goroutine. | |
| fr.ringMu.Lock() | |
| gens := fr.ring | |
| fr.ringMu.Unlock() | |
| // Write the header. | |
| nw, err := w.Write(fr.header[:]) | |
| if err != nil { | |
| return int64(nw), err | |
| } | |
| n += int64(nw) | |
| // Write all the data. | |
| for _, gen := range gens { | |
| for _, data := range gen.batches { | |
| // Write batch data. | |
| nw, err = w.Write(data) | |
| n += int64(nw) | |
| if err != nil { | |
| return n, err | |
| } | |
| } | |
| } | |
| return n, nil | |
| } | |
| type FlightRecorderConfig struct { | |
| // MinAge is a lower bound on the age of an event in the flight recorder's window. | |
| // | |
| // The flight recorder will strive to promptly discard events older than the minimum age, | |
| // but older events may appear in the window snapshot. The age setting will always be | |
| // overridden by MaxBytes. | |
| // | |
| // If this is 0, the minimum age is implementation defined, but can be assumed to be on the order | |
| // of seconds. | |
| MinAge time.Duration | |
| // MaxBytes is an upper bound on the size of the window in bytes. | |
| // | |
| // This setting takes precedence over MinAge. | |
| // However, it does not make any guarantees on the size of the data WriteTo will write, | |
| // nor does it guarantee memory overheads will always stay below MaxBytes. Treat it | |
| // as a hint. | |
| // | |
| // If this is 0, the maximum size is implementation defined. | |
| MaxBytes uint64 | |
| } | |
| //go:linkname runtime_traceClockUnitsPerSecond | |
| func runtime_traceClockUnitsPerSecond() uint64 | |
| //go:linkname runtime_traceAdvance runtime.traceAdvance | |
| func runtime_traceAdvance(stopTrace bool) | |