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