Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show pprof labels in thread names #3501

Merged
merged 2 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/api/dap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ In addition to the general [DAP spec](https://microsoft.github.io/debug-adapter-
stackTraceDepth<br>
showGlobalVariables<br>
showRegisters<br>
showPprofLabels<br>
hideSystemGoroutines<br>
goroutineFilters
</tr>
Expand Down
11 changes: 9 additions & 2 deletions service/dap/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ Type "help" followed by the name of a command for more information about it.`
dlv config substitutePath -clear

Adds or removes a path substitution rule. If -clear is used all substitutePath rules are removed.
See also Documentation/cli/substitutepath.md.`
See also Documentation/cli/substitutepath.md.

dlv config showPprofLabels <label>
dlv config showPprofLabels -clear <label>
dlv config showPprofLabels -clear

Adds or removes a label key to show in the callstack view. If -clear is used without an argument,
all labels are removed.`
msgSources = `Print list of source files.

dlv sources [<regex>]
Expand Down Expand Up @@ -138,7 +145,7 @@ func (s *Session) evaluateConfig(_, _ int, expr string) (string, error) {
Areas: []dap.InvalidatedAreas{"variables"},
},
})
case "goroutineFilters", "hideSystemGoroutines":
case "goroutineFilters", "hideSystemGoroutines", "showPprofLabels":
// Thread related data has become invalidated.
s.send(&dap.InvalidatedEvent{
Event: *newEvent("invalidated"),
Expand Down
43 changes: 43 additions & 0 deletions service/dap/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ func configureSet(sargs *launchAttachArgs, args string) (bool, string, error) {
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}

if cfgname == "showPprofLabels" {
err := configureSetShowPprofLabels(sargs, rest)
if err != nil {
return false, "", err
}
// Print the updated labels
return true, config.ConfigureListByName(sargs, cfgname, "cfgName"), nil
}

err := config.ConfigureSetSimple(rest, cfgname, field)
if err != nil {
return false, "", err
Expand Down Expand Up @@ -85,3 +94,37 @@ func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
}
return nil
}

func configureSetShowPprofLabels(args *launchAttachArgs, rest string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add documentation to command.go explaining how this configuration works please? For msgConfig

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 285116f

if strings.TrimSpace(rest) == "-clear" {
args.ShowPprofLabels = args.ShowPprofLabels[:0]
return nil
}
delete := false
argv := config.SplitQuotedFields(rest, '"')
if len(argv) == 2 && argv[0] == "-clear" {
argv = argv[1:]
delete = true
}
switch len(argv) {
case 0:
// do nothing, let caller show the current list of labels
return nil
case 1:
if delete {
for i := range args.ShowPprofLabels {
if args.ShowPprofLabels[i] == argv[0] {
copy(args.ShowPprofLabels[i:], args.ShowPprofLabels[i+1:])
args.ShowPprofLabels = args.ShowPprofLabels[:len(args.ShowPprofLabels)-1]
return nil
}
}
return fmt.Errorf("could not find label %q", argv[0])
} else {
args.ShowPprofLabels = append(args.ShowPprofLabels, argv[0])
}
default:
return fmt.Errorf("too many arguments to \"config showPprofLabels\"")
}
return nil
}
8 changes: 5 additions & 3 deletions service/dap/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,28 @@ func TestListConfig(t *testing.T) {
args: args{
args: &launchAttachArgs{},
},
want: formatConfig(0, false, false, "", false, [][2]string{}),
want: formatConfig(0, false, false, "", []string{}, false, [][2]string{}),
},
{
name: "default values",
args: args{
args: &defaultArgs,
},
want: formatConfig(50, false, false, "", false, [][2]string{}),
want: formatConfig(50, false, false, "", []string{}, false, [][2]string{}),
},
{
name: "custom values",
args: args{
args: &launchAttachArgs{
StackTraceDepth: 35,
ShowGlobalVariables: true,
GoroutineFilters: "SomeFilter",
ShowPprofLabels: []string{"SomeLabel"},
substitutePathClientToServer: [][2]string{{"hello", "world"}},
substitutePathServerToClient: [][2]string{{"world", "hello"}},
},
},
want: formatConfig(35, true, false, "", false, [][2]string{{"hello", "world"}}),
want: formatConfig(35, true, false, "SomeFilter", []string{"SomeLabel"}, false, [][2]string{{"hello", "world"}}),
},
}
for _, tt := range tests {
Expand Down
72 changes: 57 additions & 15 deletions service/dap/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ type launchAttachArgs struct {
ShowRegisters bool `cfgName:"showRegisters"`
// GoroutineFilters are the filters used when loading goroutines.
GoroutineFilters string `cfgName:"goroutineFilters"`
// ShowPprofLabels is an array of keys of pprof labels to show as a
// goroutine name in the threads view. If the array has one element, only
// that label's value will be shown; otherwise, each of the labels will be
// shown as "key:value". To show all labels, specify the single element "*".
ShowPprofLabels []string `cfgName:"showPprofLabels"`
// HideSystemGoroutines indicates if system goroutines should be removed from threads
// responses.
HideSystemGoroutines bool `cfgName:"hideSystemGoroutines"`
Expand All @@ -241,6 +246,7 @@ var defaultArgs = launchAttachArgs{
HideSystemGoroutines: false,
ShowRegisters: false,
GoroutineFilters: "",
ShowPprofLabels: []string{},
substitutePathClientToServer: [][2]string{},
substitutePathServerToClient: [][2]string{},
}
Expand Down Expand Up @@ -280,11 +286,9 @@ const (
maxStringLenInCallRetVars = 1 << 10 // 1024
)

var (
// Max number of goroutines that we will return.
// This is a var for testing
maxGoroutines = 1 << 10
)
// Max number of goroutines that we will return.
// This is a var for testing
var maxGoroutines = 1 << 10

// NewServer creates a new DAP Server. It takes an opened Listener
// via config and assumes its ownership. config.DisconnectChan has to be set;
Expand Down Expand Up @@ -355,6 +359,7 @@ func (s *Session) setLaunchAttachArgs(args LaunchAttachCommonConfig) {
s.args.ShowRegisters = args.ShowRegisters
s.args.HideSystemGoroutines = args.HideSystemGoroutines
s.args.GoroutineFilters = args.GoroutineFilters
s.args.ShowPprofLabels = args.ShowPprofLabels
if paths := args.SubstitutePath; len(paths) > 0 {
clientToServer := make([][2]string, 0, len(paths))
serverToClient := make([][2]string, 0, len(paths))
Expand Down Expand Up @@ -817,7 +822,8 @@ func (s *Session) logToConsole(msg string) {
Body: dap.OutputEventBody{
Output: msg + "\n",
Category: "console",
}})
},
})
}

