Skip to content

Commit ca1cfea

Browse files
mknyszekgopherbot
authored andcommitted
internal/trace: refactor how experimental batches are exposed
This change modifies how per-generation experimental batches are exposed. Rather than expose them on the ExperimentalEvent, it exposes it as part of the Sync event, so it's clear to the caller when the information becomes relevant and when it should be parsed. This change also adds a field to each ExperimentalEvent indicating which experiment the event is a part of. Because this information needs to appear *before* a generation is observed, we now ensure there is a sync event both before and after each generation. This means the final sync event is now a special case; previously we would only emit a sync event after each generation. This change is based on feedback from Austin Clements on the experimental events functionality. For #62627. Change-Id: I48b0fe12b22abb7ac8820a9e73447bfed8419856 Reviewed-on: https://go-review.googlesource.com/c/go/+/644215 Auto-Submit: Michael Knyszek <[email protected]> Reviewed-by: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent d7f6f6f commit ca1cfea

File tree

10 files changed

+141
-82
lines changed

10 files changed

+141
-82
lines changed

src/internal/trace/base.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,8 @@ type evTable struct {
5858
extraStringIDs map[string]extraStringID
5959
nextExtra extraStringID
6060

61-
// expData contains extra unparsed data that is accessible
62-
// only to ExperimentEvent via an EventExperimental event.
63-
expData map[event.Experiment]*ExperimentalData
61+
// expBatches contains extra unparsed data relevant to a specific experiment.
62+
expBatches map[event.Experiment][]ExperimentalBatch
6463
}
6564

6665
// addExtraString adds an extra string to the evTable and returns

src/internal/trace/event.go

+40-20
Original file line numberDiff line numberDiff line change
@@ -313,26 +313,15 @@ type ExperimentalEvent struct {
313313
// Name is the name of the event.
314314
Name string
315315

316+
// Experiment is the name of the experiment this event is a part of.
317+
Experiment string
318+
316319
// ArgNames is the names of the event's arguments in order.
317320
// This may refer to a globally shared slice. Copy before mutating.
318321
ArgNames []string
319322

320323
// Args contains the event's arguments.
321324
Args []uint64
322-
323-
// Data is additional unparsed data that is associated with the experimental event.
324-
// Data is likely to be shared across many ExperimentalEvents, so callers that parse
325-
// Data are encouraged to cache the parse result and look it up by the value of Data.
326-
Data *ExperimentalData
327-
}
328-
329-
// ExperimentalData represents some raw and unparsed sidecar data present in the trace that is
330-
// associated with certain kinds of experimental events. For example, this data may contain
331-
// tables needed to interpret ExperimentalEvent arguments, or the ExperimentEvent could just be
332-
// a placeholder for a differently encoded event that's actually present in the experimental data.
333-
type ExperimentalData struct {
334-
// Batches contain the actual experimental data, along with metadata about each batch.
335-
Batches []ExperimentalBatch
336325
}
337326

338327
// ExperimentalBatch represents a packet of unparsed data along with metadata about that packet.
@@ -658,6 +647,35 @@ func (e Event) StateTransition() StateTransition {
658647
return s
659648
}
660649

