Commit b47ec598 authored by Russ Cox's avatar Russ Cox

runtime/pprof: cpu profiling support

R=r, bradfitzgo, r2
CC=golang-dev
https://golang.org/cl/4313041
parent 059c07ca
......@@ -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
}
// 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")
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment