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