Skip to content

Commit

Permalink
Show number of running, runnable and blocked goroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-sucha authored and dominikh committed May 19, 2024
1 parent c167bd1 commit 3892d8d
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 13 deletions.
27 changes: 20 additions & 7 deletions cmd/gotraceui/canvas.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ type Canvas struct {
scrollbar widget.Scrollbar
axis Axis

memoryGraph Plot
memoryGraph Plot
goroutineGraph Plot

// State for dragging the canvas
drag struct {
Expand Down Expand Up @@ -209,7 +210,7 @@ func NewCanvasInto(cv *Canvas, dwin *DebugWindow, t *Trace) {
*cv = Canvas{
resizeMemoryTimelines: component.Resize{
Axis: layout.Vertical,
Ratio: 0.1,
Ratio: 0.2,
},
axis: Axis{cv: cv, anchor: AxisAnchorCenter},
trace: t,
Expand Down Expand Up @@ -924,13 +925,25 @@ func (cv *Canvas) Layout(win *theme.Window, gtx layout.Context) layout.Dimension

func(gtx layout.Context) layout.Dimensions {
return theme.Resize(win.Theme, &cv.resizeMemoryTimelines).Layout(win, gtx,
// Memory graph
func(win *theme.Window, gtx layout.Context) layout.Dimensions {
defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop()
cv.drag.drag.Add(gtx.Ops)
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
// Memory graph
defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop()
cv.drag.drag.Add(gtx.Ops)

dims := cv.memoryGraph.Layout(win, gtx, cv)
return dims
}),
layout.Flexed(0.5, func(gtx layout.Context) layout.Dimensions {
// Goroutine graph
defer clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops).Pop()
cv.drag.drag.Add(gtx.Ops)

dims := cv.memoryGraph.Layout(win, gtx, cv)
return dims
dims := cv.goroutineGraph.Layout(win, gtx, cv)
return dims
}),
)
},

// Timelines and scrollbar
Expand Down
40 changes: 34 additions & 6 deletions cmd/gotraceui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,7 @@ func (mwin *MainWindow) showFileOpenDialog() {
func (mwin *MainWindow) loadTraceImpl(res loadTraceResult) {
NewCanvasInto(&mwin.canvas, mwin.debugWindow, res.trace)
mwin.canvas.memoryGraph = res.plot
mwin.canvas.goroutineGraph = res.goroutinePlot
mwin.canvas.timelines = append(mwin.canvas.timelines, res.timelines...)

for _, tl := range res.timelines {
Expand Down Expand Up @@ -1436,9 +1437,10 @@ func main() {
}

type loadTraceResult struct {
trace *Trace
plot Plot
timelines []*Timeline
trace *Trace
plot Plot
goroutinePlot Plot
timelines []*Timeline
}

type progresser interface {
Expand Down Expand Up @@ -1581,6 +1583,31 @@ func loadTrace(f io.Reader, p progresser, cv *Canvas) (loadTraceResult, error) {
},
)

gg := Plot{
Name: "Goroutine count",
Unit: "goroutines",
}
gg.AddSeries(
PlotSeries{
Name: "Runnable",
Points: pt.Metrics["/gotraceui/sched/goroutines/runnable:goroutines"],
Style: PlotStaircase,
Color: colors[colorStateReady],
},
PlotSeries{
Name: "Running",
Points: pt.Metrics["/gotraceui/sched/goroutines/running:goroutines"],
Style: PlotStaircase,
Color: colors[colorStateActive],
},
PlotSeries{
Name: "Waiting",
Points: pt.Metrics["/gotraceui/sched/goroutines/waiting:goroutines"],
Style: PlotStaircase,
Color: colors[colorStateBlocked],
},
)

var goroot, gopath string
for _, fn := range tr.Functions {
if strings.HasPrefix(fn.Func, "runtime.") && strings.Count(fn.Func, ".") == 1 && strings.Contains(fn.File, filepath.Join("go", "src", "runtime")) && !strings.ContainsRune(fn.Func, os.PathSeparator) {
Expand Down Expand Up @@ -1626,9 +1653,10 @@ func loadTrace(f io.Reader, p progresser, cv *Canvas) (loadTraceResult, error) {
tr.TimeOffset = -tr.Start()

return loadTraceResult{
trace: tr,
plot: mg,
timelines: timelines,
trace: tr,
plot: mg,
goroutinePlot: gg,
timelines: timelines,
}, nil
}

Expand Down
53 changes: 53 additions & 0 deletions trace/ptrace/ptrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,31 @@ func rangeActualScope(r exptrace.Range) rangeScope {
}
}

// runningGauge builds up a gauge over time.
type runningGauge struct {
// current is the current value of the gauge.
current int64
// points capture the current value over time.
points []Point
}

// add adds a value to the gauge at time t.
// It assumes that t will not decrease with subsequent calls.
func (rg *runningGauge) add(t exptrace.Time, v int64) {
rg.current += v
if idx := len(rg.points) - 1; idx >= 0 && rg.points[idx].When == t {
rg.points[idx].Value = uint64(rg.current)
} else {
rg.points = append(rg.points, Point{When: t, Value: uint64(rg.current)})
}
}

type goroutineMetrics struct {
runnableGoroutines runningGauge
runningGoroutines runningGauge
blockedGoroutines runningGauge
}

func processEvents(r *exptrace.Reader, tr *Trace, progress func(float64)) error {
// OPT(dh): evaluate reading all events in one pass, then preallocating []Span slices based on the number
// of events we saw for Ps and Gs.
Expand Down Expand Up @@ -476,6 +501,7 @@ func processEvents(r *exptrace.Reader, tr *Trace, progress func(float64)) error
synced := false
userRegionDepths := map[exptrace.GoID]int{}
var traceStart exptrace.Time
var gm goroutineMetrics
for {
ev, err := r.ReadEvent()
if err != nil {
Expand All @@ -490,6 +516,9 @@ func processEvents(r *exptrace.Reader, tr *Trace, progress func(float64)) error

if tr.Events.Len() == 0 {
traceStart = ev.Time()
gm.runningGoroutines.add(traceStart, 0)
gm.runnableGoroutines.add(traceStart, 0)
gm.blockedGoroutines.add(traceStart, 0)
}

evID := EventID(tr.Events.Len())
Expand Down Expand Up @@ -711,6 +740,26 @@ func processEvents(r *exptrace.Reader, tr *Trace, progress func(float64)) error
if from == exptrace.GoUndetermined {
s.Start = traceStart
}
switch {
case from == exptrace.GoRunnable:
gm.runnableGoroutines.add(ev.Time(), -1)
case to == exptrace.GoRunnable:
gm.runnableGoroutines.add(ev.Time(), 1)
}
switch {
case from == exptrace.GoRunning:
gm.runningGoroutines.add(ev.Time(), -1)
case to == exptrace.GoRunning:
gm.runningGoroutines.add(ev.Time(), 1)
}
fromIsBlocked := from == exptrace.GoSyscall || from == exptrace.GoWaiting
toIsBlocked := to == exptrace.GoSyscall || to == exptrace.GoWaiting
switch {
case fromIsBlocked && !toIsBlocked:
gm.blockedGoroutines.add(ev.Time(), -1)
case !fromIsBlocked && toIsBlocked:
gm.blockedGoroutines.add(ev.Time(), 1)
}

// XXX actually, for from == exptrace.GoUndetermined, we still need to do omst of the work to update P
// spans. a proc may start, followed by a goroutine going from undetermined->running on that proc, and
Expand Down Expand Up @@ -873,6 +922,10 @@ func processEvents(r *exptrace.Reader, tr *Trace, progress func(float64)) error
}
}

tr.Metrics["/gotraceui/sched/goroutines/runnable:goroutines"] = gm.runnableGoroutines.points
tr.Metrics["/gotraceui/sched/goroutines/running:goroutines"] = gm.runningGoroutines.points
tr.Metrics["/gotraceui/sched/goroutines/waiting:goroutines"] = gm.blockedGoroutines.points

return nil
}

Expand Down

0 comments on commit 3892d8d

Please sign in to comment.