diff options
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/lockrank.go | 3 | ||||
| -rw-r--r-- | src/runtime/malloc.go | 8 | ||||
| -rw-r--r-- | src/runtime/mgcsweep.go | 9 | ||||
| -rw-r--r-- | src/runtime/mheap.go | 27 | ||||
| -rw-r--r-- | src/runtime/mklockrank.go | 1 | ||||
| -rw-r--r-- | src/runtime/runtime1.go | 10 | ||||
| -rw-r--r-- | src/runtime/stack.go | 14 | ||||
| -rw-r--r-- | src/runtime/trace.go | 37 | ||||
| -rw-r--r-- | src/runtime/traceallocfree.go | 138 | ||||
| -rw-r--r-- | src/runtime/tracebuf.go | 13 | ||||
| -rw-r--r-- | src/runtime/traceevent.go | 9 | ||||
| -rw-r--r-- | src/runtime/traceexp.go | 68 | ||||
| -rw-r--r-- | src/runtime/traceregion.go | 6 | ||||
| -rw-r--r-- | src/runtime/traceruntime.go | 10 | ||||
| -rw-r--r-- | src/runtime/tracetype.go | 77 |
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 +} |
