Source file src/runtime/trace/flightrecorder.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package trace
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"sync"
    11  	"time"
    12  	_ "unsafe" // added for go linkname usage
    13  )
    14  
    15  // FlightRecorder represents a single consumer of a Go execution
    16  // trace.
    17  // It tracks a moving window over the execution trace produced by
    18  // the runtime, always containing the most recent trace data.
    19  //
    20  // At most one flight recorder may be active at any given time,
    21  // though flight recording is allowed to be concurrently active
    22  // with a trace consumer using trace.Start.
    23  // This restriction of only a single flight recorder may be removed
    24  // in the future.
    25  type FlightRecorder struct {
    26  	err error
    27  
    28  	// State specific to the recorder.
    29  	header [16]byte
    30  	active rawGeneration
    31  	ringMu sync.Mutex
    32  	ring   []rawGeneration
    33  	freq   frequency // timestamp conversion factor, from the runtime
    34  
    35  	// Externally-set options.
    36  	targetSize   uint64
    37  	targetPeriod time.Duration
    38  
    39  	enabled bool       // whether the flight recorder is enabled.
    40  	writing sync.Mutex // protects concurrent calls to WriteTo
    41  
    42  	// The values of targetSize and targetPeriod we've committed to since the last Start.
    43  	wantSize uint64
    44  	wantDur  time.Duration
    45  }
    46  
    47  // NewFlightRecorder creates a new flight recorder from the provided configuration.
    48  func NewFlightRecorder(cfg FlightRecorderConfig) *FlightRecorder {
    49  	fr := new(FlightRecorder)
    50  	if cfg.MaxBytes != 0 {
    51  		fr.targetSize = cfg.MaxBytes
    52  	} else {
    53  		fr.targetSize = 10 << 20 // 10 MiB.
    54  	}
    55  
    56  	if cfg.MinAge != 0 {
    57  		fr.targetPeriod = cfg.MinAge
    58  	} else {
    59  		fr.targetPeriod = 10 * time.Second
    60  	}
    61  	return fr
    62  }
    63  
    64  // Start activates the flight recorder and begins recording trace data.
    65  // Only one call to trace.Start may be active at any given time.
    66  // In addition, currently only one flight recorder may be active in the program.
    67  // Returns an error if the flight recorder cannot be started or is already started.
    68  func (fr *FlightRecorder) Start() error {
    69  	if fr.enabled {
    70  		return fmt.Errorf("cannot enable a enabled flight recorder")
    71  	}
    72  	fr.wantSize = fr.targetSize
    73  	fr.wantDur = fr.targetPeriod
    74  	fr.err = nil
    75  	fr.freq = frequency(1.0 / (float64(runtime_traceClockUnitsPerSecond()) / 1e9))
    76  
    77  	// Start tracing, data is sent to a recorder which forwards it to our own
    78  	// storage.
    79  	if err := tracing.subscribeFlightRecorder(&recorder{r: fr}); err != nil {
    80  		return err
    81  	}
    82  
    83  	fr.enabled = true
    84  	return nil
    85  }
    86  
    87  // Stop ends recording of trace data. It blocks until any concurrent WriteTo calls complete.
    88  func (fr *FlightRecorder) Stop() {
    89  	if !fr.enabled {
    90  		return
    91  	}
    92  	fr.enabled = false
    93  	tracing.unsubscribeFlightRecorder()
    94  
    95  	// Reset all state. No need to lock because the reader has already exited.
    96  	fr.active = rawGeneration{}
    97  	fr.ring = nil
    98  }
    99  
   100  // Enabled returns true if the flight recorder is active.
   101  // Specifically, it will return true if Start did not return an error, and Stop has not yet been called.
   102  // It is safe to call from multiple goroutines simultaneously.
   103  func (fr *FlightRecorder) Enabled() bool { return fr.enabled }
   104  
   105  // WriteTo snapshots the moving window tracked by the flight recorder.
   106  // The snapshot is expected to contain data that is up-to-date as of when WriteTo is called,
   107  // though this is not a hard guarantee.
   108  // Only one goroutine may execute WriteTo at a time.
   109  // An error is returned upon failure to write to w, if another WriteTo call is already in-progress,
   110  // or if the flight recorder is inactive.
   111  func (fr *FlightRecorder) WriteTo(w io.Writer) (n int64, err error) {
   112  	if !fr.enabled {
   113  		return 0, fmt.Errorf("cannot snapshot a disabled flight recorder")
   114  	}
   115  	if !fr.writing.TryLock() {
   116  		// Indicates that a call to WriteTo was made while one was already in progress.
   117  		// If the caller of WriteTo sees this error, they should use the result from the other call to WriteTo.
   118  		return 0, fmt.Errorf("call to WriteTo for trace.FlightRecorder already in progress")
   119  	}
   120  	defer fr.writing.Unlock()
   121  
   122  	// Force a global buffer flush.
   123  	runtime_traceAdvance(false)
   124  
   125  	// Now that everything has been flushed and written, grab whatever we have.
   126  	//
   127  	// N.B. traceAdvance blocks until the tracer goroutine has actually written everything
   128  	// out, which means the generation we just flushed must have been already been observed
   129  	// by the recorder goroutine. Because we flushed twice, the first flush is guaranteed to
   130  	// have been both completed *and* processed by the recorder goroutine.
   131  	fr.ringMu.Lock()
   132  	gens := fr.ring
   133  	fr.ringMu.Unlock()
   134  
   135  	// Write the header.
   136  	nw, err := w.Write(fr.header[:])
   137  	if err != nil {
   138  		return int64(nw), err
   139  	}
   140  	n += int64(nw)
   141  
   142  	// Write all the data.
   143  	for _, gen := range gens {
   144  		for _, batch := range gen.batches {
   145  			// Write batch data.
   146  			nw, err = w.Write(batch.data)
   147  			n += int64(nw)
   148  			if err != nil {
   149  				return n, err
   150  			}
   151  		}
   152  	}
   153  	return n, nil
   154  }
   155  
   156  type FlightRecorderConfig struct {
   157  	// MinAge is a lower bound on the age of an event in the flight recorder's window.
   158  	//
   159  	// The flight recorder will strive to promptly discard events older than the minimum age,
   160  	// but older events may appear in the window snapshot. The age setting will always be
   161  	// overridden by MaxSize.
   162  	//
   163  	// If this is 0, the minimum age is implementation defined, but can be assumed to be on the order
   164  	// of seconds.
   165  	MinAge time.Duration
   166  
   167  	// MaxBytes is an upper bound on the size of the window in bytes.
   168  	//
   169  	// This setting takes precedence over MinAge.
   170  	// However, it does not make any guarantees on the size of the data WriteTo will write,
   171  	// nor does it guarantee memory overheads will always stay below MaxBytes. Treat it
   172  	// as a hint.
   173  	//
   174  	// If this is 0, the maximum size is implementation defined.
   175  	MaxBytes uint64
   176  }
   177  
   178  //go:linkname runtime_traceClockUnitsPerSecond
   179  func runtime_traceClockUnitsPerSecond() uint64
   180  
   181  //go:linkname runtime_traceAdvance runtime.traceAdvance
   182  func runtime_traceAdvance(stopTrace bool)
   183  

View as plain text