func (s *Session) onInitializeRequest(request *dap.InitializeRequest) {
Expand Down Expand Up @@ -889,7 +895,7 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
return
}

var args = defaultLaunchConfig // narrow copy for initializing non-zero default values
args := defaultLaunchConfig // narrow copy for initializing non-zero default values
if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
s.sendShowUserErrorResponse(request.Request,
FailedToLaunch, "Failed to launch", fmt.Sprintf("invalid debug configuration - %v", err))
Expand Down Expand Up @@ -1002,7 +1008,8 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
Body: dap.OutputEventBody{
Output: fmt.Sprintf("Build Error: %s\n%s (%s)\n", cmd, strings.TrimSpace(string(out)), err.Error()),
Category: "stderr",
}})
},
})
// Users are used to checking the Debug Console for build errors.
// No need to bother them with a visible pop-up.
s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch",
Expand Down Expand Up @@ -1035,7 +1042,7 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
argsToLog.Cwd, _ = filepath.Abs(args.Cwd)
s.config.log.Debugf("launching binary '%s' with config: %s", debugbinary, prettyPrint(argsToLog))

var redirected = false
redirected := false
switch args.OutputMode {
case "remote":
redirected = true
Expand All @@ -1062,7 +1069,8 @@ func (s *Session) onLaunchRequest(request *dap.LaunchRequest) {
Body: dap.OutputEventBody{
Output: outs,
Category: category,
}})
},
})
}
if err != nil {
if err == io.EOF {
Expand Down Expand Up @@ -1687,7 +1695,8 @@ func (s *Session) onConfigurationDoneRequest(request *dap.ConfigurationDoneReque
func (s *Session) onContinueRequest(request *dap.ContinueRequest, allowNextStateChange chan struct{}) {
s.send(&dap.ContinueResponse{
Response: *newResponse(request.Request),
Body: dap.ContinueResponseBody{AllThreadsContinued: true}})
Body: dap.ContinueResponseBody{AllThreadsContinued: true},
})
s.runUntilStopAndNotify(api.Continue, allowNextStateChange)
}

Expand Down Expand Up @@ -1762,7 +1771,8 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
Body: dap.OutputEventBody{
Output: fmt.Sprintf("Unable to retrieve goroutines: %s\n", err.Error()),
Category: "stderr",
}})
},
})
}
threads = []dap.Thread{{Id: 1, Name: "Dummy"}}
} else if len(gs) == 0 {
Expand Down Expand Up @@ -1812,10 +1822,41 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
if g.Thread != nil && g.Thread.ThreadID() != 0 {
thread = fmt.Sprintf(" (Thread %d)", g.Thread.ThreadID())
}
var labels strings.Builder
writeLabelsForKeys := func(keys []string) {
for _, k := range keys {
labelValue := g.Labels()[k]
if labelValue != "" {
labels.WriteByte(' ')
labels.WriteString(k)
labels.WriteByte(':')
labels.WriteString(labelValue)
}
}
}
if len(s.args.ShowPprofLabels) == 1 {
labelKey := s.args.ShowPprofLabels[0]
if labelKey == "*" {
keys := make([]string, 0, len(g.Labels()))
for k := range g.Labels() {
keys = append(keys, k)
}
sort.Strings(keys)
writeLabelsForKeys(keys)
} else {
labelValue := g.Labels()[labelKey]
if labelValue != "" {
labels.WriteByte(' ')
labels.WriteString(labelValue)
}
}
} else {
writeLabelsForKeys(s.args.ShowPprofLabels)
}
// File name and line number are communicated via `stackTrace`
// so no need to include them here.
loc := g.UserCurrent()
threads[i].Name = fmt.Sprintf("%s[Go %d] %s%s", selected, g.ID, fnName(&loc), thread)
threads[i].Name = fmt.Sprintf("%s[Go %d%s] %s%s", selected, g.ID, labels.String(), fnName(&loc), thread)
threads[i].Id = int(g.ID)
}
}
Expand All @@ -1836,7 +1877,7 @@ func (s *Session) onThreadsRequest(request *dap.ThreadsRequest) {
// - "remote" -- attaches client to a debugger already attached to a process.
// Required args: none (host/port are used externally to connect)
func (s *Session) onAttachRequest(request *dap.AttachRequest) {
var args = defaultAttachConfig // narrow copy for initializing non-zero default values
args := defaultAttachConfig // narrow copy for initializing non-zero default values
if err := unmarshalLaunchAttachArgs(request.Arguments, &args); err != nil {
s.sendShowUserErrorResponse(request.Request, FailedToAttach, "Failed to attach", fmt.Sprintf("invalid debug configuration - %v", err))
return
Expand Down Expand Up @@ -2597,7 +2638,7 @@ func (s *Session) convertVariableWithOpts(v *proc.Variable, qualifiedNameOrExpr

// Some of the types might be fully or partially not loaded based on LoadConfig.
// Those that are fully missing (e.g. due to hitting MaxVariableRecurse), can be reloaded in place.
var reloadVariable = func(v *proc.Variable, qualifiedNameOrExpr string) (value string) {
reloadVariable := func(v *proc.Variable, qualifiedNameOrExpr string) (value string) {
// We might be loading variables from the frame that's not topmost, so use
// frame-independent address-based expression, not fully-qualified name as per
// https://github.com/go-delve/delve/blob/master/Documentation/api/ClientHowto.md#looking-into-variables.
Expand Down Expand Up @@ -3531,6 +3572,7 @@ func newEvent(event string) *dap.Event {

const BetterBadAccessError = `invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation]
Unable to propagate EXC_BAD_ACCESS signal to target process and panic (see https://github.com/go-delve/delve/issues/852)`

const BetterNextWhileNextingError = `Unable to step while the previous step is interrupted by a breakpoint.
Use 'Continue' to resume the original step command.`

Expand Down
Loading