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 }