-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlabel.go
151 lines (133 loc) · 4.02 KB
/
label.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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 pprof
import (
"context"
"fmt"
"slices"
"strings"
)
type label struct {
key string
value string
}
// LabelSet is a set of labels.
type LabelSet struct {
list []label
}
// labelContextKey is the type of contextKeys used for profiler labels.
type labelContextKey struct{}
func labelValue(ctx context.Context) labelMap {
labels, _ := ctx.Value(labelContextKey{}).(*labelMap)
if labels == nil {
return labelMap{}
}
return *labels
}
// labelMap is the representation of the label set held in the context type.
// This is an initial implementation, but it will be replaced with something
// that admits incremental immutable modification more efficiently.
type labelMap struct {
LabelSet
}
// String satisfies Stringer and returns key, value pairs in a consistent
// order.
func (l *labelMap) String() string {
if l == nil {
return ""
}
keyVals := make([]string, 0, len(l.list))
for _, lbl := range l.list {
keyVals = append(keyVals, fmt.Sprintf("%q:%q", lbl.key, lbl.value))
}
slices.Sort(keyVals)
return "{" + strings.Join(keyVals, ", ") + "}"
}
// WithLabels returns a new [context.Context] with the given labels added.
// 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)})
}
func mergeLabelSets(left, right LabelSet) LabelSet {
if len(left.list) == 0 {
return right
} else if len(right.list) == 0 {
return left
}
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) {
case -1: // left key < right key
result = append(result, left.list[l])
l++
case 1: // right key < left key
result = append(result, right.list[r])
r++
case 0: // keys are equal, right value overwrites left value
result = append(result, right.list[r])
l++
r++
}
}
// Append the remaining elements
result = append(result, left.list[l:]...)
result = append(result, right.list[r:]...)
return LabelSet{list: result}
}
// Labels takes an even number of strings representing key-value pairs
// and makes a [LabelSet] containing them.
// A label overwrites a prior label with the same key.
// Currently only the CPU and goroutine profiles utilize any labels
// information.
// See https://golang.org/issue/23458 for details.
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)
sortedNoDupes := true
for i := 0; i+1 < len(args); i += 2 {
list = append(list, 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)
})
deduped := make([]label, 0, len(list))
for i, lbl := range list {
if i == 0 || lbl.key != list[i-1].key {
deduped = append(deduped, lbl)
} else {
deduped[len(deduped)-1] = lbl
}
}
list = deduped
}
return LabelSet{list: list}
}
// Label returns the value of the label with the given key on ctx, and a boolean indicating
// 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
}
}
return "", false
}
// ForLabels invokes f with each label set on the context.
// 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) {
break
}
}
}