diff options
| author | Peter Weinberger <pjw@google.com> | 2016-09-22 09:48:30 -0400 |
|---|---|---|
| committer | Peter Weinberger <pjw@google.com> | 2016-10-28 11:47:16 +0000 |
| commit | ca922b6d363b6ca47822188dcbc5b92d912c7a4b (patch) | |
| tree | 5bee54a86cd9db992e5d77e735b94d9a0eed9a17 /src/runtime/pprof | |
| parent | b679665a182bd6ec2989ae759df6b11142921cfb (diff) | |
| download | go-ca922b6d363b6ca47822188dcbc5b92d912c7a4b.tar.xz | |
runtime: Profile goroutines holding contended mutexes.
runtime.SetMutexProfileFraction(n int) will capture 1/n-th of stack
traces of goroutines holding contended mutexes if n > 0. From runtime/pprof,
pprot.Lookup("mutex").WriteTo writes the accumulated
stack traces to w (in essentially the same format that blocking
profiling uses).
Change-Id: Ie0b54fa4226853d99aa42c14cb529ae586a8335a
Reviewed-on: https://go-review.googlesource.com/29650
Reviewed-by: Austin Clements <austin@google.com>
Diffstat (limited to 'src/runtime/pprof')
| -rw-r--r-- | src/runtime/pprof/pprof.go | 59 | ||||
| -rw-r--r-- | src/runtime/pprof/pprof_test.go | 36 |
2 files changed, 95 insertions, 0 deletions
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go index 9fd477908c..20ccb51b00 100644 --- a/src/runtime/pprof/pprof.go +++ b/src/runtime/pprof/pprof.go @@ -99,6 +99,7 @@ import ( // heap - a sampling of all heap allocations // threadcreate - stack traces that led to the creation of new OS threads // block - stack traces that led to blocking on synchronization primitives +// mutex - stack traces of holders of contended mutexes // // These predefined profiles maintain themselves and panic on an explicit // Add or Remove method call. @@ -152,6 +153,12 @@ var blockProfile = &Profile{ write: writeBlock, } +var mutexProfile = &Profile{ + name: "mutex", + count: countMutex, + write: writeMutex, +} + func lockProfiles() { profiles.mu.Lock() if profiles.m == nil { @@ -161,6 +168,7 @@ func lockProfiles() { "threadcreate": threadcreateProfile, "heap": heapProfile, "block": blockProfile, + "mutex": mutexProfile, } } } @@ -729,6 +737,12 @@ func countBlock() int { return n } +// countMutex returns the number of records in the mutex profile. +func countMutex() int { + n, _ := runtime.MutexProfile(nil) + return n +} + // writeBlock writes the current blocking profile to w. func writeBlock(w io.Writer, debug int) error { var p []runtime.BlockProfileRecord @@ -772,4 +786,49 @@ func writeBlock(w io.Writer, debug int) error { return b.Flush() } +// writeMutex writes the current mutex profile to w. +func writeMutex(w io.Writer, debug int) error { + // TODO(pjw): too much common code with writeBlock. FIX! + var p []runtime.BlockProfileRecord + n, ok := runtime.MutexProfile(nil) + for { + p = make([]runtime.BlockProfileRecord, n+50) + n, ok = runtime.MutexProfile(p) + if ok { + p = p[:n] + break + } + } + + sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) + + b := bufio.NewWriter(w) + var tw *tabwriter.Writer + w = b + if debug > 0 { + tw = tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) + w = tw + } + + fmt.Fprintf(w, "--- mutex:\n") + fmt.Fprintf(w, "cycles/second=%v\n", runtime_cyclesPerSecond()) + fmt.Fprintf(w, "sampling period=%d\n", runtime.SetMutexProfileFraction(-1)) + for i := range p { + r := &p[i] + fmt.Fprintf(w, "%v %v @", r.Cycles, r.Count) + for _, pc := range r.Stack() { + fmt.Fprintf(w, " %#x", pc) + } + fmt.Fprint(w, "\n") + if debug > 0 { + printStackRecord(w, r.Stack(), true) + } + } + + if tw != nil { + tw.Flush() + } + return b.Flush() +} + func runtime_cyclesPerSecond() int64 diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index a0930155a5..a683a03211 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -592,6 +592,42 @@ func blockCond() { mu.Unlock() } +func TestMutexProfile(t *testing.T) { + old := runtime.SetMutexProfileFraction(1) + defer runtime.SetMutexProfileFraction(old) + if old != 0 { + t.Fatalf("need MutexProfileRate 0, got %d", old) + } + + blockMutex() + + var w bytes.Buffer + Lookup("mutex").WriteTo(&w, 1) + prof := w.String() + + if !strings.HasPrefix(prof, "--- mutex:\ncycles/second=") { + t.Errorf("Bad profile header:\n%v", prof) + } + prof = strings.Trim(prof, "\n") + lines := strings.Split(prof, "\n") + if len(lines) != 6 { + t.Errorf("expected 6 lines, got %d %q\n%s", len(lines), prof, prof) + } + if len(lines) < 6 { + return + } + // checking that the line is like "35258904 1 @ 0x48288d 0x47cd28 0x458931" + r2 := `^\d+ 1 @(?: 0x[[:xdigit:]]+)+` + //r2 := "^[0-9]+ 1 @ 0x[0-9a-f x]+$" + if ok, err := regexp.MatchString(r2, lines[3]); err != nil || !ok { + t.Errorf("%q didn't match %q", lines[3], r2) + } + r3 := "^#.*runtime/pprof_test.blockMutex.*$" + if ok, err := regexp.MatchString(r3, lines[5]); err != nil || !ok { + t.Errorf("%q didn't match %q", lines[5], r3) + } +} + func func1(c chan int) { <-c } func func2(c chan int) { <-c } func func3(c chan int) { <-c } |
