// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Code generated by "gen.bash" from internal/trace/v2; DO NOT EDIT. //go:build go1.21 package testkit import ( "bytes" "encoding/binary" "fmt" "os" "regexp" "strings" "golang.org/x/exp/trace" "golang.org/x/exp/trace/internal/event" "golang.org/x/exp/trace/internal/event/go122" "golang.org/x/exp/trace/internal/raw" "golang.org/x/exp/trace/internal/version" "golang.org/x/tools/txtar" ) func Main(f func(*Trace)) { // Create an output file. out, err := os.Create(os.Args[1]) if err != nil { panic(err.Error()) } defer out.Close() // Create a new trace. trace := NewTrace() // Call the generator. f(trace) // Write out the generator's state. if _, err := out.Write(trace.Generate()); err != nil { panic(err.Error()) } } // Trace represents an execution trace for testing. // // It does a little bit of work to ensure that the produced trace is valid, // just for convenience. It mainly tracks batches and batch sizes (so they're // trivially correct), tracks strings and stacks, and makes sure emitted string // and stack batches are valid. That last part can be controlled by a few options. // // Otherwise, it performs no validation on the trace at all. type Trace struct { // Trace data state. ver version.Version names map[string]event.Type specs []event.Spec events []raw.Event gens []*Generation validTimestamps bool // Expectation state. bad bool badMatch *regexp.Regexp } // NewTrace creates a new trace. func NewTrace() *Trace { ver := version.Go122 return &Trace{ names: event.Names(ver.Specs()), specs: ver.Specs(), validTimestamps: true, } } // ExpectFailure writes down that the trace should be broken. The caller // must provide a pattern matching the expected error produced by the parser. func (t *Trace) ExpectFailure(pattern string) { t.bad = true t.badMatch = regexp.MustCompile(pattern) } // ExpectSuccess writes down that the trace should successfully parse. func (t *Trace) ExpectSuccess() { t.bad = false } // RawEvent emits an event into the trace. name must correspond to one // of the names in Specs() result for the version that was passed to // this trace. func (t *Trace) RawEvent(typ event.Type, data []byte, args ...uint64) { t.events = append(t.events, t.createEvent(typ, data, args...)) } // DisableTimestamps makes the timestamps for all events generated after // this call zero. Raw events are exempted from this because the caller // has to pass their own timestamp into those events anyway. func (t *Trace) DisableTimestamps() { t.validTimestamps = false } // Generation creates a new trace generation. // // This provides more structure than Event to allow for more easily // creating complex traces that are mostly or completely correct. func (t *Trace) Generation(gen uint64) *Generation { g := &Generation{ trace: t, gen: gen, strings: make(map[string]uint64), stacks: make(map[stack]uint64), } t.gens = append(t.gens, g) return g } // Generate creates a test file for the trace. func (t *Trace) Generate() []byte { // Trace file contents. var buf bytes.Buffer tw, err := raw.NewTextWriter(&buf, version.Go122) if err != nil { panic(err.Error()) } // Write raw top-level events. for _, e := range t.events { tw.WriteEvent(e) } // Write generations. for _, g := range t.gens { g.writeEventsTo(tw) } // Expectation file contents. expect := []byte("SUCCESS\n") if t.bad { expect = []byte(fmt.Sprintf("FAILURE %q\n", t.badMatch)) } // Create the test file's contents. return txtar.Format(&txtar.Archive{ Files: []txtar.File{ {Name: "expect", Data: expect}, {Name: "trace", Data: buf.Bytes()}, }, }) } func (t *Trace) createEvent(ev event.Type, data []byte, args ...uint64) raw.Event { spec := t.specs[ev] if ev != go122.EvStack { if arity := len(spec.Args); len(args) != arity { panic(fmt.Sprintf("expected %d args for %s, got %d", arity, spec.Name, len(args))) } } return raw.Event{ Version: version.Go122, Ev: ev, Args: args, Data: data, } } type stack struct { stk [32]trace.StackFrame len int } var ( NoString = "" NoStack = []trace.StackFrame{} ) // Generation represents a single generation in the trace. type Generation struct { trace *Trace gen uint64 batches []*Batch strings map[string]uint64 stacks map[stack]uint64 // Options applied when Trace.Generate is called. ignoreStringBatchSizeLimit bool ignoreStackBatchSizeLimit bool } // Batch starts a new event batch in the trace data. // // This is convenience function for generating correct batches. func (g *Generation) Batch(thread trace.ThreadID, time Time) *Batch { if !g.trace.validTimestamps { time = 0 } b := &Batch{ gen: g, thread: thread, timestamp: time, } g.batches = append(g.batches, b) return b } // String registers a string with the trace. // // This is a convenience function for easily adding correct // strings to traces. func (g *Generation) String(s string) uint64 { if len(s) == 0 { return 0 } if id, ok := g.strings[s]; ok { return id } id := uint64(len(g.strings) + 1) g.strings[s] = id return id } // Stack registers a stack with the trace. // // This is a convenience function for easily adding correct // stacks to traces. func (g *Generation) Stack(stk []trace.StackFrame) uint64 { if len(stk) == 0 { return 0 } if len(stk) > 32 { panic("stack too big for test") } var stkc stack copy(stkc.stk[:], stk) stkc.len = len(stk) if id, ok := g.stacks[stkc]; ok { return id } id := uint64(len(g.stacks) + 1) g.stacks[stkc] = id return id } // writeEventsTo emits event batches in the generation to tw. func (g *Generation) writeEventsTo(tw *raw.TextWriter) { // Write event batches for the generation. for _, b := range g.batches { b.writeEventsTo(tw) } // Write frequency. b := g.newStructuralBatch() b.RawEvent(go122.EvFrequency, nil, 15625000) b.writeEventsTo(tw) // Write stacks. b = g.newStructuralBatch() b.RawEvent(go122.EvStacks, nil) for stk, id := range g.stacks { stk := stk.stk[:stk.len] args := []uint64{id} for _, f := range stk { args = append(args, f.PC, g.String(f.Func), g.String(f.File), f.Line) } b.RawEvent(go122.EvStack, nil, args...) // Flush the batch if necessary. if !g.ignoreStackBatchSizeLimit && b.size > go122.MaxBatchSize/2 { b.writeEventsTo(tw) b = g.newStructuralBatch() } } b.writeEventsTo(tw) // Write strings. b = g.newStructuralBatch() b.RawEvent(go122.EvStrings, nil) for s, id := range g.strings { b.RawEvent(go122.EvString, []byte(s), id) // Flush the batch if necessary. if !g.ignoreStringBatchSizeLimit && b.size > go122.MaxBatchSize/2 { b.writeEventsTo(tw) b = g.newStructuralBatch() } } b.writeEventsTo(tw) } func (g *Generation) newStructuralBatch() *Batch { return &Batch{gen: g, thread: trace.NoThread} } // Batch represents an event batch. type Batch struct { gen *Generation thread trace.ThreadID timestamp Time size uint64 events []raw.Event } // Event emits an event into a batch. name must correspond to one // of the names in Specs() result for the version that was passed to // this trace. Callers must omit the timestamp delta. func (b *Batch) Event(name string, args ...any) { ev, ok := b.gen.trace.names[name] if !ok { panic(fmt.Sprintf("invalid or unknown event %s", name)) } var uintArgs []uint64 argOff := 0 if b.gen.trace.specs[ev].IsTimedEvent { if b.gen.trace.validTimestamps { uintArgs = []uint64{1} } else { uintArgs = []uint64{0} } argOff = 1 } spec := b.gen.trace.specs[ev] if arity := len(spec.Args) - argOff; len(args) != arity { panic(fmt.Sprintf("expected %d args for %s, got %d", arity, spec.Name, len(args))) } for i, arg := range args { uintArgs = append(uintArgs, b.uintArgFor(arg, spec.Args[i+argOff])) } b.RawEvent(ev, nil, uintArgs...) } func (b *Batch) uintArgFor(arg any, argSpec string) uint64 { components := strings.SplitN(argSpec, "_", 2) typStr := components[0] if len(components) == 2 { typStr = components[1] } var u uint64 switch typStr { case "value": u = arg.(uint64) case "stack": u = b.gen.Stack(arg.([]trace.StackFrame)) case "seq": u = uint64(arg.(Seq)) case "pstatus": u = uint64(arg.(go122.ProcStatus)) case "gstatus": u = uint64(arg.(go122.GoStatus)) case "g": u = uint64(arg.(trace.GoID)) case "m": u = uint64(arg.(trace.ThreadID)) case "p": u = uint64(arg.(trace.ProcID)) case "string": u = b.gen.String(arg.(string)) case "task": u = uint64(arg.(trace.TaskID)) default: panic(fmt.Sprintf("unsupported arg type %q for spec %q", typStr, argSpec)) } return u } // RawEvent emits an event into a batch. name must correspond to one // of the names in Specs() result for the version that was passed to // this trace. func (b *Batch) RawEvent(typ event.Type, data []byte, args ...uint64) { ev := b.gen.trace.createEvent(typ, data, args...) // Compute the size of the event and add it to the batch. b.size += 1 // One byte for the event header. var buf [binary.MaxVarintLen64]byte for _, arg := range args { b.size += uint64(binary.PutUvarint(buf[:], arg)) } if len(data) != 0 { b.size += uint64(binary.PutUvarint(buf[:], uint64(len(data)))) b.size += uint64(len(data)) } // Add the event. b.events = append(b.events, ev) } // writeEventsTo emits events in the batch, including the batch header, to tw. func (b *Batch) writeEventsTo(tw *raw.TextWriter) { tw.WriteEvent(raw.Event{ Version: version.Go122, Ev: go122.EvEventBatch, Args: []uint64{b.gen.gen, uint64(b.thread), uint64(b.timestamp), b.size}, }) for _, e := range b.events { tw.WriteEvent(e) } } // Seq represents a sequence counter. type Seq uint64 // Time represents a low-level trace timestamp (which does not necessarily // correspond to nanoseconds, like trace.Time does). type Time uint64