mirror of
https://github.com/golang/go
synced 2024-11-19 23:24:45 -07:00
runtime/pprof: cpu profiling support
R=r, bradfitzgo, r2 CC=golang-dev https://golang.org/cl/4313041
This commit is contained in:
parent
059c07cab0
commit
b47ec598b7
@ -184,7 +184,6 @@ NOTEST=\
|
||||
net/dict\
|
||||
rand\
|
||||
runtime/cgo\
|
||||
runtime/pprof\
|
||||
syscall\
|
||||
testing\
|
||||
testing/iotest\
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WriteHeapProfile writes a pprof-formatted heap profile to w.
|
||||
@ -105,3 +106,71 @@ func WriteHeapProfile(w io.Writer) os.Error {
|
||||
}
|
||||
return b.Flush()
|
||||
}
|
||||
|
||||
var cpu struct {
|
||||
sync.Mutex
|
||||
profiling bool
|
||||
done chan bool
|
||||
}
|
||||
|
||||
// StartCPUProfile enables CPU profiling for the current process.
|
||||
// While profiling, the profile will be buffered and written to w.
|
||||
// StartCPUProfile returns an error if profiling is already enabled.
|
||||
func StartCPUProfile(w io.Writer) os.Error {
|
||||
// The runtime routines allow a variable profiling rate,
|
||||
// but in practice operating systems cannot trigger signals
|
||||
// at more than about 500 Hz, and our processing of the
|
||||
// signal is not cheap (mostly getting the stack trace).
|
||||
// 100 Hz is a reasonable choice: it is frequent enough to
|
||||
// produce useful data, rare enough not to bog down the
|
||||
// system, and a nice round number to make it easy to
|
||||
// convert sample counts to seconds. Instead of requiring
|
||||
// each client to specify the frequency, we hard code it.
|
||||
const hz = 100
|
||||
|
||||
// Avoid queueing behind StopCPUProfile.
|
||||
// Could use TryLock instead if we had it.
|
||||
if cpu.profiling {
|
||||
return fmt.Errorf("cpu profiling already in use")
|
||||
}
|
||||
|
||||
cpu.Lock()
|
||||
defer cpu.Unlock()
|
||||
if cpu.done == nil {
|
||||
cpu.done = make(chan bool)
|
||||
}
|
||||
// Double-check.
|
||||
if cpu.profiling {
|
||||
return fmt.Errorf("cpu profiling already in use")
|
||||
}
|
||||
cpu.profiling = true
|
||||
runtime.SetCPUProfileRate(hz)
|
||||
go profileWriter(w)
|
||||
return nil
|
||||
}
|
||||
|
||||
func profileWriter(w io.Writer) {
|
||||
for {
|
||||
data := runtime.CPUProfile()
|
||||
if data == nil {
|
||||
break
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
||||
cpu.done <- true
|
||||
}
|
||||
|
||||
// StopCPUProfile stops the current CPU profile, if any.
|
||||
// StopCPUProfile only returns after all the writes for the
|
||||
// profile have completed.
|
||||
func StopCPUProfile() {
|
||||
cpu.Lock()
|
||||
defer cpu.Unlock()
|
||||
|
||||
if !cpu.profiling {
|
||||
return
|
||||
}
|
||||
cpu.profiling = false
|
||||
runtime.SetCPUProfileRate(0)
|
||||
<-cpu.done
|
||||
}
|
||||
|
69
src/pkg/runtime/pprof/pprof_test.go
Normal file
69
src/pkg/runtime/pprof/pprof_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
// 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.
|
||||
|
||||
package pprof_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"hash/crc32"
|
||||
"runtime"
|
||||
. "runtime/pprof"
|
||||
"strings"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestCPUProfile(t *testing.T) {
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 100000)
|
||||
var prof bytes.Buffer
|
||||
if err := StartCPUProfile(&prof); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// This loop takes about a quarter second on a 2 GHz laptop.
|
||||
// We only need to get one 100 Hz clock tick, so we've got
|
||||
// a 25x safety buffer.
|
||||
for i := 0; i < 1000; i++ {
|
||||
crc32.ChecksumIEEE(buf)
|
||||
}
|
||||
StopCPUProfile()
|
||||
|
||||
// Convert []byte to []uintptr.
|
||||
bytes := prof.Bytes()
|
||||
val := *(*[]uintptr)(unsafe.Pointer(&bytes))
|
||||
val = val[:len(bytes)/unsafe.Sizeof(uintptr(0))]
|
||||
|
||||
if len(val) < 10 {
|
||||
t.Fatalf("profile too short: %#x", val)
|
||||
}
|
||||
if val[0] != 0 || val[1] != 3 || val[2] != 0 || val[3] != 1e6/100 || val[4] != 0 {
|
||||
t.Fatalf("unexpected header %#x", val[:5])
|
||||
}
|
||||
|
||||
// Check that profile is well formed and contains ChecksumIEEE.
|
||||
found := false
|
||||
val = val[5:]
|
||||
for len(val) > 0 {
|
||||
if len(val) < 2 || val[0] < 1 || val[1] < 1 || uintptr(len(val)) < 2+val[1] {
|
||||
t.Fatalf("malformed profile. leftover: %#x", val)
|
||||
}
|
||||
for _, pc := range val[2 : 2+val[1]] {
|
||||
f := runtime.FuncForPC(pc)
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(f.Name(), "ChecksumIEEE") {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
val = val[2+val[1]:]
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("did not find ChecksumIEEE in the profile")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user