Skip to content

Commit

Permalink
Add support for saving the current configuration settings (#535)
Browse files Browse the repository at this point in the history
Add support for saving the current configuration settings as a named
configuration and reloading such a configuration later.

All of the configurations are stored in a settings file, by default:
~/.config/pprof.json

A struct named config replaces the old map[string]string which used
to store the set of configurable options. Instead, each option is
now a field in the config struct.

The UI now has a new 'Config' menu with

Save as: prompts for a name and saves current config.
Default: applies default config.
$X: for every config named X: applies settings from X.

The currently selected config is highlighted.

Every named config has a delete button to control deletion.

Some semi-related changes:
1. Both filefunctions and functions in the granularity group had an
   initial value of true, which is incorrect since these are mutually
   incompatible choices. Set filefunctions' initial value to false.
2. Renamed the group for sorting from "cumulative" to "sort".
3. Store testing.T in TestUI instead of leaving the field nil.
  • Loading branch information
ghemawat authored May 7, 2020
1 parent 160c429 commit 427632f
Show file tree
Hide file tree
Showing 16 changed files with 1,485 additions and 586 deletions.
34 changes: 34 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,3 +387,37 @@ the symbolization handler.

* **-symbolize=demangle=templates:** Demangle, and trim function parameters, but
not template parameters.

# Web Interface

When the user requests a web interface (by supplying an `-http=[host]:[port]`
argument on the command-line), pprof starts a web server and opens a browser
window pointing at that server. The web interface provided by the server allows
the user to interactively view profile data in multiple formats.

The top of the display is a header that contains some buttons and menus.

## Config

The `Config` menu allows the user to save the current refinement
settings (e.g., the focus and hide list) as a named configuration. A
saved configuration can later be re-applied to reinstitue the saved
refinements. The `Config` menu contains:

**Save as ...**: shows a dialog where the user can type in a
configuration name. The current refinement settings are saved under
the specified name.

**Default**: switches back to the default view by removing all refinements.

The `Config` menu also contains an entry per named
configuration. Selecting such an entry applies that configuration. The
currently selected entry is marked with a ✓. Clicking on the 🗙 on the
right-hand side of such an entry deletes the configuration (after
prompting the user to confirm).

## TODO: cover the following issues:

* Overall layout
* Menu entries
* Explanation of all the views
129 changes: 69 additions & 60 deletions internal/driver/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
flagHTTP := flag.String("http", "", "Present interactive web UI at the specified http host:port")
flagNoBrowser := flag.Bool("no_browser", false, "Skip opening a browswer for the interactive web UI")

// Flags used during command processing
installedFlags := installFlags(flag)
// Flags that set configuration properties.
cfg := currentConfig()
configFlagSetter := installConfigFlags(flag, &cfg)

flagCommands := make(map[string]*bool)
flagParamCommands := make(map[string]*string)
Expand Down Expand Up @@ -107,8 +108,8 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
}
}

// Report conflicting options
if err := updateFlags(installedFlags); err != nil {
// Apply any specified flags to cfg.
if err := configFlagSetter(); err != nil {
return nil, nil, err
}

Expand All @@ -124,18 +125,18 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
return nil, nil, errors.New("-no_browser only makes sense with -http")
}

si := pprofVariables["sample_index"].value
si := cfg.SampleIndex
si = sampleIndex(flagTotalDelay, si, "delay", "-total_delay", o.UI)
si = sampleIndex(flagMeanDelay, si, "delay", "-mean_delay", o.UI)
si = sampleIndex(flagContentions, si, "contentions", "-contentions", o.UI)
si = sampleIndex(flagInUseSpace, si, "inuse_space", "-inuse_space", o.UI)
si = sampleIndex(flagInUseObjects, si, "inuse_objects", "-inuse_objects", o.UI)
si = sampleIndex(flagAllocSpace, si, "alloc_space", "-alloc_space", o.UI)
si = sampleIndex(flagAllocObjects, si, "alloc_objects", "-alloc_objects", o.UI)
pprofVariables.set("sample_index", si)
cfg.SampleIndex = si

if *flagMeanDelay {
pprofVariables.set("mean", "true")
cfg.Mean = true
}

source := &source{
Expand All @@ -154,7 +155,7 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
return nil, nil, err
}

normalize := pprofVariables["normalize"].boolValue()
normalize := cfg.Normalize
if normalize && len(source.Base) == 0 {
return nil, nil, errors.New("must have base profile to normalize by")
}
Expand All @@ -163,6 +164,8 @@ func parseFlags(o *plugin.Options) (*source, []string, error) {
if bu, ok := o.Obj.(*binutils.Binutils); ok {
bu.SetTools(*flagTools)
}

setCurrentConfig(cfg)
return source, cmd, nil
}

Expand Down Expand Up @@ -194,66 +197,72 @@ func dropEmpty(list []*string) []string {
return l
}

// installFlags creates command line flags for pprof variables.
func installFlags(flag plugin.FlagSet) flagsInstalled {
f := flagsInstalled{
ints: make(map[string]*int),
bools: make(map[string]*bool),
floats: make(map[string]*float64),
strings: make(map[string]*string),
}
for n, v := range pprofVariables {
switch v.kind {
case boolKind:
if v.group != "" {
// Set all radio variables to false to identify conflicts.
f.bools[n] = flag.Bool(n, false, v.help)
// installConfigFlags creates command line flags for configuration
// fields and returns a function which can be called after flags have
// been parsed to copy any flags specified on the command line to
// *cfg.
func installConfigFlags(flag plugin.FlagSet, cfg *config) func() error {
// List of functions for setting the different parts of a config.
var setters []func()
var err error // Holds any errors encountered while running setters.

for _, field := range configFields {
n := field.name
help := configHelp[n]
var setter func()
switch ptr := cfg.fieldPtr(field).(type) {
case *bool:
f := flag.Bool(n, *ptr, help)
setter = func() { *ptr = *f }
case *int:
f := flag.Int(n, *ptr, help)
setter = func() { *ptr = *f }
case *float64:
f := flag.Float64(n, *ptr, help)
setter = func() { *ptr = *f }
case *string:
if len(field.choices) == 0 {
f := flag.String(n, *ptr, help)
setter = func() { *ptr = *f }
} else {
f.bools[n] = flag.Bool(n, v.boolValue(), v.help)
// Make a separate flag per possible choice.
// Set all flags to initially false so we can
// identify conflicts.
bools := make(map[string]*bool)
for _, choice := range field.choices {
bools[choice] = flag.Bool(choice, false, configHelp[choice])
}
setter = func() {
var set []string
for k, v := range bools {
if *v {
set = append(set, k)
}
}
switch len(set) {
case 0:
// Leave as default value.
case 1:
*ptr = set[0]
default:
err = fmt.Errorf("conflicting options set: %v", set)
}
}
}
case intKind:
f.ints[n] = flag.Int(n, v.intValue(), v.help)
case floatKind:
f.floats[n] = flag.Float64(n, v.floatValue(), v.help)
case stringKind:
f.strings[n] = flag.String(n, v.value, v.help)
}
setters = append(setters, setter)
}
return f
}

// updateFlags updates the pprof variables according to the flags
// parsed in the command line.
func updateFlags(f flagsInstalled) error {
vars := pprofVariables
groups := map[string]string{}
for n, v := range f.bools {
vars.set(n, fmt.Sprint(*v))
if *v {
g := vars[n].group
if g != "" && groups[g] != "" {
return fmt.Errorf("conflicting options %q and %q set", n, groups[g])
return func() error {
// Apply the setter for every flag.
for _, setter := range setters {
setter()
if err != nil {
return err
}
groups[g] = n
}
return nil
}
for n, v := range f.ints {
vars.set(n, fmt.Sprint(*v))
}
for n, v := range f.floats {
vars.set(n, fmt.Sprint(*v))
}
for n, v := range f.strings {
vars.set(n, *v)
}
return nil
}

type flagsInstalled struct {
ints map[string]*int
bools map[string]*bool
floats map[string]*float64
strings map[string]*string
}

// isBuildID determines if the profile may contain a build ID, by
Expand Down
Loading

0 comments on commit 427632f

Please sign in to comment.