| // 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() | |
| } |