Skip to content

Commit 38c2c12

Browse files
dfinkelhyangah
authored andcommitted
runtime/pprof: plumb labels for goroutine profiles
Goroutines are directly associated with labels. It's relatively easy to plumb those through without creating goroutine-locals in the wild. This is accomplished by splitting out most of the code from the public `runtime.GoroutineProfile` into a new unexported `runtime.goroutineProfileWithLabels`, which then has a thin wrapper linked into the `runtime/pprof` package as `runtime_goroutineProfileWithLabels`. (mirroring the way labels get associated with the `g` for a goroutine in the first place) Per-#6104, OS-thread creation profiles are a bit useless, as `M`s tend to be created be created by a background goroutine. As such, I decided not to add support for capturing the labels at `M`-creation-time, since the stack-traces seem to always come out `nil` for my simple test binaries. This change currently provides labels for debug=0 and debug=1, as debug=2 is currently entirely generated by the runtime package and I don't see a clean way of getting the `labelMap` type handled properly within the `runtime` package. Update the comment added in cl/131275 to mention goroutine support for labels. Updates #23458 Change-Id: Ia4b558893d7d10156b77121cd9b70c4ccd9e1889 Reviewed-on: https://go-review.googlesource.com/c/go/+/189318 Reviewed-by: Hyang-Ah Hana Kim <[email protected]>
1 parent 0329c91 commit 38c2c12

File tree

5 files changed

+193
-29
lines changed

5 files changed

+193
-29
lines changed

src/runtime/mprof.go

+32-9
Original file line numberDiff line numberDiff line change
@@ -711,13 +711,16 @@ func ThreadCreateProfile(p []StackRecord) (n int, ok bool) {
711711
return
712712
}
713713

