import (
"internal/abi"
+ "internal/profilerecord"
"internal/runtime/atomic"
"runtime/internal/sys"
"unsafe"
// includes inlined frames. We may record more than this many
// "physical" frames when using frame pointer unwinding to account
// for deferred handling of skipping frames & inline expansion.
- maxLogicalStack = 32
+ maxLogicalStack = 128
// maxSkip is to account for deferred inline expansion
// when using frame pointer unwinding. We record the stack
// with "physical" frame pointers but handle skipping "logical"
// Called by malloc to record a profiled block.
func mProf_Malloc(mp *m, p unsafe.Pointer, size uintptr) {
- nstk := callers(4, mp.profStack)
+ if mp.profStack == nil {
+ // mp.profStack is nil if we happen to sample an allocation during the
+ // initialization of mp. This case is rare, so we just ignore such
+ // allocations. Change MemProfileRate to 1 if you need to reproduce such
+ // cases for testing purposes.
+ return
+ }
+ // Only use the part of mp.profStack we need and ignore the extra space
+ // reserved for delayed inline expansion with frame pointer unwinding.
+ nstk := callers(4, mp.profStack[:maxLogicalStack])
index := (mProfCycle.read() + 2) % uint32(len(memRecord{}.future))
b := stkbucket(memProfile, size, mp.profStack[:nstk], true)
print("requested skip=", skip)
throw("invalid skip value")
}
-
gp := getg()
mp := acquirem() // we must not be preempted while accessing profstack
nstk := 1
// the testing package's -test.memprofile flag instead
// of calling MemProfile directly.
func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) {
+ return memProfileInternal(len(p), inuseZero, func(r profilerecord.MemProfileRecord) {
+ copyMemProfileRecord(&p[0], r)
+ p = p[1:]
+ })
+}
+
+// memProfileInternal returns the number of records n in the profile. If there
+// are less than size records, copyFn is invoked for each record, and ok returns
+// true.
+func memProfileInternal(size int, inuseZero bool, copyFn func(profilerecord.MemProfileRecord)) (n int, ok bool) {
cycle := mProfCycle.read()
// If we're between mProf_NextCycle and mProf_Flush, take care
// of flushing to the active profile so we only have to look
}
}
}
- if n <= len(p) {
+ if n <= size {
ok = true
- idx := 0
for b := head; b != nil; b = b.allnext {
mp := b.mp()
if inuseZero || mp.active.alloc_bytes != mp.active.free_bytes {
- record(&p[idx], b)
- idx++
+ r := profilerecord.MemProfileRecord{
+ AllocBytes: int64(mp.active.alloc_bytes),
+ FreeBytes: int64(mp.active.free_bytes),
+ AllocObjects: int64(mp.active.allocs),
+ FreeObjects: int64(mp.active.frees),
+ Stack: b.stk(),
+ }
+ copyFn(r)
}
}
}
return
}
-// Write b's data to r.
-func record(r *MemProfileRecord, b *bucket) {
- mp := b.mp()
- r.AllocBytes = int64(mp.active.alloc_bytes)
- r.FreeBytes = int64(mp.active.free_bytes)
- r.AllocObjects = int64(mp.active.allocs)
- r.FreeObjects = int64(mp.active.frees)
+func copyMemProfileRecord(dst *MemProfileRecord, src profilerecord.MemProfileRecord) {
+ dst.AllocBytes = src.AllocBytes
+ dst.FreeBytes = src.FreeBytes
+ dst.AllocObjects = src.AllocObjects
+ dst.FreeObjects = src.FreeObjects
if raceenabled {
- racewriterangepc(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0), getcallerpc(), abi.FuncPCABIInternal(MemProfile))
+ racewriterangepc(unsafe.Pointer(&dst.Stack0[0]), unsafe.Sizeof(dst.Stack0), getcallerpc(), abi.FuncPCABIInternal(MemProfile))
}
if msanenabled {
- msanwrite(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0))
+ msanwrite(unsafe.Pointer(&dst.Stack0[0]), unsafe.Sizeof(dst.Stack0))
}
if asanenabled {
- asanwrite(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0))
+ asanwrite(unsafe.Pointer(&dst.Stack0[0]), unsafe.Sizeof(dst.Stack0))
}
- i := copy(r.Stack0[:], b.stk())
- clear(r.Stack0[i:])
+ i := copy(dst.Stack0[:], src.Stack)
+ clear(dst.Stack0[i:])
+}
+
+//go:linkname pprof_memProfileInternal
+func pprof_memProfileInternal(p []profilerecord.MemProfileRecord, inuseZero bool) (n int, ok bool) {
+ return memProfileInternal(len(p), inuseZero, func(r profilerecord.MemProfileRecord) {
+ p[0] = r
+ p = p[1:]
+ })
}
func iterate_memprof(fn func(*bucket, uintptr, *uintptr, uintptr, uintptr, uintptr)) {
// the [testing] package's -test.blockprofile flag instead
// of calling BlockProfile directly.
func BlockProfile(p []BlockProfileRecord) (n int, ok bool) {
+ return blockProfileInternal(len(p), func(r profilerecord.BlockProfileRecord) {
+ copyBlockProfileRecord(&p[0], r)
+ p = p[1:]
+ })
+}
+
+// blockProfileInternal returns the number of records n in the profile. If there
+// are less than size records, copyFn is invoked for each record, and ok returns
+// true.
+func blockProfileInternal(size int, copyFn func(profilerecord.BlockProfileRecord)) (n int, ok bool) {
lock(&profBlockLock)
head := (*bucket)(bbuckets.Load())
for b := head; b != nil; b = b.allnext {
n++
}
- if n <= len(p) {
+ if n <= size {
ok = true
for b := head; b != nil; b = b.allnext {
bp := b.bp()
- r := &p[0]
- r.Count = int64(bp.count)
+ r := profilerecord.BlockProfileRecord{
+ Count: int64(bp.count),
+ Cycles: bp.cycles,
+ Stack: b.stk(),
+ }
// Prevent callers from having to worry about division by zero errors.
// See discussion on http://golang.org/cl/299991.
if r.Count == 0 {
r.Count = 1
}
- r.Cycles = bp.cycles
- if raceenabled {
- racewriterangepc(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0), getcallerpc(), abi.FuncPCABIInternal(BlockProfile))
- }
- if msanenabled {
- msanwrite(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0))
- }
- if asanenabled {
- asanwrite(unsafe.Pointer(&r.Stack0[0]), unsafe.Sizeof(r.Stack0))
- }
- i := fpunwindExpand(r.Stack0[:], b.stk())
- clear(r.Stack0[i:])
- p = p[1:]
+ copyFn(r)
}
}
unlock(&profBlockLock)
return
}
+func copyBlockProfileRecord(dst *BlockProfileRecord, src profilerecord.BlockProfileRecord) {
+ dst.Count = src.Count
+ dst.Cycles = src.Cycles
+ if raceenabled {
+ racewriterangepc(unsafe.Pointer(&dst.Stack0[0]), unsafe.Sizeof(dst.Stack0), getcallerpc(), abi.FuncPCABIInternal(BlockProfile))
+ }
+ if msanenabled {
+ msanwrite(unsafe.Pointer(&dst.Stack0[0]), unsafe.Sizeof(dst.Stack0))
+ }
+ if asanenabled {
+ asanwrite(unsafe.Pointer(&dst.Stack0[0]), unsafe.Sizeof(dst.Stack0))
+ }
+ i := fpunwindExpand(dst.Stack0[:], src.Stack)
+ clear(dst.Stack0[i:])
+}
+
+//go:linkname pprof_blockProfileInternal
+func pprof_blockProfileInternal(p []profilerecord.BlockProfileRecord) (n int, ok bool) {
+ return blockProfileInternal(len(p), func(r profilerecord.BlockProfileRecord) {
+ p[0] = r
+ p = p[1:]
+ })
+}
+
// MutexProfile returns n, the number of records in the current mutex profile.
// If len(p) >= n, MutexProfile copies the profile into p and returns n, true.
// Otherwise, MutexProfile does not change p, and returns n, false.
// Most clients should use the [runtime/pprof] package
// instead of calling MutexProfile directly.
func MutexProfile(p []BlockProfileRecord) (n int, ok bool) {
+ return mutexProfileInternal(len(p), func(r profilerecord.BlockProfileRecord) {
+ copyBlockProfileRecord(&p[0], r)
+ p = p[1:]
+ })
+}
+
+// mutexProfileInternal returns the number of records n in the profile. If there
+// are less than size records, copyFn is invoked for each record, and ok returns
+// true.
+func mutexProfileInternal(size int, copyFn func(profilerecord.BlockProfileRecord)) (n int, ok bool) {
lock(&profBlockLock)
head := (*bucket)(xbuckets.Load())
for b := head; b != nil; b = b.allnext {
n++
}
- if n <= len(p) {
+ if n <= size {
ok = true
for b := head; b != nil; b = b.allnext {
bp := b.bp()
- r := &p[0]
- r.Count = int64(bp.count)
- r.Cycles = bp.cycles
- i := fpunwindExpand(r.Stack0[:], b.stk())
- clear(r.Stack0[i:])
- p = p[1:]
+ r := profilerecord.BlockProfileRecord{
+ Count: int64(bp.count),
+ Cycles: bp.cycles,
+ Stack: b.stk(),
+ }
+ copyFn(r)
}
}
unlock(&profBlockLock)
return
}
+//go:linkname pprof_mutexProfileInternal
+func pprof_mutexProfileInternal(p []profilerecord.BlockProfileRecord) (n int, ok bool) {
+ return mutexProfileInternal(len(p), func(r profilerecord.BlockProfileRecord) {
+ p[0] = r
+ p = p[1:]
+ })
+}
+
// ThreadCreateProfile returns n, the number of records in the thread creation profile.
// If len(p) >= n, ThreadCreateProfile copies the profile into p and returns n, true.
// If len(p) < n, ThreadCreateProfile does not change p and returns n, false.
// Most clients should use the runtime/pprof package instead
// of calling ThreadCreateProfile directly.
func ThreadCreateProfile(p []StackRecord) (n int, ok bool) {
+ return threadCreateProfileInternal(len(p), func(r profilerecord.StackRecord) {
+ copy(p[0].Stack0[:], r.Stack)
+ p = p[1:]
+ })
+}
+
+// threadCreateProfileInternal returns the number of records n in the profile.
+// If there are less than size records, copyFn is invoked for each record, and
+// ok returns true.
+func threadCreateProfileInternal(size int, copyFn func(profilerecord.StackRecord)) (n int, ok bool) {
first := (*m)(atomic.Loadp(unsafe.Pointer(&allm)))
for mp := first; mp != nil; mp = mp.alllink {
n++
}
- if n <= len(p) {
+ if n <= size {
ok = true
- i := 0
for mp := first; mp != nil; mp = mp.alllink {
- p[i].Stack0 = mp.createstack
- i++
+ r := profilerecord.StackRecord{Stack: mp.createstack[:]}
+ copyFn(r)
}
}
return
}
-//go:linkname runtime_goroutineProfileWithLabels runtime/pprof.runtime_goroutineProfileWithLabels
-func runtime_goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
+//go:linkname pprof_threadCreateInternal
+func pprof_threadCreateInternal(p []profilerecord.StackRecord) (n int, ok bool) {
+ return threadCreateProfileInternal(len(p), func(r profilerecord.StackRecord) {
+ p[0] = r
+ p = p[1:]
+ })
+}
+
+//go:linkname pprof_goroutineProfileWithLabels
+func pprof_goroutineProfileWithLabels(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
return goroutineProfileWithLabels(p, labels)
}
// labels may be nil. If labels is non-nil, it must have the same length as p.
-func goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
+func goroutineProfileWithLabels(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
if labels != nil && len(labels) != len(p) {
labels = nil
}
sema uint32
active bool
offset atomic.Int64
- records []StackRecord
+ records []profilerecord.StackRecord
labels []unsafe.Pointer
}{
sema: 1,
return (*atomic.Uint32)(p).CompareAndSwap(uint32(old), uint32(new))
}
-func goroutineProfileWithLabelsConcurrent(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
+func goroutineProfileWithLabelsConcurrent(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
if len(p) == 0 {
// An empty slice is obviously too small. Return a rough
// allocation estimate without bothering to STW. As long as
ourg := getg()
+ pcbuf := makeProfStack() // see saveg() for explanation
stw := stopTheWorld(stwGoroutineProfile)
// Using gcount while the world is stopped should give us a consistent view
// of the number of live goroutines, minus the number of goroutines that are
sp := getcallersp()
pc := getcallerpc()
systemstack(func() {
- saveg(pc, sp, ourg, &p[0])
+ saveg(pc, sp, ourg, &p[0], pcbuf)
})
if labels != nil {
labels[0] = ourg.labels
if fing != nil {
fing.goroutineProfiled.Store(goroutineProfileSatisfied)
if readgstatus(fing) != _Gdead && !isSystemGoroutine(fing, false) {
- doRecordGoroutineProfile(fing)
+ doRecordGoroutineProfile(fing, pcbuf)
}
}
startTheWorld(stw)
// call will start by adding itself to the profile (before the act of
// executing can cause any changes in its stack).
forEachGRace(func(gp1 *g) {
- tryRecordGoroutineProfile(gp1, Gosched)
+ tryRecordGoroutineProfile(gp1, pcbuf, Gosched)
})
stw = stopTheWorld(stwGoroutineProfileCleanup)
if getg().m.p.ptr() == nil {
throw("no P available, write barriers are forbidden")
}
- tryRecordGoroutineProfile(gp1, osyield)
+ tryRecordGoroutineProfile(gp1, nil, osyield)
}
// tryRecordGoroutineProfile ensures that gp1 has the appropriate representation
// in the current goroutine profile: either that it should not be profiled, or
// that a snapshot of its call stack and labels are now in the profile.
-func tryRecordGoroutineProfile(gp1 *g, yield func()) {
+func tryRecordGoroutineProfile(gp1 *g, pcbuf []uintptr, yield func()) {
if readgstatus(gp1) == _Gdead {
// Dead goroutines should not appear in the profile. Goroutines that
// start while profile collection is active will get goroutineProfiled
// in this limbo.
mp := acquirem()
if gp1.goroutineProfiled.CompareAndSwap(goroutineProfileAbsent, goroutineProfileInProgress) {
- doRecordGoroutineProfile(gp1)
+ doRecordGoroutineProfile(gp1, pcbuf)
gp1.goroutineProfiled.Store(goroutineProfileSatisfied)
}
releasem(mp)
// goroutine that is coordinating the goroutine profile (running on its own
// stack), or from the scheduler in preparation to execute gp1 (running on the
// system stack).
-func doRecordGoroutineProfile(gp1 *g) {
+func doRecordGoroutineProfile(gp1 *g, pcbuf []uintptr) {
if readgstatus(gp1) == _Grunning {
print("doRecordGoroutineProfile gp1=", gp1.goid, "\n")
throw("cannot read stack of running goroutine")
// set gp1.goroutineProfiled to goroutineProfileInProgress and so are still
// preventing it from being truly _Grunnable. So we'll use the system stack
// to avoid schedule delays.
- systemstack(func() { saveg(^uintptr(0), ^uintptr(0), gp1, &goroutineProfile.records[offset]) })
+ systemstack(func() { saveg(^uintptr(0), ^uintptr(0), gp1, &goroutineProfile.records[offset], pcbuf) })
if goroutineProfile.labels != nil {
goroutineProfile.labels[offset] = gp1.labels
}
}
-func goroutineProfileWithLabelsSync(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
+func goroutineProfileWithLabelsSync(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
gp := getg()
isOK := func(gp1 *g) bool {
return gp1 != gp && readgstatus(gp1) != _Gdead && !isSystemGoroutine(gp1, false)
}
+ pcbuf := makeProfStack() // see saveg() for explanation
stw := stopTheWorld(stwGoroutineProfile)
// World is stopped, no locking required.
sp := getcallersp()
pc := getcallerpc()
systemstack(func() {
- saveg(pc, sp, gp, &r[0])
+ saveg(pc, sp, gp, &r[0], pcbuf)
})
r = r[1:]
// The world is stopped, so it cannot use cgocall (which will be
// blocked at exitsyscall). Do it on the system stack so it won't
// call into the schedular (see traceback.go:cgoContextPCs).
- systemstack(func() { saveg(^uintptr(0), ^uintptr(0), gp1, &r[0]) })
+ systemstack(func() { saveg(^uintptr(0), ^uintptr(0), gp1, &r[0], pcbuf) })
if labels != nil {
lbl[0] = gp1.labels
lbl = lbl[1:]
// Most clients should use the [runtime/pprof] package instead
// of calling GoroutineProfile directly.
func GoroutineProfile(p []StackRecord) (n int, ok bool) {
+ records := make([]profilerecord.StackRecord, len(p))
+ n, ok = goroutineProfileInternal(records)
+ if !ok {
+ return
+ }
+ for i, mr := range records[0:n] {
+ copy(p[i].Stack0[:], mr.Stack)
+ }
+ return
+}
+func goroutineProfileInternal(p []profilerecord.StackRecord) (n int, ok bool) {
return goroutineProfileWithLabels(p, nil)
}
-func saveg(pc, sp uintptr, gp *g, r *StackRecord) {
+func saveg(pc, sp uintptr, gp *g, r *profilerecord.StackRecord, pcbuf []uintptr) {
+ // To reduce memory usage, we want to allocate a r.Stack that is just big
+ // enough to hold gp's stack trace. Naively we might achieve this by
+ // recording our stack trace into mp.profStack, and then allocating a
+ // r.Stack of the right size. However, mp.profStack is also used for
+ // allocation profiling, so it could get overwritten if the slice allocation
+ // gets profiled. So instead we record the stack trace into a temporary
+ // pcbuf which is usually given to us by our caller. When it's not, we have
+ // to allocate one here. This will only happen for goroutines that were in a
+ // syscall when the goroutine profile started or for goroutines that manage
+ // to execute before we finish iterating over all the goroutines.
+ if pcbuf == nil {
+ pcbuf = makeProfStack()
+ }
+
var u unwinder
u.initAt(pc, sp, 0, gp, unwindSilentErrors)
- n := tracebackPCs(&u, 0, r.Stack0[:])
- if n < len(r.Stack0) {
- r.Stack0[n] = 0
- }
+ n := tracebackPCs(&u, 0, pcbuf)
+ r.Stack = make([]uintptr, n)
+ copy(r.Stack, pcbuf)
}
// Stack formats a stack trace of the calling goroutine into buf
"bufio"
"fmt"
"internal/abi"
+ "internal/profilerecord"
"io"
"runtime"
"sort"
// as the pprof-proto format output. Translations from cycle count to time duration
// are done because The proto expects count and time (nanoseconds) instead of count
// and the number of cycles for block, contention profiles.
-func printCountCycleProfile(w io.Writer, countName, cycleName string, records []runtime.BlockProfileRecord) error {
+func printCountCycleProfile(w io.Writer, countName, cycleName string, records []profilerecord.BlockProfileRecord) error {
// Output profile in protobuf form.
b := newProfileBuilder(w)
b.pbValueType(tagProfile_PeriodType, countName, "count")
b.pbValueType(tagProfile_SampleType, countName, "count")
b.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds")
- cpuGHz := float64(runtime_cyclesPerSecond()) / 1e9
+ cpuGHz := float64(pprof_cyclesPerSecond()) / 1e9
values := []int64{0, 0}
var locs []uint64
+ expandedStack := pprof_makeProfStack()
for _, r := range records {
values[0] = r.Count
values[1] = int64(float64(r.Cycles) / cpuGHz)
// For count profiles, all stack addresses are
// return PCs, which is what appendLocsForStack expects.
- locs = b.appendLocsForStack(locs[:0], r.Stack())
+ n := pprof_fpunwindExpand(expandedStack[:], r.Stack)
+ locs = b.appendLocsForStack(locs[:0], expandedStack[:n])
b.pbSample(values, locs, nil)
}
b.build()
// the two calls—so allocate a few extra records for safety
// and also try again if we're very unlucky.
// The loop should only execute one iteration in the common case.
- var p []runtime.MemProfileRecord
- n, ok := runtime.MemProfile(nil, true)
+ var p []profilerecord.MemProfileRecord
+ n, ok := pprof_memProfileInternal(nil, true)
for {
// Allocate room for a slightly bigger profile,
// in case a few more entries have been added
// since the call to MemProfile.
- p = make([]runtime.MemProfileRecord, n+50)
- n, ok = runtime.MemProfile(p, true)
+ p = make([]profilerecord.MemProfileRecord, n+50)
+ n, ok = pprof_memProfileInternal(p, true)
if ok {
p = p[0:n]
break
fmt.Fprintf(w, "%d: %d [%d: %d] @",
r.InUseObjects(), r.InUseBytes(),
r.AllocObjects, r.AllocBytes)
- for _, pc := range r.Stack() {
+ for _, pc := range r.Stack {
fmt.Fprintf(w, " %#x", pc)
}
fmt.Fprintf(w, "\n")
- printStackRecord(w, r.Stack(), false)
+ printStackRecord(w, r.Stack, false)
}
// Print memstats information too.
// Until https://golang.org/issues/6104 is addressed, wrap
// ThreadCreateProfile because there's no point in tracking labels when we
// don't get any stack-traces.
- return writeRuntimeProfile(w, debug, "threadcreate", func(p []runtime.StackRecord, _ []unsafe.Pointer) (n int, ok bool) {
- return runtime.ThreadCreateProfile(p)
+ return writeRuntimeProfile(w, debug, "threadcreate", func(p []profilerecord.StackRecord, _ []unsafe.Pointer) (n int, ok bool) {
+ return pprof_threadCreateInternal(p)
})
}
return runtime.NumGoroutine()
}
-// runtime_goroutineProfileWithLabels is defined in runtime/mprof.go
-func runtime_goroutineProfileWithLabels(p []runtime.StackRecord, labels []unsafe.Pointer) (n int, ok bool)
-
// writeGoroutine writes the current runtime GoroutineProfile to w.
func writeGoroutine(w io.Writer, debug int) error {
if debug >= 2 {
return writeGoroutineStacks(w)
}
- return writeRuntimeProfile(w, debug, "goroutine", runtime_goroutineProfileWithLabels)
+ return writeRuntimeProfile(w, debug, "goroutine", pprof_goroutineProfileWithLabels)
}
func writeGoroutineStacks(w io.Writer) error {
return err
}
-func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]runtime.StackRecord, []unsafe.Pointer) (int, bool)) error {
+func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]profilerecord.StackRecord, []unsafe.Pointer) (int, bool)) error {
// Find out how many records there are (fetch(nil)),
// allocate that many records, and get the data.
// There's a race—more records might be added between
// the two calls—so allocate a few extra records for safety
// and also try again if we're very unlucky.
// The loop should only execute one iteration in the common case.
- var p []runtime.StackRecord
+ var p []profilerecord.StackRecord
var labels []unsafe.Pointer
n, ok := fetch(nil, nil)
// Allocate room for a slightly bigger profile,
// in case a few more entries have been added
// since the call to ThreadProfile.
- p = make([]runtime.StackRecord, n+10)
+ p = make([]profilerecord.StackRecord, n+10)
labels = make([]unsafe.Pointer, n+10)
n, ok = fetch(p, labels)
if ok {
}
type runtimeProfile struct {
- stk []runtime.StackRecord
+ stk []profilerecord.StackRecord
labels []unsafe.Pointer
}
func (p *runtimeProfile) Len() int { return len(p.stk) }
-func (p *runtimeProfile) Stack(i int) []uintptr { return p.stk[i].Stack() }
+func (p *runtimeProfile) Stack(i int) []uintptr { return p.stk[i].Stack }
func (p *runtimeProfile) Label(i int) *labelMap { return (*labelMap)(p.labels[i]) }
var cpu struct {
// writeBlock writes the current blocking profile to w.
func writeBlock(w io.Writer, debug int) error {
- return writeProfileInternal(w, debug, "contention", runtime.BlockProfile)
+ return writeProfileInternal(w, debug, "contention", pprof_blockProfileInternal)
}
// writeMutex writes the current mutex profile to w.
func writeMutex(w io.Writer, debug int) error {
- return writeProfileInternal(w, debug, "mutex", runtime.MutexProfile)
+ return writeProfileInternal(w, debug, "mutex", pprof_mutexProfileInternal)
}
// writeProfileInternal writes the current blocking or mutex profile depending on the passed parameters.
-func writeProfileInternal(w io.Writer, debug int, name string, runtimeProfile func([]runtime.BlockProfileRecord) (int, bool)) error {
- var p []runtime.BlockProfileRecord
+func writeProfileInternal(w io.Writer, debug int, name string, runtimeProfile func([]profilerecord.BlockProfileRecord) (int, bool)) error {
+ var p []profilerecord.BlockProfileRecord
n, ok := runtimeProfile(nil)
for {
- p = make([]runtime.BlockProfileRecord, n+50)
+ p = make([]profilerecord.BlockProfileRecord, n+50)
n, ok = runtimeProfile(p)
if ok {
p = p[:n]
w = tw
fmt.Fprintf(w, "--- %v:\n", name)
- fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond())
+ fmt.Fprintf(w, "cycles/second=%v\n", pprof_cyclesPerSecond())
if name == "mutex" {
fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1))
}
+ expandedStack := pprof_makeProfStack()
for i := range p {
r := &p[i]
fmt.Fprintf(w, "%v %v @", r.Cycles, r.Count)
- for _, pc := range r.Stack() {
+ n := pprof_fpunwindExpand(expandedStack, r.Stack)
+ stack := expandedStack[:n]
+ for _, pc := range stack {
fmt.Fprintf(w, " %#x", pc)
}
fmt.Fprint(w, "\n")
if debug > 0 {
- printStackRecord(w, r.Stack(), true)
+ printStackRecord(w, stack, true)
}
}
return b.Flush()
}
-func runtime_cyclesPerSecond() int64
+//go:linkname pprof_goroutineProfileWithLabels runtime.pprof_goroutineProfileWithLabels
+func pprof_goroutineProfileWithLabels(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool)
+
+//go:linkname pprof_cyclesPerSecond runtime.pprof_cyclesPerSecond
+func pprof_cyclesPerSecond() int64
+
+//go:linkname pprof_memProfileInternal runtime.pprof_memProfileInternal
+func pprof_memProfileInternal(p []profilerecord.MemProfileRecord, inuseZero bool) (n int, ok bool)
+
+//go:linkname pprof_blockProfileInternal runtime.pprof_blockProfileInternal
+func pprof_blockProfileInternal(p []profilerecord.BlockProfileRecord) (n int, ok bool)
+
+//go:linkname pprof_mutexProfileInternal runtime.pprof_mutexProfileInternal
+func pprof_mutexProfileInternal(p []profilerecord.BlockProfileRecord) (n int, ok bool)
+
+//go:linkname pprof_threadCreateInternal runtime.pprof_threadCreateInternal
+func pprof_threadCreateInternal(p []profilerecord.StackRecord) (n int, ok bool)
+
+//go:linkname pprof_fpunwindExpand runtime.pprof_fpunwindExpand
+func pprof_fpunwindExpand(dst, src []uintptr) int
+
+//go:linkname pprof_makeProfStack runtime.pprof_makeProfStack
+func pprof_makeProfStack() []uintptr