From: Russ Cox Date: Wed, 23 Mar 2011 17:54:31 +0000 (-0400) Subject: runtime/pprof: cpu profiling support X-Git-Tag: weekly.2011-03-28~54 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=b47ec598b79bb28a14bce146fc47cb9a4700ba3a;p=gostls13.git runtime/pprof: cpu profiling support R=r, bradfitzgo, r2 CC=golang-dev https://golang.org/cl/4313041 --- diff --git a/src/pkg/Makefile b/src/pkg/Makefile index 24b304346d..3a2a479f5e 100644 --- a/src/pkg/Makefile +++ b/src/pkg/Makefile @@ -184,7 +184,6 @@ NOTEST=\ net/dict\ rand\ runtime/cgo\ - runtime/pprof\ syscall\ testing\ testing/iotest\ diff --git a/src/pkg/runtime/pprof/pprof.go b/src/pkg/runtime/pprof/pprof.go index 9bee511281..fdeceb4e8d 100644 --- a/src/pkg/runtime/pprof/pprof.go +++ b/src/pkg/runtime/pprof/pprof.go @@ -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 +} diff --git a/src/pkg/runtime/pprof/pprof_test.go b/src/pkg/runtime/pprof/pprof_test.go new file mode 100644 index 0000000000..603465eaa5 --- /dev/null +++ b/src/pkg/runtime/pprof/pprof_test.go @@ -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") + } +}