Skip to content

Commit 2c0a988

Browse files
rhyshmknyszek
authored andcommitted
runtime: add CPU samples to execution trace
When the CPU profiler and execution tracer are both active, report the CPU profile samples in the execution trace data stream. Include only samples that arrive on the threads known to the runtime, but include them even when running g0 (such as near the scheduler) or if there's no P (such as near syscalls). Render them in "go tool trace" as instantaneous events. For #16895 Change-Id: I0aa501a7b450c971e510961c0290838729033f7f Reviewed-on: https://go-review.googlesource.com/c/go/+/400795 Reviewed-by: Michael Knyszek <[email protected]> Run-TryBot: Rhys Hiltner <[email protected]> Reviewed-by: David Chase <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 52bd1c4 commit 2c0a988

File tree

8 files changed

+400
-16
lines changed

8 files changed

+400
-16
lines changed

src/cmd/trace/trace.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ func init() {
3030
http.Handle("/static/", http.FileServer(http.FS(staticContent)))
3131
}
3232

33-
3433
// httpTrace serves either whole trace (goid==0) or trace for goid goroutine.
3534
func httpTrace(w http.ResponseWriter, r *http.Request) {
3635
_, err := parseTrace()
@@ -719,6 +718,11 @@ func generateTrace(params *traceParams, consumer traceConsumer) error {
719718
ctx.emitInstant(ev, "task start", "user event")
720719
case trace.EvUserTaskEnd:
721720
ctx.emitInstant(ev, "task end", "user event")
721+
case trace.EvCPUSample:
722+
if ev.P >= 0 {
723+
// only show in this UI when there's an associated P
724+
ctx.emitInstant(ev, "CPU profile sample", "")
725+
}
722726
}
723727
// Emit any counter updates.
724728
ctx.emitThreadCounters(ev)

src/internal/trace/order.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ const (
5252
// incorrect (condition observed on some machines).
5353
func order1007(m map[int][]*Event) (events []*Event, err error) {
5454
pending := 0
55+
// The ordering of CPU profile sample events in the data stream is based on
56+
// when each run of the signal handler was able to acquire the spinlock,
57+
// with original timestamps corresponding to when ReadTrace pulled the data
58+
// off of the profBuf queue. Re-sort them by the timestamp we captured
59+
// inside the signal handler.
60+
sort.Stable(eventList(m[ProfileP]))
5561
var batches []*eventBatch
5662
for _, v := range m {
5763
pending += len(v)

src/internal/trace/parser.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const (
7575
NetpollP // depicts network unblocks
7676
SyscallP // depicts returns from syscalls
7777
GCP // depicts GC state
78+
ProfileP // depicts recording of CPU profile samples
7879
)
7980

8081
// ParseResult is the result of Parse.
@@ -150,7 +151,7 @@ func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]stri
150151
return
151152
}
152153
switch ver {
153-
case 1005, 1007, 1008, 1009, 1010, 1011:
154+
case 1005, 1007, 1008, 1009, 1010, 1011, 1019:
154155
// Note: When adding a new version, add canned traces
155156
// from the old version to the test suite using mkcanned.bash.
156157
break
@@ -448,8 +449,27 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even
448449
case EvUserLog:
449450
// e.Args 0: taskID, 1:keyID, 2: stackID
450451
e.SArgs = []string{strings[e.Args[1]], raw.sargs[0]}
452+
case EvCPUSample:
453+
e.Ts = int64(e.Args[0])
454+
e.P = int(e.Args[1])
455+
e.G = e.Args[2]
456+
e.Args[0] = 0
457+
}
458+
switch raw.typ {
459+
default:
460+
batches[lastP] = append(batches[lastP], e)
461+
case EvCPUSample:
462+
// Most events are written out by the active P at the exact
463+
// moment they describe. CPU profile samples are different
464+
// because they're written to the tracing log after some delay,
465+
// by a separate worker goroutine, into a separate buffer.
466+
//
467+
// We keep these in their own batch until all of the batches are
468+
// merged in timestamp order. We also (right before the merge)
469+
// re-sort these events by the timestamp captured in the
470+
// profiling signal handler.
471+
batches[ProfileP] = append(batches[ProfileP], e)
451472
}
452-
batches[lastP] = append(batches[lastP], e)
453473
}
454474
}
455475
if len(batches) == 0 {
@@ -1058,7 +1078,8 @@ const (
10581078
EvUserTaskEnd = 46 // end of task [timestamp, internal task id, stack]
10591079
EvUserRegion = 47 // trace.WithRegion [timestamp, internal task id, mode(0:start, 1:end), stack, name string]
10601080
EvUserLog = 48 // trace.Log [timestamp, internal id, key string id, stack, value string]
1061-
EvCount = 49
1081+
EvCPUSample = 49 // CPU profiling sample [timestamp, stack, real timestamp, real P id (-1 when absent), goroutine id]
1082+
EvCount = 50
10621083
)
10631084

10641085
var EventDescriptions = [EvCount]struct {
@@ -1117,4 +1138,5 @@ var EventDescriptions = [EvCount]struct {
11171138
EvUserTaskEnd: {"UserTaskEnd", 1011, true, []string{"taskid"}, nil},
11181139
EvUserRegion: {"UserRegion", 1011, true, []string{"taskid", "mode", "typeid"}, []string{"name"}},
11191140
EvUserLog: {"UserLog", 1011, true, []string{"id", "keyid"}, []string{"category", "message"}},
1141+
EvCPUSample: {"CPUSample", 1019, true, []string{"ts", "p", "g"}, nil},
11201142
}

src/runtime/cpuprof.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@ import (
1919
"unsafe"
2020
)
2121

22-
const maxCPUProfStack = 64
22+
const (
23+
maxCPUProfStack = 64
24+
25+
// profBufWordCount is the size of the CPU profile buffer's storage for the
26+
// header and stack of each sample, measured in 64-bit words. Every sample
27+
// has a required header of two words. With a small additional header (a
28+
// word or two) and stacks at the profiler's maximum length of 64 frames,
29+
// that capacity can support 1900 samples or 19 thread-seconds at a 100 Hz
30+
// sample rate, at a cost of 1 MiB.
31+
profBufWordCount = 1 << 17
32+
// profBufTagCount is the size of the CPU profile buffer's storage for the
33+
// goroutine tags associated with each sample. A capacity of 1<<14 means
34+
// room for 16k samples, or 160 thread-seconds at a 100 Hz sample rate.
35+
profBufTagCount = 1 << 14
36+
)
2337

2438
type cpuProfile struct {
2539
lock mutex
@@ -70,7 +84,7 @@ func SetCPUProfileRate(hz int) {
7084
}
7185

7286
cpuprof.on = true
73-
cpuprof.log = newProfBuf(1, 1<<17, 1<<14)
87+
cpuprof.log = newProfBuf(1, profBufWordCount, profBufTagCount)
7488
hdr := [1]uint64{uint64(hz)}
7589
cpuprof.log.write(nil, nanotime(), hdr[:], nil)
7690
setcpuprofilerate(int32(hz))
@@ -93,6 +107,7 @@ func SetCPUProfileRate(hz int) {
93107
func (p *cpuProfile) add(tagPtr *unsafe.Pointer, stk []uintptr) {
94108
// Simple cas-lock to coordinate with setcpuprofilerate.
95109
for !atomic.Cas(&prof.signalLock, 0, 1) {
110+
// TODO: Is it safe to osyield here? https://go.dev/issue/52672
96111
osyield()
97112
}
98113

@@ -125,8 +140,11 @@ func (p *cpuProfile) addNonGo(stk []uintptr) {
125140
// Simple cas-lock to coordinate with SetCPUProfileRate.
126141
// (Other calls to add or addNonGo should be blocked out
127142
// by the fact that only one SIGPROF can be handled by the
128-
// process at a time. If not, this lock will serialize those too.)
143+
// process at a time. If not, this lock will serialize those too.
144+
// The use of timer_create(2) on Linux to request process-targeted
145+
// signals may have changed this.)
129146
for !atomic.Cas(&prof.signalLock, 0, 1) {
147+
// TODO: Is it safe to osyield here? https://go.dev/issue/52672
130148
osyield()
131149
}
132150

src/runtime/proc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4595,6 +4595,12 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
45954595
tagPtr = &gp.m.curg.labels
45964596
}
45974597
cpuprof.add(tagPtr, stk[:n])
4598+
4599+
var pp *p
4600+
if gp != nil && gp.m != nil {
4601+
pp = gp.m.p.ptr()
4602+
}
4603+
traceCPUSample(gp, pp, stk[:n])
45984604
}
45994605
getg().m.mallocing--
46004606
}

0 commit comments

Comments
 (0)