blob: 4f2d3aa92a412497a3b003736d32ae24c654ab5f [file] [log] [blame]
// 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"
"slices"
"time"
_ "unsafe" // added for go linkname usage
)
// A recorder receives bytes from the runtime tracer, processes it.
type recorder struct {
r *FlightRecorder
headerReceived bool
}
func (w *recorder) Write(b []byte) (n int, err error) {
r := w.r
defer func() {
if err != nil {
// Propagate errors to the flightrecorder.
if r.err == nil {
r.err = err
}
}
}()
if !w.headerReceived {
if len(b) < len(r.header) {
return 0, fmt.Errorf("expected at least %d bytes in the first write", len(r.header))
}
r.header = ([16]byte)(b[:16])
n += 16
w.headerReceived = true
}
if len(b) == n {
return 0, nil
}
ba, nb, err := readBatch(b[n:]) // Every write from the runtime is guaranteed to be a complete batch.
if err != nil {
return len(b) - int(nb) - n, err
}
n += int(nb)
// Append the batch to the current generation.
if ba.gen != 0 && r.active.gen == 0 {
r.active.gen = ba.gen
}
if ba.time != 0 && (r.active.minTime == 0 || r.active.minTime > r.freq.mul(ba.time)) {
r.active.minTime = r.freq.mul(ba.time)
}
r.active.size += len(ba.data)
r.active.batches = append(r.active.batches, ba.data)
return len(b), nil
}
func (w *recorder) endGeneration() {
r := w.r
// Check if we're entering a new generation.
r.ringMu.Lock()
// Get the current trace clock time.
now := traceTimeNow(r.freq)
// Add the current generation to the ring. Make sure we always have at least one
// complete generation by putting the active generation onto the new list, regardless
// of whatever our settings are.
//
// N.B. Let's completely replace the ring here, so that WriteTo can just make a copy
// and not worry about aliasing. This creates allocations, but at a very low rate.
newRing := []rawGeneration{r.active}
size := r.active.size
for i := len(r.ring) - 1; i >= 0; i-- {
// Stop adding older generations if the new ring already exceeds the thresholds.
// This ensures we keep generations that cross a threshold, but not any that lie
// entirely outside it.
if uint64(size) > r.wantSize || now.Sub(newRing[len(newRing)-1].minTime) > r.wantDur {
break
}
size += r.ring[i].size
newRing = append(newRing, r.ring[i])
}
slices.Reverse(newRing)
r.ring = newRing
r.ringMu.Unlock()
// Start a new active generation.
r.active = rawGeneration{}
}
type rawGeneration struct {
gen uint64
size int
minTime eventTime
batches [][]byte
}
func traceTimeNow(freq frequency) eventTime {
return freq.mul(timestamp(runtime_traceClockNow()))
}
//go:linkname runtime_traceClockNow runtime.traceClockNow
func runtime_traceClockNow() uint64
// frequency is nanoseconds per timestamp unit.
type frequency float64
// mul multiplies an unprocessed to produce a time in nanoseconds.
func (f frequency) mul(t timestamp) eventTime {
return eventTime(float64(t) * float64(f))
}
// eventTime is a timestamp in nanoseconds.
//
// It corresponds to the monotonic clock on the platform that the
// trace was taken, and so is possible to correlate with timestamps
// for other traces taken on the same machine using the same clock
// (i.e. no reboots in between).
//
// The actual absolute value of the timestamp is only meaningful in
// relation to other timestamps from the same clock.
//
// BUG: Timestamps coming from traces on Windows platforms are
// only comparable with timestamps from the same trace. Timestamps
// across traces cannot be compared, because the system clock is
// not used as of Go 1.22.
//
// BUG: Traces produced by Go versions 1.21 and earlier cannot be
// compared with timestamps from other traces taken on the same
// machine. This is because the system clock was not used at all
// to collect those timestamps.
type eventTime int64
// Sub subtracts t0 from t, returning the duration in nanoseconds.
func (t eventTime) Sub(t0 eventTime) time.Duration {
return time.Duration(int64(t) - int64(t0))
}