]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: use cgo traceback for SIGPROF
authorIan Lance Taylor <iant@golang.org>
Fri, 29 Apr 2016 22:20:27 +0000 (15:20 -0700)
committerIan Lance Taylor <iant@golang.org>
Wed, 4 May 2016 00:08:19 +0000 (00:08 +0000)
If we collected a cgo traceback when entering the SIGPROF signal
handler, record it as part of the profiling stack trace.

This serves as the promised test for https://golang.org/cl/21055 .

Change-Id: I5f60cd6cea1d9b7c3932211483a6bfab60ed21d2
Reviewed-on: https://go-review.googlesource.com/22650
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
src/runtime/crash_cgo_test.go
src/runtime/proc.go
src/runtime/testdata/testprogcgo/pprof.go [new file with mode: 0644]

index 0c6b3e887a6152258a02a8fc8c3c764fb6872a56..f9d665650bdc481d38374b34d4c4d481285fe780 100644 (file)
@@ -7,6 +7,7 @@
 package runtime_test
 
 import (
+       "bytes"
        "fmt"
        "internal/testenv"
        "os/exec"
@@ -232,3 +233,32 @@ func TestCgoTracebackContext(t *testing.T) {
                t.Errorf("expected %q got %v", want, got)
        }
 }
+
+func TestCgoPprof(t *testing.T) {
+       if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
+               t.Skipf("not yet supported on %s/%s", runtime.GOOS, runtime.GOARCH)
+       }
+       testenv.MustHaveGoRun(t)
+
+       exe, err := buildTestProg(t, "testprogcgo")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       got, err := testEnv(exec.Command(exe, "CgoPprof")).CombinedOutput()
+       if err != nil {
+               t.Fatal(err)
+       }
+       fn := strings.TrimSpace(string(got))
+
+       top, err := exec.Command("go", "tool", "pprof", "-top", "-nodecount=1", exe, fn).CombinedOutput()
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       t.Logf("%s", top)
+
+       if !bytes.Contains(top, []byte("cpuHog")) {
+               t.Error("missing cpuHog in pprof output")
+       }
+}
index 541fbaf8fbc54d8e4454a1347f8ea22b1a3f08c1..d7e51d7deb872ad025bb83e0b0c089250b81ddae 100644 (file)
@@ -3085,12 +3085,24 @@ func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
        var haveStackLock *g
        n := 0
        if mp.ncgo > 0 && mp.curg != nil && mp.curg.syscallpc != 0 && mp.curg.syscallsp != 0 {
-               // Cgo, we can't unwind and symbolize arbitrary C code,
-               // so instead collect Go stack that leads to the cgo call.
-               // This is especially important on windows, since all syscalls are cgo calls.
+               cgoOff := 0
+               // Check cgoCallersUse to make sure that we are not
+               // interrupting other code that is fiddling with
+               // cgoCallers.  We are running in a signal handler
+               // with all signals blocked, so we don't have to worry
+               // about any other code interrupting us.
+               if atomic.Load(&mp.cgoCallersUse) == 0 && mp.cgoCallers != nil && mp.cgoCallers[0] != 0 {
+                       for cgoOff < len(mp.cgoCallers) && mp.cgoCallers[cgoOff] != 0 {
+                               cgoOff++
+                       }
+                       copy(stk[:], mp.cgoCallers[:cgoOff])
+                       mp.cgoCallers[0] = 0
+               }
+
+               // Collect Go stack that leads to the cgo call.
                if gcTryLockStackBarriers(mp.curg) {
                        haveStackLock = mp.curg
-                       n = gentraceback(mp.curg.syscallpc, mp.curg.syscallsp, 0, mp.curg, 0, &stk[0], len(stk), nil, nil, 0)
+                       n = gentraceback(mp.curg.syscallpc, mp.curg.syscallsp, 0, mp.curg, 0, &stk[cgoOff], len(stk)-cgoOff, nil, nil, 0)
                }
        } else if traceback {
                var flags uint = _TraceTrap
diff --git a/src/runtime/testdata/testprogcgo/pprof.go b/src/runtime/testdata/testprogcgo/pprof.go
new file mode 100644 (file)
index 0000000..04ac4fe
--- /dev/null
@@ -0,0 +1,96 @@
+// Copyright 2016 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 main
+
+// Run a slow C function saving a CPU profile.
+
+/*
+#include <stdint.h>
+
+int salt1;
+int salt2;
+
+void cpuHog() {
+       int foo = salt1;
+       int i;
+
+       for (i = 0; i < 100000; i++) {
+               if (foo > 0) {
+                       foo *= foo;
+               } else {
+                       foo *= foo + 1;
+               }
+       }
+       salt2 = foo;
+}
+
+static int cpuHogCount;
+
+struct cgoTracebackArg {
+       uintptr_t  context;
+       uintptr_t* buf;
+       uintptr_t  max;
+};
+
+// pprofCgoTraceback is passed to runtime.SetCgoTraceback.
+// For testing purposes it pretends that all CPU hits in C code are in cpuHog.
+void pprofCgoTraceback(void* parg) {
+       struct cgoTracebackArg* arg = (struct cgoTracebackArg*)(parg);
+       arg->buf[0] = (uintptr_t)(cpuHog) + 0x10;
+       arg->buf[1] = 0;
+       ++cpuHogCount;
+}
+
+// getCpuHogCount fetches the number of times we've seen cpuHog in the
+// traceback.
+int getCpuHogCount() {
+       return cpuHogCount;
+}
+*/
+import "C"
+
+import (
+       "fmt"
+       "io/ioutil"
+       "os"
+       "runtime"
+       "runtime/pprof"
+       "time"
+       "unsafe"
+)
+
+func init() {
+       register("CgoPprof", CgoPprof)
+}
+
+func CgoPprof() {
+       runtime.SetCgoTraceback(0, unsafe.Pointer(C.pprofCgoTraceback), nil, nil)
+
+       f, err := ioutil.TempFile("", "prof")
+       if err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(2)
+       }
+
+       if err := pprof.StartCPUProfile(f); err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(2)
+       }
+
+       t0 := time.Now()
+       for C.getCpuHogCount() < 2 && time.Since(t0) < time.Second {
+               C.cpuHog()
+       }
+
+       pprof.StopCPUProfile()
+
+       name := f.Name()
+       if err := f.Close(); err != nil {
+               fmt.Fprintln(os.Stderr, err)
+               os.Exit(2)
+       }
+
+       fmt.Println(name)
+}