714-
// GoroutineProfile returns n, the number of records in the active goroutine stack profile.
715-
// If len(p) >= n, GoroutineProfile copies the profile into p and returns n, true.
716-
// If len(p) < n, GoroutineProfile does not change p and returns n, false.
717-
//
718-
// Most clients should use the runtime/pprof package instead
719-
// of calling GoroutineProfile directly.
720-
func GoroutineProfile(p []StackRecord) (n int, ok bool) {
714+
//go:linkname runtime_goroutineProfileWithLabels runtime/pprof.runtime_goroutineProfileWithLabels
715+
func runtime_goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
716+
return goroutineProfileWithLabels(p, labels)
717+
}
718+
719+
// labels may be nil. If labels is non-nil, it must have the same length as p.
720+
func goroutineProfileWithLabels(p []StackRecord, labels []unsafe.Pointer) (n int, ok bool) {
721+
if labels != nil && len(labels) != len(p) {
722+
labels = nil
723+
}
721724
gp := getg()
722725

723726
isOK := func(gp1 *g) bool {
@@ -737,7 +740,7 @@ func GoroutineProfile(p []StackRecord) (n int, ok bool) {
737740

738741
if n <= len(p) {
739742
ok = true
740-
r := p
743+
r, lbl := p, labels
741744

742745
// Save current goroutine.
743746
sp := getcallersp()
@@ -747,6 +750,12 @@ func GoroutineProfile(p []StackRecord) (n int, ok bool) {
747750
})
748751
r = r[1:]
749752

753+
// If we have a place to put our goroutine labelmap, insert it there.
754+
if labels != nil {
755+
lbl[0] = gp.labels
756+
lbl = lbl[1:]
757+
}
758+
750759
// Save other goroutines.
751760
for _, gp1 := range allgs {
752761
if isOK(gp1) {
@@ -756,16 +765,30 @@ func GoroutineProfile(p []StackRecord) (n int, ok bool) {
756765
break
757766
}
758767
saveg(^uintptr(0), ^uintptr(0), gp1, &r[0])
768+
if labels != nil {
769+
lbl[0] = gp1.labels
770+
lbl = lbl[1:]
771+
}
759772
r = r[1:]
760773
}
761774
}
762775
}
763776

764777
startTheWorld()
765-
766778
return n, ok
767779
}
768780

781+
// GoroutineProfile returns n, the number of records in the active goroutine stack profile.
782+
// If len(p) >= n, GoroutineProfile copies the profile into p and returns n, true.
783+
// If len(p) < n, GoroutineProfile does not change p and returns n, false.
784+
//
785+
// Most clients should use the runtime/pprof package instead
786+
// of calling GoroutineProfile directly.
787+
func GoroutineProfile(p []StackRecord) (n int, ok bool) {
788+
789+
return goroutineProfileWithLabels(p, nil)
790+
}
791+
769792
func saveg(pc, sp uintptr, gp *g, r *StackRecord) {
770793
n := gentraceback(pc, sp, 0, gp, 0, &r.Stack0[0], len(r.Stack0), nil, nil, 0)
771794
if n < len(r.Stack0) {

src/runtime/pprof/label.go

+22-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ package pprof
66

77
import (
88
"context"
9+
"fmt"
10+
"sort"
11+
"strings"
912
)
1013

1114
type label struct {
@@ -34,6 +37,23 @@ func labelValue(ctx context.Context) labelMap {
3437
// that admits incremental immutable modification more efficiently.
3538
type labelMap map[string]string
3639

40+
// String statisfies Stringer and returns key, value pairs in a consistent
41+
// order.
42+
func (l *labelMap) String() string {
43+
if l == nil {
44+
return ""
45+
}
46+
keyVals := make([]string, 0, len(*l))
47+
48+
for k, v := range *l {
49+
keyVals = append(keyVals, fmt.Sprintf("%q:%q", k, v))
50+
}
51+
52+
sort.Strings(keyVals)
53+
54+
return "{" + strings.Join(keyVals, ", ") + "}"
55+
}
56+
3757
// WithLabels returns a new context.Context with the given labels added.
3858
// A label overwrites a prior label with the same key.
3959
func WithLabels(ctx context.Context, labels LabelSet) context.Context {
@@ -54,7 +74,8 @@ func WithLabels(ctx context.Context, labels LabelSet) context.Context {
5474
// Labels takes an even number of strings representing key-value pairs
5575
// and makes a LabelSet containing them.
5676
// A label overwrites a prior label with the same key.
57-
// Currently only CPU profile utilizes labels information.
77+
// Currently only the CPU and goroutine profiles utilize any labels
78+
// information.
5879
// See https://golang.org/issue/23458 for details.
5980
func Labels(args ...string) LabelSet {
6081
if len(args)%2 != 0 {

src/runtime/pprof/label_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,35 @@ func TestContextLabels(t *testing.T) {
8080
t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels)
8181
}
8282
}
83+
84+
func TestLabelMapStringer(t *testing.T) {
85+
for _, tbl := range []struct {
86+
m labelMap
87+
expected string
88+
}{
89+
{
90+
m: labelMap{
91+
// empty map
92+
},
93+
expected: "{}",
94+
}, {
95+
m: labelMap{
96+
"foo": "bar",
97+
},
98+
expected: `{"foo":"bar"}`,
99+
}, {
100+
m: labelMap{
101+
"foo": "bar",
102+
"key1": "value1",
103+
"key2": "value2",
104+
"key3": "value3",
105+
"key4WithNewline": "\nvalue4",
106+
},
107+
expected: `{"foo":"bar", "key1":"value1", "key2":"value2", "key3":"value3", "key4WithNewline":"\nvalue4"}`,
108+
},
109+
} {
110+
if got := tbl.m.String(); tbl.expected != got {
111+
t.Errorf("%#v.String() = %q; want %q", tbl.m, got, tbl.expected)
112+
}
113+
}
114+
}

src/runtime/pprof/pprof.go

+41-12
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ type stackProfile [][]uintptr
357357

358358
func (x stackProfile) Len() int { return len(x) }
359359
func (x stackProfile) Stack(i int) []uintptr { return x[i] }
360+
func (x stackProfile) Label(i int) *labelMap { return nil }
360361

361362
// A countProfile is a set of stack traces to be printed as counts
362363
// grouped by stack trace. There are multiple implementations:
@@ -365,6 +366,7 @@ func (x stackProfile) Stack(i int) []uintptr { return x[i] }
365366
type countProfile interface {
366367
Len() int
367368
Stack(i int) []uintptr
369+
Label(i int) *labelMap
368370
}
369371

370372
// printCountCycleProfile outputs block profile records (for block or mutex profiles)
@@ -402,20 +404,24 @@ func printCountCycleProfile(w io.Writer, countName, cycleName string, scaler fun
402404
func printCountProfile(w io.Writer, debug int, name string, p countProfile) error {
403405
// Build count of each stack.
404406
var buf bytes.Buffer
405-
key := func(stk []uintptr) string {
407+
key := func(stk []uintptr, lbls *labelMap) string {
406408
buf.Reset()
407409
fmt.Fprintf(&buf, "@")
408410
for _, pc := range stk {
409411
fmt.Fprintf(&buf, " %#x", pc)
410412
}
413+
if lbls != nil {
414+
buf.WriteString("\n# labels: ")
415+
buf.WriteString(lbls.String())
416+
}
411417
return buf.String()
412418
}
413419
count := map[string]int{}
414420
index := map[string]int{}
415421
var keys []string
416422
n := p.Len()
417423
for i := 0; i < n; i++ {
418-
k := key(p.Stack(i))
424+
k := key(p.Stack(i), p.Label(i))
419425
if count[k] == 0 {
420426
index[k] = i
421427
keys = append(keys, k)
@@ -449,7 +455,16 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro
449455
// For count profiles, all stack addresses are
450456
// return PCs, which is what appendLocsForStack expects.
451457
locs = b.appendLocsForStack(locs[:0], p.Stack(index[k]))
452-
b.pbSample(values, locs, nil)
458+
idx := index[k]
459+
var labels func()
460+
if p.Label(idx) != nil {
461+
labels = func() {
462+
for k, v := range *p.Label(idx) {
463+
b.pbLabel(tagSample_Label, k, v, 0)
464+
}
465+
}
466+
}
467+
b.pbSample(values, locs, labels)
453468
}
454469
b.build()
455470
return nil
@@ -645,20 +660,28 @@ func countThreadCreate() int {
645660

646661
// writeThreadCreate writes the current runtime ThreadCreateProfile to w.
647662
func writeThreadCreate(w io.Writer, debug int) error {
648-
return writeRuntimeProfile(w, debug, "threadcreate", runtime.ThreadCreateProfile)
663+
// Until https://golang.org/issues/6104 is addressed, wrap
664+
// ThreadCreateProfile because there's no point in tracking labels when we
665+
// don't get any stack-traces.
666+
return writeRuntimeProfile(w, debug, "threadcreate", func(p []runtime.StackRecord, _ []unsafe.Pointer) (n int, ok bool) {
667+
return runtime.ThreadCreateProfile(p)
668+
})
649669
}
650670

651671
// countGoroutine returns the number of goroutines.
652672
func countGoroutine() int {
653673
return runtime.NumGoroutine()
654674
}
655675

676+
// runtime_goroutineProfileWithLabels is defined in runtime/mprof.go
677+
func runtime_goroutineProfileWithLabels(p []runtime.StackRecord, labels []unsafe.Pointer) (n int, ok bool)
678+
656679
// writeGoroutine writes the current runtime GoroutineProfile to w.
657680
func writeGoroutine(w io.Writer, debug int) error {
658681
if debug >= 2 {
659682
return writeGoroutineStacks(w)
660683
}
661-
return writeRuntimeProfile(w, debug, "goroutine", runtime.GoroutineProfile)
684+
return writeRuntimeProfile(w, debug, "goroutine", runtime_goroutineProfileWithLabels)
662685
}
663686

664687
func writeGoroutineStacks(w io.Writer) error {
@@ -682,35 +705,41 @@ func writeGoroutineStacks(w io.Writer) error {
682705
return err
683706
}
684707

685-
func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]runtime.StackRecord) (int, bool)) error {
708+
func writeRuntimeProfile(w io.Writer, debug int, name string, fetch func([]runtime.StackRecord, []unsafe.Pointer) (int, bool)) error {
686709
// Find out how many records there are (fetch(nil)),
687710
// allocate that many records, and get the data.
688711
// There's a race—more records might be added between
689712
// the two calls—so allocate a few extra records for safety
690713
// and also try again if we're very unlucky.
691714
// The loop should only execute one iteration in the common case.
692715
var p []runtime.StackRecord
693-
n, ok := fetch(nil)
716+
var labels []unsafe.Pointer
717+
n, ok := fetch(nil, nil)
694718
for {
695719
// Allocate room for a slightly bigger profile,
696720
// in case a few more entries have been added
697721
// since the call to ThreadProfile.
698722
p = make([]runtime.StackRecord, n+10)
699-
n, ok = fetch(p)
723+
labels = make([]unsafe.Pointer, n+10)
724+
n, ok = fetch(p, labels)
700725
if ok {
701726
p = p[0:n]
702727
break
703728
}
704729
// Profile grew; try again.
705730
}
706731

707-
return printCountProfile(w, debug, name, runtimeProfile(p))
732+
return printCountProfile(w, debug, name, &runtimeProfile{p, labels})
708733
}
709734

710-
type runtimeProfile []runtime.StackRecord
735+
type runtimeProfile struct {
736+
stk []runtime.StackRecord
737+
labels []unsafe.Pointer
738+
}
711739

712-
func (p runtimeProfile) Len() int { return len(p) }
713-
func (p runtimeProfile) Stack(i int) []uintptr { return p[i].Stack() }
740+
func (p *runtimeProfile) Len() int { return len(p.stk) }
741+
func (p *runtimeProfile) Stack(i int) []uintptr { return p.stk[i].Stack() }
742+
func (p *runtimeProfile) Label(i int) *labelMap { return (*labelMap)(p.labels[i]) }
714743

715744
var cpu struct {
716745
sync.Mutex

0 commit comments

Comments
 (0)