golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/trace/generation.go (about)

     1  // Copyright 2023 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  // Code generated by "gen.bash" from internal/trace/v2; DO NOT EDIT.
     6  
     7  //go:build go1.21
     8  
     9  package trace
    10  
    11  import (
    12  	"bufio"
    13  	"bytes"
    14  	"cmp"
    15  	"encoding/binary"
    16  	"fmt"
    17  	"io"
    18  	"slices"
    19  	"strings"
    20  
    21  	"golang.org/x/exp/trace/internal/event"
    22  	"golang.org/x/exp/trace/internal/event/go122"
    23  )
    24  
    25  // generation contains all the trace data for a single
    26  // trace generation. It is purely data: it does not
    27  // track any parse state nor does it contain a cursor
    28  // into the generation.
    29  type generation struct {
    30  	gen        uint64
    31  	batches    map[ThreadID][]batch
    32  	cpuSamples []cpuSample
    33  	*evTable
    34  }
    35  
    36  // spilledBatch represents a batch that was read out for the next generation,
    37  // while reading the previous one. It's passed on when parsing the next
    38  // generation.
    39  type spilledBatch struct {
    40  	gen uint64
    41  	*batch
    42  }
    43  
    44  // readGeneration buffers and decodes the structural elements of a trace generation
    45  // out of r. spill is the first batch of the new generation (already buffered and
    46  // parsed from reading the last generation). Returns the generation and the first
    47  // batch read of the next generation, if any.
    48  func readGeneration(r *bufio.Reader, spill *spilledBatch) (*generation, *spilledBatch, error) {
    49  	g := &generation{
    50  		evTable: &evTable{
    51  			pcs: make(map[uint64]frame),
    52  		},
    53  		batches: make(map[ThreadID][]batch),
    54  	}
    55  	// Process the spilled batch.
    56  	if spill != nil {
    57  		g.gen = spill.gen
    58  		if err := processBatch(g, *spill.batch); err != nil {
    59  			return nil, nil, err
    60  		}
    61  		spill = nil
    62  	}
    63  	// Read batches one at a time until we either hit EOF or
    64  	// the next generation.
    65  	for {
    66  		b, gen, err := readBatch(r)
    67  		if err == io.EOF {
    68  			break
    69  		}
    70  		if err != nil {
    71  			return nil, nil, err
    72  		}
    73  		if gen == 0 {
    74  			// 0 is a sentinel used by the runtime, so we'll never see it.
    75  			return nil, nil, fmt.Errorf("invalid generation number %d", gen)
    76  		}
    77  		if g.gen == 0 {
    78  			// Initialize gen.
    79  			g.gen = gen
    80  		}
    81  		if gen == g.gen+1 { // TODO: advance this the same way the runtime does.
    82  			spill = &spilledBatch{gen: gen, batch: &b}
    83  			break
    84  		}
    85  		if gen != g.gen {
    86  			// N.B. Fail as fast as possible if we see this. At first it
    87  			// may seem prudent to be fault-tolerant and assume we have a
    88  			// complete generation, parsing and returning that first. However,
    89  			// if the batches are mixed across generations then it's likely
    90  			// we won't be able to parse this generation correctly at all.
    91  			// Rather than return a cryptic error in that case, indicate the
    92  			// problem as soon as we see it.
    93  			return nil, nil, fmt.Errorf("generations out of order")
    94  		}
    95  		if err := processBatch(g, b); err != nil {
    96  			return nil, nil, err
    97  		}
    98  	}
    99  
   100  	// Check some invariants.
   101  	if g.freq == 0 {
   102  		return nil, nil, fmt.Errorf("no frequency event found")
   103  	}
   104  	// N.B. Trust that the batch order is correct. We can't validate the batch order
   105  	// by timestamp because the timestamps could just be plain wrong. The source of
   106  	// truth is the order things appear in the trace and the partial order sequence
   107  	// numbers on certain events. If it turns out the batch order is actually incorrect
   108  	// we'll very likely fail to advance a partial order from the frontier.
   109  
   110  	// Compactify stacks and strings for better lookup performance later.
   111  	g.stacks.compactify()
   112  	g.strings.compactify()
   113  
   114  	// Validate stacks.
   115  	if err := validateStackStrings(&g.stacks, &g.strings, g.pcs); err != nil {
   116  		return nil, nil, err
   117  	}
   118  
   119  	// Fix up the CPU sample timestamps, now that we have freq.
   120  	for i := range g.cpuSamples {
   121  		s := &g.cpuSamples[i]
   122  		s.time = g.freq.mul(timestamp(s.time))
   123  	}
   124  	// Sort the CPU samples.
   125  	slices.SortFunc(g.cpuSamples, func(a, b cpuSample) int {
   126  		return cmp.Compare(a.time, b.time)
   127  	})
   128  	return g, spill, nil
   129  }
   130  
   131  // processBatch adds the batch to the generation.
   132  func processBatch(g *generation, b batch) error {
   133  	switch {
   134  	case b.isStringsBatch():
   135  		if err := addStrings(&g.strings, b); err != nil {
   136  			return err
   137  		}
   138  	case b.isStacksBatch():
   139  		if err := addStacks(&g.stacks, g.pcs, b); err != nil {
   140  			return err
   141  		}
   142  	case b.isCPUSamplesBatch():
   143  		samples, err := addCPUSamples(g.cpuSamples, b)
   144  		if err != nil {
   145  			return err
   146  		}
   147  		g.cpuSamples = samples
   148  	case b.isFreqBatch():
   149  		freq, err := parseFreq(b)
   150  		if err != nil {
   151  			return err
   152  		}
   153  		if g.freq != 0 {
   154  			return fmt.Errorf("found multiple frequency events")
   155  		}
   156  		g.freq = freq
   157  	default:
   158  		g.batches[b.m] = append(g.batches[b.m], b)
   159  	}
   160  	return nil
   161  }
   162  
   163  // validateStackStrings makes sure all the string references in
   164  // the stack table are present in the string table.
   165  func validateStackStrings(
   166  	stacks *dataTable[stackID, stack],
   167  	strings *dataTable[stringID, string],
   168  	frames map[uint64]frame,
   169  ) error {
   170  	var err error
   171  	stacks.forEach(func(id stackID, stk stack) bool {
   172  		for _, pc := range stk.pcs {
   173  			frame, ok := frames[pc]
   174  			if !ok {
   175  				err = fmt.Errorf("found unknown pc %x for stack %d", pc, id)
   176  				return false
   177  			}
   178  			_, ok = strings.get(frame.funcID)
   179  			if !ok {
   180  				err = fmt.Errorf("found invalid func string ID %d for stack %d", frame.funcID, id)
   181  				return false
   182  			}
   183  			_, ok = strings.get(frame.fileID)
   184  			if !ok {
   185  				err = fmt.Errorf("found invalid file string ID %d for stack %d", frame.fileID, id)
   186  				return false
   187  			}
   188  		}
   189  		return true
   190  	})
   191  	return err
   192  }
   193  
   194  // addStrings takes a batch whose first byte is an EvStrings event
   195  // (indicating that the batch contains only strings) and adds each
   196  // string contained therein to the provided strings map.
   197  func addStrings(stringTable *dataTable[stringID, string], b batch) error {
   198  	if !b.isStringsBatch() {
   199  		return fmt.Errorf("internal error: addStrings called on non-string batch")
   200  	}
   201  	r := bytes.NewReader(b.data)
   202  	hdr, err := r.ReadByte() // Consume the EvStrings byte.
   203  	if err != nil || event.Type(hdr) != go122.EvStrings {
   204  		return fmt.Errorf("missing strings batch header")
   205  	}
   206  
   207  	var sb strings.Builder
   208  	for r.Len() != 0 {
   209  		// Read the header.
   210  		ev, err := r.ReadByte()
   211  		if err != nil {
   212  			return err
   213  		}
   214  		if event.Type(ev) != go122.EvString {
   215  			return fmt.Errorf("expected string event, got %d", ev)
   216  		}
   217  
   218  		// Read the string's ID.
   219  		id, err := binary.ReadUvarint(r)
   220  		if err != nil {
   221  			return err
   222  		}
   223  
   224  		// Read the string's length.
   225  		len, err := binary.ReadUvarint(r)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		if len > go122.MaxStringSize {
   230  			return fmt.Errorf("invalid string size %d, maximum is %d", len, go122.MaxStringSize)
   231  		}
   232  
   233  		// Copy out the string.
   234  		n, err := io.CopyN(&sb, r, int64(len))
   235  		if n != int64(len) {
   236  			return fmt.Errorf("failed to read full string: read %d but wanted %d", n, len)
   237  		}
   238  		if err != nil {
   239  			return fmt.Errorf("copying string data: %w", err)
   240  		}
   241  
   242  		// Add the string to the map.
   243  		s := sb.String()
   244  		sb.Reset()
   245  		if err := stringTable.insert(stringID(id), s); err != nil {
   246  			return err
   247  		}
   248  	}
   249  	return nil
   250  }
   251  
   252  // addStacks takes a batch whose first byte is an EvStacks event
   253  // (indicating that the batch contains only stacks) and adds each
   254  // string contained therein to the provided stacks map.
   255  func addStacks(stackTable *dataTable[stackID, stack], pcs map[uint64]frame, b batch) error {
   256  	if !b.isStacksBatch() {
   257  		return fmt.Errorf("internal error: addStacks called on non-stacks batch")
   258  	}
   259  	r := bytes.NewReader(b.data)
   260  	hdr, err := r.ReadByte() // Consume the EvStacks byte.
   261  	if err != nil || event.Type(hdr) != go122.EvStacks {
   262  		return fmt.Errorf("missing stacks batch header")
   263  	}
   264  
   265  	for r.Len() != 0 {
   266  		// Read the header.
   267  		ev, err := r.ReadByte()
   268  		if err != nil {
   269  			return err
   270  		}
   271  		if event.Type(ev) != go122.EvStack {
   272  			return fmt.Errorf("expected stack event, got %d", ev)
   273  		}
   274  
   275  		// Read the stack's ID.
   276  		id, err := binary.ReadUvarint(r)
   277  		if err != nil {
   278  			return err
   279  		}
   280  
   281  		// Read how many frames are in each stack.
   282  		nFrames, err := binary.ReadUvarint(r)
   283  		if err != nil {
   284  			return err
   285  		}
   286  		if nFrames > go122.MaxFramesPerStack {
   287  			return fmt.Errorf("invalid stack size %d, maximum is %d", nFrames, go122.MaxFramesPerStack)
   288  		}
   289  
   290  		// Each frame consists of 4 fields: pc, funcID (string), fileID (string), line.
   291  		frames := make([]uint64, 0, nFrames)
   292  		for i := uint64(0); i < nFrames; i++ {
   293  			// Read the frame data.
   294  			pc, err := binary.ReadUvarint(r)
   295  			if err != nil {
   296  				return fmt.Errorf("reading frame %d's PC for stack %d: %w", i+1, id, err)
   297  			}
   298  			funcID, err := binary.ReadUvarint(r)
   299  			if err != nil {
   300  				return fmt.Errorf("reading frame %d's funcID for stack %d: %w", i+1, id, err)
   301  			}
   302  			fileID, err := binary.ReadUvarint(r)
   303  			if err != nil {
   304  				return fmt.Errorf("reading frame %d's fileID for stack %d: %w", i+1, id, err)
   305  			}
   306  			line, err := binary.ReadUvarint(r)
   307  			if err != nil {
   308  				return fmt.Errorf("reading frame %d's line for stack %d: %w", i+1, id, err)
   309  			}
   310  			frames = append(frames, pc)
   311  
   312  			if _, ok := pcs[pc]; !ok {
   313  				pcs[pc] = frame{
   314  					pc:     pc,
   315  					funcID: stringID(funcID),
   316  					fileID: stringID(fileID),
   317  					line:   line,
   318  				}
   319  			}
   320  		}
   321  
   322  		// Add the stack to the map.
   323  		if err := stackTable.insert(stackID(id), stack{pcs: frames}); err != nil {
   324  			return err
   325  		}
   326  	}
   327  	return nil
   328  }
   329  
   330  // addCPUSamples takes a batch whose first byte is an EvCPUSamples event
   331  // (indicating that the batch contains only CPU samples) and adds each
   332  // sample contained therein to the provided samples list.
   333  func addCPUSamples(samples []cpuSample, b batch) ([]cpuSample, error) {
   334  	if !b.isCPUSamplesBatch() {
   335  		return nil, fmt.Errorf("internal error: addCPUSamples called on non-CPU-sample batch")
   336  	}
   337  	r := bytes.NewReader(b.data)
   338  	hdr, err := r.ReadByte() // Consume the EvCPUSamples byte.
   339  	if err != nil || event.Type(hdr) != go122.EvCPUSamples {
   340  		return nil, fmt.Errorf("missing CPU samples batch header")
   341  	}
   342  
   343  	for r.Len() != 0 {
   344  		// Read the header.
   345  		ev, err := r.ReadByte()
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  		if event.Type(ev) != go122.EvCPUSample {
   350  			return nil, fmt.Errorf("expected CPU sample event, got %d", ev)
   351  		}
   352  
   353  		// Read the sample's timestamp.
   354  		ts, err := binary.ReadUvarint(r)
   355  		if err != nil {
   356  			return nil, err
   357  		}
   358  
   359  		// Read the sample's M.
   360  		m, err := binary.ReadUvarint(r)
   361  		if err != nil {
   362  			return nil, err
   363  		}
   364  		mid := ThreadID(m)
   365  
   366  		// Read the sample's P.
   367  		p, err := binary.ReadUvarint(r)
   368  		if err != nil {
   369  			return nil, err
   370  		}
   371  		pid := ProcID(p)
   372  
   373  		// Read the sample's G.
   374  		g, err := binary.ReadUvarint(r)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  		goid := GoID(g)
   379  		if g == 0 {
   380  			goid = NoGoroutine
   381  		}
   382  
   383  		// Read the sample's stack.
   384  		s, err := binary.ReadUvarint(r)
   385  		if err != nil {
   386  			return nil, err
   387  		}
   388  
   389  		// Add the sample to the slice.
   390  		samples = append(samples, cpuSample{
   391  			schedCtx: schedCtx{
   392  				M: mid,
   393  				P: pid,
   394  				G: goid,
   395  			},
   396  			time:  Time(ts), // N.B. this is really a "timestamp," not a Time.
   397  			stack: stackID(s),
   398  		})
   399  	}
   400  	return samples, nil
   401  }
   402  
   403  // parseFreq parses out a lone EvFrequency from a batch.
   404  func parseFreq(b batch) (frequency, error) {
   405  	if !b.isFreqBatch() {
   406  		return 0, fmt.Errorf("internal error: parseFreq called on non-frequency batch")
   407  	}
   408  	r := bytes.NewReader(b.data)
   409  	r.ReadByte() // Consume the EvFrequency byte.
   410  
   411  	// Read the frequency. It'll come out as timestamp units per second.
   412  	f, err := binary.ReadUvarint(r)
   413  	if err != nil {
   414  		return 0, err
   415  	}
   416  	// Convert to nanoseconds per timestamp unit.
   417  	return frequency(1.0 / (float64(f) / 1e9)), nil
   418  }