delve/service/api/command.go
Alessandro Arzilli 0b35fe6d42
proc,service,terminal: add ways to list goroutines waiting on a channel (#3481)
Adds -chan option to the goroutines command to list only the goroutines
running on a specified channel.
Also when printing a variable if it is a channel also print the list of
goroutines that are waiting on it.
2023-08-23 13:02:34 -07:00

169 lines
4.0 KiB
Go

package api
import (
"errors"
"fmt"
"strconv"
"strings"
)
type PrintGoroutinesFlags uint8
const (
PrintGoroutinesStack PrintGoroutinesFlags = 1 << iota
PrintGoroutinesLabels
PrintGoroutinesExec
)
type FormatGoroutineLoc int
const (
FglRuntimeCurrent = FormatGoroutineLoc(iota)
FglUserCurrent
FglGo
FglStart
)
const (
maxGroupMembers = 5
maxGoroutineGroups = 50
)
// The number of goroutines we're going to request on each RPC call
const goroutineBatchSize = 10000
func ParseGoroutineArgs(argstr string) ([]ListGoroutinesFilter, GoroutineGroupingOptions, FormatGoroutineLoc, PrintGoroutinesFlags, int, int, string, error) {
args := strings.Split(argstr, " ")
var filters []ListGoroutinesFilter
var group GoroutineGroupingOptions
var fgl = FglUserCurrent
var flags PrintGoroutinesFlags
var depth = 10
var batchSize = goroutineBatchSize
var cmd string
group.MaxGroupMembers = maxGroupMembers
group.MaxGroups = maxGoroutineGroups
for i := 0; i < len(args); i++ {
arg := args[i]
switch arg {
case "-u":
fgl = FglUserCurrent
case "-r":
fgl = FglRuntimeCurrent
case "-g":
fgl = FglGo
case "-s":
fgl = FglStart
case "-l":
flags |= PrintGoroutinesLabels
case "-t":
flags |= PrintGoroutinesStack
// optional depth argument
if i+1 < len(args) && len(args[i+1]) > 0 {
n, err := strconv.Atoi(args[i+1])
if err == nil {
depth = n
i++
}
}
case "-w", "-with":
filter, err := readGoroutinesFilter(args, &i)
if err != nil {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
filters = append(filters, *filter)
case "-wo", "-without":
filter, err := readGoroutinesFilter(args, &i)
if err != nil {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
filter.Negated = true
filters = append(filters, *filter)
case "-group":
var err error
group.GroupBy, err = readGoroutinesFilterKind(args, i+1)
if err != nil {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
i++
if group.GroupBy == GoroutineLabel {
if i+1 >= len(args) {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
group.GroupByKey = args[i+1]
i++
}
batchSize = 0 // grouping only works well if run on all goroutines
case "-chan":
i++
if i >= len(args) {
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", errors.New("not enough arguments after -chan")
}
filters = append(filters, ListGoroutinesFilter{Kind: GoroutineWaitingOnChannel, Arg: args[i]})
case "-exec":
flags |= PrintGoroutinesExec
cmd = strings.Join(args[i+1:], " ")
i = len(args)
case "":
// nothing to do
default:
return nil, GoroutineGroupingOptions{}, 0, 0, 0, 0, "", fmt.Errorf("wrong argument: '%s'", arg)
}
}
return filters, group, fgl, flags, depth, batchSize, cmd, nil
}
func readGoroutinesFilterKind(args []string, i int) (GoroutineField, error) {
if i >= len(args) {
return GoroutineFieldNone, fmt.Errorf("%s must be followed by an argument", args[i-1])
}
switch args[i] {
case "curloc":
return GoroutineCurrentLoc, nil
case "userloc":
return GoroutineUserLoc, nil
case "goloc":
return GoroutineGoLoc, nil
case "startloc":
return GoroutineStartLoc, nil
case "label":
return GoroutineLabel, nil
case "running":
return GoroutineRunning, nil
case "user":
return GoroutineUser, nil
default:
return GoroutineFieldNone, fmt.Errorf("unrecognized argument to %s %s", args[i-1], args[i])
}
}
func readGoroutinesFilter(args []string, pi *int) (*ListGoroutinesFilter, error) {
r := new(ListGoroutinesFilter)
var err error
r.Kind, err = readGoroutinesFilterKind(args, *pi+1)
if err != nil {
return nil, err
}
*pi++
switch r.Kind {
case GoroutineRunning, GoroutineUser:
return r, nil
}
if *pi+1 >= len(args) {
return nil, fmt.Errorf("%s %s needs to be followed by an expression", args[*pi-1], args[*pi])
}
r.Arg = args[*pi+1]
*pi++
return r, nil
}