mirror of
https://github.com/autc04/Retro68.git
synced 2024-09-07 13:54:33 +00:00
455 lines
13 KiB
Go
455 lines
13 KiB
Go
// Copyright 2011 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.
|
|
|
|
// CPU profiling.
|
|
// Based on algorithms and data structures used in
|
|
// https://github.com/google/pprof.
|
|
//
|
|
// The main difference between this code and the google-perftools
|
|
// code is that this code is written to allow copying the profile data
|
|
// to an arbitrary io.Writer, while the google-perftools code always
|
|
// writes to an operating system file.
|
|
//
|
|
// The signal handler for the profiling clock tick adds a new stack trace
|
|
// to a hash table tracking counts for recent traces. Most clock ticks
|
|
// hit in the cache. In the event of a cache miss, an entry must be
|
|
// evicted from the hash table, copied to a log that will eventually be
|
|
// written as profile data. The google-perftools code flushed the
|
|
// log itself during the signal handler. This code cannot do that, because
|
|
// the io.Writer might block or need system calls or locks that are not
|
|
// safe to use from within the signal handler. Instead, we split the log
|
|
// into two halves and let the signal handler fill one half while a goroutine
|
|
// is writing out the other half. When the signal handler fills its half, it
|
|
// offers to swap with the goroutine. If the writer is not done with its half,
|
|
// we lose the stack trace for this clock tick (and record that loss).
|
|
// The goroutine interacts with the signal handler by calling getprofile() to
|
|
// get the next log piece to write, implicitly handing back the last log
|
|
// piece it obtained.
|
|
//
|
|
// The state of this dance between the signal handler and the goroutine
|
|
// is encoded in the Profile.handoff field. If handoff == 0, then the goroutine
|
|
// is not using either log half and is waiting (or will soon be waiting) for
|
|
// a new piece by calling notesleep(&p.wait). If the signal handler
|
|
// changes handoff from 0 to non-zero, it must call notewakeup(&p.wait)
|
|
// to wake the goroutine. The value indicates the number of entries in the
|
|
// log half being handed off. The goroutine leaves the non-zero value in
|
|
// place until it has finished processing the log half and then flips the number
|
|
// back to zero. Setting the high bit in handoff means that the profiling is over,
|
|
// and the goroutine is now in charge of flushing the data left in the hash table
|
|
// to the log and returning that data.
|
|
//
|
|
// The handoff field is manipulated using atomic operations.
|
|
// For the most part, the manipulation of handoff is orderly: if handoff == 0
|
|
// then the signal handler owns it and can change it to non-zero.
|
|
// If handoff != 0 then the goroutine owns it and can change it to zero.
|
|
// If that were the end of the story then we would not need to manipulate
|
|
// handoff using atomic operations. The operations are needed, however,
|
|
// in order to let the log closer set the high bit to indicate "EOF" safely
|
|
// in the situation when normally the goroutine "owns" handoff.
|
|
|
|
package runtime
|
|
|
|
import (
|
|
"runtime/internal/atomic"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
numBuckets = 1 << 10
|
|
logSize = 1 << 17
|
|
assoc = 4
|
|
maxCPUProfStack = 64
|
|
)
|
|
|
|
type cpuprofEntry struct {
|
|
count uintptr
|
|
depth int
|
|
stack [maxCPUProfStack]uintptr
|
|
}
|
|
|
|
//go:notinheap
|
|
type cpuProfile struct {
|
|
on bool // profiling is on
|
|
wait note // goroutine waits here
|
|
count uintptr // tick count
|
|
evicts uintptr // eviction count
|
|
lost uintptr // lost ticks that need to be logged
|
|
|
|
// Active recent stack traces.
|
|
hash [numBuckets]struct {
|
|
entry [assoc]cpuprofEntry
|
|
}
|
|
|
|
// Log of traces evicted from hash.
|
|
// Signal handler has filled log[toggle][:nlog].
|
|
// Goroutine is writing log[1-toggle][:handoff].
|
|
log [2][logSize / 2]uintptr
|
|
nlog int
|
|
toggle int32
|
|
handoff uint32
|
|
|
|
// Writer state.
|
|
// Writer maintains its own toggle to avoid races
|
|
// looking at signal handler's toggle.
|
|
wtoggle uint32
|
|
wholding bool // holding & need to release a log half
|
|
flushing bool // flushing hash table - profile is over
|
|
eodSent bool // special end-of-data record sent; => flushing
|
|
}
|
|
|
|
var (
|
|
cpuprofLock mutex
|
|
cpuprof *cpuProfile
|
|
|
|
eod = [3]uintptr{0, 1, 0}
|
|
)
|
|
|
|
func setcpuprofilerate(hz int32) {
|
|
systemstack(func() {
|
|
setcpuprofilerate_m(hz)
|
|
})
|
|
}
|
|
|
|
// lostProfileData is a no-op function used in profiles
|
|
// to mark the number of profiling stack traces that were
|
|
// discarded due to slow data writers.
|
|
func lostProfileData() {}
|
|
|
|
// SetCPUProfileRate sets the CPU profiling rate to hz samples per second.
|
|
// If hz <= 0, SetCPUProfileRate turns off profiling.
|
|
// If the profiler is on, the rate cannot be changed without first turning it off.
|
|
//
|
|
// Most clients should use the runtime/pprof package or
|
|
// the testing package's -test.cpuprofile flag instead of calling
|
|
// SetCPUProfileRate directly.
|
|
func SetCPUProfileRate(hz int) {
|
|
// Clamp hz to something reasonable.
|
|
if hz < 0 {
|
|
hz = 0
|
|
}
|
|
if hz > 1000000 {
|
|
hz = 1000000
|
|
}
|
|
|
|
lock(&cpuprofLock)
|
|
if hz > 0 {
|
|
if cpuprof == nil {
|
|
cpuprof = (*cpuProfile)(sysAlloc(unsafe.Sizeof(cpuProfile{}), &memstats.other_sys))
|
|
if cpuprof == nil {
|
|
print("runtime: cpu profiling cannot allocate memory\n")
|
|
unlock(&cpuprofLock)
|
|
return
|
|
}
|
|
}
|
|
if cpuprof.on || cpuprof.handoff != 0 {
|
|
print("runtime: cannot set cpu profile rate until previous profile has finished.\n")
|
|
unlock(&cpuprofLock)
|
|
return
|
|
}
|
|
|
|
cpuprof.on = true
|
|
// pprof binary header format.
|
|
// https://github.com/gperftools/gperftools/blob/master/src/profiledata.cc#L119
|
|
p := &cpuprof.log[0]
|
|
p[0] = 0 // count for header
|
|
p[1] = 3 // depth for header
|
|
p[2] = 0 // version number
|
|
p[3] = uintptr(1e6 / hz) // period (microseconds)
|
|
p[4] = 0
|
|
cpuprof.nlog = 5
|
|
cpuprof.toggle = 0
|
|
cpuprof.wholding = false
|
|
cpuprof.wtoggle = 0
|
|
cpuprof.flushing = false
|
|
cpuprof.eodSent = false
|
|
noteclear(&cpuprof.wait)
|
|
|
|
setcpuprofilerate(int32(hz))
|
|
} else if cpuprof != nil && cpuprof.on {
|
|
setcpuprofilerate(0)
|
|
cpuprof.on = false
|
|
|
|
// Now add is not running anymore, and getprofile owns the entire log.
|
|
// Set the high bit in cpuprof.handoff to tell getprofile.
|
|
for {
|
|
n := cpuprof.handoff
|
|
if n&0x80000000 != 0 {
|
|
print("runtime: setcpuprofile(off) twice\n")
|
|
}
|
|
if atomic.Cas(&cpuprof.handoff, n, n|0x80000000) {
|
|
if n == 0 {
|
|
// we did the transition from 0 -> nonzero so we wake getprofile
|
|
notewakeup(&cpuprof.wait)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
unlock(&cpuprofLock)
|
|
}
|
|
|
|
// add adds the stack trace to the profile.
|
|
// It is called from signal handlers and other limited environments
|
|
// and cannot allocate memory or acquire locks that might be
|
|
// held at the time of the signal, nor can it use substantial amounts
|
|
// of stack. It is allowed to call evict.
|
|
//go:nowritebarrierrec
|
|
func (p *cpuProfile) add(pc []uintptr) {
|
|
p.addWithFlushlog(pc, p.flushlog)
|
|
}
|
|
|
|
// addWithFlushlog implements add and addNonGo.
|
|
// It is called from signal handlers and other limited environments
|
|
// and cannot allocate memory or acquire locks that might be
|
|
// held at the time of the signal, nor can it use substantial amounts
|
|
// of stack. It may be called by a signal handler with no g or m.
|
|
// It is allowed to call evict, passing the flushlog parameter.
|
|
//go:nosplit
|
|
//go:nowritebarrierrec
|
|
func (p *cpuProfile) addWithFlushlog(pc []uintptr, flushlog func() bool) {
|
|
if len(pc) > maxCPUProfStack {
|
|
pc = pc[:maxCPUProfStack]
|
|
}
|
|
|
|
// Compute hash.
|
|
h := uintptr(0)
|
|
for _, x := range pc {
|
|
h = h<<8 | (h >> (8 * (unsafe.Sizeof(h) - 1)))
|
|
h += x * 41
|
|
}
|
|
p.count++
|
|
|
|
// Add to entry count if already present in table.
|
|
b := &p.hash[h%numBuckets]
|
|
Assoc:
|
|
for i := range b.entry {
|
|
e := &b.entry[i]
|
|
if e.depth != len(pc) {
|
|
continue
|
|
}
|
|
for j := range pc {
|
|
if e.stack[j] != pc[j] {
|
|
continue Assoc
|
|
}
|
|
}
|
|
e.count++
|
|
return
|
|
}
|
|
|
|
// Evict entry with smallest count.
|
|
var e *cpuprofEntry
|
|
for i := range b.entry {
|
|
if e == nil || b.entry[i].count < e.count {
|
|
e = &b.entry[i]
|
|
}
|
|
}
|
|
if e.count > 0 {
|
|
if !p.evict(e, flushlog) {
|
|
// Could not evict entry. Record lost stack.
|
|
p.lost++
|
|
return
|
|
}
|
|
p.evicts++
|
|
}
|
|
|
|
// Reuse the newly evicted entry.
|
|
e.depth = len(pc)
|
|
e.count = 1
|
|
copy(e.stack[:], pc)
|
|
}
|
|
|
|
// evict copies the given entry's data into the log, so that
|
|
// the entry can be reused. evict is called from add, which
|
|
// is called from the profiling signal handler, so it must not
|
|
// allocate memory or block, and it may be called with no g or m.
|
|
// It is safe to call flushlog. evict returns true if the entry was
|
|
// copied to the log, false if there was no room available.
|
|
//go:nosplit
|
|
//go:nowritebarrierrec
|
|
func (p *cpuProfile) evict(e *cpuprofEntry, flushlog func() bool) bool {
|
|
d := e.depth
|
|
nslot := d + 2
|
|
log := &p.log[p.toggle]
|
|
if p.nlog+nslot > len(log) {
|
|
if !flushlog() {
|
|
return false
|
|
}
|
|
log = &p.log[p.toggle]
|
|
}
|
|
|
|
q := p.nlog
|
|
log[q] = e.count
|
|
q++
|
|
log[q] = uintptr(d)
|
|
q++
|
|
copy(log[q:], e.stack[:d])
|
|
q += d
|
|
p.nlog = q
|
|
e.count = 0
|
|
return true
|
|
}
|
|
|
|
// flushlog tries to flush the current log and switch to the other one.
|
|
// flushlog is called from evict, called from add, called from the signal handler,
|
|
// so it cannot allocate memory or block. It can try to swap logs with
|
|
// the writing goroutine, as explained in the comment at the top of this file.
|
|
//go:nowritebarrierrec
|
|
func (p *cpuProfile) flushlog() bool {
|
|
if !atomic.Cas(&p.handoff, 0, uint32(p.nlog)) {
|
|
return false
|
|
}
|
|
notewakeup(&p.wait)
|
|
|
|
p.toggle = 1 - p.toggle
|
|
log := &p.log[p.toggle]
|
|
q := 0
|
|
if p.lost > 0 {
|
|
lostPC := funcPC(lostProfileData)
|
|
log[0] = p.lost
|
|
log[1] = 1
|
|
log[2] = lostPC
|
|
q = 3
|
|
p.lost = 0
|
|
}
|
|
p.nlog = q
|
|
return true
|
|
}
|
|
|
|
// addNonGo is like add, but runs on a non-Go thread.
|
|
// It can't do anything that might need a g or an m.
|
|
// With this entry point, we don't try to flush the log when evicting an
|
|
// old entry. Instead, we just drop the stack trace if we're out of space.
|
|
//go:nosplit
|
|
//go:nowritebarrierrec
|
|
func (p *cpuProfile) addNonGo(pc []uintptr) {
|
|
p.addWithFlushlog(pc, func() bool { return false })
|
|
}
|
|
|
|
// getprofile blocks until the next block of profiling data is available
|
|
// and returns it as a []byte. It is called from the writing goroutine.
|
|
func (p *cpuProfile) getprofile() []byte {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
if p.wholding {
|
|
// Release previous log to signal handling side.
|
|
// Loop because we are racing against SetCPUProfileRate(0).
|
|
for {
|
|
n := p.handoff
|
|
if n == 0 {
|
|
print("runtime: phase error during cpu profile handoff\n")
|
|
return nil
|
|
}
|
|
if n&0x80000000 != 0 {
|
|
p.wtoggle = 1 - p.wtoggle
|
|
p.wholding = false
|
|
p.flushing = true
|
|
goto Flush
|
|
}
|
|
if atomic.Cas(&p.handoff, n, 0) {
|
|
break
|
|
}
|
|
}
|
|
p.wtoggle = 1 - p.wtoggle
|
|
p.wholding = false
|
|
}
|
|
|
|
if p.flushing {
|
|
goto Flush
|
|
}
|
|
|
|
if !p.on && p.handoff == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Wait for new log.
|
|
notetsleepg(&p.wait, -1)
|
|
noteclear(&p.wait)
|
|
|
|
switch n := p.handoff; {
|
|
case n == 0:
|
|
print("runtime: phase error during cpu profile wait\n")
|
|
return nil
|
|
case n == 0x80000000:
|
|
p.flushing = true
|
|
goto Flush
|
|
default:
|
|
n &^= 0x80000000
|
|
|
|
// Return new log to caller.
|
|
p.wholding = true
|
|
|
|
return uintptrBytes(p.log[p.wtoggle][:n])
|
|
}
|
|
|
|
// In flush mode.
|
|
// Add is no longer being called. We own the log.
|
|
// Also, p.handoff is non-zero, so flushlog will return false.
|
|
// Evict the hash table into the log and return it.
|
|
Flush:
|
|
for i := range p.hash {
|
|
b := &p.hash[i]
|
|
for j := range b.entry {
|
|
e := &b.entry[j]
|
|
if e.count > 0 && !p.evict(e, p.flushlog) {
|
|
// Filled the log. Stop the loop and return what we've got.
|
|
break Flush
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return pending log data.
|
|
if p.nlog > 0 {
|
|
// Note that we're using toggle now, not wtoggle,
|
|
// because we're working on the log directly.
|
|
n := p.nlog
|
|
p.nlog = 0
|
|
return uintptrBytes(p.log[p.toggle][:n])
|
|
}
|
|
|
|
// Made it through the table without finding anything to log.
|
|
if !p.eodSent {
|
|
// We may not have space to append this to the partial log buf,
|
|
// so we always return a new slice for the end-of-data marker.
|
|
p.eodSent = true
|
|
return uintptrBytes(eod[:])
|
|
}
|
|
|
|
// Finally done. Clean up and return nil.
|
|
p.flushing = false
|
|
if !atomic.Cas(&p.handoff, p.handoff, 0) {
|
|
print("runtime: profile flush racing with something\n")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func uintptrBytes(p []uintptr) (ret []byte) {
|
|
pp := (*slice)(unsafe.Pointer(&p))
|
|
rp := (*slice)(unsafe.Pointer(&ret))
|
|
|
|
rp.array = pp.array
|
|
rp.len = pp.len * int(unsafe.Sizeof(p[0]))
|
|
rp.cap = rp.len
|
|
|
|
return
|
|
}
|
|
|
|
// CPUProfile returns the next chunk of binary CPU profiling stack trace data,
|
|
// blocking until data is available. If profiling is turned off and all the profile
|
|
// data accumulated while it was on has been returned, CPUProfile returns nil.
|
|
// The caller must save the returned data before calling CPUProfile again.
|
|
//
|
|
// Most clients should use the runtime/pprof package or
|
|
// the testing package's -test.cpuprofile flag instead of calling
|
|
// CPUProfile directly.
|
|
func CPUProfile() []byte {
|
|
return cpuprof.getprofile()
|
|
}
|
|
|
|
//go:linkname runtime_pprof_runtime_cyclesPerSecond runtime_pprof.runtime_cyclesPerSecond
|
|
func runtime_pprof_runtime_cyclesPerSecond() int64 {
|
|
return tickspersecond()
|
|
}
|