diff --git a/src/runtime/pprof/label.go b/src/runtime/pprof/label.go new file mode 100644 index 0000000000..35e51a3f13 --- /dev/null +++ b/src/runtime/pprof/label.go @@ -0,0 +1,130 @@ +package pprof + +// Goroutine labels, as in upstream Go. The label set lives in the +// context.Context, so nothing here needs the runtime. +// +// This is upstream's pre-1.24 form. Go 1.24 moved the label type into +// internal/runtime/pprof/label, which TinyGo does not have. + +import ( + "context" + "fmt" + "slices" + "strings" +) + +// label is a single key/value pair in a LabelSet. +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(nil) + } + return *labels +} + +// labelMap is the representation of the label set held in the context type. +type labelMap map[string]string + +// 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)) + for k, v := range *l { + keyVals = append(keyVals, fmt.Sprintf("%q:%q", k, v)) + } + 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) + childLabels := make(labelMap, len(parentLabels)) + // TODO(matloob): replace the map implementation with something + // more efficient so creating a child context WithLabels doesn't need + // to clone the map. + for k, v := range parentLabels { + childLabels[k] = v + } + for _, label := range labels.list { + childLabels[label.key] = label.value + } + return context.WithValue(ctx, labelContextKey{}, &childLabels) +} + +// 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) + for i := 0; i+1 < len(args); i += 2 { + list = append(list, label{key: args[i], value: args[i+1]}) + } + 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) + v, ok := ctxLabels[key] + return v, ok +} + +// 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) + keys := make([]string, 0, len(ctxLabels)) + for k := range ctxLabels { + keys = append(keys, k) + } + slices.Sort(keys) + for _, k := range keys { + if !f(k, ctxLabels[k]) { + break + } + } +} + +// SetGoroutineLabels sets the current goroutine's labels to match ctx. +// +// This is a no-op under TinyGo, whose profiler is itself a stub, so nothing +// would read the labels back. The label API itself travels in the context. +func SetGoroutineLabels(ctx context.Context) {} + +// Do calls f with a copy of the parent context with the given labels added to +// the parent's label map. Goroutines spawned while executing f will inherit +// the augmented label-set. Each key/value pair in labels is inserted into the +// label map in the order provided, overriding any previous value for the same +// key. The augmented label map will be set for the duration of the call to f +// and restored once f returns. +func Do(ctx context.Context, labels LabelSet, f func(context.Context)) { + defer SetGoroutineLabels(ctx) + ctx = WithLabels(ctx, labels) + SetGoroutineLabels(ctx) + f(ctx) +} diff --git a/src/runtime/stack.go b/src/runtime/stack.go index c152208839..d998dd006d 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -15,6 +15,12 @@ func (f *Func) FileLine(pc uintptr) (file string, line int) { return "", 0 } +// Entry returns the entry address of the function. It is stubbed like the +// rest of runtime.Func, so that callers such as testify/mock compile. +func (f *Func) Entry() uintptr { + return 0 +} + func Caller(skip int) (pc uintptr, file string, line int, ok bool) { return 0, "", 0, false }