Skip to content

Commit

Permalink
add way to list goroutines waiting on a channel
Browse files Browse the repository at this point in the history
  • Loading branch information
a committed Aug 26, 2023
1 parent 9579e3f commit 0744408
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 20 deletions.
54 changes: 47 additions & 7 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,11 @@ func printVar(out io.Writer, args string) error {
} else {
fmt.Fprintln(out, valstr)
}

if val.Kind == reflect.Chan {
chanGoroutines(val)
}

return nil
}

Expand Down Expand Up @@ -1721,7 +1726,6 @@ func stackCommand(out io.Writer, args string) error {
func goroutinesCommand(out io.Writer, args string) error {
wnd.Lock()
defer wnd.Unlock()
style := wnd.Style()
c := scrollbackEditor.Append(true)
defer c.End()

Expand All @@ -1734,6 +1738,13 @@ func goroutinesCommand(out io.Writer, args string) error {
return err
}

printGoroutines(c, gs)

return nil
}

func printGoroutines(c *richtext.Ctor, gs []*api.Goroutine) {
style := wnd.Style()
for _, g := range gs {
if g.ID == curGid {
c.Text("* ")
Expand All @@ -1754,7 +1765,16 @@ func goroutinesCommand(out io.Writer, args string) error {
locType = "Start"
}
loc := goroutineGetDisplayLiocation(g)
c.Text(fmt.Sprintf("Goroutine %d - %s: ", g.ID, locType))
gid := g.ID
writeLink(c, style, fmt.Sprintf("Goroutine %d", g.ID), func() {
state, err := client.SwitchGoroutine(gid)
if err != nil {
fmt.Fprintf(&editorWriter{true}, "Could not switch goroutine: %v\n", err)
} else {
go refreshState(refreshToUserFrame, clearGoroutineSwitch, state)
}
})
c.Text(fmt.Sprintf(" - %s: ", locType))
writeLinkToLocation(c, style, loc.File, loc.Line, loc.PC)
c.Text(fmt.Sprintf(" %s (%#x)", loc.Function.Name(), loc.PC))

Expand All @@ -1768,8 +1788,6 @@ func goroutinesCommand(out io.Writer, args string) error {

c.Text("\n")
}

return nil
}

func goroutineFormatWaitReason(g *api.Goroutine) string {
Expand Down Expand Up @@ -2179,13 +2197,17 @@ func writeGoroutineLong(w io.Writer, g *api.Goroutine, prefix string) {
prefix, formatLocation(g.GoStatementLoc))
}

func writeLinkToLocation(c *richtext.Ctor, style *style.Style, file string, line int, pc uint64) {
func writeLink(c *richtext.Ctor, style *style.Style, text string, fn func()) {
c.SetStyle(richtext.TextStyle{Face: style.Font, Color: linkColor, Flags: richtext.Underline})
c.Link(fmt.Sprintf("%s:%d", ShortenFilePath(file), line), linkHoverColor, func() {
c.Link(text, linkHoverColor, fn)
c.SetStyle(richtext.TextStyle{Face: style.Font})
}

func writeLinkToLocation(c *richtext.Ctor, style *style.Style, file string, line int, pc uint64) {
writeLink(c, style, fmt.Sprintf("%s:%d", ShortenFilePath(file), line), func() {
listingPanel.pinnedLoc = &api.Location{File: file, Line: line, PC: pc}
go refreshState(refreshToSameFrame, clearNothing, nil)
})
c.SetStyle(richtext.TextStyle{Face: style.Font})
}

func printStack(c *richtext.Ctor, stack []api.Stackframe, ind string) {
Expand Down Expand Up @@ -2392,3 +2414,21 @@ func getVariableLoadConfig() api.LoadConfig {
}
return cfg
}

func chanGoroutines(v *Variable) {
scope := currentEvalScope()
gs, _, _, _, err := client.ListGoroutinesWithFilter(0, 100, []api.ListGoroutinesFilter{{Kind: api.GoroutineWaitingOnChannel, Arg: fmt.Sprintf("*(*%q)(%#x)", v.Type, v.Addr)}}, nil, &scope)
if err != nil {
fmt.Fprintf(&editorWriter{true}, "error getting list of goroutines for channel: %v", err)
return
}
c := scrollbackEditor.Append(true)
defer c.End()

if v.Expression != "" && len(v.Expression) < 30 {
c.Text(fmt.Sprintf("Goroutines waiting on channel %s:\n", v.Expression))
} else {
c.Text("Goroutines waiting on channel:\n")
}
printGoroutines(c, gs)
}
2 changes: 1 addition & 1 deletion info.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ func loadGoroutines(p *asyncLoad) {
goroutinesPanel.rules = append(goroutinesPanel.rules, newGoroutineFilterRule())
}

gs, groups, _, tooManyGroups, err := client.ListGoroutinesWithFilter(0, lim, filters, groupby)
gs, groups, _, tooManyGroups, err := client.ListGoroutinesWithFilter(0, lim, filters, groupby, nil)
if err != nil {
p.done(err)
return
Expand Down
6 changes: 6 additions & 0 deletions infovars.go
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,12 @@ func showExprMenu(parentw *nucular.Window, exprMenuIdx int, v *Variable, clipb [
}
}

if v.Kind == reflect.Chan {
if w.MenuItem(label.TA("Channel goroutines", "LC")) {
go chanGoroutines(v)
}
}

if w.MenuItem(label.TA("Copy to clipboard", "LC")) {
clipboard.Set(string(clipb))
}
Expand Down
17 changes: 9 additions & 8 deletions internal/dlvclient/service/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,14 +633,15 @@ type ListGoroutinesFilter struct {
type GoroutineField uint8

const (
GoroutineFieldNone GoroutineField = iota
GoroutineCurrentLoc // the goroutine's CurrentLoc
GoroutineUserLoc // the goroutine's UserLoc
GoroutineGoLoc // the goroutine's GoStatementLoc
GoroutineStartLoc // the goroutine's StartLoc
GoroutineLabel // the goroutine's label
GoroutineRunning // the goroutine is running
GoroutineUser // the goroutine is a user goroutine
GoroutineFieldNone GoroutineField = iota
GoroutineCurrentLoc // the goroutine's CurrentLoc
GoroutineUserLoc // the goroutine's UserLoc
GoroutineGoLoc // the goroutine's GoStatementLoc
GoroutineStartLoc // the goroutine's StartLoc
GoroutineLabel // the goroutine's label
GoroutineRunning // the goroutine is running
GoroutineUser // the goroutine is a user goroutine
GoroutineWaitingOnChannel // the goroutine is waiting on the channel specified by the argument
)

// GoroutineGroup represents a group of goroutines in the return value of
Expand Down
6 changes: 3 additions & 3 deletions internal/dlvclient/service/rpc2/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,16 +349,16 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([

func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) {
var out ListGoroutinesOut
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}}, &out)
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, nil, api.GoroutineGroupingOptions{}, nil}, &out)
return out.Goroutines, out.Nextg, err
}

func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) {
func (c *RPCClient) ListGoroutinesWithFilter(start, count int, filters []api.ListGoroutinesFilter, group *api.GoroutineGroupingOptions, scope *api.EvalScope) ([]*api.Goroutine, []api.GoroutineGroup, int, bool, error) {
if group == nil {
group = &api.GoroutineGroupingOptions{}
}
var out ListGoroutinesOut
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group}, &out)
err := c.call("ListGoroutines", ListGoroutinesIn{start, count, filters, *group, scope}, &out)
return out.Goroutines, out.Groups, out.Nextg, out.TooManyGroups, err
}

Expand Down
2 changes: 2 additions & 0 deletions internal/dlvclient/service/rpc2/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ type ListGoroutinesIn struct {

Filters []api.ListGoroutinesFilter
api.GoroutineGroupingOptions

EvalScope *api.EvalScope
}

type ListGoroutinesOut struct {
Expand Down
13 changes: 12 additions & 1 deletion internal/starbind/starlark_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,15 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
return starlark.None, decorateError(thread, err)
}
}
if len(args) > 4 && args[4] != starlark.None {
err := unmarshalStarlarkValue(args[4], &rpcArgs.EvalScope, "EvalScope")
if err != nil {
return starlark.None, decorateError(thread, err)
}
} else {
scope := env.ctx.Scope()
rpcArgs.EvalScope = &scope
}
for _, kv := range kwargs {
var err error
switch kv[0].(starlark.String) {
Expand All @@ -1153,6 +1162,8 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Filters, "Filters")
case "GoroutineGroupingOptions":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.GoroutineGroupingOptions, "GoroutineGroupingOptions")
case "EvalScope":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.EvalScope, "EvalScope")
default:
err = fmt.Errorf("unknown argument %q", kv[0])
}
Expand All @@ -1166,7 +1177,7 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) {
}
return env.interfaceToStarlarkValue(rpcRet), nil
})
doc["goroutines"] = "builtin goroutines(Start, Count, Filters, GoroutineGroupingOptions)\n\ngoroutines lists all goroutines.\nIf Count is specified ListGoroutines will return at the first Count\ngoroutines and an index in Nextg, that can be passed as the Start\nparameter, to get more goroutines from ListGoroutines.\nPassing a value of Start that wasn't returned by ListGoroutines will skip\nan undefined number of goroutines.\n\nIf arg.Filters are specified the list of returned goroutines is filtered\napplying the specified filters.\nFor example:\n\n\tListGoroutinesFilter{ Kind: ListGoroutinesFilterUserLoc, Negated: false, Arg: \"afile.go\" }\n\nwill only return goroutines whose UserLoc contains \"afile.go\" as a substring.\nMore specifically a goroutine matches a location filter if the specified\nlocation, formatted like this:\n\n\tfilename:lineno in function\n\ncontains Arg[0] as a substring.\n\nFilters can also be applied to goroutine labels:\n\n\tListGoroutineFilter{ Kind: ListGoroutinesFilterLabel, Negated: false, Arg: \"key=value\" }\n\nthis filter will only return goroutines that have a key=value label.\n\nIf arg.GroupBy is not GoroutineFieldNone then the goroutines will\nbe grouped with the specified criterion.\nIf the value of arg.GroupBy is GoroutineLabel goroutines will\nbe grouped by the value of the label with key GroupByKey.\nFor each group a maximum of MaxGroupMembers example goroutines are\nreturned, as well as the total number of goroutines in the group."
doc["goroutines"] = "builtin goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope)\n\ngoroutines lists all goroutines.\nIf Count is specified ListGoroutines will return at the first Count\ngoroutines and an index in Nextg, that can be passed as the Start\nparameter, to get more goroutines from ListGoroutines.\nPassing a value of Start that wasn't returned by ListGoroutines will skip\nan undefined number of goroutines.\n\nIf arg.Filters are specified the list of returned goroutines is filtered\napplying the specified filters.\nFor example:\n\n\tListGoroutinesFilter{ Kind: ListGoroutinesFilterUserLoc, Negated: false, Arg: \"afile.go\" }\n\nwill only return goroutines whose UserLoc contains \"afile.go\" as a substring.\nMore specifically a goroutine matches a location filter if the specified\nlocation, formatted like this:\n\n\tfilename:lineno in function\n\ncontains Arg[0] as a substring.\n\nFilters can also be applied to goroutine labels:\n\n\tListGoroutineFilter{ Kind: ListGoroutinesFilterLabel, Negated: false, Arg: \"key=value\" }\n\nthis filter will only return goroutines that have a key=value label.\n\nIf arg.GroupBy is not GoroutineFieldNone then the goroutines will\nbe grouped with the specified criterion.\nIf the value of arg.GroupBy is GoroutineLabel goroutines will\nbe grouped by the value of the label with key GroupByKey.\nFor each group a maximum of MaxGroupMembers example goroutines are\nreturned, as well as the total number of goroutines in the group."
r["local_vars"] = starlark.NewBuiltin("local_vars", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := isCancelled(thread); err != nil {
return starlark.None, decorateError(thread, err)
Expand Down

0 comments on commit 0744408

Please sign in to comment.