aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
authorMichael Anthony Knyszek <mknyszek@google.com>2024-04-24 16:26:39 +0000
committerGopher Robot <gobot@golang.org>2024-05-08 17:47:01 +0000
commit724bab150541efefa4b3ea27bf3d4a064e9fab8c (patch)
treea15bfa520133d4d038013bd4ee285f1bcbb4608a /src/runtime
parent11047345f53fb1484e76fd59d6e044c219d204e5 (diff)
downloadgo-724bab150541efefa4b3ea27bf3d4a064e9fab8c.tar.xz
runtime: add traceallocfree GODEBUG for alloc/free events in traces
This change adds expensive alloc/free events to traces, guarded by a GODEBUG that can be set at run time by mutating the GODEBUG environment variable. This supersedes the alloc/free trace deleted in a previous CL. There are two parts to this CL. The first part is adding a mechanism for exposing experimental events through the tracer and trace parser. This boils down to a new ExperimentalEvent event type in the parser API which simply reveals the raw event data for the event. Each experimental event can also be associated with "experimental data" which is associated with a particular generation. This experimental data is just exposed as a bag of bytes that supplements the experimental events. In the runtime, this CL organizes experimental events by experiment. An experiment is defined by a set of experimental events and a single special batch type. Batches of this special type are exposed through the parser's API as the aforementioned "experimental data". The second part of this CL is defining the AllocFree experiment, which defines 9 new experimental events covering heap object alloc/frees, span alloc/frees, and goroutine stack alloc/frees. It also generates special batches that contain a type table: a mapping of IDs to type information. Change-Id: I965c00e3dcfdf5570f365ff89d0f70d8aeca219c Reviewed-on: https://go-review.googlesource.com/c/go/+/583377 Reviewed-by: Michael Pratt <mpratt@google.com> Auto-Submit: Michael Knyszek <mknyszek@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/lockrank.go3
-rw-r--r--src/runtime/malloc.go8
-rw-r--r--src/runtime/mgcsweep.go9
-rw-r--r--src/runtime/mheap.go27
-rw-r--r--src/runtime/mklockrank.go1
-rw-r--r--src/runtime/runtime1.go10
-rw-r--r--src/runtime/stack.go14
-rw-r--r--src/runtime/trace.go37
-rw-r--r--src/runtime/traceallocfree.go138
-rw-r--r--src/runtime/tracebuf.go13
-rw-r--r--src/runtime/traceevent.go9
-rw-r--r--src/runtime/traceexp.go68
-rw-r--r--src/runtime/traceregion.go6
-rw-r--r--src/runtime/traceruntime.go10
-rw-r--r--src/runtime/tracetype.go77
15 files changed, 424 insertions, 6 deletions
diff --git a/src/runtime/lockrank.go b/src/runtime/lockrank.go
index 33b0387686..432ace728b 100644
--- a/src/runtime/lockrank.go
+++ b/src/runtime/lockrank.go
@@ -50,6 +50,7 @@ const (
lockRankFin
lockRankSpanSetSpine
lockRankMspanSpecial
+ lockRankTraceTypeTab
// MPROF
lockRankGcBitsArenas
lockRankProfInsert
@@ -119,6 +120,7 @@ var lockNames = []string{
lockRankFin: "fin",
lockRankSpanSetSpine: "spanSetSpine",
lockRankMspanSpecial: "mspanSpecial",
+ lockRankTraceTypeTab: "traceTypeTab",
lockRankGcBitsArenas: "gcBitsArenas",
lockRankProfInsert: "profInsert",
lockRankProfBlock: "profBlock",
@@ -197,6 +199,7 @@ var lockPartialOrder [][]lockRank = [][]lockRank{
lockRankFin: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings},
lockRankSpanSetSpine: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings},
lockRankMspanSpecial: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings},
+ lockRankTraceTypeTab: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings},
lockRankGcBitsArenas: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings, lockRankMspanSpecial},
lockRankProfInsert: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings},
lockRankProfBlock: {lockRankSysmon, lockRankScavenge, lockRankForcegc, lockRankSweepWaiters, lockRankAssistQueue, lockRankSweep, lockRankTestR, lockRankTimerSend, lockRankExecW, lockRankCpuprof, lockRankPollDesc, lockRankWakeableSleep, lockRankHchan, lockRankAllocmR, lockRankExecR, lockRankSched, lockRankAllg, lockRankAllp, lockRankNotifyList, lockRankTimers, lockRankTimer, lockRankItab, lockRankReflectOffs, lockRankUserArenaState, lockRankTraceBuf, lockRankTraceStrings},
diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go
index a572900eb7..96c4761bc8 100644
--- a/src/runtime/malloc.go
+++ b/src/runtime/malloc.go
@@ -1265,6 +1265,14 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
// Init functions are executed sequentially in a single goroutine.
inittrace.bytes += uint64(fullSize)
}
+
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.HeapObjectAlloc(uintptr(x), typ)
+ traceRelease(trace)
+ }
+ }
}
if assistG != nil {
diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go
index da66bfa596..ae252cd47a 100644
--- a/src/runtime/mgcsweep.go
+++ b/src/runtime/mgcsweep.go
@@ -608,13 +608,20 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
spanHasNoSpecials(s)
}
- if debug.clobberfree != 0 || raceenabled || msanenabled || asanenabled {
+ if traceAllocFreeEnabled() || debug.clobberfree != 0 || raceenabled || msanenabled || asanenabled {
// Find all newly freed objects.
mbits := s.markBitsForBase()
abits := s.allocBitsForIndex(0)
for i := uintptr(0); i < uintptr(s.nelems); i++ {
if !mbits.isMarked() && (abits.index < uintptr(s.freeindex) || abits.isMarked()) {
x := s.base() + i*s.elemsize
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.HeapObjectFree(x)
+ traceRelease(trace)
+ }
+ }
if debug.clobberfree != 0 {
clobberfree(unsafe.Pointer(x), size)
}
diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go
index a68f855cab..2b7d434587 100644
--- a/src/runtime/mheap.go
+++ b/src/runtime/mheap.go
@@ -1367,6 +1367,15 @@ HaveSpan:
memstats.heapStats.release()
pageTraceAlloc(pp, now, base, npages)
+
+ // Trace the span alloc.
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.SpanAlloc(s)
+ traceRelease(trace)
+ }
+ }
return s
}
@@ -1549,6 +1558,15 @@ func (h *mheap) freeSpan(s *mspan) {
systemstack(func() {
pageTraceFree(getg().m.p.ptr(), 0, s.base(), s.npages)
+ // Trace the span free.
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.SpanFree(s)
+ traceRelease(trace)
+ }
+ }
+
lock(&h.lock)
if msanenabled {
// Tell msan that this entire span is no longer in use.
@@ -1581,6 +1599,15 @@ func (h *mheap) freeSpan(s *mspan) {
func (h *mheap) freeManual(s *mspan, typ spanAllocType) {
pageTraceFree(getg().m.p.ptr(), 0, s.base(), s.npages)
+ // Trace the span free.
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.SpanFree(s)
+ traceRelease(trace)
+ }
+ }
+
s.needzero = 1
lock(&h.lock)
h.freeSpanLocked(s, typ)
diff --git a/src/runtime/mklockrank.go b/src/runtime/mklockrank.go
index 2b4b5e99cd..1239b4a546 100644
--- a/src/runtime/mklockrank.go
+++ b/src/runtime/mklockrank.go
@@ -125,6 +125,7 @@ allg,
< fin,
spanSetSpine,
mspanSpecial,
+ traceTypeTab,
MPROF;
# We can acquire gcBitsArenas for pinner bits, and
diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go
index dd19242cb4..c54ba19d07 100644
--- a/src/runtime/runtime1.go
+++ b/src/runtime/runtime1.go
@@ -337,6 +337,15 @@ var debug struct {
malloc bool
inittrace int32
sbrk int32
+ // traceallocfree controls whether execution traces contain
+ // detailed trace data about memory allocation. This value
+ // affects debug.malloc only if it is != 0 and the execution
+ // tracer is enabled, in which case debug.malloc will be
+ // set to "true" if it isn't already while tracing is enabled.
+ // It will be set while the world is stopped, so it's safe.
+ // The value of traceallocfree can be changed any time in response
+ // to os.Setenv("GODEBUG").
+ traceallocfree atomic.Int32
panicnil atomic.Int32
@@ -376,6 +385,7 @@ var dbgvars = []*dbgVar{
{name: "scheddetail", value: &debug.scheddetail},
{name: "schedtrace", value: &debug.schedtrace},
{name: "traceadvanceperiod", value: &debug.traceadvanceperiod},
+ {name: "traceallocfree", atomic: &debug.traceallocfree},
{name: "tracecheckstackownership", value: &debug.traceCheckStackOwnership},
{name: "tracebackancestors", value: &debug.tracebackancestors},
{name: "tracefpunwindoff", value: &debug.tracefpunwindoff},
diff --git a/src/runtime/stack.go b/src/runtime/stack.go
index 6679cd993d..6d24814271 100644
--- a/src/runtime/stack.go
+++ b/src/runtime/stack.go
@@ -415,6 +415,13 @@ func stackalloc(n uint32) stack {
v = unsafe.Pointer(s.base())
}
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoroutineStackAlloc(uintptr(v), uintptr(n))
+ traceRelease(trace)
+ }
+ }
if raceenabled {
racemalloc(v, uintptr(n))
}
@@ -458,6 +465,13 @@ func stackfree(stk stack) {
}
return
}
+ if traceAllocFreeEnabled() {
+ trace := traceAcquire()
+ if trace.ok() {
+ trace.GoroutineStackFree(uintptr(v))
+ traceRelease(trace)
+ }
+ }
if msanenabled {
msanfree(v, n)
}
diff --git a/src/runtime/trace.go b/src/runtime/trace.go
index 6e0808ea76..49ac3e2d45 100644
--- a/src/runtime/trace.go
+++ b/src/runtime/trace.go
@@ -67,6 +67,7 @@ var trace struct {
// There are 2 of each: one for gen%2, one for 1-gen%2.
stackTab [2]traceStackTable // maps stack traces to unique ids
stringTab [2]traceStringTable // maps strings to unique ids
+ typeTab [2]traceTypeTable // maps type pointers to unique ids
// cpuLogRead accepts CPU profile samples from the signal handler where
// they're generated. There are two profBufs here: one for gen%2, one for
@@ -110,6 +111,10 @@ var trace struct {
// as a publication barrier.
enabled bool
+ // enabledWithAllocFree is set if debug.traceallocfree is != 0 when tracing begins.
+ // It follows the same synchronization protocol as enabled.
+ enabledWithAllocFree bool
+
// Trace generation counter.
gen atomic.Uintptr
lastNonZeroGen uintptr // last non-zero value of gen
@@ -126,6 +131,12 @@ var trace struct {
//
// Mutated only during stop-the-world.
seqGC uint64
+
+ // minPageHeapAddr is the minimum address of the page heap when tracing started.
+ minPageHeapAddr uint64
+
+ // debugMalloc is the value of debug.malloc before tracing began.
+ debugMalloc bool
}
// Trace public API.
@@ -216,6 +227,10 @@ func StartTrace() error {
// Prevent sysmon from running any code that could generate events.
lock(&sched.sysmonlock)
+ // Grab the minimum page heap address. All Ps are stopped, so it's safe to read this since
+ // nothing can allocate heap memory.
+ trace.minPageHeapAddr = uint64(mheap_.pages.inUse.ranges[0].base.addr())
+
// Reset mSyscallID on all Ps while we have them stationary and the trace is disabled.
for _, pp := range allp {
pp.trace.mSyscallID = -1
@@ -236,6 +251,12 @@ func StartTrace() error {
// After trace.gen is updated, other Ms may start creating trace buffers and emitting
// data into them.
trace.enabled = true
+ if debug.traceallocfree.Load() != 0 {
+ // Enable memory events since the GODEBUG is set.
+ trace.debugMalloc = debug.malloc
+ trace.enabledWithAllocFree = true
+ debug.malloc = true
+ }
trace.gen.Store(firstGen)
// Wait for exitingSyscall to drain.
@@ -267,6 +288,11 @@ func StartTrace() error {
tl.GCActive()
}
+ // Dump a snapshot of memory, if enabled.
+ if trace.enabledWithAllocFree {
+ traceSnapshotMemory()
+ }
+
// Record the heap goal so we have it at the very beginning of the trace.
tl.HeapGoal()
@@ -556,6 +582,7 @@ func traceAdvance(stopTrace bool) {
// stacks may generate new strings.
traceCPUFlush(gen)
trace.stackTab[gen%2].dump(gen)
+ trace.typeTab[gen%2].dump(gen)
trace.stringTab[gen%2].reset(gen)
// That's it. This generation is done producing buffers.
@@ -585,6 +612,16 @@ func traceAdvance(stopTrace bool) {
// Finish off CPU profile reading.
traceStopReadCPU()
+
+ // Reset debug.malloc if necessary. Note that this is set in a racy
+ // way; that's OK. Some mallocs may still enter into the debug.malloc
+ // block, but they won't generate events because tracing is disabled.
+ // That is, it's OK if mallocs read a stale debug.malloc or
+ // trace.enabledWithAllocFree value.
+ if trace.enabledWithAllocFree {
+ trace.enabledWithAllocFree = false
+ debug.malloc = trace.debugMalloc
+ }
} else {
// Go over each P and emit a status event for it if necessary.
//
diff --git a/src/runtime/traceallocfree.go b/src/runtime/traceallocfree.go
new file mode 100644
index 0000000000..e1190394ed
--- /dev/null
+++ b/src/runtime/traceallocfree.go
@@ -0,0 +1,138 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Runtime -> tracer API for memory events.
+
+package runtime
+
+import (
+ "internal/abi"
+ "runtime/internal/sys"
+)
+
+// traceSnapshotMemory takes a snapshot of all runtime memory that there are events for
+// (heap spans, heap objects, goroutine stacks, etc.) and writes out events for them.
+//
+// The world must be stopped and tracing must be enabled when this function is called.
+func traceSnapshotMemory() {
+ assertWorldStopped()
+
+ // Start tracing.
+ trace := traceAcquire()
+ if !trace.ok() {
+ throw("traceSnapshotMemory: tracing is not enabled")
+ }
+
+ // Write out all the goroutine stacks.
+ forEachGRace(func(gp *g) {
+ trace.GoroutineStackExists(gp.stack.lo, gp.stack.hi-gp.stack.lo)
+ })
+
+ // Write out all the heap spans and heap objects.
+ for _, s := range mheap_.allspans {
+ if s.state.get() == mSpanDead {
+ continue
+ }
+ // It's some kind of span, so trace that it exists.
+ trace.SpanExists(s)
+
+ // Write out allocated objects if it's a heap span.
+ if s.state.get() != mSpanInUse {
+ continue
+ }
+
+ // Find all allocated objects.
+ abits := s.allocBitsForIndex(0)
+ for i := uintptr(0); i < uintptr(s.nelems); i++ {
+ if !abits.isMarked() {
+ continue
+ }
+ x := s.base() + i*s.elemsize
+ trace.HeapObjectExists(x, s.typePointersOfUnchecked(x).typ)
+ abits.advance()
+ }
+ }
+ traceRelease(trace)
+}
+
+func traceSpanTypeAndClass(s *mspan) traceArg {
+ if s.state.get() == mSpanInUse {
+ return traceArg(s.spanclass) << 1
+ }
+ return traceArg(1)
+}
+
+// SpanExists records an event indicating that the span exists.
+func (tl traceLocker) SpanExists(s *mspan) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSpan, traceSpanID(s), traceArg(s.npages), traceSpanTypeAndClass(s))
+}
+
+// SpanAlloc records an event indicating that the span has just been allocated.
+func (tl traceLocker) SpanAlloc(s *mspan) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSpanAlloc, traceSpanID(s), traceArg(s.npages), traceSpanTypeAndClass(s))
+}
+
+// SpanFree records an event indicating that the span is about to be freed.
+func (tl traceLocker) SpanFree(s *mspan) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvSpanFree, traceSpanID(s))
+}
+
+// traceSpanID creates a trace ID for the span s for the trace.
+func traceSpanID(s *mspan) traceArg {
+ return traceArg(uint64(s.base())-trace.minPageHeapAddr) / pageSize
+}
+
+// HeapObjectExists records that an object already exists at addr with the provided type.
+// The type is optional, and the size of the slot occupied the object is inferred from the
+// span containing it.
+func (tl traceLocker) HeapObjectExists(addr uintptr, typ *abi.Type) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapObject, traceHeapObjectID(addr), tl.rtype(typ))
+}
+
+// HeapObjectAlloc records that an object was newly allocated at addr with the provided type.
+// The type is optional, and the size of the slot occupied the object is inferred from the
+// span containing it.
+func (tl traceLocker) HeapObjectAlloc(addr uintptr, typ *abi.Type) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapObjectAlloc, traceHeapObjectID(addr), tl.rtype(typ))
+}
+
+// HeapObjectFree records that an object at addr is about to be freed.
+func (tl traceLocker) HeapObjectFree(addr uintptr) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvHeapObjectFree, traceHeapObjectID(addr))
+}
+
+// traceHeapObjectID creates a trace ID for a heap object at address addr.
+func traceHeapObjectID(addr uintptr) traceArg {
+ return traceArg(uint64(addr)-trace.minPageHeapAddr) / 8
+}
+
+// GoroutineStackExists records that a goroutine stack already exists at address base with the provided size.
+func (tl traceLocker) GoroutineStackExists(base, size uintptr) {
+ order := traceCompressStackSize(size)
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoroutineStack, traceGoroutineStackID(base), order)
+}
+
+// GoroutineStackAlloc records that a goroutine stack was newly allocated at address base with the provided size..
+func (tl traceLocker) GoroutineStackAlloc(base, size uintptr) {
+ order := traceCompressStackSize(size)
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoroutineStackAlloc, traceGoroutineStackID(base), order)
+}
+
+// GoroutineStackFree records that a goroutine stack at address base is about to be freed.
+func (tl traceLocker) GoroutineStackFree(base uintptr) {
+ tl.eventWriter(traceGoRunning, traceProcRunning).commit(traceEvGoroutineStackFree, traceGoroutineStackID(base))
+}
+
+// traceGoroutineStackID creates a trace ID for the goroutine stack from its base address.
+func traceGoroutineStackID(base uintptr) traceArg {
+ return traceArg(uint64(base)-trace.minPageHeapAddr) / fixedStack
+}
+
+// traceCompressStackSize assumes size is a power of 2 and returns log2(size).
+func traceCompressStackSize(size uintptr) traceArg {
+ if size&(size-1) != 0 {
+ throw("goroutine stack size is not a power of 2")
+ }
+ return traceArg(sys.Len64(uint64(size)))
+}
diff --git a/src/runtime/tracebuf.go b/src/runtime/tracebuf.go
index 711a2c1f02..db4adf53e9 100644
--- a/src/runtime/tracebuf.go
+++ b/src/runtime/tracebuf.go
@@ -59,7 +59,7 @@ func (w traceWriter) end() {
func (w traceWriter) ensure(maxSize int) (traceWriter, bool) {
refill := w.traceBuf == nil || !w.available(maxSize)
if refill {
- w = w.refill()
+ w = w.refill(traceNoExperiment)
}
return w, refill
}
@@ -78,7 +78,9 @@ func (w traceWriter) flush() traceWriter {
}
// refill puts w.traceBuf on the queue of full buffers and refresh's w's buffer.
-func (w traceWriter) refill() traceWriter {
+//
+// exp indicates whether the refilled batch should be EvExperimentalBatch.
+func (w traceWriter) refill(exp traceExperiment) traceWriter {
systemstack(func() {
lock(&trace.lock)
if w.traceBuf != nil {
@@ -112,7 +114,12 @@ func (w traceWriter) refill() traceWriter {
}
// Write the buffer's header.
- w.byte(byte(traceEvEventBatch))
+ if exp == traceNoExperiment {
+ w.byte(byte(traceEvEventBatch))
+ } else {
+ w.byte(byte(traceEvExperimentalBatch))
+ w.byte(byte(exp))
+ }
w.varint(uint64(w.gen))
w.varint(uint64(mID))
w.varint(uint64(ts))
diff --git a/src/runtime/traceevent.go b/src/runtime/traceevent.go
index bdb3f3e445..2a869fb515 100644
--- a/src/runtime/traceevent.go
+++ b/src/runtime/traceevent.go
@@ -7,6 +7,7 @@
package runtime
import (
+ "internal/abi"
"runtime/internal/sys"
)
@@ -87,6 +88,9 @@ const (
// GoStatus with stack.
traceEvGoStatusStack // goroutine status at the start of a generation, with a stack [timestamp, goroutine ID, M ID, status, stack ID]
+
+ // Batch event for an experimental batch with a custom format.
+ traceEvExperimentalBatch // start of extra data [experiment ID, generation, M ID, timestamp, batch length, batch data...]
)
// traceArg is a simple wrapper type to help ensure that arguments passed
@@ -198,3 +202,8 @@ func (tl traceLocker) string(s string) traceArg {
func (tl traceLocker) uniqueString(s string) traceArg {
return traceArg(trace.stringTab[tl.gen%2].emit(tl.gen, s))
}
+
+// rtype returns a traceArg representing typ which may be passed to write.
+func (tl traceLocker) rtype(typ *abi.Type) traceArg {
+ return traceArg(trace.typeTab[tl.gen%2].put(typ))
+}
diff --git a/src/runtime/traceexp.go b/src/runtime/traceexp.go
new file mode 100644
index 0000000000..9fc85df5a8
--- /dev/null
+++ b/src/runtime/traceexp.go
@@ -0,0 +1,68 @@
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime
+
+// traceExpWriter is a wrapper around trace writer that produces traceEvExperimentalBatch
+// batches. This means that the data written to the writer need not conform to the standard
+// trace format.
+type traceExpWriter struct {
+ traceWriter
+ exp traceExperiment
+}
+
+// unsafeTraceExpWriter produces a traceExpWriter that doesn't lock the trace.
+//
+// It should only be used in contexts where either:
+// - Another traceLocker is held.
+// - trace.gen is prevented from advancing.
+//
+// buf may be nil.
+func unsafeTraceExpWriter(gen uintptr, buf *traceBuf, exp traceExperiment) traceExpWriter {
+ return traceExpWriter{traceWriter{traceLocker: traceLocker{gen: gen}, traceBuf: buf}, exp}
+}
+
+// ensure makes sure that at least maxSize bytes are available to write.
+//
+// Returns whether the buffer was flushed.
+func (w traceExpWriter) ensure(maxSize int) (traceExpWriter, bool) {
+ refill := w.traceBuf == nil || !w.available(maxSize)
+ if refill {
+ w.traceWriter = w.traceWriter.refill(w.exp)
+ }
+ return w, refill
+}
+
+// traceExperiment is an enumeration of the different kinds of experiments supported for tracing.
+type traceExperiment uint8
+
+const (
+ // traceNoExperiment indicates no experiment.
+ traceNoExperiment traceExperiment = iota
+
+ // traceExperimentAllocFree is an experiment to add alloc/free events to the trace.
+ traceExperimentAllocFree
+)
+
+// Experimental events.
+const (
+ _ traceEv = 127 + iota
+
+ // Experimental events for ExperimentAllocFree.
+
+ // Experimental heap span events. IDs map reversibly to base addresses.
+ traceEvSpan // heap span exists [timestamp, id, npages, type/class]
+ traceEvSpanAlloc // heap span alloc [timestamp, id, npages, type/class]
+ traceEvSpanFree // heap span free [timestamp, id]
+
+ // Experimental heap object events. IDs map reversibly to addresses.
+ traceEvHeapObject // heap object exists [timestamp, id, type]
+ traceEvHeapObjectAlloc // heap object alloc [timestamp, id, type]
+ traceEvHeapObjectFree // heap object free [timestamp, id]
+
+ // Experimental goroutine stack events. IDs map reversibly to addresses.
+ traceEvGoroutineStack // stack exists [timestamp, id, order]
+ traceEvGoroutineStackAlloc // stack alloc [timestamp, id, order]
+ traceEvGoroutineStackFree // stack free [timestamp, id]
+)
diff --git a/src/runtime/traceregion.go b/src/runtime/traceregion.go
index fdc6fbdb32..43eef9c92b 100644
--- a/src/runtime/traceregion.go
+++ b/src/runtime/traceregion.go
@@ -104,7 +104,9 @@ func (a *traceRegionAlloc) drop() {
a.full = block.next
sysFree(unsafe.Pointer(block), unsafe.Sizeof(traceRegionAllocBlock{}), &memstats.other_sys)
}
- sysFree(a.current.Load(), unsafe.Sizeof(traceRegionAllocBlock{}), &memstats.other_sys)
- a.current.Store(nil)
+ if current := a.current.Load(); current != nil {
+ sysFree(current, unsafe.Sizeof(traceRegionAllocBlock{}), &memstats.other_sys)
+ a.current.Store(nil)
+ }
a.dropping.Store(false)
}
diff --git a/src/runtime/traceruntime.go b/src/runtime/traceruntime.go
index 285f264463..5497913066 100644
--- a/src/runtime/traceruntime.go
+++ b/src/runtime/traceruntime.go
@@ -59,6 +59,8 @@ func traceLockInit() {
lockInit(&trace.stringTab[1].tab.mem.lock, lockRankTraceStrings)
lockInit(&trace.stackTab[0].tab.mem.lock, lockRankTraceStackTab)
lockInit(&trace.stackTab[1].tab.mem.lock, lockRankTraceStackTab)
+ lockInit(&trace.typeTab[0].tab.mem.lock, lockRankTraceTypeTab)
+ lockInit(&trace.typeTab[1].tab.mem.lock, lockRankTraceTypeTab)
lockInit(&trace.lock, lockRankTrace)
}
@@ -142,6 +144,14 @@ func traceEnabled() bool {
return trace.enabled
}
+// traceAllocFreeEnabled returns true if the trace is currently enabled
+// and alloc/free events are also enabled.
+//
+//go:nosplit
+func traceAllocFreeEnabled() bool {
+ return trace.enabledWithAllocFree
+}
+
// traceShuttingDown returns true if the trace is currently shutting down.
func traceShuttingDown() bool {
return trace.shutdown.Load()
diff --git a/src/runtime/tracetype.go b/src/runtime/tracetype.go
new file mode 100644
index 0000000000..41dce9c9f2
--- /dev/null
+++ b/src/runtime/tracetype.go
@@ -0,0 +1,77 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Trace stack table and acquisition.
+
+package runtime
+
+import (
+ "internal/abi"
+ "internal/goarch"
+ "unsafe"
+)
+
+// traceTypeTable maps stack traces (arrays of PC's) to unique uint32 ids.
+// It is lock-free for reading.
+type traceTypeTable struct {
+ tab traceMap
+}
+
+// put returns a unique id for the type typ and caches it in the table,
+// if it's seeing it for the first time.
+//
+// N.B. typ must be kept alive forever for this to work correctly.
+func (t *traceTypeTable) put(typ *abi.Type) uint64 {
+ if typ == nil {
+ return 0
+ }
+ // Insert the pointer to the type itself.
+ id, _ := t.tab.put(noescape(unsafe.Pointer(&typ)), goarch.PtrSize)
+ return id
+}
+
+// dump writes all previously cached types to trace buffers and
+// releases all memory and resets state. It must only be called once the caller
+// can guarantee that there are no more writers to the table.
+func (t *traceTypeTable) dump(gen uintptr) {
+ w := unsafeTraceExpWriter(gen, nil, traceExperimentAllocFree)
+ if root := (*traceMapNode)(t.tab.root.Load()); root != nil {
+ w = dumpTypesRec(root, w)
+ }
+ w.flush().end()
+ t.tab.reset()
+}
+
+func dumpTypesRec(node *traceMapNode, w traceExpWriter) traceExpWriter {
+ typ := (*abi.Type)(*(*unsafe.Pointer)(unsafe.Pointer(&node.data[0])))
+ typName := toRType(typ).string()
+
+ // The maximum number of bytes required to hold the encoded type.
+ maxBytes := 1 + 5*traceBytesPerNumber + len(typName)
+
+ // Estimate the size of this record. This
+ // bound is pretty loose, but avoids counting
+ // lots of varint sizes.
+ //
+ // Add 1 because we might also write traceEvTypes.
+ w, _ = w.ensure(1 + maxBytes)
+
+ // Emit type.
+ w.varint(uint64(node.id))
+ w.varint(uint64(uintptr(unsafe.Pointer(typ))))
+ w.varint(uint64(typ.Size()))
+ w.varint(uint64(typ.PtrBytes))
+ w.varint(uint64(len(typName)))
+ w.stringData(typName)
+
+ // Recursively walk all child nodes.
+ for i := range node.children {
+ child := node.children[i].Load()
+ if child == nil {
+ continue
+ }
+ w = dumpTypesRec((*traceMapNode)(child), w)
+ }
+ return w
+}