650+
// Sync returns details that are relevant for the following events, up to but excluding the
651+
// next EventSync event.
652+
func (e Event) Sync() Sync {
653+
if e.Kind() != EventSync {
654+
panic("Sync called on non-Sync event")
655+
}
656+
var expBatches map[string][]ExperimentalBatch
657+
if e.table != nil {
658+
expBatches = make(map[string][]ExperimentalBatch)
659+
for exp, batches := range e.table.expBatches {
660+
expBatches[go122.Experiments()[exp]] = batches
661+
}
662+
}
663+
return Sync{
664+
N: int(e.base.args[0]),
665+
ExperimentalBatches: expBatches,
666+
}
667+
}
668+
669+
// Sync contains details potentially relevant to all the following events, up to but excluding
670+
// the next EventSync event.
671+
type Sync struct {
672+
// N indicates that this is the Nth sync event in the trace.
673+
N int
674+
675+
// ExperimentalBatches contain all the unparsed batches of data for a given experiment.
676+
ExperimentalBatches map[string][]ExperimentalBatch
677+
}
678+
661679
// Experimental returns a view of the raw event for an experimental event.
662680
//
663681
// Panics if Kind != EventExperimental.
@@ -668,10 +686,10 @@ func (e Event) Experimental() ExperimentalEvent {
668686
spec := go122.Specs()[e.base.typ]
669687
argNames := spec.Args[1:] // Skip timestamp; already handled.
670688
return ExperimentalEvent{
671-
Name: spec.Name,
672-
ArgNames: argNames,
673-
Args: e.base.args[:len(argNames)],
674-
Data: e.table.expData[spec.Experiment],
689+
Name: spec.Name,
690+
Experiment: go122.Experiments()[spec.Experiment],
691+
ArgNames: argNames,
692+
Args: e.base.args[:len(argNames)],
675693
}
676694
}
677695

@@ -848,8 +866,8 @@ func (e Event) validateTableIDs() error {
848866
return nil
849867
}
850868

