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