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 }