// Copyright 2023 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 traceimport ()// generation contains all the trace data for a single// trace generation. It is purely data: it does not// track any parse state nor does it contain a cursor// into the generation.type generation struct { gen uint64 batches map[ThreadID][]batch cpuSamples []cpuSample *evTable}// 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// generation.type spilledBatch struct { gen uint64 *batch}// readGeneration 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// parsed from reading the last generation). Returns the generation and the first// batch read of the next generation, if any.//// If gen is non-nil, it is valid and must be processed before handling the returned// error.func readGeneration( *bufio.Reader, *spilledBatch) (*generation, *spilledBatch, error) { := &generation{evTable: &evTable{pcs: make(map[uint64]frame), },batches: make(map[ThreadID][]batch), }// Process the spilled batch.if != nil { .gen = .genif := processBatch(, *.batch); != nil {returnnil, nil, } = nil }// Read batches one at a time until we either hit EOF or // the next generation.varerrorfor { , , := readBatch()if == io.EOF {break }if != nil {if .gen != 0 {// This is an error reading the first batch of the next generation. // This is fine. Let's forge ahead assuming that what we've got so // far is fine. = break }returnnil, nil, }if == 0 {// 0 is a sentinel used by the runtime, so we'll never see it.returnnil, nil, fmt.Errorf("invalid generation number %d", ) }if .gen == 0 {// Initialize gen. .gen = }if == .gen+1 { // TODO: advance this the same way the runtime does. = &spilledBatch{gen: , batch: &}break }if != .gen {// N.B. Fail as fast as possible if we see this. At first it // may seem prudent to be fault-tolerant and assume we have a // complete generation, parsing and returning that first. However, // if the batches are mixed across generations then it's likely // we won't be able to parse this generation correctly at all. // Rather than return a cryptic error in that case, indicate the // problem as soon as we see it.returnnil, nil, fmt.Errorf("generations out of order") }if := processBatch(, ); != nil {returnnil, nil, } }// Check some invariants.if .freq == 0 {returnnil, nil, fmt.Errorf("no frequency 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. .stacks.compactify() .strings.compactify()// Validate stacks.if := validateStackStrings(&.stacks, &.strings, .pcs); != nil {returnnil, nil, }// Fix up the CPU sample timestamps, now that we have freq.for := range .cpuSamples { := &.cpuSamples[] .time = .freq.mul(timestamp(.time)) }// Sort the CPU samples.slices.SortFunc(.cpuSamples, func(, cpuSample) int {returncmp.Compare(.time, .time) })return , , }// processBatch adds the batch to the generation.func processBatch( *generation, batch) error {switch {case .isStringsBatch():if := addStrings(&.strings, ); != nil {return }case .isStacksBatch():if := addStacks(&.stacks, .pcs, ); != nil {return }case .isCPUSamplesBatch(): , := addCPUSamples(.cpuSamples, )if != nil {return } .cpuSamples = case .isFreqBatch(): , := parseFreq()if != nil {return }if .freq != 0 {returnfmt.Errorf("found multiple frequency events") } .freq = case .exp != event.NoExperiment:if .expData == nil { .expData = make(map[event.Experiment]*ExperimentalData) }if := addExperimentalData(.expData, ); != nil {return }default: .batches[.m] = append(.batches[.m], ) }returnnil}// validateStackStrings makes sure all the string references in// the stack table are present in the string table.func validateStackStrings( *dataTable[stackID, stack], *dataTable[stringID, string],map[uint64]frame,) error {varerror .forEach(func( stackID, stack) bool {for , := range .pcs { , := []if ! { = fmt.Errorf("found unknown pc %x for stack %d", , )returnfalse } _, = .get(.funcID)if ! { = fmt.Errorf("found invalid func string ID %d for stack %d", .funcID, )returnfalse } _, = .get(.fileID)if ! { = fmt.Errorf("found invalid file string ID %d for stack %d", .fileID, )returnfalse } }returntrue })return}// addStrings takes a batch whose first byte is an EvStrings event// (indicating that the batch contains only strings) and adds each// string contained therein to the provided strings map.func addStrings( *dataTable[stringID, string], batch) error {if !.isStringsBatch() {returnfmt.Errorf("internal error: addStrings called on non-string batch") } := bytes.NewReader(.data) , := .ReadByte() // Consume the EvStrings byte.if != nil || event.Type() != go122.EvStrings {returnfmt.Errorf("missing strings batch header") }varstrings.Builderfor .Len() != 0 {// Read the header. , := .ReadByte()if != nil {return }ifevent.Type() != go122.EvString {returnfmt.Errorf("expected string event, got %d", ) }// Read the string's ID. , := binary.ReadUvarint()if != nil {return }// Read the string's length. , := binary.ReadUvarint()if != nil {return }if > go122.MaxStringSize {returnfmt.Errorf("invalid string size %d, maximum is %d", , go122.MaxStringSize) }// Copy out the string. , := io.CopyN(&, , int64())if != int64() {returnfmt.Errorf("failed to read full string: read %d but wanted %d", , ) }if != nil {returnfmt.Errorf("copying string data: %w", ) }// Add the string to the map. := .String() .Reset()if := .insert(stringID(), ); != nil {return } }returnnil}// addStacks takes a batch whose first byte is an EvStacks event// (indicating that the batch contains only stacks) and adds each// string contained therein to the provided stacks map.func addStacks( *dataTable[stackID, stack], map[uint64]frame, batch) error {if !.isStacksBatch() {returnfmt.Errorf("internal error: addStacks called on non-stacks batch") } := bytes.NewReader(.data) , := .ReadByte() // Consume the EvStacks byte.if != nil || event.Type() != go122.EvStacks {returnfmt.Errorf("missing stacks batch header") }for .Len() != 0 {// Read the header. , := .ReadByte()if != nil {return }ifevent.Type() != go122.EvStack {returnfmt.Errorf("expected stack event, got %d", ) }// Read the stack's ID. , := binary.ReadUvarint()if != nil {return }// Read how many frames are in each stack. , := binary.ReadUvarint()if != nil {return }if > go122.MaxFramesPerStack {returnfmt.Errorf("invalid stack size %d, maximum is %d", , go122.MaxFramesPerStack) }// Each frame consists of 4 fields: pc, funcID (string), fileID (string), line. := make([]uint64, 0, )for := uint64(0); < ; ++ {// Read the frame data. , := binary.ReadUvarint()if != nil {returnfmt.Errorf("reading frame %d's PC for stack %d: %w", +1, , ) } , := binary.ReadUvarint()if != nil {returnfmt.Errorf("reading frame %d's funcID for stack %d: %w", +1, , ) } , := binary.ReadUvarint()if != nil {returnfmt.Errorf("reading frame %d's fileID for stack %d: %w", +1, , ) } , := binary.ReadUvarint()if != nil {returnfmt.Errorf("reading frame %d's line for stack %d: %w", +1, , ) } = append(, )if , := []; ! { [] = frame{pc: ,funcID: stringID(),fileID: stringID(),line: , } } }// Add the stack to the map.if := .insert(stackID(), stack{pcs: }); != nil {return } }returnnil}// addCPUSamples takes a batch whose first byte is an EvCPUSamples event// (indicating that the batch contains only CPU samples) and adds each// sample contained therein to the provided samples list.func addCPUSamples( []cpuSample, batch) ([]cpuSample, error) {if !.isCPUSamplesBatch() {returnnil, fmt.Errorf("internal error: addCPUSamples called on non-CPU-sample batch") } := bytes.NewReader(.data) , := .ReadByte() // Consume the EvCPUSamples byte.if != nil || event.Type() != go122.EvCPUSamples {returnnil, fmt.Errorf("missing CPU samples batch header") }for .Len() != 0 {// Read the header. , := .ReadByte()if != nil {returnnil, }ifevent.Type() != go122.EvCPUSample {returnnil, fmt.Errorf("expected CPU sample event, got %d", ) }// Read the sample's timestamp. , := binary.ReadUvarint()if != nil {returnnil, }// Read the sample's M. , := binary.ReadUvarint()if != nil {returnnil, } := ThreadID()// Read the sample's P. , := binary.ReadUvarint()if != nil {returnnil, } := ProcID()// Read the sample's G. , := binary.ReadUvarint()if != nil {returnnil, } := GoID()if == 0 { = NoGoroutine }// Read the sample's stack. , := binary.ReadUvarint()if != nil {returnnil, }// Add the sample to the slice. = append(, cpuSample{schedCtx: schedCtx{M: ,P: ,G: , },time: Time(), // N.B. this is really a "timestamp," not a Time.stack: stackID(), }) }return , nil}// parseFreq parses out a lone EvFrequency from a batch.func parseFreq( batch) (frequency, error) {if !.isFreqBatch() {return0, fmt.Errorf("internal error: parseFreq called on non-frequency batch") } := bytes.NewReader(.data) .ReadByte() // Consume the EvFrequency byte.// Read the frequency. It'll come out as timestamp units per second. , := binary.ReadUvarint()if != nil {return0, }// Convert to nanoseconds per timestamp unit.returnfrequency(1.0 / (float64() / 1e9)), nil}// addExperimentalData takes an experimental batch and adds it to the ExperimentalData// for the experiment its a part of.func addExperimentalData( map[event.Experiment]*ExperimentalData, batch) error {if .exp == event.NoExperiment {returnfmt.Errorf("internal error: addExperimentalData called on non-experimental batch") } , := [.exp]if ! { = new(ExperimentalData) [.exp] = } .Batches = append(.Batches, ExperimentalBatch{Thread: .m,Data: .data, })returnnil}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.