Skip to content

Commit a517131

Browse files
nsrip-ddmknyszek
authored andcommitted
cmd/trace/v2,internal/trace: use correct frame for identifying goroutines
To determine the identity of a goroutine for displaying in the trace UI, we should use the root frame from a call stack. This will be the starting function for the goroutine and is the same for each call stack from a given goroutine. The new tracer no longer includes starting PCs for goroutines which existed at the start of tracing, so we can't use a PC for grouping together goroutines any more. Instead, we just use the name of the entry function for grouping. Fixes golang#65574 Change-Id: I5324653316f1acf0ab90c30680f181060ea45dd7 Reviewed-on: https://go-review.googlesource.com/c/go/+/562455 Reviewed-by: Michael Knyszek <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent 2057ad0 commit a517131

File tree

4 files changed

+44
-50
lines changed

4 files changed

+44
-50
lines changed

src/cmd/trace/v2/goroutines.go

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,30 @@ import (
1717
"net/http"
1818
"slices"
1919
"sort"
20-
"strconv"
2120
"strings"
2221
"time"
2322
)
2423

2524
// GoroutinesHandlerFunc returns a HandlerFunc that serves list of goroutine groups.
2625
func GoroutinesHandlerFunc(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.HandlerFunc {
2726
return func(w http.ResponseWriter, r *http.Request) {
28-
// goroutineGroup describes a group of goroutines grouped by start PC.
27+
// goroutineGroup describes a group of goroutines grouped by name.
2928
type goroutineGroup struct {
30-
ID uint64 // Unique identifier (PC).
3129
Name string // Start function.
3230
N int // Total number of goroutines in this group.
3331
ExecTime time.Duration // Total execution time of all goroutines in this group.
3432
}
35-
// Accumulate groups by PC.
36-
groupsByPC := make(map[uint64]goroutineGroup)
33+
// Accumulate groups by Name.
34+
groupsByName := make(map[string]goroutineGroup)
3735
for _, summary := range summaries {
38-
group := groupsByPC[summary.PC]
39-
group.ID = summary.PC
36+
group := groupsByName[summary.Name]
4037
group.Name = summary.Name
4138
group.N++
4239
group.ExecTime += summary.ExecTime
43-
groupsByPC[summary.PC] = group
40+
groupsByName[summary.Name] = group
4441
}
4542
var groups []goroutineGroup
46-
for pc, group := range groupsByPC {
47-
group.ID = pc
48-
// If goroutine didn't run during the trace (no sampled PC),
49-
// the v.ID and v.Name will be zero value.
50-
if group.ID == 0 && group.Name == "" {
51-
group.Name = "(Inactive, no stack trace sampled)"
52-
}
43+
for _, group := range groupsByName {
5344
groups = append(groups, group)
5445
}
5546
slices.SortFunc(groups, func(a, b goroutineGroup) int {
@@ -92,7 +83,7 @@ Click a start location to view more details about that group.<br>
9283
</tr>
9384
{{range $}}
9485
<tr>
95-
<td><code><a href="/goroutine?id={{.ID}}">{{.Name}}</a></code></td>
86+
<td><code><a href="/goroutine?name={{.Name}}">{{or .Name "(Inactive, no stack trace sampled)"}}</a></code></td>
9687
<td>{{.N}}</td>
9788
<td>{{.ExecTime}}</td>
9889
</tr>
@@ -106,11 +97,7 @@ Click a start location to view more details about that group.<br>
10697
// goroutines in a particular group.
10798
func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.HandlerFunc {
10899
return func(w http.ResponseWriter, r *http.Request) {
109-
pc, err := strconv.ParseUint(r.FormValue("id"), 10, 64)
110-
if err != nil {
111-
http.Error(w, fmt.Sprintf("failed to parse id parameter '%v': %v", r.FormValue("id"), err), http.StatusInternalServerError)
112-
return
113-
}
100+
goroutineName := r.FormValue("name")
114101

115102
type goroutine struct {
116103
*trace.GoroutineSummary
@@ -130,7 +117,7 @@ func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.H
130117
for _, summary := range summaries {
131118
totalExecTime += summary.ExecTime
132119

133-
if summary.PC != pc {
120+
if summary.Name != goroutineName {
134121
continue
135122
}
136123
nonOverlappingStats := summary.NonOverlappingStats()
@@ -198,9 +185,8 @@ func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.H
198185
}
199186
sort.Strings(allRangeStats)
200187

201-
err = templGoroutine.Execute(w, struct {
188+
err := templGoroutine.Execute(w, struct {
202189
Name string
203-
PC uint64
204190
N int
205191
ExecTimePercent string
206192
MaxTotal time.Duration
@@ -209,7 +195,6 @@ func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.H
209195
RangeStats []string
210196
}{
211197
Name: name,
212-
PC: pc,
213198
N: len(goroutines),
214199
ExecTimePercent: execTimePercent,
215200
MaxTotal: maxTotalTime,
@@ -339,19 +324,19 @@ Table of contents
339324
</tr>
340325
<tr>
341326
<td>Network wait profile:</td>
342-
<td> <a href="/io?id={{.PC}}">graph</a> <a href="/io?id={{.PC}}&raw=1" download="io.profile">(download)</a></td>
327+
<td> <a href="/io?name={{.Name}}">graph</a> <a href="/io?name={{.Name}}&raw=1" download="io.profile">(download)</a></td>
343328
</tr>
344329
<tr>
345330
<td>Sync block profile:</td>
346-
<td> <a href="/block?id={{.PC}}">graph</a> <a href="/block?id={{.PC}}&raw=1" download="block.profile">(download)</a></td>
331+
<td> <a href="/block?name={{.Name}}">graph</a> <a href="/block?name={{.Name}}&raw=1" download="block.profile">(download)</a></td>
347332
</tr>
348333
<tr>
349334
<td>Syscall profile:</td>
350-
<td> <a href="/syscall?id={{.PC}}">graph</a> <a href="/syscall?id={{.PC}}&raw=1" download="syscall.profile">(download)</a></td>
335+
<td> <a href="/syscall?name={{.Name}}">graph</a> <a href="/syscall?name={{.Name}}&raw=1" download="syscall.profile">(download)</a></td>
351336
</tr>
352337
<tr>
353338
<td>Scheduler wait profile:</td>
354-
<td> <a href="/sched?id={{.PC}}">graph</a> <a href="/sched?id={{.PC}}&raw=1" download="sched.profile">(download)</a></td>
339+
<td> <a href="/sched?name={{.Name}}">graph</a> <a href="/sched?name={{.Name}}&raw=1" download="sched.profile">(download)</a></td>
355340
</tr>
356341
</table>
357342

src/cmd/trace/v2/pprof.go

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,14 @@ import (
1414
tracev2 "internal/trace/v2"
1515
"net/http"
1616
"slices"
17-
"strconv"
1817
"strings"
1918
"time"
2019
)
2120

2221
func pprofByGoroutine(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc {
2322
return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {
24-
id := r.FormValue("id")
25-
gToIntervals, err := pprofMatchingGoroutines(id, t)
23+
name := r.FormValue("name")
24+
gToIntervals, err := pprofMatchingGoroutines(name, t)
2625
if err != nil {
2726
return nil, err
2827
}
@@ -44,20 +43,12 @@ func pprofByRegion(compute computePprofFunc, t *parsedTrace) traceviewer.Profile
4443
}
4544
}
4645

47-
// pprofMatchingGoroutines parses the goroutine type id string (i.e. pc)
48-
// and returns the ids of goroutines of the matching type and its interval.
46+
// pprofMatchingGoroutines returns the ids of goroutines of the matching name and its interval.
4947
// If the id string is empty, returns nil without an error.
50-
func pprofMatchingGoroutines(id string, t *parsedTrace) (map[tracev2.GoID][]interval, error) {
51-
if id == "" {
52-
return nil, nil
53-
}
54-
pc, err := strconv.ParseUint(id, 10, 64) // id is string
55-
if err != nil {
56-
return nil, fmt.Errorf("invalid goroutine type: %v", id)
57-
}
48+
func pprofMatchingGoroutines(name string, t *parsedTrace) (map[tracev2.GoID][]interval, error) {
5849
res := make(map[tracev2.GoID][]interval)
5950
for _, g := range t.summary.Goroutines {
60-
if g.PC != pc {
51+
if g.Name != name {
6152
continue
6253
}
6354
endTime := g.EndTime
@@ -66,8 +57,8 @@ func pprofMatchingGoroutines(id string, t *parsedTrace) (map[tracev2.GoID][]inte
6657
}
6758
res[g.ID] = []interval{{start: g.StartTime, end: endTime}}
6859
}
69-
if len(res) == 0 && id != "" {
70-
return nil, fmt.Errorf("failed to find matching goroutines for ID: %s", id)
60+
if len(res) == 0 {
61+
return nil, fmt.Errorf("failed to find matching goroutines for name: %s", name)
7162
}
7263
return res, nil
7364
}

src/internal/trace/summary.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type Summary struct {
2121
type GoroutineSummary struct {
2222
ID tracev2.GoID
2323
Name string // A non-unique human-friendly identifier for the goroutine.
24-
PC uint64 // The start PC of the goroutine.
24+
PC uint64 // The first PC we saw for the entry function of the goroutine
2525
CreationTime tracev2.Time // Timestamp of the first appearance in the trace.
2626
StartTime tracev2.Time // Timestamp of the first time it started running. 0 if the goroutine never ran.
2727
EndTime tracev2.Time // Timestamp of when the goroutine exited. 0 if the goroutine never exited.
@@ -385,20 +385,25 @@ func (s *Summarizer) Event(ev *tracev2.Event) {
385385
}
386386

387387
// The goroutine hasn't been identified yet. Take the transition stack
388-
// and identify the goroutine by the bottom-most frame of that stack.
389-
// This bottom-most frame will be identical for all transitions on this
388+
// and identify the goroutine by the root frame of that stack.
389+
// This root frame will be identical for all transitions on this
390390
// goroutine, because it represents its immutable start point.
391-
if g.PC == 0 {
391+
if g.Name == "" {
392392
stk := st.Stack
393393
if stk != tracev2.NoStack {
394394
var frame tracev2.StackFrame
395395
var ok bool
396396
stk.Frames(func(f tracev2.StackFrame) bool {
397397
frame = f
398398
ok = true
399-
return false
399+
return true
400400
})
401401
if ok {
402+
// NB: this PC won't actually be consistent for
403+
// goroutines which existed at the start of the
404+
// trace. The UI doesn't use it directly; this
405+
// mainly serves as an indication that we
406+
// actually saw a call stack for the goroutine
402407
g.PC = frame.PC
403408
g.Name = frame.Func
404409
}

src/internal/trace/summary_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ func TestSummarizeGoroutinesTrace(t *testing.T) {
1818
hasSyncBlockTime bool
1919
hasGCMarkAssistTime bool
2020
)
21+
22+
assertContainsGoroutine(t, summaries, "runtime.gcBgMarkWorker")
23+
assertContainsGoroutine(t, summaries, "main.main.func1")
24+
2125
for _, summary := range summaries {
2226
basicGoroutineSummaryChecks(t, summary)
2327
hasSchedWaitTime = hasSchedWaitTime || summary.SchedWaitTime > 0
@@ -232,6 +236,15 @@ func TestSummarizeTasksTrace(t *testing.T) {
232236
}
233237
}
234238

239+
func assertContainsGoroutine(t *testing.T, summaries map[tracev2.GoID]*GoroutineSummary, name string) {
240+
for _, summary := range summaries {
241+
if summary.Name == name {
242+
return
243+
}
244+
}
245+
t.Errorf("missing goroutine %s", name)
246+
}
247+
235248
func basicGoroutineSummaryChecks(t *testing.T, summary *GoroutineSummary) {
236249
if summary.ID == tracev2.NoGoroutine {
237250
t.Error("summary found for no goroutine")

0 commit comments

Comments
 (0)