diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2024-04-24 16:26:39 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2024-05-08 17:47:01 +0000 |
| commit | 724bab150541efefa4b3ea27bf3d4a064e9fab8c (patch) | |
| tree | a15bfa520133d4d038013bd4ee285f1bcbb4608a /src/runtime/traceallocfree.go | |
| parent | 11047345f53fb1484e76fd59d6e044c219d204e5 (diff) | |
| download | go-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/traceallocfree.go')
| -rw-r--r-- | src/runtime/traceallocfree.go | 138 |
1 files changed, 138 insertions, 0 deletions
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))) +} |
