internal/trace: don't report regions on system goroutines

If a goroutine is started within a user region, internal/trace assigns
the child goroutine a nameless region for its entire lifetime which is
assosciated the same task as the parent's region.

This is not strictly necessary: a child goroutine is not necessarily
related to the task unless it performs some task operation (in which
case it will be associated with the task through the standard means).

However, it can be quite handy to see child goroutines within a region,
which may be child worker goroutines that you simply didn't perform task
operations on.

If the first GC occurs during a region, the GC worker goroutines will
also inherit a child region. We know for sure that these aren't related
to the task, so filter them out from the region list.

Note that we can't exclude system goroutines from setting activeRegions
in EvGoCreate handling, because we don't know the goroutine start
function name until the first EvGoStart.

Fixes #53784.

Change-Id: Ic83d84e23858a8400a76d1ae2f1418ef49951178
Reviewed-on: https://go-review.googlesource.com/c/go/+/416858
Run-TryBot: Michael Pratt <mpratt@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Michael Pratt 2022-07-11 15:34:26 -04:00
parent 846490110a
commit 123a6328b7
3 changed files with 30 additions and 17 deletions

View file

@ -571,7 +571,7 @@ func generateTrace(params *traceParams, consumer traceConsumer) error {
fname := stk[0].Fn fname := stk[0].Fn
info.name = fmt.Sprintf("G%v %s", newG, fname) info.name = fmt.Sprintf("G%v %s", newG, fname)
info.isSystemG = isSystemGoroutine(fname) info.isSystemG = trace.IsSystemGoroutine(fname)
ctx.gcount++ ctx.gcount++
setGState(ev, newG, gDead, gRunnable) setGState(ev, newG, gDead, gRunnable)
@ -1129,12 +1129,6 @@ func (ctx *traceContext) buildBranch(parent frameNode, stk []*trace.Frame) int {
return ctx.buildBranch(node, stk) return ctx.buildBranch(node, stk)
} }
func isSystemGoroutine(entryFn string) bool {
// This mimics runtime.isSystemGoroutine as closely as
// possible.
return entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
}
// firstTimestamp returns the timestamp of the first event record. // firstTimestamp returns the timestamp of the first event record.
func firstTimestamp() int64 { func firstTimestamp() int64 {
res, _ := parseTrace() res, _ := parseTrace()

View file

@ -4,7 +4,10 @@
package trace package trace
import "sort" import (
"sort"
"strings"
)
// GDesc contains statistics and execution details of a single goroutine. // GDesc contains statistics and execution details of a single goroutine.
type GDesc struct { type GDesc struct {
@ -126,10 +129,17 @@ func (g *GDesc) finalize(lastTs, activeGCStartTime int64, trigger *Event) {
finalStat := g.snapshotStat(lastTs, activeGCStartTime) finalStat := g.snapshotStat(lastTs, activeGCStartTime)
g.GExecutionStat = finalStat g.GExecutionStat = finalStat
for _, s := range g.activeRegions {
s.End = trigger // System goroutines are never part of regions, even though they
s.GExecutionStat = finalStat.sub(s.GExecutionStat) // "inherit" a task due to creation (EvGoCreate) from within a region.
g.Regions = append(g.Regions, s) // This may happen e.g. if the first GC is triggered within a region,
// starting the GC worker goroutines.
if !IsSystemGoroutine(g.Name) {
for _, s := range g.activeRegions {
s.End = trigger
s.GExecutionStat = finalStat.sub(s.GExecutionStat)
g.Regions = append(g.Regions, s)
}
} }
*(g.gdesc) = gdesc{} *(g.gdesc) = gdesc{}
} }
@ -158,10 +168,13 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc {
case EvGoCreate: case EvGoCreate:
g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)} g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)}
g.blockSchedTime = ev.Ts g.blockSchedTime = ev.Ts
// When a goroutine is newly created, inherit the // When a goroutine is newly created, inherit the task
// task of the active region. For ease handling of // of the active region. For ease handling of this
// this case, we create a fake region description with // case, we create a fake region description with the
// the task id. // task id. This isn't strictly necessary as this
// goroutine may not be assosciated with the task, but
// it can be convenient to see all children created
// during a region.
if creatorG := gs[ev.G]; creatorG != nil && len(creatorG.gdesc.activeRegions) > 0 { if creatorG := gs[ev.G]; creatorG != nil && len(creatorG.gdesc.activeRegions) > 0 {
regions := creatorG.gdesc.activeRegions regions := creatorG.gdesc.activeRegions
s := regions[len(regions)-1] s := regions[len(regions)-1]
@ -336,3 +349,9 @@ func RelatedGoroutines(events []*Event, goid uint64) map[uint64]bool {
gmap[0] = true // for GC events gmap[0] = true // for GC events
return gmap return gmap
} }
func IsSystemGoroutine(entryFn string) bool {
// This mimics runtime.isSystemGoroutine as closely as
// possible.
return entryFn != "runtime.main" && strings.HasPrefix(entryFn, "runtime.")
}

View file

@ -1120,7 +1120,7 @@ func tracebackHexdump(stk stack, frame *stkframe, bad uintptr) {
// system (that is, the finalizer goroutine) is considered a user // system (that is, the finalizer goroutine) is considered a user
// goroutine. // goroutine.
func isSystemGoroutine(gp *g, fixed bool) bool { func isSystemGoroutine(gp *g, fixed bool) bool {
// Keep this in sync with cmd/trace/trace.go:isSystemGoroutine. // Keep this in sync with internal/trace.IsSystemGoroutine.
f := findfunc(gp.startpc) f := findfunc(gp.startpc)
if !f.valid() { if !f.valid() {
return false return false