Trace experiments

Execution traces allow for trialing new events on an experimental basis via trace experiments. This document is a guide that explains how you can define your own trace experiments.

Note that if you’re just trying to do some debugging or perform some light instrumentation, then a trace experiment is way overkill. Use runtime/trace.Log instead. Even if you’re just trying to create a proof-of-concept for a low-frequency event, runtime/trace.Log will probably be easier overall if you can make it work.

Consider a trace experiment if:

Defining a new experiment

To define a new experiment, modify internal/trace/tracev2 to define a new Experiment enum value.

An experiment consists of two parts: timed events and experimental batches. Timed events are events like any other and follow the same format. They are easier to order and require less work to make use of. Experimental batches are essentially bags of bytes that correspond to an entire trace generation. What they contain and how they’re interpreted is totally up to you, but they’re most often useful for tables that your other events can refer into. For example, the AllocFree experiment uses them to store type information that allocation events can refer to.

Defining new events

  1. Define your new experiment event types (by convention, experimental events types start at ID 127, so look for the const block defining events starting there).
  2. Describe your new events in specs. Use the documentation for Spec to write your new specs, and check your work by running the tests in the internal/trace/tracev2 package. If you wish for your event argument to be interpreted in a particular way, follow the naming convention in src/internal/trace/tracev2/spec.go. For example, if you intend to emit a string argument, make sure the argument name has the suffix string.
  3. Add ordering and validation logic for your new events to src/internal/trace/order.go by listing handlers for those events in the orderingDispatch table. If your events are always emitted in a regular user goroutine context, then the handler should be trivial and just validate the scheduling context to match userGoReqs. If it’s more complicated, see (*ordering).advanceAllocFree for a slightly more complicated example that handles events from a larger variety of execution environments. If you need to encode a partial ordering, look toward the scheduler events (names beginning with Go) or just ask someone for help.
  4. Add your new events to the tracev2Type2Kind table in src/internal/trace/event.go.

Emitting data

Emitting your new events

  1. Define helper methods on runtime.traceEventWriter for emitting your events.
  2. Instrument the runtime with calls to these helper methods. Make sure to call traceAcquire and traceRelease around the operation your event represents, otherwise it will not be emitted atomically with that operation completing, resulting in a potentially misleading trace.

Emitting experimental batches

To emit experimental batches, use the runtime.unsafeTraceExpWriter to write experimental batches associated with your experiment. Heed the warnings and make sure that while you write them, the trace generation cannot advance. Note that each experiment can only have one distinguishable set of batches.

Recovering experimental data

Recovering experimental events from the trace

Experimental events will appear in the event stream as an event with the EventExperimental Kind. Use the Experimental method to collect the raw data inserted into the trace. It’s essentially up to you to interpret the event from here. I recommend writing a thin wrapper API to present a cleaner interface if you so desire.

Recovering experimental batches

Parse out all the experimental batches from Sync events as they come. These experimental batches are all for the same generation as all the experimental events up until the next Sync event.