mirror of
https://github.com/golang/go.git
synced 2025-10-19 19:13:18 +00:00
internal/trace: add end-of-generation signal to trace
This change takes the EvEndOfGeneration event and promotes it to a real event that appears in the trace. This allows the trace parser to unambiguously identify truncated traces vs. broken traces. It also makes a lot of the logic around parsing simpler, because there's no more batch spilling necessary. Fixes #73904. Change-Id: I37c359b32b6b5f894825aafc02921adeaacf2595 Reviewed-on: https://go-review.googlesource.com/c/go/+/693398 Reviewed-by: Carlos Amedee <carlos@golang.org> Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
cb814bd5bc
commit
4a7fde922f
11 changed files with 284 additions and 146 deletions
|
@ -44,6 +44,10 @@ func (b *batch) isSyncBatch(ver version.Version) bool {
|
||||||
(tracev2.EventType(b.data[0]) == tracev2.EvSync && ver >= version.Go125))
|
(tracev2.EventType(b.data[0]) == tracev2.EvSync && ver >= version.Go125))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *batch) isEndOfGeneration() bool {
|
||||||
|
return b.exp == tracev2.NoExperiment && len(b.data) > 0 && tracev2.EventType(b.data[0]) == tracev2.EvEndOfGeneration
|
||||||
|
}
|
||||||
|
|
||||||
// readBatch reads the next full batch from r.
|
// readBatch reads the next full batch from r.
|
||||||
func readBatch(r interface {
|
func readBatch(r interface {
|
||||||
io.Reader
|
io.Reader
|
||||||
|
@ -54,6 +58,9 @@ func readBatch(r interface {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return batch{}, 0, err
|
return batch{}, 0, err
|
||||||
}
|
}
|
||||||
|
if typ := tracev2.EventType(b); typ == tracev2.EvEndOfGeneration {
|
||||||
|
return batch{m: NoThread, exp: tracev2.NoExperiment, data: []byte{b}}, 0, nil
|
||||||
|
}
|
||||||
if typ := tracev2.EventType(b); typ != tracev2.EvEventBatch && typ != tracev2.EvExperimentalBatch {
|
if typ := tracev2.EventType(b); typ != tracev2.EvEventBatch && typ != tracev2.EvExperimentalBatch {
|
||||||
return batch{}, 0, fmt.Errorf("expected batch event, got event %d", typ)
|
return batch{}, 0, fmt.Errorf("expected batch event, got event %d", typ)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"cmp"
|
"cmp"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
|
@ -32,22 +33,102 @@ type generation struct {
|
||||||
*evTable
|
*evTable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readGeneration buffers and decodes the structural elements of a trace generation
|
||||||
|
// out of r.
|
||||||
|
func readGeneration(r *bufio.Reader, ver version.Version) (*generation, error) {
|
||||||
|
if ver < version.Go126 {
|
||||||
|
return nil, errors.New("internal error: readGeneration called for <1.26 trace")
|
||||||
|
}
|
||||||
|
g := &generation{
|
||||||
|
evTable: &evTable{
|
||||||
|
pcs: make(map[uint64]frame),
|
||||||
|
},
|
||||||
|
batches: make(map[ThreadID][]batch),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read batches one at a time until we either hit the next generation.
|
||||||
|
for {
|
||||||
|
b, gen, err := readBatch(r)
|
||||||
|
if err == io.EOF {
|
||||||
|
if len(g.batches) != 0 {
|
||||||
|
return nil, errors.New("incomplete generation found; trace likely truncated")
|
||||||
|
}
|
||||||
|
return nil, nil // All done.
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if g.gen == 0 {
|
||||||
|
// Initialize gen.
|
||||||
|
g.gen = gen
|
||||||
|
}
|
||||||
|
if b.isEndOfGeneration() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if gen == 0 {
|
||||||
|
// 0 is a sentinel used by the runtime, so we'll never see it.
|
||||||
|
return nil, fmt.Errorf("invalid generation number %d", gen)
|
||||||
|
}
|
||||||
|
if gen != g.gen {
|
||||||
|
return nil, fmt.Errorf("broken trace: missing end-of-generation event, or generations are interleaved")
|
||||||
|
}
|
||||||
|
if g.minTs == 0 || b.time < g.minTs {
|
||||||
|
g.minTs = b.time
|
||||||
|
}
|
||||||
|
if err := processBatch(g, b, ver); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check some invariants.
|
||||||
|
if g.freq == 0 {
|
||||||
|
return nil, fmt.Errorf("no frequency event found")
|
||||||
|
}
|
||||||
|
if !g.hasClockSnapshot {
|
||||||
|
return nil, fmt.Errorf("no clock snapshot event found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// N.B. Trust that the batch order is correct. We can't validate the batch order
|
||||||
|
// by timestamp because the timestamps could just be plain wrong. The source of
|
||||||
|
// truth is the order things appear in the trace and the partial order sequence
|
||||||
|
// numbers on certain events. If it turns out the batch order is actually incorrect
|
||||||
|
// we'll very likely fail to advance a partial order from the frontier.
|
||||||
|
|
||||||
|
// Compactify stacks and strings for better lookup performance later.
|
||||||
|
g.stacks.compactify()
|
||||||
|
g.strings.compactify()
|
||||||
|
|
||||||
|
// Validate stacks.
|
||||||
|
if err := validateStackStrings(&g.stacks, &g.strings, g.pcs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have the frequency, fix up CPU samples.
|
||||||
|
fixUpCPUSamples(g.cpuSamples, g.freq)
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
// spilledBatch represents a batch that was read out for the next generation,
|
// spilledBatch represents a batch that was read out for the next generation,
|
||||||
// while reading the previous one. It's passed on when parsing the next
|
// while reading the previous one. It's passed on when parsing the next
|
||||||
// generation.
|
// generation.
|
||||||
|
//
|
||||||
|
// Used only for trace versions < Go126.
|
||||||
type spilledBatch struct {
|
type spilledBatch struct {
|
||||||
gen uint64
|
gen uint64
|
||||||
*batch
|
*batch
|
||||||
}
|
}
|
||||||
|
|
||||||
// readGeneration buffers and decodes the structural elements of a trace generation
|
// readGenerationWithSpill buffers and decodes the structural elements of a trace generation
|
||||||
// out of r. spill is the first batch of the new generation (already buffered and
|
// out of r. spill is the first batch of the new generation (already buffered and
|
||||||
// parsed from reading the last generation). Returns the generation and the first
|
// parsed from reading the last generation). Returns the generation and the first
|
||||||
// batch read of the next generation, if any.
|
// batch read of the next generation, if any.
|
||||||
//
|
//
|
||||||
// If gen is non-nil, it is valid and must be processed before handling the returned
|
// If gen is non-nil, it is valid and must be processed before handling the returned
|
||||||
// error.
|
// error.
|
||||||
func readGeneration(r *bufio.Reader, spill *spilledBatch, ver version.Version) (*generation, *spilledBatch, error) {
|
func readGenerationWithSpill(r *bufio.Reader, spill *spilledBatch, ver version.Version) (*generation, *spilledBatch, error) {
|
||||||
|
if ver >= version.Go126 {
|
||||||
|
return nil, nil, errors.New("internal error: readGenerationWithSpill called for Go 1.26+ trace")
|
||||||
|
}
|
||||||
g := &generation{
|
g := &generation{
|
||||||
evTable: &evTable{
|
evTable: &evTable{
|
||||||
pcs: make(map[uint64]frame),
|
pcs: make(map[uint64]frame),
|
||||||
|
@ -56,6 +137,7 @@ func readGeneration(r *bufio.Reader, spill *spilledBatch, ver version.Version) (
|
||||||
}
|
}
|
||||||
// Process the spilled batch.
|
// Process the spilled batch.
|
||||||
if spill != nil {
|
if spill != nil {
|
||||||
|
// Process the spilled batch, which contains real data.
|
||||||
g.gen = spill.gen
|
g.gen = spill.gen
|
||||||
g.minTs = spill.batch.time
|
g.minTs = spill.batch.time
|
||||||
if err := processBatch(g, *spill.batch, ver); err != nil {
|
if err := processBatch(g, *spill.batch, ver); err != nil {
|
||||||
|
@ -63,8 +145,7 @@ func readGeneration(r *bufio.Reader, spill *spilledBatch, ver version.Version) (
|
||||||
}
|
}
|
||||||
spill = nil
|
spill = nil
|
||||||
}
|
}
|
||||||
// Read batches one at a time until we either hit EOF or
|
// Read batches one at a time until we either hit the next generation.
|
||||||
// the next generation.
|
|
||||||
var spillErr error
|
var spillErr error
|
||||||
for {
|
for {
|
||||||
b, gen, err := readBatch(r)
|
b, gen, err := readBatch(r)
|
||||||
|
@ -73,7 +154,7 @@ func readGeneration(r *bufio.Reader, spill *spilledBatch, ver version.Version) (
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if g.gen != 0 {
|
if g.gen != 0 {
|
||||||
// This is an error reading the first batch of the next generation.
|
// This may be an error reading the first batch of the next generation.
|
||||||
// This is fine. Let's forge ahead assuming that what we've got so
|
// This is fine. Let's forge ahead assuming that what we've got so
|
||||||
// far is fine.
|
// far is fine.
|
||||||
spillErr = err
|
spillErr = err
|
||||||
|
@ -89,7 +170,8 @@ func readGeneration(r *bufio.Reader, spill *spilledBatch, ver version.Version) (
|
||||||
// Initialize gen.
|
// Initialize gen.
|
||||||
g.gen = gen
|
g.gen = gen
|
||||||
}
|
}
|
||||||
if gen == g.gen+1 { // TODO: advance this the same way the runtime does.
|
if gen == g.gen+1 {
|
||||||
|
// TODO: Increment the generation with wraparound the same way the runtime does.
|
||||||
spill = &spilledBatch{gen: gen, batch: &b}
|
spill = &spilledBatch{gen: gen, batch: &b}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -134,15 +216,8 @@ func readGeneration(r *bufio.Reader, spill *spilledBatch, ver version.Version) (
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix up the CPU sample timestamps, now that we have freq.
|
// Now that we have the frequency, fix up CPU samples.
|
||||||
for i := range g.cpuSamples {
|
fixUpCPUSamples(g.cpuSamples, g.freq)
|
||||||
s := &g.cpuSamples[i]
|
|
||||||
s.time = g.freq.mul(timestamp(s.time))
|
|
||||||
}
|
|
||||||
// Sort the CPU samples.
|
|
||||||
slices.SortFunc(g.cpuSamples, func(a, b cpuSample) int {
|
|
||||||
return cmp.Compare(a.time, b.time)
|
|
||||||
})
|
|
||||||
return g, spill, spillErr
|
return g, spill, spillErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +249,8 @@ func processBatch(g *generation, b batch, ver version.Version) error {
|
||||||
if err := addExperimentalBatch(g.expBatches, b); err != nil {
|
if err := addExperimentalBatch(g.expBatches, b); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case b.isEndOfGeneration():
|
||||||
|
return errors.New("internal error: unexpectedly processing EndOfGeneration; broken trace?")
|
||||||
default:
|
default:
|
||||||
if _, ok := g.batches[b.m]; !ok {
|
if _, ok := g.batches[b.m]; !ok {
|
||||||
g.batchMs = append(g.batchMs, b.m)
|
g.batchMs = append(g.batchMs, b.m)
|
||||||
|
@ -512,3 +589,15 @@ func addExperimentalBatch(expBatches map[tracev2.Experiment][]ExperimentalBatch,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixUpCPUSamples(samples []cpuSample, freq frequency) {
|
||||||
|
// Fix up the CPU sample timestamps.
|
||||||
|
for i := range samples {
|
||||||
|
s := &samples[i]
|
||||||
|
s.time = freq.mul(timestamp(s.time))
|
||||||
|
}
|
||||||
|
// Sort the CPU samples.
|
||||||
|
slices.SortFunc(samples, func(a, b cpuSample) int {
|
||||||
|
return cmp.Compare(a.time, b.time)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -322,6 +322,14 @@ func (g *Generation) writeEventsTo(tw *raw.TextWriter) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.writeEventsTo(tw)
|
b.writeEventsTo(tw)
|
||||||
|
|
||||||
|
// Write end-of-generation event if necessary.
|
||||||
|
if g.trace.ver >= version.Go126 {
|
||||||
|
tw.WriteEvent(raw.Event{
|
||||||
|
Version: g.trace.ver,
|
||||||
|
Ev: tracev2.EvEndOfGeneration,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generation) newStructuralBatch() *Batch {
|
func (g *Generation) newStructuralBatch() *Batch {
|
||||||
|
|
|
@ -6,6 +6,7 @@ package trace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
|
@ -22,18 +23,28 @@ import (
|
||||||
// event as the first event, and a Sync event as the last event.
|
// event as the first event, and a Sync event as the last event.
|
||||||
// (There may also be any number of Sync events in the middle, too.)
|
// (There may also be any number of Sync events in the middle, too.)
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
version version.Version
|
version version.Version
|
||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
lastTs Time
|
lastTs Time
|
||||||
gen *generation
|
gen *generation
|
||||||
|
frontier []*batchCursor
|
||||||
|
cpuSamples []cpuSample
|
||||||
|
order ordering
|
||||||
|
syncs int
|
||||||
|
done bool
|
||||||
|
|
||||||
|
// Spill state.
|
||||||
|
//
|
||||||
|
// Traces before Go 1.26 had no explicit end-of-generation signal, and
|
||||||
|
// so the first batch of the next generation needed to be parsed to identify
|
||||||
|
// a new generation. This batch is the "spilled" so we don't lose track
|
||||||
|
// of it when parsing the next generation.
|
||||||
|
//
|
||||||
|
// This is unnecessary after Go 1.26 because of an explicit end-of-generation
|
||||||
|
// signal.
|
||||||
spill *spilledBatch
|
spill *spilledBatch
|
||||||
spillErr error // error from reading spill
|
spillErr error // error from reading spill
|
||||||
spillErrSync bool // whether we emitted a Sync before reporting spillErr
|
spillErrSync bool // whether we emitted a Sync before reporting spillErr
|
||||||
frontier []*batchCursor
|
|
||||||
cpuSamples []cpuSample
|
|
||||||
order ordering
|
|
||||||
syncs int
|
|
||||||
done bool
|
|
||||||
|
|
||||||
v1Events *traceV1Converter
|
v1Events *traceV1Converter
|
||||||
}
|
}
|
||||||
|
@ -54,7 +65,7 @@ func NewReader(r io.Reader) (*Reader, error) {
|
||||||
return &Reader{
|
return &Reader{
|
||||||
v1Events: convertV1Trace(tr),
|
v1Events: convertV1Trace(tr),
|
||||||
}, nil
|
}, nil
|
||||||
case version.Go122, version.Go123, version.Go125:
|
case version.Go122, version.Go123, version.Go125, version.Go126:
|
||||||
return &Reader{
|
return &Reader{
|
||||||
version: v,
|
version: v,
|
||||||
r: br,
|
r: br,
|
||||||
|
@ -139,52 +150,14 @@ func (r *Reader) ReadEvent() (e Event, err error) {
|
||||||
|
|
||||||
// Check if we need to refresh the generation.
|
// Check if we need to refresh the generation.
|
||||||
if len(r.frontier) == 0 && len(r.cpuSamples) == 0 {
|
if len(r.frontier) == 0 && len(r.cpuSamples) == 0 {
|
||||||
if r.spillErr != nil {
|
if r.version < version.Go126 {
|
||||||
if r.spillErrSync {
|
return r.nextGenWithSpill()
|
||||||
return Event{}, r.spillErr
|
|
||||||
}
|
|
||||||
r.spillErrSync = true
|
|
||||||
r.syncs++
|
|
||||||
return syncEvent(nil, r.lastTs, r.syncs), nil
|
|
||||||
}
|
}
|
||||||
if r.gen != nil && r.spill == nil {
|
gen, err := readGeneration(r.r, r.version)
|
||||||
// If we have a generation from the last read,
|
if err != nil {
|
||||||
// and there's nothing left in the frontier, and
|
return Event{}, err
|
||||||
// there's no spilled batch, indicating that there's
|
|
||||||
// no further generation, it means we're done.
|
|
||||||
// Emit the final sync event.
|
|
||||||
r.done = true
|
|
||||||
r.syncs++
|
|
||||||
return syncEvent(nil, r.lastTs, r.syncs), nil
|
|
||||||
}
|
}
|
||||||
// Read the next generation.
|
return r.installGen(gen)
|
||||||
r.gen, r.spill, r.spillErr = readGeneration(r.r, r.spill, r.version)
|
|
||||||
if r.gen == nil {
|
|
||||||
r.spillErrSync = true
|
|
||||||
r.syncs++
|
|
||||||
return syncEvent(nil, r.lastTs, r.syncs), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset CPU samples cursor.
|
|
||||||
r.cpuSamples = r.gen.cpuSamples
|
|
||||||
|
|
||||||
// Reset frontier.
|
|
||||||
for _, m := range r.gen.batchMs {
|
|
||||||
batches := r.gen.batches[m]
|
|
||||||
bc := &batchCursor{m: m}
|
|
||||||
ok, err := bc.nextEvent(batches, r.gen.freq)
|
|
||||||
if err != nil {
|
|
||||||
return Event{}, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
// Turns out there aren't actually any events in these batches.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.frontier = heapInsert(r.frontier, bc)
|
|
||||||
}
|
|
||||||
r.syncs++
|
|
||||||
// Always emit a sync event at the beginning of the generation.
|
|
||||||
return syncEvent(r.gen.evTable, r.gen.freq.mul(r.gen.minTs), r.syncs), nil
|
|
||||||
}
|
}
|
||||||
tryAdvance := func(i int) (bool, error) {
|
tryAdvance := func(i int) (bool, error) {
|
||||||
bc := r.frontier[i]
|
bc := r.frontier[i]
|
||||||
|
@ -251,6 +224,78 @@ func (r *Reader) ReadEvent() (e Event, err error) {
|
||||||
return ev, nil
|
return ev, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nextGenWithSpill reads the generation and calls nextGen while
|
||||||
|
// also handling any spilled batches.
|
||||||
|
func (r *Reader) nextGenWithSpill() (Event, error) {
|
||||||
|
if r.version >= version.Go126 {
|
||||||
|
return Event{}, errors.New("internal error: nextGenWithSpill called for Go 1.26+ trace")
|
||||||
|
}
|
||||||
|
if r.spillErr != nil {
|
||||||
|
if r.spillErrSync {
|
||||||
|
return Event{}, r.spillErr
|
||||||
|
}
|
||||||
|
r.spillErrSync = true
|
||||||
|
r.syncs++
|
||||||
|
return syncEvent(nil, r.lastTs, r.syncs), nil
|
||||||
|
}
|
||||||
|
if r.gen != nil && r.spill == nil {
|
||||||
|
// If we have a generation from the last read,
|
||||||
|
// and there's nothing left in the frontier, and
|
||||||
|
// there's no spilled batch, indicating that there's
|
||||||
|
// no further generation, it means we're done.
|
||||||
|
// Emit the final sync event.
|
||||||
|
r.done = true
|
||||||
|
r.syncs++
|
||||||
|
return syncEvent(nil, r.lastTs, r.syncs), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the next generation.
|
||||||
|
var gen *generation
|
||||||
|
gen, r.spill, r.spillErr = readGenerationWithSpill(r.r, r.spill, r.version)
|
||||||
|
if gen == nil {
|
||||||
|
r.gen = nil
|
||||||
|
r.spillErrSync = true
|
||||||
|
r.syncs++
|
||||||
|
return syncEvent(nil, r.lastTs, r.syncs), nil
|
||||||
|
}
|
||||||
|
return r.installGen(gen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// installGen installs the new generation into the Reader and returns
|
||||||
|
// a Sync event for the new generation.
|
||||||
|
func (r *Reader) installGen(gen *generation) (Event, error) {
|
||||||
|
if gen == nil {
|
||||||
|
// Emit the final sync event.
|
||||||
|
r.gen = nil
|
||||||
|
r.done = true
|
||||||
|
r.syncs++
|
||||||
|
return syncEvent(nil, r.lastTs, r.syncs), nil
|
||||||
|
}
|
||||||
|
r.gen = gen
|
||||||
|
|
||||||
|
// Reset CPU samples cursor.
|
||||||
|
r.cpuSamples = r.gen.cpuSamples
|
||||||
|
|
||||||
|
// Reset frontier.
|
||||||
|
for _, m := range r.gen.batchMs {
|
||||||
|
batches := r.gen.batches[m]
|
||||||
|
bc := &batchCursor{m: m}
|
||||||
|
ok, err := bc.nextEvent(batches, r.gen.freq)
|
||||||
|
if err != nil {
|
||||||
|
return Event{}, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// Turns out there aren't actually any events in these batches.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.frontier = heapInsert(r.frontier, bc)
|
||||||
|
}
|
||||||
|
r.syncs++
|
||||||
|
|
||||||
|
// Always emit a sync event at the beginning of the generation.
|
||||||
|
return syncEvent(r.gen.evTable, r.gen.freq.mul(r.gen.minTs), r.syncs), nil
|
||||||
|
}
|
||||||
|
|
||||||
func dumpFrontier(frontier []*batchCursor) string {
|
func dumpFrontier(frontier []*batchCursor) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
for _, bc := range frontier {
|
for _, bc := range frontier {
|
||||||
|
|
|
@ -87,8 +87,8 @@ const (
|
||||||
EvSync // start of a sync batch [...EvFrequency|EvClockSnapshot]
|
EvSync // start of a sync batch [...EvFrequency|EvClockSnapshot]
|
||||||
EvClockSnapshot // snapshot of trace, mono and wall clocks [timestamp, mono, sec, nsec]
|
EvClockSnapshot // snapshot of trace, mono and wall clocks [timestamp, mono, sec, nsec]
|
||||||
|
|
||||||
// Reserved internal in-band end-of-generation signal. Must never appear in the trace. Added in Go 1.25.
|
// In-band end-of-generation signal. Added in Go 1.26.
|
||||||
// This could be used as an explicit in-band end-of-generation signal in the future.
|
// Used in Go 1.25 only internally.
|
||||||
EvEndOfGeneration
|
EvEndOfGeneration
|
||||||
|
|
||||||
NumEvents
|
NumEvents
|
||||||
|
|
|
@ -21,7 +21,8 @@ const (
|
||||||
Go122 Version = 22 // v2
|
Go122 Version = 22 // v2
|
||||||
Go123 Version = 23 // v2
|
Go123 Version = 23 // v2
|
||||||
Go125 Version = 25 // v2
|
Go125 Version = 25 // v2
|
||||||
Current = Go125
|
Go126 Version = 26 // v2
|
||||||
|
Current = Go126
|
||||||
)
|
)
|
||||||
|
|
||||||
var versions = map[Version][]tracev2.EventSpec{
|
var versions = map[Version][]tracev2.EventSpec{
|
||||||
|
@ -33,7 +34,8 @@ var versions = map[Version][]tracev2.EventSpec{
|
||||||
|
|
||||||
Go122: tracev2.Specs()[:tracev2.EvUserLog+1], // All events after are Go 1.23+.
|
Go122: tracev2.Specs()[:tracev2.EvUserLog+1], // All events after are Go 1.23+.
|
||||||
Go123: tracev2.Specs()[:tracev2.EvExperimentalBatch+1], // All events after are Go 1.25+.
|
Go123: tracev2.Specs()[:tracev2.EvExperimentalBatch+1], // All events after are Go 1.25+.
|
||||||
Go125: tracev2.Specs(),
|
Go125: tracev2.Specs()[:tracev2.EvClockSnapshot+1], // All events after are Go 1.26+.
|
||||||
|
Go126: tracev2.Specs(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specs returns the set of event.Specs for this version.
|
// Specs returns the set of event.Specs for this version.
|
||||||
|
|
|
@ -754,24 +754,7 @@ func traceRegisterLabelsAndReasons(gen uintptr) {
|
||||||
// was on has been returned, ReadTrace returns nil. The caller must copy the
|
// was on has been returned, ReadTrace returns nil. The caller must copy the
|
||||||
// returned data before calling ReadTrace again.
|
// returned data before calling ReadTrace again.
|
||||||
// ReadTrace must be called from one goroutine at a time.
|
// ReadTrace must be called from one goroutine at a time.
|
||||||
func ReadTrace() []byte {
|
func ReadTrace() (buf []byte) {
|
||||||
for {
|
|
||||||
buf := readTrace()
|
|
||||||
|
|
||||||
// Skip over the end-of-generation signal which must not appear
|
|
||||||
// in the final trace.
|
|
||||||
if len(buf) == 1 && tracev2.EventType(buf[0]) == tracev2.EvEndOfGeneration {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTrace is the implementation of ReadTrace, except with an additional
|
|
||||||
// in-band signal as to when the buffer is for a new generation.
|
|
||||||
//
|
|
||||||
//go:linkname readTrace runtime/trace.runtime_readTrace
|
|
||||||
func readTrace() (buf []byte) {
|
|
||||||
top:
|
top:
|
||||||
var park bool
|
var park bool
|
||||||
systemstack(func() {
|
systemstack(func() {
|
||||||
|
@ -842,7 +825,7 @@ func readTrace0() (buf []byte, park bool) {
|
||||||
if !trace.headerWritten {
|
if !trace.headerWritten {
|
||||||
trace.headerWritten = true
|
trace.headerWritten = true
|
||||||
unlock(&trace.lock)
|
unlock(&trace.lock)
|
||||||
return []byte("go 1.25 trace\x00\x00\x00"), false
|
return []byte("go 1.26 trace\x00\x00\x00"), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the next buffer.
|
// Read the next buffer.
|
||||||
|
|
|
@ -12,72 +12,77 @@ import (
|
||||||
// timestamp is an unprocessed timestamp.
|
// timestamp is an unprocessed timestamp.
|
||||||
type timestamp uint64
|
type timestamp uint64
|
||||||
|
|
||||||
// batch represents a batch of trace events.
|
|
||||||
// It is unparsed except for its header.
|
|
||||||
type batch struct {
|
type batch struct {
|
||||||
m threadID
|
|
||||||
time timestamp
|
time timestamp
|
||||||
|
gen uint64
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// threadID is the runtime-internal M structure's ID. This is unique
|
|
||||||
// for each OS thread.
|
|
||||||
type threadID int64
|
|
||||||
|
|
||||||
// readBatch copies b and parses the trace batch header inside.
|
// readBatch copies b and parses the trace batch header inside.
|
||||||
// Returns the batch, the generation, bytes read, and an error.
|
// Returns the batch, bytes read, and an error.
|
||||||
func readBatch(b []byte) (batch, uint64, uint64, error) {
|
func readBatch(b []byte) (batch, uint64, error) {
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return batch{}, 0, 0, fmt.Errorf("batch is empty")
|
return batch{}, 0, fmt.Errorf("batch is empty")
|
||||||
}
|
}
|
||||||
data := make([]byte, len(b))
|
data := make([]byte, len(b))
|
||||||
if nw := copy(data, b); nw != len(b) {
|
copy(data, b)
|
||||||
return batch{}, 0, 0, fmt.Errorf("unexpected error copying batch")
|
|
||||||
}
|
|
||||||
// Read batch header byte.
|
|
||||||
if typ := tracev2.EventType(b[0]); typ != tracev2.EvEventBatch && typ != tracev2.EvExperimentalBatch {
|
|
||||||
return batch{}, 0, 1, fmt.Errorf("expected batch event, got event %d", typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the batch header: gen (generation), thread (M) ID, base timestamp
|
// Read batch header byte.
|
||||||
// for the batch.
|
if typ := tracev2.EventType(b[0]); typ == tracev2.EvEndOfGeneration {
|
||||||
|
if len(b) != 1 {
|
||||||
|
return batch{}, 1, fmt.Errorf("unexpected end of generation in batch of size >1")
|
||||||
|
}
|
||||||
|
return batch{data: data}, 1, nil
|
||||||
|
}
|
||||||
|
if typ := tracev2.EventType(b[0]); typ != tracev2.EvEventBatch && typ != tracev2.EvExperimentalBatch {
|
||||||
|
return batch{}, 1, fmt.Errorf("expected batch event, got event %d", typ)
|
||||||
|
}
|
||||||
total := 1
|
total := 1
|
||||||
b = b[1:]
|
b = b[1:]
|
||||||
|
|
||||||
|
// Read the generation
|
||||||
gen, n, err := readUvarint(b)
|
gen, n, err := readUvarint(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return batch{}, gen, uint64(total + n), fmt.Errorf("error reading batch gen: %w", err)
|
return batch{}, uint64(total + n), fmt.Errorf("error reading batch gen: %w", err)
|
||||||
}
|
|
||||||
total += n
|
|
||||||
b = b[n:]
|
|
||||||
m, n, err := readUvarint(b)
|
|
||||||
if err != nil {
|
|
||||||
return batch{}, gen, uint64(total + n), fmt.Errorf("error reading batch M ID: %w", err)
|
|
||||||
}
|
|
||||||
total += n
|
|
||||||
b = b[n:]
|
|
||||||
ts, n, err := readUvarint(b)
|
|
||||||
if err != nil {
|
|
||||||
return batch{}, gen, uint64(total + n), fmt.Errorf("error reading batch timestamp: %w", err)
|
|
||||||
}
|
}
|
||||||
total += n
|
total += n
|
||||||
b = b[n:]
|
b = b[n:]
|
||||||
|
|
||||||
// Read in the size of the batch to follow.
|
// Read the M (discard it).
|
||||||
|
_, n, err = readUvarint(b)
|
||||||
|
if err != nil {
|
||||||
|
return batch{}, uint64(total + n), fmt.Errorf("error reading batch M ID: %w", err)
|
||||||
|
}
|
||||||
|
total += n
|
||||||
|
b = b[n:]
|
||||||
|
|
||||||
|
// Read the timestamp.
|
||||||
|
ts, n, err := readUvarint(b)
|
||||||
|
if err != nil {
|
||||||
|
return batch{}, uint64(total + n), fmt.Errorf("error reading batch timestamp: %w", err)
|
||||||
|
}
|
||||||
|
total += n
|
||||||
|
b = b[n:]
|
||||||
|
|
||||||
|
// Read the size of the batch to follow.
|
||||||
size, n, err := readUvarint(b)
|
size, n, err := readUvarint(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return batch{}, gen, uint64(total + n), fmt.Errorf("error reading batch size: %w", err)
|
return batch{}, uint64(total + n), fmt.Errorf("error reading batch size: %w", err)
|
||||||
}
|
}
|
||||||
if size > tracev2.MaxBatchSize {
|
if size > tracev2.MaxBatchSize {
|
||||||
return batch{}, gen, uint64(total + n), fmt.Errorf("invalid batch size %d, maximum is %d", size, tracev2.MaxBatchSize)
|
return batch{}, uint64(total + n), fmt.Errorf("invalid batch size %d, maximum is %d", size, tracev2.MaxBatchSize)
|
||||||
}
|
}
|
||||||
total += n
|
total += n
|
||||||
total += int(size)
|
total += int(size)
|
||||||
|
if total != len(data) {
|
||||||
|
return batch{}, uint64(total), fmt.Errorf("expected complete batch")
|
||||||
|
}
|
||||||
data = data[:total]
|
data = data[:total]
|
||||||
|
|
||||||
// Return the batch.
|
// Return the batch.
|
||||||
return batch{
|
return batch{
|
||||||
m: threadID(m),
|
gen: gen,
|
||||||
time: timestamp(ts),
|
time: timestamp(ts),
|
||||||
data: data,
|
data: data,
|
||||||
}, gen, uint64(total), nil
|
}, uint64(total), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,9 +141,9 @@ func (fr *FlightRecorder) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
|
||||||
// Write all the data.
|
// Write all the data.
|
||||||
for _, gen := range gens {
|
for _, gen := range gens {
|
||||||
for _, batch := range gen.batches {
|
for _, data := range gen.batches {
|
||||||
// Write batch data.
|
// Write batch data.
|
||||||
nw, err = w.Write(batch.data)
|
nw, err = w.Write(data)
|
||||||
n += int64(nw)
|
n += int64(nw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, err
|
return n, err
|
||||||
|
|
|
@ -41,21 +41,21 @@ func (w *recorder) Write(b []byte) (n int, err error) {
|
||||||
if len(b) == n {
|
if len(b) == n {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
ba, gen, nb, err := readBatch(b[n:]) // Every write from the runtime is guaranteed to be a complete batch.
|
ba, nb, err := readBatch(b[n:]) // Every write from the runtime is guaranteed to be a complete batch.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return len(b) - int(nb) - n, err
|
return len(b) - int(nb) - n, err
|
||||||
}
|
}
|
||||||
n += int(nb)
|
n += int(nb)
|
||||||
|
|
||||||
// Append the batch to the current generation.
|
// Append the batch to the current generation.
|
||||||
if r.active.gen == 0 {
|
if ba.gen != 0 && r.active.gen == 0 {
|
||||||
r.active.gen = gen
|
r.active.gen = ba.gen
|
||||||
}
|
}
|
||||||
if r.active.minTime == 0 || r.active.minTime > r.freq.mul(ba.time) {
|
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.minTime = r.freq.mul(ba.time)
|
||||||
}
|
}
|
||||||
r.active.size += len(ba.data)
|
r.active.size += len(ba.data)
|
||||||
r.active.batches = append(r.active.batches, ba)
|
r.active.batches = append(r.active.batches, ba.data)
|
||||||
|
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ type rawGeneration struct {
|
||||||
gen uint64
|
gen uint64
|
||||||
size int
|
size int
|
||||||
minTime eventTime
|
minTime eventTime
|
||||||
batches []batch
|
batches [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func traceTimeNow(freq frequency) eventTime {
|
func traceTimeNow(freq frequency) eventTime {
|
||||||
|
|
|
@ -155,7 +155,7 @@ func (t *traceMultiplexer) startLocked() error {
|
||||||
t.subscribersMu.Unlock()
|
t.subscribersMu.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
header := runtime_readTrace()
|
header := runtime.ReadTrace()
|
||||||
if traceStartWriter != nil {
|
if traceStartWriter != nil {
|
||||||
traceStartWriter.Write(header)
|
traceStartWriter.Write(header)
|
||||||
}
|
}
|
||||||
|
@ -164,10 +164,16 @@ func (t *traceMultiplexer) startLocked() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
data := runtime_readTrace()
|
data := runtime.ReadTrace()
|
||||||
if data == nil {
|
if data == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if traceStartWriter != nil {
|
||||||
|
traceStartWriter.Write(data)
|
||||||
|
}
|
||||||
|
if flightRecorder != nil {
|
||||||
|
flightRecorder.Write(data)
|
||||||
|
}
|
||||||
if len(data) == 1 && tracev2.EventType(data[0]) == tracev2.EvEndOfGeneration {
|
if len(data) == 1 && tracev2.EventType(data[0]) == tracev2.EvEndOfGeneration {
|
||||||
if flightRecorder != nil {
|
if flightRecorder != nil {
|
||||||
flightRecorder.endGeneration()
|
flightRecorder.endGeneration()
|
||||||
|
@ -187,13 +193,6 @@ func (t *traceMultiplexer) startLocked() error {
|
||||||
if frIsNew {
|
if frIsNew {
|
||||||
flightRecorder.Write(header)
|
flightRecorder.Write(header)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if traceStartWriter != nil {
|
|
||||||
traceStartWriter.Write(data)
|
|
||||||
}
|
|
||||||
if flightRecorder != nil {
|
|
||||||
flightRecorder.Write(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue