Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
34 changes: 28 additions & 6 deletions internal/driver/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ var pprofVariables = variables{

// Filtering options
"nodecount": &variable{intKind, "-1", "", helpText(
"Max number of nodes to show",
"<n> is the max number of nodes to show",
"Uses heuristics to limit the number of locations to be displayed.",
"On graphs, dotted edges represent paths through nodes that have been removed.")},
"nodefraction": &variable{floatKind, "0.005", "", "Hide nodes below <f>*total"},
Expand Down Expand Up @@ -208,14 +208,14 @@ var pprofVariables = variables{
"Discard tags that match this regexp")},
// Heap profile options
"divide_by": &variable{floatKind, "1", "", helpText(
"Ratio to divide all samples before visualization",
"<f> is the ratio to divide all samples before visualization",
"Divide all samples values by a constant, eg the number of processors or jobs.")},
"mean": &variable{boolKind, "f", "", helpText(
"Average sample value over first value (count)",
"For memory profiles, report average memory per allocation.",
"For time-based profiles, report average time per event.")},
"sample_index": &variable{stringKind, "", "", helpText(
"Sample value to report (0-based index or name)",
"Sample value to report (<x> is 0-based index or name)",
"Profiles contain multiple values per sample.",
"Use sample_index=i to select the ith value (starting at 0).")},
"normalize": &variable{boolKind, "f", "", helpText(
Expand Down Expand Up @@ -246,6 +246,22 @@ func helpText(s ...string) string {
return strings.Join(s, "\n") + "\n"
}

func addVariablesPostFix(name string, vr *variable) string {
var postfix string
switch vr.kind {
case intKind:
postfix = "=<n>"
case floatKind:
postfix = "=<f>"
case stringKind:
postfix = "=<s>"
}
if name == "sample_index" {
postfix = "=<x>"
}
return postfix
}

// usage returns a string describing the pprof commands and variables.
// if commandLine is set, the output reflect cli usage.
func usage(commandLine bool) string {
Expand All @@ -254,7 +270,7 @@ func usage(commandLine bool) string {
prefix = "-"
}
fmtHelp := func(c, d string) string {
return fmt.Sprintf(" %-16s %s", c, strings.SplitN(d, "\n", 2)[0])
return fmt.Sprintf(" %-22s %s", c, strings.SplitN(d, "\n", 2)[0])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Curious: why did 16 change to 22?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To accommodate extra texts (e.g. =) added to the help message and really long option name (i.e. relative_percentages)

}

var commands []string
Expand All @@ -273,7 +289,11 @@ func usage(commandLine bool) string {
}

help = help + strings.Join(commands, "\n") + "\n\n" +
" Options:\n"
" Options:\n" +
" General format is <option>=<value>\n" +
" <f> is a float, <n> is an integer, and <s> is a string\n" +
" If the option is a boolean value, then you can simply type <option>\n" +
" Type \"help <option>\" for detailed usage\n\n"

// Print help for variables after sorting them.
// Collect radio variables by their group name to print them together.
Expand All @@ -284,7 +304,9 @@ func usage(commandLine bool) string {
radioOptions[vr.group] = append(radioOptions[vr.group], name)
continue
}
variables = append(variables, fmtHelp(prefix+name, vr.help))
option := prefix + name + addVariablesPostFix(name, vr)

variables = append(variables, fmtHelp(option, vr.help))
}
sort.Strings(variables)

Expand Down
9 changes: 9 additions & 0 deletions internal/driver/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ func interactive(p *profile.Profile, o *plugin.Options) error {
value = strings.TrimSpace(value)
}
if v := pprofVariables[name]; v != nil {
// All non-bool options require inputs
if v.kind != boolKind && value == "" {
example := name + addVariablesPostFix(name, v)
o.UI.PrintErr(fmt.Errorf("please input a value, e.g. %v", example))
continue
}
if name == "sample_index" {
// Error check sample_index=xxx to ensure xxx is a valid sample type.
index, err := p.SampleIndexByName(value)
Expand Down Expand Up @@ -267,6 +273,9 @@ func parseCommandLine(input []string) ([]string, variables, error) {
}
}
if c == nil {
if v := pprofVariables[name]; v != nil {
return nil, nil, fmt.Errorf("do you mean: \"%v=%v\"?", name, args[0])
Copy link
Collaborator

Choose a reason for hiding this comment

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

"did you mean"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Use backtick string to avoid having to escape double quotes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you really need %v here? Would %s be sufficient? Avoid %v unless required, it can leak implementation details in a funny way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}
return nil, nil, fmt.Errorf("unrecognized command: %q", name)
}

Expand Down
100 changes: 42 additions & 58 deletions internal/driver/interactive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,62 +40,53 @@ func TestShell(t *testing.T) {
savedVariables := pprofVariables
defer func() { pprofVariables = savedVariables }()

// Random interleave of independent scripts
pprofVariables = testVariables(savedVariables)
shortcuts1, scScript1 := makeShortcuts(interleave(script, 2), 1)
shortcuts2, scScript2 := makeShortcuts(interleave(script, 1), 2)

// pass in HTTPTransport when setting defaults, because otherwise default
// transport will try to add flags to the default flag set.
o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)})
o.UI = newUI(t, interleave(script, 0))
if err := interactive(p, o); err != nil {
t.Error("first attempt:", err)
}
// Random interleave of independent scripts
pprofVariables = testVariables(savedVariables)
o.UI = newUI(t, interleave(script, 1))
if err := interactive(p, o); err != nil {
t.Error("second attempt:", err)
var testcases = []struct {
name string
input []string
shortcuts shortcuts
allowRx string
numAllowRxMatches int
propagateError bool
Copy link
Collaborator

Choose a reason for hiding this comment

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

This flag seems to be false in all test cases, is this expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now fixed.
The conditional logic on line 79 was incorrect and is fixed now.

}{
{"Random interleave of independent scripts 1", interleave(script, 0), pprofShortcuts, "", 0, false},
{"Random interleave of independent scripts 2", interleave(script, 1), pprofShortcuts, "", 0, false},
{"Random interleave of independent scripts with shortcuts 1", scScript1, shortcuts1, "", 0, false},
{"Random interleave of independent scripts with shortcuts 2", scScript2, shortcuts2, "", 0, false},
{"Group with invalid vale", []string{"cumulative=this"}, pprofShortcuts, `unrecognized value for cumulative: "this". Use one of cum, flat`, 1, false},
Copy link
Contributor

Choose a reason for hiding this comment

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

(super nit) I think this is a typo: vale => value

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

{"No special value provided for the option", []string{"sample_index"}, pprofShortcuts, `please input a value, e.g. sample_index=<x>`, 1, false},
{"No string value provided for the option", []string{"focus="}, pprofShortcuts, `please input a value, e.g. focus=<s>`, 1, false},
{"No float value provided for the option", []string{"divide_by"}, pprofShortcuts, `please input a value, e.g. divide_by=<f>`, 1, false},
{"Helpful input format reminder", []string{"sample_index 0"}, pprofShortcuts, `do you mean: "sample_index=0`, 1, false},
{"Verify propagation of IO errors", []string{"**error**"}, pprofShortcuts, "", 0, false},
}

// Random interleave of independent scripts with shortcuts
pprofVariables = testVariables(savedVariables)
var scScript []string
pprofShortcuts, scScript = makeShortcuts(interleave(script, 2), 1)
o.UI = newUI(t, scScript)
if err := interactive(p, o); err != nil {
t.Error("first shortcut attempt:", err)
}
o := setDefaults(&plugin.Options{HTTPTransport: transport.New(nil)})
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
pprofVariables = testVariables(savedVariables)
pprofShortcuts = tc.shortcuts
ui := &proftest.TestUI{
T: t,
Input: tc.input,
AllowRx: tc.allowRx,
}
o.UI = ui

// Random interleave of independent scripts with shortcuts
pprofVariables = testVariables(savedVariables)
pprofShortcuts, scScript = makeShortcuts(interleave(script, 1), 2)
o.UI = newUI(t, scScript)
if err := interactive(p, o); err != nil {
t.Error("second shortcut attempt:", err)
}
err := interactive(p, o)
if (tc.propagateError && err == nil) || (tc.propagateError && err != nil) {
t.Errorf("%v: %v", tc.name, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: use %s when possible

t.Errorf("%s: %v", tc.name, err)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

}

// Group with invalid value
pprofVariables = testVariables(savedVariables)
ui := &proftest.TestUI{
T: t,
Input: []string{"cumulative=this"},
AllowRx: `unrecognized value for cumulative: "this". Use one of cum, flat`,
}
o.UI = ui
if err := interactive(p, o); err != nil {
t.Error("invalid group value:", err)
}
// Confirm error message written out once.
if ui.NumAllowRxMatches != 1 {
t.Errorf("want error message to be printed 1 time, got %v", ui.NumAllowRxMatches)
}
// Verify propagation of IO errors
pprofVariables = testVariables(savedVariables)
o.UI = newUI(t, []string{"**error**"})
if err := interactive(p, o); err == nil {
t.Error("expected IO error, got nil")
// Confirm error message written out once.
if tc.numAllowRxMatches != ui.NumAllowRxMatches {
t.Errorf("want error message to be printed %v time(s), got %v",
Copy link
Contributor

Choose a reason for hiding this comment

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

Use %d instead of %v?
(I know this line already existed, but nice to be able to make it a bit more idiomatic.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

tc.numAllowRxMatches, ui.NumAllowRxMatches)
}
})
}

}

var testCommands = commands{
Expand Down Expand Up @@ -132,7 +123,7 @@ var script = []string{
"f=-1;f=-2.5;check f=-2.5;f=0.0001;check f=0.0001",
"check ff=0;ff=-1.01;check ff=-1.01;ff=100;check ff=100",
"s=one;s=two;check s=two",
"ss=tree;check ss=tree;ss=;check ss;ss=forest;check ss=forest",
"ss=tree;check ss=tree;ss=forest;check ss=forest",
"ta=true;check ta=true;check tb=false;check tc=false;tb=1;check tb=true;check ta=false;check tc=false;tc=yes;check tb=false;check ta=false;check tc=true",
}

Expand Down Expand Up @@ -162,13 +153,6 @@ func makeShortcuts(input []string, seed int) (shortcuts, []string) {
return s, output
}

func newUI(t *testing.T, input []string) plugin.UI {
return &proftest.TestUI{
T: t,
Input: input,
}
}

func checkValue(p *profile.Profile, cmd []string, vars variables, o *plugin.Options) error {
if len(cmd) != 2 {
return fmt.Errorf("expected len(cmd)==2, got %v", cmd)
Expand Down