| // 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) |