diff --git a/_fixtures/goroutineLabels.go b/_fixtures/goroutineLabels.go new file mode 100644 index 00000000..2b903cb8 --- /dev/null +++ b/_fixtures/goroutineLabels.go @@ -0,0 +1,23 @@ +package main + +import ( + "context" + "runtime" + "runtime/pprof" +) + +func main() { + ctx := context.Background() + labels := pprof.Labels("k1", "v1", "k2", "v2") + runtime.Breakpoint() + pprof.Do(ctx, labels, f) +} + +var dummy int + +func f(ctx context.Context) { + a := dummy + runtime.Breakpoint() + dummy++ + dummy = a +} \ No newline at end of file diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 92a134ae..6e03ac4f 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -2305,6 +2305,27 @@ func TestIssue561(t *testing.T) { }) } +func TestGoroutineLables(t *testing.T) { + withTestProcess("goroutineLabels", t, func(p proc.Process, fixture protest.Fixture) { + assertNoError(proc.Continue(p), t, "Continue()") + g, err := proc.GetG(p.CurrentThread()) + assertNoError(err, t, "GetG()") + if len(g.Labels) != 0 { + t.Fatalf("No labels expected") + } + + assertNoError(proc.Continue(p), t, "Continue()") + g, err = proc.GetG(p.CurrentThread()) + assertNoError(err, t, "GetG()") + if v := g.Labels["k1"]; v != "v1" { + t.Errorf("Unexpected label value k1=%v", v) + } + if v := g.Labels["k2"]; v != "v2" { + t.Errorf("Unexpected label value k2=%v", v) + } + }) +} + func TestStepOut(t *testing.T) { testseq2(t, "testnextprog", "main.helloworld", []seqTest{{contContinue, 13}, {contStepout, 35}}) } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 08651dca..bcf4c123 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -210,6 +210,8 @@ type G struct { variable *Variable Unreadable error // could not read the G struct + + Labels map[string]string // G's pprof labels } // Defer returns the top-most defer of the goroutine. @@ -558,6 +560,27 @@ func (v *Variable) parseG() (*G, error) { status, _ := constant.Int64Val(v.fieldVariable("atomicstatus").Value) f, l, fn := v.bi.PCToLine(uint64(pc)) + + var labels map[string]string + if labelsVar := v.loadFieldNamed("labels"); labelsVar != nil && len(labelsVar.Children) == 1 { + if address := labelsVar.Children[0]; address.Addr != 0 { + labelMapType, _ := v.bi.findType("runtime/pprof.labelMap") + if labelMapType != nil { + labelMap := newVariable("", address.Addr, labelMapType, v.bi, v.mem) + labelMap.loadValue(loadFullValue) + labels = map[string]string{} + // iterate through map as it is done in other places + for i := range labelMap.Children { + if i % 2 == 0 { + k := labelMap.Children[i] + v := labelMap.Children[i+1] + labels[constant.StringVal(k.Value)] = constant.StringVal(v.Value) + } + } + } + } + } + g := &G{ ID: int(id), GoPC: uint64(gopc), @@ -573,6 +596,7 @@ func (v *Variable) parseG() (*G, error) { stkbarPos: int(stkbarPos), stackhi: stackhi, stacklo: stacklo, + Labels: labels, } return g, nil } diff --git a/service/api/conversions.go b/service/api/conversions.go index 3333866c..606e21f1 100644 --- a/service/api/conversions.go +++ b/service/api/conversions.go @@ -263,6 +263,7 @@ func ConvertGoroutine(g *proc.G) *Goroutine { GoStatementLoc: ConvertLocation(g.Go()), StartLoc: ConvertLocation(g.StartLoc()), ThreadID: tid, + Labels: g.Labels, } if g.Unreadable != nil { r.Unreadable = g.Unreadable.Error() diff --git a/service/api/types.go b/service/api/types.go index db06c7b8..c401216c 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -308,6 +308,8 @@ type Goroutine struct { // ID of the associated thread for running goroutines ThreadID int `json:"threadID"` Unreadable string `json:"unreadable"` + // Goroutine's pprof labels + Labels map[string]string `json:"labels,omitempty"` } // DebuggerCommand is a command which changes the debugger's execution state.