github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/internal/trace/v2/base.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  // This file contains data types that all implementations of the trace format
     6  // parser need to provide to the rest of the package.
     7  
     8  package trace
     9  
    10  import (
    11  	"fmt"
    12  	"math"
    13  	"strings"
    14  
    15  	"internal/trace/v2/event"
    16  	"internal/trace/v2/event/go122"
    17  	"internal/trace/v2/version"
    18  )
    19  
    20  // maxArgs is the maximum number of arguments for "plain" events,
    21  // i.e. anything that could reasonably be represented as a Base.
    22  const maxArgs = 5
    23  
    24  // baseEvent is the basic unprocessed event. This serves as a common
    25  // fundamental data structure across.
    26  type baseEvent struct {
    27  	typ  event.Type
    28  	time Time
    29  	args [maxArgs - 1]uint64
    30  }
    31  
    32  // extra returns a slice representing extra available space in args
    33  // that the parser can use to pass data up into Event.
    34  func (e *baseEvent) extra(v version.Version) []uint64 {
    35  	switch v {
    36  	case version.Go122:
    37  		return e.args[len(go122.Specs()[e.typ].Args)-1:]
    38  	}
    39  	panic(fmt.Sprintf("unsupported version: go 1.%d", v))
    40  }
    41  
    42  // evTable contains the per-generation data necessary to
    43  // interpret an individual event.
    44  type evTable struct {
    45  	freq    frequency
    46  	strings dataTable[stringID, string]
    47  	stacks  dataTable[stackID, stack]
    48  	pcs     map[uint64]frame
    49  
    50  	// extraStrings are strings that get generated during
    51  	// parsing but haven't come directly from the trace, so
    52  	// they don't appear in strings.
    53  	extraStrings   []string
    54  	extraStringIDs map[string]extraStringID
    55  	nextExtra      extraStringID
    56  }
    57  
    58  // addExtraString adds an extra string to the evTable and returns
    59  // a unique ID for the string in the table.
    60  func (t *evTable) addExtraString(s string) extraStringID {
    61  	if s == "" {
    62  		return 0
    63  	}
    64  	if t.extraStringIDs == nil {
    65  		t.extraStringIDs = make(map[string]extraStringID)
    66  	}
    67  	if id, ok := t.extraStringIDs[s]; ok {
    68  		return id
    69  	}
    70  	t.nextExtra++
    71  	id := t.nextExtra
    72  	t.extraStrings = append(t.extraStrings, s)
    73  	t.extraStringIDs[s] = id
    74  	return id
    75  }
    76  
    77  // getExtraString returns the extra string for the provided ID.
    78  // The ID must have been produced by addExtraString for this evTable.
    79  func (t *evTable) getExtraString(id extraStringID) string {
    80  	if id == 0 {
    81  		return ""
    82  	}
    83  	return t.extraStrings[id-1]
    84  }
    85  
    86  // dataTable is a mapping from EIs to Es.
    87  type dataTable[EI ~uint64, E any] struct {
    88  	present []uint8
    89  	dense   []E
    90  	sparse  map[EI]E
    91  }
    92  
    93  // insert tries to add a mapping from id to s.
    94  //
    95  // Returns an error if a mapping for id already exists, regardless
    96  // of whether or not s is the same in content. This should be used
    97  // for validation during parsing.
    98  func (d *dataTable[EI, E]) insert(id EI, data E) error {
    99  	if d.sparse == nil {
   100  		d.sparse = make(map[EI]E)
   101  	}
   102  	if existing, ok := d.get(id); ok {
   103  		return fmt.Errorf("multiple %Ts with the same ID: id=%d, new=%v, existing=%v", data, id, data, existing)
   104  	}
   105  	d.sparse[id] = data
   106  	return nil
   107  }
   108  
   109  // compactify attempts to compact sparse into dense.
   110  //
   111  // This is intended to be called only once after insertions are done.
   112  func (d *dataTable[EI, E]) compactify() {
   113  	if d.sparse == nil || len(d.dense) != 0 {
   114  		// Already compactified.
   115  		return
   116  	}
   117  	// Find the range of IDs.
   118  	maxID := EI(0)
   119  	minID := ^EI(0)
   120  	for id := range d.sparse {
   121  		if id > maxID {
   122  			maxID = id
   123  		}
   124  		if id < minID {
   125  			minID = id
   126  		}
   127  	}
   128  	if maxID >= math.MaxInt {
   129  		// We can't create a slice big enough to hold maxID elements
   130  		return
   131  	}
   132  	// We're willing to waste at most 2x memory.
   133  	if int(maxID-minID) > max(len(d.sparse), 2*len(d.sparse)) {
   134  		return
   135  	}
   136  	if int(minID) > len(d.sparse) {
   137  		return
   138  	}
   139  	size := int(maxID) + 1
   140  	d.present = make([]uint8, (size+7)/8)
   141  	d.dense = make([]E, size)
   142  	for id, data := range d.sparse {
   143  		d.dense[id] = data
   144  		d.present[id/8] |= uint8(1) << (id % 8)
   145  	}
   146  	d.sparse = nil
   147  }
   148  
   149  // get returns the E for id or false if it doesn't
   150  // exist. This should be used for validation during parsing.
   151  func (d *dataTable[EI, E]) get(id EI) (E, bool) {
   152  	if id == 0 {
   153  		return *new(E), true
   154  	}
   155  	if uint64(id) < uint64(len(d.dense)) {
   156  		if d.present[id/8]&(uint8(1)<<(id%8)) != 0 {
   157  			return d.dense[id], true
   158  		}
   159  	} else if d.sparse != nil {
   160  		if data, ok := d.sparse[id]; ok {
   161  			return data, true
   162  		}
   163  	}
   164  	return *new(E), false
   165  }
   166  
   167  // forEach iterates over all ID/value pairs in the data table.
   168  func (d *dataTable[EI, E]) forEach(yield func(EI, E) bool) bool {
   169  	for id, value := range d.dense {
   170  		if d.present[id/8]&(uint8(1)<<(id%8)) == 0 {
   171  			continue
   172  		}
   173  		if !yield(EI(id), value) {
   174  			return false
   175  		}
   176  	}
   177  	if d.sparse == nil {
   178  		return true
   179  	}
   180  	for id, value := range d.sparse {
   181  		if !yield(id, value) {
   182  			return false
   183  		}
   184  	}
   185  	return true
   186  }
   187  
   188  // mustGet returns the E for id or panics if it fails.
   189  //
   190  // This should only be used if id has already been validated.
   191  func (d *dataTable[EI, E]) mustGet(id EI) E {
   192  	data, ok := d.get(id)
   193  	if !ok {
   194  		panic(fmt.Sprintf("expected id %d in %T table", id, data))
   195  	}
   196  	return data
   197  }
   198  
   199  // frequency is nanoseconds per timestamp unit.
   200  type frequency float64
   201  
   202  // mul multiplies an unprocessed to produce a time in nanoseconds.
   203  func (f frequency) mul(t timestamp) Time {
   204  	return Time(float64(t) * float64(f))
   205  }
   206  
   207  // stringID is an index into the string table for a generation.
   208  type stringID uint64
   209  
   210  // extraStringID is an index into the extra string table for a generation.
   211  type extraStringID uint64
   212  
   213  // stackID is an index into the stack table for a generation.
   214  type stackID uint64
   215  
   216  // cpuSample represents a CPU profiling sample captured by the trace.
   217  type cpuSample struct {
   218  	schedCtx
   219  	time  Time
   220  	stack stackID
   221  }
   222  
   223  // asEvent produces a complete Event from a cpuSample. It needs
   224  // the evTable from the generation that created it.
   225  //
   226  // We don't just store it as an Event in generation to minimize
   227  // the amount of pointer data floating around.
   228  func (s cpuSample) asEvent(table *evTable) Event {
   229  	// TODO(mknyszek): This is go122-specific, but shouldn't be.
   230  	// Generalize this in the future.
   231  	e := Event{
   232  		table: table,
   233  		ctx:   s.schedCtx,
   234  		base: baseEvent{
   235  			typ:  go122.EvCPUSample,
   236  			time: s.time,
   237  		},
   238  	}
   239  	e.base.args[0] = uint64(s.stack)
   240  	return e
   241  }
   242  
   243  // stack represents a goroutine stack sample.
   244  type stack struct {
   245  	pcs []uint64
   246  }
   247  
   248  func (s stack) String() string {
   249  	var sb strings.Builder
   250  	for _, frame := range s.pcs {
   251  		fmt.Fprintf(&sb, "\t%#v\n", frame)
   252  	}
   253  	return sb.String()
   254  }
   255  
   256  // frame represents a single stack frame.
   257  type frame struct {
   258  	pc     uint64
   259  	funcID stringID
   260  	fileID stringID
   261  	line   uint64
   262  }