Skip to content

Commit 6133c1e

Browse files
dominikhgopherbot
authored andcommitted
internal/trace/v2: support old trace format
Add support for traces from Go 1.11–1.19 by converting old traces to the Go 1.22 format on the fly. We import Gotraceui's trace parser, which is an optimized parser based on Go 1.19's internal/trace package, and further modify it for the needs of the conversion process. With the optimized parser, loading old traces using the new API is twice as fast and uses less total memory than 'go tool trace' did in older versions. The new parser does not, however, support traces from versions older than 1.11. This commit does not update cmd/trace to use the new API for old traces. Change-Id: If9380aa515e29445ff624274d1760ee945ca4816 Reviewed-on: https://go-review.googlesource.com/c/go/+/557356 Reviewed-by: Michael Knyszek <[email protected]> Auto-Submit: Michael Knyszek <[email protected]> Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 508bb17 commit 6133c1e

24 files changed

+2608
-21
lines changed

src/go/build/deps_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,10 @@ var depsRules = `
632632
FMT, encoding/binary, internal/trace/v2/version
633633
< internal/trace/v2/raw;
634634
635-
FMT, encoding/binary, internal/trace/v2/version
635+
FMT, internal/trace/v2/event, internal/trace/v2/version, io, sort, encoding/binary
636+
< internal/trace/v2/internal/oldtrace;
637+
638+
FMT, encoding/binary, internal/trace/v2/version, internal/trace/v2/internal/oldtrace
636639
< internal/trace/v2;
637640
638641
regexp, internal/trace/v2, internal/trace/v2/raw, internal/txtar

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const (
4141
EvGoSyscallBegin // syscall enter [timestamp, P seq, stack ID]
4242
EvGoSyscallEnd // syscall exit [timestamp]
4343
EvGoSyscallEndBlocked // syscall exit and it blocked at some point [timestamp]
44-
EvGoStatus // goroutine status at the start of a generation [timestamp, goroutine ID, status]
44+
EvGoStatus // goroutine status at the start of a generation [timestamp, goroutine ID, thread ID, status]
4545

4646
// STW.
4747
EvSTWBegin // STW start [timestamp, kind]
@@ -66,7 +66,7 @@ const (
6666
EvUserTaskEnd // end of a task [timestamp, internal task ID, stack ID]
6767
EvUserRegionBegin // trace.{Start,With}Region [timestamp, internal task ID, name string ID, stack ID]
6868
EvUserRegionEnd // trace.{End,With}Region [timestamp, internal task ID, name string ID, stack ID]
69-
EvUserLog // trace.Log [timestamp, internal task ID, key string ID, stack, value string ID]
69+
EvUserLog // trace.Log [timestamp, internal task ID, key string ID, value string ID, stack]
7070
)
7171

7272
// EventString returns the name of a Go 1.22 event.
@@ -108,7 +108,7 @@ var specs = [...]event.Spec{
108108
},
109109
EvCPUSample: event.Spec{
110110
Name: "CPUSample",
111-
Args: []string{"time", "p", "g", "m", "stack"},
111+
Args: []string{"time", "m", "p", "g", "stack"},
112112
// N.B. There's clearly a timestamp here, but these Events
113113
// are special in that they don't appear in the regular
114114
// M streams.

src/internal/trace/v2/generation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ func addStacks(stackTable *dataTable[stackID, stack], pcs map[uint64]frame, b ba
328328
// sample contained therein to the provided samples list.
329329
func addCPUSamples(samples []cpuSample, b batch) ([]cpuSample, error) {
330330
if !b.isCPUSamplesBatch() {
331-
return nil, fmt.Errorf("internal error: addStrings called on non-string batch")
331+
return nil, fmt.Errorf("internal error: addCPUSamples called on non-CPU-sample batch")
332332
}
333333
r := bytes.NewReader(b.data)
334334
hdr, err := r.ReadByte() // Consume the EvCPUSamples byte.
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2024 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 oldtrace
6+
7+
import "errors"
8+
9+
type orderEvent struct {
10+
ev Event
11+
proc *proc
12+
}
13+
14+
type gStatus int
15+
16+
type gState struct {
17+
seq uint64
18+
status gStatus
19+
}
20+
21+
const (
22+
gDead gStatus = iota
23+
gRunnable
24+
gRunning
25+
gWaiting
26+
27+
unordered = ^uint64(0)
28+
garbage = ^uint64(0) - 1
29+
noseq = ^uint64(0)
30+
seqinc = ^uint64(0) - 1
31+
)
32+
33+
// stateTransition returns goroutine state (sequence and status) when the event
34+
// becomes ready for merging (init) and the goroutine state after the event (next).
35+
func stateTransition(ev *Event) (g uint64, init, next gState) {
36+
// Note that we have an explicit return in each case, as that produces slightly better code (tested on Go 1.19).
37+
38+
switch ev.Type {
39+
case EvGoCreate:
40+
g = ev.Args[0]
41+
init = gState{0, gDead}
42+
next = gState{1, gRunnable}
43+
return
44+
case EvGoWaiting, EvGoInSyscall:
45+
g = ev.G
46+
init = gState{1, gRunnable}
47+
next = gState{2, gWaiting}
48+
return
49+
case EvGoStart, EvGoStartLabel:
50+
g = ev.G
51+
init = gState{ev.Args[1], gRunnable}
52+
next = gState{ev.Args[1] + 1, gRunning}
53+
return
54+
case EvGoStartLocal:
55+
// noseq means that this event is ready for merging as soon as
56+
// frontier reaches it (EvGoStartLocal is emitted on the same P
57+
// as the corresponding EvGoCreate/EvGoUnblock, and thus the latter
58+
// is already merged).
59+
// seqinc is a stub for cases when event increments g sequence,
60+
// but since we don't know current seq we also don't know next seq.
61+
g = ev.G
62+
init = gState{noseq, gRunnable}
63+
next = gState{seqinc, gRunning}
64+
return
65+
case EvGoBlock, EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
66+
EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoSleep,
67+
EvGoSysBlock, EvGoBlockGC:
68+
g = ev.G
69+
init = gState{noseq, gRunning}
70+
next = gState{noseq, gWaiting}
71+
return
72+
case EvGoSched, EvGoPreempt:
73+
g = ev.G
74+
init = gState{noseq, gRunning}
75+
next = gState{noseq, gRunnable}
76+
return
77+
case EvGoUnblock, EvGoSysExit:
78+
g = ev.Args[0]
79+
init = gState{ev.Args[1], gWaiting}
80+
next = gState{ev.Args[1] + 1, gRunnable}
81+
return
82+
case EvGoUnblockLocal, EvGoSysExitLocal:
83+
g = ev.Args[0]
84+
init = gState{noseq, gWaiting}
85+
next = gState{seqinc, gRunnable}
86+
return
87+
case EvGCStart:
88+
g = garbage
89+
init = gState{ev.Args[0], gDead}
90+
next = gState{ev.Args[0] + 1, gDead}
91+
return
92+
default:
93+
// no ordering requirements
94+
g = unordered
95+
return
96+
}
97+
}
98+
99+
func transitionReady(g uint64, curr, init gState) bool {
100+
return g == unordered || (init.seq == noseq || init.seq == curr.seq) && init.status == curr.status
101+
}
102+
103+
func transition(gs map[uint64]gState, g uint64, init, next gState) error {
104+
if g == unordered {
105+
return nil
106+
}
107+
curr := gs[g]
108+
if !transitionReady(g, curr, init) {
109+
// See comment near the call to transition, where we're building the frontier, for details on how this could
110+
// possibly happen.
111+
return errors.New("encountered impossible goroutine state transition")
112+
}
113+
switch next.seq {
114+
case noseq:
115+
next.seq = curr.seq
116+
case seqinc:
117+
next.seq = curr.seq + 1
118+
}
119+
gs[g] = next
120+
return nil
121+
}
122+
123+
type orderEventList []orderEvent
124+
125+
func (l *orderEventList) Less(i, j int) bool {
126+
return (*l)[i].ev.Ts < (*l)[j].ev.Ts
127+
}
128+
129+
type eventList []Event
130+
131+
func (l *eventList) Len() int {
132+
return len(*l)
133+
}
134+
135+
func (l *eventList) Less(i, j int) bool {
136+
return (*l)[i].Ts < (*l)[j].Ts
137+
}
138+
139+
func (l *eventList) Swap(i, j int) {
140+
(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
141+
}
142+
143+
func (h *orderEventList) Push(x orderEvent) {
144+
*h = append(*h, x)
145+
heapUp(h, len(*h)-1)
146+
}
147+
148+
func (h *orderEventList) Pop() orderEvent {
149+
n := len(*h) - 1
150+
(*h)[0], (*h)[n] = (*h)[n], (*h)[0]
151+
heapDown(h, 0, n)
152+
x := (*h)[len(*h)-1]
153+
*h = (*h)[:len(*h)-1]
154+
return x
155+
}
156+
157+
func heapUp(h *orderEventList, j int) {
158+
for {
159+
i := (j - 1) / 2 // parent
160+
if i == j || !h.Less(j, i) {
161+
break
162+
}
163+
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
164+
j = i
165+
}
166+
}
167+
168+
func heapDown(h *orderEventList, i0, n int) bool {
169+
i := i0
170+
for {
171+
j1 := 2*i + 1
172+
if j1 >= n || j1 < 0 { // j1 < 0 after int overflow
173+
break
174+
}
175+
j := j1 // left child
176+
if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
177+
j = j2 // = 2*i + 2 // right child
178+
}
179+
if !h.Less(j, i) {
180+
break
181+
}
182+
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
183+
i = j
184+
}
185+
return i > i0
186+
}

0 commit comments

Comments
 (0)