851-
func syncEvent(table *evTable, ts Time) Event {
852-
return Event{
869+
func syncEvent(table *evTable, ts Time, n int) Event {
870+
ev := Event{
853871
table: table,
854872
ctx: schedCtx{
855873
G: NoGoroutine,
@@ -861,4 +879,6 @@ func syncEvent(table *evTable, ts Time) Event {
861879
time: ts,
862880
},
863881
}
882+
ev.base.args[0] = uint64(n)
883+
return ev
864884
}

src/internal/trace/event/go122/event.go

+8
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ const (
8686
AllocFree event.Experiment = 1 + iota
8787
)
8888

89+
func Experiments() []string {
90+
return experiments[:]
91+
}
92+
93+
var experiments = [...]string{
94+
AllocFree: "AllocFree",
95+
}
96+
8997
// Experimental events.
9098
const (
9199
_ event.Type = 127 + iota

src/internal/trace/event_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import "testing"
88

99
func TestPanicEvent(t *testing.T) {
1010
// Use a sync event for this because it doesn't have any extra metadata.
11-
ev := syncEvent(nil, 0)
11+
ev := syncEvent(nil, 0, 0)
1212

1313
mustPanic(t, func() {
1414
_ = ev.Range()

src/internal/trace/gc.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func MutatorUtilizationV2(events []Event, flags UtilFlags) [][]MutatorUtil {
7575
states := make(map[GoID]GoState)
7676
bgMark := make(map[GoID]bool)
7777
procs := []procsCount{}
78-
seenSync := false
78+
nSync := 0
7979

8080
// Helpers.
8181
handleSTW := func(r Range) bool {
@@ -97,7 +97,7 @@ func MutatorUtilizationV2(events []Event, flags UtilFlags) [][]MutatorUtil {
9797
// Process the event.
9898
switch ev.Kind() {
9999
case EventSync:
100-
seenSync = true
100+
nSync = ev.Sync().N
101101
case EventMetric:
102102
m := ev.Metric()
103103
if m.Name != "/sched/gomaxprocs:threads" {
@@ -135,9 +135,9 @@ func MutatorUtilizationV2(events []Event, flags UtilFlags) [][]MutatorUtil {
135135

136136
switch ev.Kind() {
137137
case EventRangeActive:
138-
if seenSync {
139-
// If we've seen a sync, then we can be sure we're not finding out about
140-
// something late; we have complete information after that point, and these
138+
if nSync > 1 {
139+
// If we've seen a full generation, then we can be sure we're not finding out
140+
// about something late; we have complete information after that point, and these
141141
// active events will just be redundant.
142142
break
143143
}

src/internal/trace/generation.go

+12-13
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type generation struct {
2727
batches map[ThreadID][]batch
2828
batchMs []ThreadID
2929
cpuSamples []cpuSample
30+
minTs timestamp
3031
*evTable
3132
}
3233

@@ -100,6 +101,9 @@ func readGeneration(r *bufio.Reader, spill *spilledBatch) (*generation, *spilled
100101
// problem as soon as we see it.
101102
return nil, nil, fmt.Errorf("generations out of order")
102103
}
104+
if g.minTs == 0 || b.time < g.minTs {
105+
g.minTs = b.time
106+
}
103107
if err := processBatch(g, b); err != nil {
104108
return nil, nil, err
105109
}
@@ -163,10 +167,10 @@ func processBatch(g *generation, b batch) error {
163167
}
164168
g.freq = freq
165169
case b.exp != event.NoExperiment:
166-
if g.expData == nil {
167-
g.expData = make(map[event.Experiment]*ExperimentalData)
170+
if g.expBatches == nil {
171+
g.expBatches = make(map[event.Experiment][]ExperimentalBatch)
168172
}
169-
if err := addExperimentalData(g.expData, b); err != nil {
173+
if err := addExperimentalBatch(g.expBatches, b); err != nil {
170174
return err
171175
}
172176
default:
@@ -435,18 +439,13 @@ func parseFreq(b batch) (frequency, error) {
435439
return frequency(1.0 / (float64(f) / 1e9)), nil
436440
}
437441

438-
// addExperimentalData takes an experimental batch and adds it to the ExperimentalData
439-
// for the experiment its a part of.
440-
func addExperimentalData(expData map[event.Experiment]*ExperimentalData, b batch) error {
442+
// addExperimentalBatch takes an experimental batch and adds it to the list of experimental
443+
// batches for the experiment its a part of.
444+
func addExperimentalBatch(expBatches map[event.Experiment][]ExperimentalBatch, b batch) error {
441445
if b.exp == event.NoExperiment {
442-
return fmt.Errorf("internal error: addExperimentalData called on non-experimental batch")
443-
}
444-
ed, ok := expData[b.exp]
445-
if !ok {
446-
ed = new(ExperimentalData)
447-
expData[b.exp] = ed
446+
return fmt.Errorf("internal error: addExperimentalBatch called on non-experimental batch")
448447
}
449-
ed.Batches = append(ed.Batches, ExperimentalBatch{
448+
expBatches[b.exp] = append(expBatches[b.exp], ExperimentalBatch{
450449
Thread: b.m,
451450
Data: b.data,
452451
})

src/internal/trace/internal/oldtrace/parser.go

+8
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,14 @@ func (l *Events) Pop() (*Event, bool) {
343343
return ptr, true
344344
}
345345

346+
func (l *Events) Peek() (*Event, bool) {
347+
if l.off == l.n {
348+
return nil, false
349+
}
350+
a, b := l.index(l.off)
351+
return &l.buckets[a][b], true
352+
}
353+
346354
func (l *Events) All() func(yield func(ev *Event) bool) {
347355
return func(yield func(ev *Event) bool) {
348356
for i := 0; i < l.Len(); i++ {

src/internal/trace/reader.go

+45-24
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,21 @@ import (
1717
)
1818

1919
// Reader reads a byte stream, validates it, and produces trace events.
20+
//
21+
// Provided the trace is non-empty the Reader always produces a Sync
22+
// event as the first event, and a Sync event as the last event.
23+
// (There may also be any number of Sync events in the middle, too.)
2024
type Reader struct {
21-
r *bufio.Reader
22-
lastTs Time
23-
gen *generation
24-
spill *spilledBatch
25-
spillErr error // error from reading spill
26-
frontier []*batchCursor
27-
cpuSamples []cpuSample
28-
order ordering
29-
emittedSync bool
25+
r *bufio.Reader
26+
lastTs Time
27+
gen *generation
28+
spill *spilledBatch
29+
spillErr error // error from reading spill
30+
frontier []*batchCursor
31+
cpuSamples []cpuSample
32+
order ordering
33+
syncs int
34+
done bool
3035

3136
go121Events *oldTraceConverter
3237
}
@@ -56,8 +61,6 @@ func NewReader(r io.Reader) (*Reader, error) {
5661
gStates: make(map[GoID]*gState),
5762
activeTasks: make(map[TaskID]taskState),
5863
},
59-
// Don't emit a sync event when we first go to emit events.
60-
emittedSync: true,
6164
}, nil
6265
default:
6366
return nil, fmt.Errorf("unknown or unsupported version go 1.%d", v)
@@ -66,13 +69,30 @@ func NewReader(r io.Reader) (*Reader, error) {
6669

6770
// ReadEvent reads a single event from the stream.
6871
//
69-
// If the stream has been exhausted, it returns an invalid
70-
// event and io.EOF.
72+
// If the stream has been exhausted, it returns an invalid event and io.EOF.
7173
func (r *Reader) ReadEvent() (e Event, err error) {
74+
// Return only io.EOF if we're done.
75+
if r.done {
76+
return Event{}, io.EOF
77+
}
78+
79+
// Handle old execution traces.
7280
if r.go121Events != nil {
81+
if r.syncs == 0 {
82+
// Always emit a sync event first, if we have any events at all.
83+
ev, ok := r.go121Events.events.Peek()
84+
if ok {
85+
r.syncs++
86+
return syncEvent(r.go121Events.evt, Time(ev.Ts-1), r.syncs), nil
87+
}
88+
}
7389
ev, err := r.go121Events.next()
74-
if err != nil {
75-
// XXX do we have to emit an EventSync when the trace is done?
90+
if err == io.EOF {
91+
// Always emit a sync event at the end.
92+
r.done = true
93+
r.syncs++
94+
return syncEvent(nil, r.go121Events.lastTs+1, r.syncs), nil
95+
} else if err != nil {
7696
return Event{}, err
7797
}
7898
return ev, nil
@@ -115,10 +135,6 @@ func (r *Reader) ReadEvent() (e Event, err error) {
115135

116136
// Check if we need to refresh the generation.
117137
if len(r.frontier) == 0 && len(r.cpuSamples) == 0 {
118-
if !r.emittedSync {
119-
r.emittedSync = true
120-
return syncEvent(r.gen.evTable, r.lastTs), nil
121-
}
122138
if r.spillErr != nil {
123139
return Event{}, r.spillErr
124140
}
@@ -127,8 +143,10 @@ func (r *Reader) ReadEvent() (e Event, err error) {
127143
// and there's nothing left in the frontier, and
128144
// there's no spilled batch, indicating that there's
129145
// no further generation, it means we're done.
130-
// Return io.EOF.
131-
return Event{}, io.EOF
146+
// Emit the final sync event.
147+
r.done = true
148+
r.syncs++
149+
return syncEvent(nil, r.lastTs, r.syncs), nil
132150
}
133151
// Read the next generation.
134152
var err error
@@ -155,9 +173,12 @@ func (r *Reader) ReadEvent() (e Event, err error) {
155173
}
156174
r.frontier = heapInsert(r.frontier, bc)
157175
}
158-
159-
// Reset emittedSync.
160-
r.emittedSync = false
176+
r.syncs++
177+
if r.lastTs == 0 {
178+
r.lastTs = r.gen.freq.mul(r.gen.minTs)
179+
}
180+
// Always emit a sync event at the beginning of the generation.
181+
return syncEvent(r.gen.evTable, r.lastTs, r.syncs), nil
161182
}
162183
tryAdvance := func(i int) (bool, error) {
163184
bc := r.frontier[i]

0 commit comments

Comments
 (0)