]> Cypherpunks repositories - gostls13.git/commitdiff
runtime/pprof: cpu profiling support
authorRuss Cox <rsc@golang.org>
Wed, 23 Mar 2011 17:54:31 +0000 (13:54 -0400)
committerRuss Cox <rsc@golang.org>
Wed, 23 Mar 2011 17:54:31 +0000 (13:54 -0400)
R=r, bradfitzgo, r2
CC=golang-dev
https://golang.org/cl/4313041

src/pkg/Makefile
src/pkg/runtime/pprof/pprof.go
src/pkg/runtime/pprof/pprof_test.go [new file with mode: 0644]

index 24b304346de177c2be4defe763e04b398b28c920..3a2a479f5e1a23d90588fc8dbeb74ce91704615a 100644 (file)
@@ -184,7 +184,6 @@ NOTEST=\
        net/dict\
        rand\
        runtime/cgo\
-       runtime/pprof\
        syscall\
        testing\
        testing/iotest\
index 9bee5112819dff25ded674bbe3023ec77973f6a6..fdeceb4e8dc6747e612615f92c38cceccc188b8b 100644 (file)
@@ -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 (file)
index 0000000..603465e
--- /dev/null
@@ -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")
+       }
+}