blob: d371e0c80ec9ea1d14feedccbd2c9a9c52a8d8b1 [file] [log] [blame]
// 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