aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/pprof
diff options
context:
space:
mode:
authorDavid Finkel <davidf@vimeo.com>2025-05-23 16:04:08 -0400
committerGopher Robot <gobot@golang.org>2025-11-24 20:07:56 -0800
commit6851795fb6cda61e2c8396c36da187a2bd87b29e (patch)
tree26c06ff565a5e5910dfce7b28f180023b8699feb /src/runtime/pprof
parent0921e1db83d3e67032999b5a2f54f5ede8ba39b5 (diff)
downloadgo-6851795fb6cda61e2c8396c36da187a2bd87b29e.tar.xz
runtime: add GODEBUG=tracebacklabels=1 to include pprof labels in tracebacks
Copy LabelSet to an internal package as label.Set, and include (escaped) labels within goroutine stack dumps. Labels are added to the goroutine header as quoted key:value pairs, so the line may get long if there are a lot of labels. To handle escaping, we add a printescaped function to the runtime and hook it up to the print function in the compiler with a new runtime.quoted type that's a sibling to runtime.hex. (in fact, we leverage some of the machinery from printhex to generate escape sequences). The escaping can be improved for printable runes outside basic ASCII (particularly for languages using non-latin stripts). Additionally, invalid UTF-8 can be improved. So we can experiment with the output format make this opt-in via a a new tracebacklabels GODEBUG var. Updates #23458 Updates #76349 Change-Id: I08e78a40c55839a809236fff593ef2090c13c036 Reviewed-on: https://go-review.googlesource.com/c/go/+/694119 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Auto-Submit: Michael Pratt <mpratt@google.com> Reviewed-by: Alan Donovan <adonovan@google.com>
Diffstat (limited to 'src/runtime/pprof')
-rw-r--r--src/runtime/pprof/label.go65
-rw-r--r--src/runtime/pprof/label_test.go23
-rw-r--r--src/runtime/pprof/pprof.go4
-rw-r--r--src/runtime/pprof/pprof_test.go7
-rw-r--r--src/runtime/pprof/proto.go4
-rw-r--r--src/runtime/pprof/runtime_test.go7
6 files changed, 55 insertions, 55 deletions
diff --git a/src/runtime/pprof/label.go b/src/runtime/pprof/label.go
index 4c1d8d38ce..09dd1de651 100644
--- a/src/runtime/pprof/label.go
+++ b/src/runtime/pprof/label.go
@@ -7,18 +7,14 @@ package pprof
import (
"context"
"fmt"
+ "internal/runtime/pprof/label"
"slices"
"strings"
)
-type label struct {
- key string
- value string
-}
-
// LabelSet is a set of labels.
type LabelSet struct {
- list []label
+ list []label.Label
}
// labelContextKey is the type of contextKeys used for profiler labels.
@@ -36,7 +32,7 @@ func labelValue(ctx context.Context) labelMap {
// This is an initial implementation, but it will be replaced with something
// that admits incremental immutable modification more efficiently.
type labelMap struct {
- LabelSet
+ label.Set
}
// String satisfies Stringer and returns key, value pairs in a consistent
@@ -45,10 +41,10 @@ func (l *labelMap) String() string {
if l == nil {
return ""
}
- keyVals := make([]string, 0, len(l.list))
+ keyVals := make([]string, 0, len(l.Set.List))
- for _, lbl := range l.list {
- keyVals = append(keyVals, fmt.Sprintf("%q:%q", lbl.key, lbl.value))
+ for _, lbl := range l.Set.List {
+ keyVals = append(keyVals, fmt.Sprintf("%q:%q", lbl.Key, lbl.Value))
}
slices.Sort(keyVals)
@@ -59,38 +55,39 @@ func (l *labelMap) String() string {
// A label overwrites a prior label with the same key.
func WithLabels(ctx context.Context, labels LabelSet) context.Context {
parentLabels := labelValue(ctx)
- return context.WithValue(ctx, labelContextKey{}, &labelMap{mergeLabelSets(parentLabels.LabelSet, labels)})
+ return context.WithValue(ctx, labelContextKey{}, &labelMap{mergeLabelSets(parentLabels.Set, labels)})
}
-func mergeLabelSets(left, right LabelSet) LabelSet {
- if len(left.list) == 0 {
- return right
+func mergeLabelSets(left label.Set, right LabelSet) label.Set {
+ if len(left.List) == 0 {
+ return label.NewSet(right.list)
} else if len(right.list) == 0 {
return left
}
+ lList, rList := left.List, right.list
l, r := 0, 0
- result := make([]label, 0, len(right.list))
- for l < len(left.list) && r < len(right.list) {
- switch strings.Compare(left.list[l].key, right.list[r].key) {
+ result := make([]label.Label, 0, len(rList))
+ for l < len(lList) && r < len(rList) {
+ switch strings.Compare(lList[l].Key, rList[r].Key) {
case -1: // left key < right key
- result = append(result, left.list[l])
+ result = append(result, lList[l])
l++
case 1: // right key < left key
- result = append(result, right.list[r])
+ result = append(result, rList[r])
r++
case 0: // keys are equal, right value overwrites left value
- result = append(result, right.list[r])
+ result = append(result, rList[r])
l++
r++
}
}
// Append the remaining elements
- result = append(result, left.list[l:]...)
- result = append(result, right.list[r:]...)
+ result = append(result, lList[l:]...)
+ result = append(result, rList[r:]...)
- return LabelSet{list: result}
+ return label.NewSet(result)
}
// Labels takes an even number of strings representing key-value pairs
@@ -103,20 +100,20 @@ func Labels(args ...string) LabelSet {
if len(args)%2 != 0 {
panic("uneven number of arguments to pprof.Labels")
}
- list := make([]label, 0, len(args)/2)
+ list := make([]label.Label, 0, len(args)/2)
sortedNoDupes := true
for i := 0; i+1 < len(args); i += 2 {
- list = append(list, label{key: args[i], value: args[i+1]})
+ list = append(list, label.Label{Key: args[i], Value: args[i+1]})
sortedNoDupes = sortedNoDupes && (i < 2 || args[i] > args[i-2])
}
if !sortedNoDupes {
// slow path: keys are unsorted, contain duplicates, or both
- slices.SortStableFunc(list, func(a, b label) int {
- return strings.Compare(a.key, b.key)
+ slices.SortStableFunc(list, func(a, b label.Label) int {
+ return strings.Compare(a.Key, b.Key)
})
- deduped := make([]label, 0, len(list))
+ deduped := make([]label.Label, 0, len(list))
for i, lbl := range list {
- if i == 0 || lbl.key != list[i-1].key {
+ if i == 0 || lbl.Key != list[i-1].Key {
deduped = append(deduped, lbl)
} else {
deduped[len(deduped)-1] = lbl
@@ -131,9 +128,9 @@ func Labels(args ...string) LabelSet {
// whether that label exists.
func Label(ctx context.Context, key string) (string, bool) {
ctxLabels := labelValue(ctx)
- for _, lbl := range ctxLabels.list {
- if lbl.key == key {
- return lbl.value, true
+ for _, lbl := range ctxLabels.Set.List {
+ if lbl.Key == key {
+ return lbl.Value, true
}
}
return "", false
@@ -143,8 +140,8 @@ func Label(ctx context.Context, key string) (string, bool) {
// The function f should return true to continue iteration or false to stop iteration early.
func ForLabels(ctx context.Context, f func(key, value string) bool) {
ctxLabels := labelValue(ctx)
- for _, lbl := range ctxLabels.list {
- if !f(lbl.key, lbl.value) {
+ for _, lbl := range ctxLabels.Set.List {
+ if !f(lbl.Key, lbl.Value) {
break
}
}
diff --git a/src/runtime/pprof/label_test.go b/src/runtime/pprof/label_test.go
index 3018693c24..ded8b29575 100644
--- a/src/runtime/pprof/label_test.go
+++ b/src/runtime/pprof/label_test.go
@@ -7,19 +7,20 @@ package pprof
import (
"context"
"fmt"
+ "internal/runtime/pprof/label"
"reflect"
"slices"
"strings"
"testing"
)
-func labelsSorted(ctx context.Context) []label {
- ls := []label{}
+func labelsSorted(ctx context.Context) []label.Label {
+ ls := []label.Label{}
ForLabels(ctx, func(key, value string) bool {
- ls = append(ls, label{key, value})
+ ls = append(ls, label.Label{Key: key, Value: value})
return true
})
- slices.SortFunc(ls, func(a, b label) int { return strings.Compare(a.key, b.key) })
+ slices.SortFunc(ls, func(a, b label.Label) int { return strings.Compare(a.Key, b.Key) })
return ls
}
@@ -39,7 +40,7 @@ func TestContextLabels(t *testing.T) {
t.Errorf(`Label(ctx, "key"): got %v, %v; want "value", ok`, v, ok)
}
gotLabels := labelsSorted(ctx)
- wantLabels := []label{{"key", "value"}}
+ wantLabels := []label.Label{{Key: "key", Value: "value"}}
if !reflect.DeepEqual(gotLabels, wantLabels) {
t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels)
}
@@ -51,7 +52,7 @@ func TestContextLabels(t *testing.T) {
t.Errorf(`Label(ctx, "key2"): got %v, %v; want "value2", ok`, v, ok)
}
gotLabels = labelsSorted(ctx)
- wantLabels = []label{{"key", "value"}, {"key2", "value2"}}
+ wantLabels = []label.Label{{Key: "key", Value: "value"}, {Key: "key2", Value: "value2"}}
if !reflect.DeepEqual(gotLabels, wantLabels) {
t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels)
}
@@ -63,7 +64,7 @@ func TestContextLabels(t *testing.T) {
t.Errorf(`Label(ctx, "key3"): got %v, %v; want "value3", ok`, v, ok)
}
gotLabels = labelsSorted(ctx)
- wantLabels = []label{{"key", "value3"}, {"key2", "value2"}}
+ wantLabels = []label.Label{{Key: "key", Value: "value3"}, {Key: "key2", Value: "value2"}}
if !reflect.DeepEqual(gotLabels, wantLabels) {
t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels)
}
@@ -75,7 +76,7 @@ func TestContextLabels(t *testing.T) {
t.Errorf(`Label(ctx, "key4"): got %v, %v; want "value4b", ok`, v, ok)
}
gotLabels = labelsSorted(ctx)
- wantLabels = []label{{"key", "value3"}, {"key2", "value2"}, {"key4", "value4b"}}
+ wantLabels = []label.Label{{Key: "key", Value: "value3"}, {Key: "key2", Value: "value2"}, {Key: "key4", Value: "value4b"}}
if !reflect.DeepEqual(gotLabels, wantLabels) {
t.Errorf("(sorted) labels on context: got %v, want %v", gotLabels, wantLabels)
}
@@ -93,18 +94,18 @@ func TestLabelMapStringer(t *testing.T) {
expected: "{}",
}, {
m: labelMap{
- Labels("foo", "bar"),
+ label.NewSet(Labels("foo", "bar").list),
},
expected: `{"foo":"bar"}`,
}, {
m: labelMap{
- Labels(
+ label.NewSet(Labels(
"foo", "bar",
"key1", "value1",
"key2", "value2",
"key3", "value3",
"key4WithNewline", "\nvalue4",
- ),
+ ).list),
},
expected: `{"foo":"bar", "key1":"value1", "key2":"value2", "key3":"value3", "key4WithNewline":"\nvalue4"}`,
},
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go
index c617a8b26a..c27df22897 100644
--- a/src/runtime/pprof/pprof.go
+++ b/src/runtime/pprof/pprof.go
@@ -547,8 +547,8 @@ func printCountProfile(w io.Writer, debug int, name string, p countProfile) erro
var labels func()
if p.Label(idx) != nil {
labels = func() {
- for _, lbl := range p.Label(idx).list {
- b.pbLabel(tagSample_Label, lbl.key, lbl.value, 0)
+ for _, lbl := range p.Label(idx).Set.List {
+ b.pbLabel(tagSample_Label, lbl.Key, lbl.Value, 0)
}
}
}
diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go
index 4c9279c5a6..e46e4f9d27 100644
--- a/src/runtime/pprof/pprof_test.go
+++ b/src/runtime/pprof/pprof_test.go
@@ -12,6 +12,7 @@ import (
"fmt"
"internal/abi"
"internal/profile"
+ "internal/runtime/pprof/label"
"internal/syscall/unix"
"internal/testenv"
"io"
@@ -1462,11 +1463,11 @@ func TestGoroutineCounts(t *testing.T) {
goroutineProf.WriteTo(&w, 1)
prof := w.String()
- labels := labelMap{Labels("label", "value")}
+ labels := labelMap{label.NewSet(Labels("label", "value").list)}
labelStr := "\n# labels: " + labels.String()
- selfLabel := labelMap{Labels("self-label", "self-value")}
+ selfLabel := labelMap{label.NewSet(Labels("self-label", "self-value").list)}
selfLabelStr := "\n# labels: " + selfLabel.String()
- fingLabel := labelMap{Labels("fing-label", "fing-value")}
+ fingLabel := labelMap{label.NewSet(Labels("fing-label", "fing-value").list)}
fingLabelStr := "\n# labels: " + fingLabel.String()
orderedPrefix := []string{
"\n50 @ ",
diff --git a/src/runtime/pprof/proto.go b/src/runtime/pprof/proto.go
index 28ceb81542..5ad917f14a 100644
--- a/src/runtime/pprof/proto.go
+++ b/src/runtime/pprof/proto.go
@@ -367,8 +367,8 @@ func (b *profileBuilder) build() error {
var labels func()
if e.tag != nil {
labels = func() {
- for _, lbl := range (*labelMap)(e.tag).list {
- b.pbLabel(tagSample_Label, lbl.key, lbl.value, 0)
+ for _, lbl := range (*labelMap)(e.tag).Set.List {
+ b.pbLabel(tagSample_Label, lbl.Key, lbl.Value, 0)
}
}
}
diff --git a/src/runtime/pprof/runtime_test.go b/src/runtime/pprof/runtime_test.go
index 353ed8a3f1..acdd4c8d15 100644
--- a/src/runtime/pprof/runtime_test.go
+++ b/src/runtime/pprof/runtime_test.go
@@ -92,9 +92,10 @@ func getProfLabel() map[string]string {
if l == nil {
return map[string]string{}
}
- m := make(map[string]string, len(l.list))
- for _, lbl := range l.list {
- m[lbl.key] = lbl.value
+ ls := l.Set.List
+ m := make(map[string]string, len(ls))
+ for _, lbl := range ls {
+ m[lbl.Key] = lbl.Value
}
return m
}