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

Add support for saving the current configuration settings #535

Merged
merged 4 commits into from
May 7, 2020
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
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