github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/trace/v2/internal/testgen/go122/trace.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 testkit 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "fmt" 11 "os" 12 "regexp" 13 "strings" 14 15 "github.com/go-asm/go/trace/v2" 16 "github.com/go-asm/go/trace/v2/event" 17 "github.com/go-asm/go/trace/v2/event/go122" 18 "github.com/go-asm/go/trace/v2/raw" 19 "github.com/go-asm/go/trace/v2/version" 20 "github.com/go-asm/go/txtar" 21 ) 22 23 func Main(f func(*Trace)) { 24 // Create an output file. 25 out, err := os.Create(os.Args[1]) 26 if err != nil { 27 panic(err.Error()) 28 } 29 defer out.Close() 30 31 // Create a new trace. 32 trace := NewTrace() 33 34 // Call the generator. 35 f(trace) 36 37 // Write out the generator's state. 38 if _, err := out.Write(trace.Generate()); err != nil { 39 panic(err.Error()) 40 } 41 } 42 43 // Trace represents an execution trace for testing. 44 // 45 // It does a little bit of work to ensure that the produced trace is valid, 46 // just for convenience. It mainly tracks batches and batch sizes (so they're 47 // trivially correct), tracks strings and stacks, and makes sure emitted string 48 // and stack batches are valid. That last part can be controlled by a few options. 49 // 50 // Otherwise, it performs no validation on the trace at all. 51 type Trace struct { 52 // Trace data state. 53 ver version.Version 54 names map[string]event.Type 55 specs []event.Spec 56 events []raw.Event 57 gens []*Generation 58 validTimestamps bool 59 60 // Expectation state. 61 bad bool 62 badMatch *regexp.Regexp 63 } 64 65 // NewTrace creates a new trace. 66 func NewTrace() *Trace { 67 ver := version.Go122 68 return &Trace{ 69 names: event.Names(ver.Specs()), 70 specs: ver.Specs(), 71 validTimestamps: true, 72 } 73 } 74 75 // ExpectFailure writes down that the trace should be broken. The caller 76 // must provide a pattern matching the expected error produced by the parser. 77 func (t *Trace) ExpectFailure(pattern string) { 78 t.bad = true 79 t.badMatch = regexp.MustCompile(pattern) 80 } 81 82 // ExpectSuccess writes down that the trace should successfully parse. 83 func (t *Trace) ExpectSuccess() { 84 t.bad = false 85 } 86 87 // RawEvent emits an event into the trace. name must correspond to one 88 // of the names in Specs() result for the version that was passed to 89 // this trace. 90 func (t *Trace) RawEvent(typ event.Type, data []byte, args ...uint64) { 91 t.events = append(t.events, t.createEvent(typ, data, args...)) 92 } 93 94 // DisableTimestamps makes the timestamps for all events generated after 95 // this call zero. Raw events are exempted from this because the caller 96 // has to pass their own timestamp into those events anyway. 97 func (t *Trace) DisableTimestamps() { 98 t.validTimestamps = false 99 } 100 101 // Generation creates a new trace generation. 102 // 103 // This provides more structure than Event to allow for more easily 104 // creating complex traces that are mostly or completely correct. 105 func (t *Trace) Generation(gen uint64) *Generation { 106 g := &Generation{ 107 trace: t, 108 gen: gen, 109 strings: make(map[string]uint64), 110 stacks: make(map[stack]uint64), 111 } 112 t.gens = append(t.gens, g) 113 return g 114 } 115 116 // Generate creates a test file for the trace. 117 func (t *Trace) Generate() []byte { 118 // Trace file contents. 119 var buf bytes.Buffer 120 tw, err := raw.NewTextWriter(&buf, version.Go122) 121 if err != nil { 122 panic(err.Error()) 123 } 124 125 // Write raw top-level events. 126 for _, e := range t.events { 127 tw.WriteEvent(e) 128 } 129 130 // Write generations. 131 for _, g := range t.gens { 132 g.writeEventsTo(tw) 133 } 134 135 // Expectation file contents. 136 expect := []byte("SUCCESS\n") 137 if t.bad { 138 expect = []byte(fmt.Sprintf("FAILURE %q\n", t.badMatch)) 139 } 140 141 // Create the test file's contents. 142 return txtar.Format(&txtar.Archive{ 143 Files: []txtar.File{ 144 {Name: "expect", Data: expect}, 145 {Name: "trace", Data: buf.Bytes()}, 146 }, 147 }) 148 } 149 150 func (t *Trace) createEvent(ev event.Type, data []byte, args ...uint64) raw.Event { 151 spec := t.specs[ev] 152 if ev != go122.EvStack { 153 if arity := len(spec.Args); len(args) != arity { 154 panic(fmt.Sprintf("expected %d args for %s, got %d", arity, spec.Name, len(args))) 155 } 156 } 157 return raw.Event{ 158 Version: version.Go122, 159 Ev: ev, 160 Args: args, 161 Data: data, 162 } 163 } 164 165 type stack struct { 166 stk [32]trace.StackFrame 167 len int 168 } 169 170 var ( 171 NoString = "" 172 NoStack = []trace.StackFrame{} 173 ) 174 175 // Generation represents a single generation in the trace. 176 type Generation struct { 177 trace *Trace 178 gen uint64 179 batches []*Batch 180 strings map[string]uint64 181 stacks map[stack]uint64 182 183 // Options applied when Trace.Generate is called. 184 ignoreStringBatchSizeLimit bool 185 ignoreStackBatchSizeLimit bool 186 } 187 188 // Batch starts a new event batch in the trace data. 189 // 190 // This is convenience function for generating correct batches. 191 func (g *Generation) Batch(thread trace.ThreadID, time Time) *Batch { 192 if !g.trace.validTimestamps { 193 time = 0 194 } 195 b := &Batch{ 196 gen: g, 197 thread: thread, 198 timestamp: time, 199 } 200 g.batches = append(g.batches, b) 201 return b 202 } 203 204 // String registers a string with the trace. 205 // 206 // This is a convenience function for easily adding correct 207 // strings to traces. 208 func (g *Generation) String(s string) uint64 { 209 if len(s) == 0 { 210 return 0 211 } 212 if id, ok := g.strings[s]; ok { 213 return id 214 } 215 id := uint64(len(g.strings) + 1) 216 g.strings[s] = id 217 return id 218 } 219 220 // Stack registers a stack with the trace. 221 // 222 // This is a convenience function for easily adding correct 223 // stacks to traces. 224 func (g *Generation) Stack(stk []trace.StackFrame) uint64 { 225 if len(stk) == 0 { 226 return 0 227 } 228 if len(stk) > 32 { 229 panic("stack too big for test") 230 } 231 var stkc stack 232 copy(stkc.stk[:], stk) 233 stkc.len = len(stk) 234 if id, ok := g.stacks[stkc]; ok { 235 return id 236 } 237 id := uint64(len(g.stacks) + 1) 238 g.stacks[stkc] = id 239 return id 240 } 241 242 // writeEventsTo emits event batches in the generation to tw. 243 func (g *Generation) writeEventsTo(tw *raw.TextWriter) { 244 // Write event batches for the generation. 245 for _, b := range g.batches { 246 b.writeEventsTo(tw) 247 } 248 249 // Write frequency. 250 b := g.newStructuralBatch() 251 b.RawEvent(go122.EvFrequency, nil, 15625000) 252 b.writeEventsTo(tw) 253 254 // Write stacks. 255 b = g.newStructuralBatch() 256 b.RawEvent(go122.EvStacks, nil) 257 for stk, id := range g.stacks { 258 stk := stk.stk[:stk.len] 259 args := []uint64{id} 260 for _, f := range stk { 261 args = append(args, f.PC, g.String(f.Func), g.String(f.File), f.Line) 262 } 263 b.RawEvent(go122.EvStack, nil, args...) 264 265 // Flush the batch if necessary. 266 if !g.ignoreStackBatchSizeLimit && b.size > go122.MaxBatchSize/2 { 267 b.writeEventsTo(tw) 268 b = g.newStructuralBatch() 269 } 270 } 271 b.writeEventsTo(tw) 272 273 // Write strings. 274 b = g.newStructuralBatch() 275 b.RawEvent(go122.EvStrings, nil) 276 for s, id := range g.strings { 277 b.RawEvent(go122.EvString, []byte(s), id) 278 279 // Flush the batch if necessary. 280 if !g.ignoreStringBatchSizeLimit && b.size > go122.MaxBatchSize/2 { 281 b.writeEventsTo(tw) 282 b = g.newStructuralBatch() 283 } 284 } 285 b.writeEventsTo(tw) 286 } 287 288 func (g *Generation) newStructuralBatch() *Batch { 289 return &Batch{gen: g, thread: trace.NoThread} 290 } 291 292 // Batch represents an event batch. 293 type Batch struct { 294 gen *Generation 295 thread trace.ThreadID 296 timestamp Time 297 size uint64 298 events []raw.Event 299 } 300 301 // Event emits an event into a batch. name must correspond to one 302 // of the names in Specs() result for the version that was passed to 303 // this trace. Callers must omit the timestamp delta. 304 func (b *Batch) Event(name string, args ...any) { 305 ev, ok := b.gen.trace.names[name] 306 if !ok { 307 panic(fmt.Sprintf("invalid or unknown event %s", name)) 308 } 309 var uintArgs []uint64 310 argOff := 0 311 if b.gen.trace.specs[ev].IsTimedEvent { 312 if b.gen.trace.validTimestamps { 313 uintArgs = []uint64{1} 314 } else { 315 uintArgs = []uint64{0} 316 } 317 argOff = 1 318 } 319 spec := b.gen.trace.specs[ev] 320 if arity := len(spec.Args) - argOff; len(args) != arity { 321 panic(fmt.Sprintf("expected %d args for %s, got %d", arity, spec.Name, len(args))) 322 } 323 for i, arg := range args { 324 uintArgs = append(uintArgs, b.uintArgFor(arg, spec.Args[i+argOff])) 325 } 326 b.RawEvent(ev, nil, uintArgs...) 327 } 328 329 func (b *Batch) uintArgFor(arg any, argSpec string) uint64 { 330 components := strings.SplitN(argSpec, "_", 2) 331 typStr := components[0] 332 if len(components) == 2 { 333 typStr = components[1] 334 } 335 var u uint64 336 switch typStr { 337 case "value": 338 u = arg.(uint64) 339 case "stack": 340 u = b.gen.Stack(arg.([]trace.StackFrame)) 341 case "seq": 342 u = uint64(arg.(Seq)) 343 case "pstatus": 344 u = uint64(arg.(go122.ProcStatus)) 345 case "gstatus": 346 u = uint64(arg.(go122.GoStatus)) 347 case "g": 348 u = uint64(arg.(trace.GoID)) 349 case "m": 350 u = uint64(arg.(trace.ThreadID)) 351 case "p": 352 u = uint64(arg.(trace.ProcID)) 353 case "string": 354 u = b.gen.String(arg.(string)) 355 case "task": 356 u = uint64(arg.(trace.TaskID)) 357 default: 358 panic(fmt.Sprintf("unsupported arg type %q for spec %q", typStr, argSpec)) 359 } 360 return u 361 } 362 363 // RawEvent emits an event into a batch. name must correspond to one 364 // of the names in Specs() result for the version that was passed to 365 // this trace. 366 func (b *Batch) RawEvent(typ event.Type, data []byte, args ...uint64) { 367 ev := b.gen.trace.createEvent(typ, data, args...) 368 369 // Compute the size of the event and add it to the batch. 370 b.size += 1 // One byte for the event header. 371 var buf [binary.MaxVarintLen64]byte 372 for _, arg := range args { 373 b.size += uint64(binary.PutUvarint(buf[:], arg)) 374 } 375 if len(data) != 0 { 376 b.size += uint64(binary.PutUvarint(buf[:], uint64(len(data)))) 377 b.size += uint64(len(data)) 378 } 379 380 // Add the event. 381 b.events = append(b.events, ev) 382 } 383 384 // writeEventsTo emits events in the batch, including the batch header, to tw. 385 func (b *Batch) writeEventsTo(tw *raw.TextWriter) { 386 tw.WriteEvent(raw.Event{ 387 Version: version.Go122, 388 Ev: go122.EvEventBatch, 389 Args: []uint64{b.gen.gen, uint64(b.thread), uint64(b.timestamp), b.size}, 390 }) 391 for _, e := range b.events { 392 tw.WriteEvent(e) 393 } 394 } 395 396 // Seq represents a sequence counter. 397 type Seq uint64 398 399 // Time represents a low-level trace timestamp (which does not necessarily 400 // correspond to nanoseconds, like trace.Time does). 401 type Time uint64