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 }