diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go index b702fd345d..901467fa9a 100644 --- a/src/runtime/pprof/pprof.go +++ b/src/runtime/pprof/pprof.go @@ -296,22 +296,25 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro } return buf.String() } - m := map[string]int{} + count := map[string]int{} + index := map[string]int{} + var keys []string n := p.Len() for i := 0; i < n; i++ { - m[key(p.Stack(i))]++ + k := key(p.Stack(i)) + if count[k] == 0 { + index[k] = i + keys = append(keys, k) + } + count[k]++ } - // Print stacks, listing count on first occurrence of a unique stack. - for i := 0; i < n; i++ { - stk := p.Stack(i) - s := key(stk) - if count := m[s]; count != 0 { - fmt.Fprintf(w, "%d %s\n", count, s) - if debug > 0 { - printStackRecord(w, stk, false) - } - delete(m, s) + sort.Sort(&keysByCount{keys, count}) + + for _, k := range keys { + fmt.Fprintf(w, "%d %s\n", count[k], k) + if debug > 0 { + printStackRecord(w, p.Stack(index[k]), false) } } @@ -321,6 +324,23 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro return b.Flush() } +// keysByCount sorts keys with higher counts first, breaking ties by key string order. +type keysByCount struct { + keys []string + count map[string]int +} + +func (x *keysByCount) Len() int { return len(x.keys) } +func (x *keysByCount) Swap(i, j int) { x.keys[i], x.keys[j] = x.keys[j], x.keys[i] } +func (x *keysByCount) Less(i, j int) bool { + ki, kj := x.keys[i], x.keys[j] + ci, cj := x.count[ki], x.count[kj] + if ci != cj { + return ci > cj + } + return ki < kj +} + // printStackRecord prints the function + source line information // for a single stack trace. func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) { diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index d232257c20..fa0af59b37 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -578,3 +578,47 @@ func blockCond() { c.Wait() mu.Unlock() } + +func func1(c chan int) { <-c } +func func2(c chan int) { <-c } +func func3(c chan int) { <-c } +func func4(c chan int) { <-c } + +func TestGoroutineCounts(t *testing.T) { + c := make(chan int) + for i := 0; i < 100; i++ { + if i%10 == 0 { + go func1(c) + continue + } + if i%2 == 0 { + go func2(c) + continue + } + go func3(c) + } + time.Sleep(10 * time.Millisecond) // let goroutines block on channel + + var w bytes.Buffer + Lookup("goroutine").WriteTo(&w, 1) + prof := w.String() + + if !containsInOrder(prof, "\n50 @ ", "\n40 @", "\n10 @", "\n1 @") { + t.Errorf("expected sorted goroutine counts:\n%s", prof) + } + + close(c) + + time.Sleep(10 * time.Millisecond) // let goroutines exit +} + +func containsInOrder(s string, all ...string) bool { + for _, t := range all { + i := strings.Index(s, t) + if i < 0 { + return false + } + s = s[i+len(t):] + } + return true +}