diff options
author | Tianon Gravi <admwiggin@gmail.com> | 2015-01-15 11:54:00 -0700 |
---|---|---|
committer | Tianon Gravi <admwiggin@gmail.com> | 2015-01-15 11:54:00 -0700 |
commit | f154da9e12608589e8d5f0508f908a0c3e88a1bb (patch) | |
tree | f8255d51e10c6f1e0ed69702200b966c9556a431 /src/cmd/pprof/internal/driver/interactive.go | |
parent | 8d8329ed5dfb9622c82a9fbec6fd99a580f9c9f6 (diff) | |
download | golang-upstream/1.4.tar.gz |
Imported Upstream version 1.4upstream/1.4
Diffstat (limited to 'src/cmd/pprof/internal/driver/interactive.go')
-rw-r--r-- | src/cmd/pprof/internal/driver/interactive.go | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/src/cmd/pprof/internal/driver/interactive.go b/src/cmd/pprof/internal/driver/interactive.go new file mode 100644 index 000000000..13009bf7e --- /dev/null +++ b/src/cmd/pprof/internal/driver/interactive.go @@ -0,0 +1,492 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package driver + +import ( + "fmt" + "io" + "regexp" + "sort" + "strconv" + "strings" + + "cmd/pprof/internal/commands" + "cmd/pprof/internal/plugin" + "cmd/pprof/internal/profile" +) + +var profileFunctionNames = []string{} + +// functionCompleter replaces provided substring with a function +// name retrieved from a profile if a single match exists. Otherwise, +// it returns unchanged substring. It defaults to no-op if the profile +// is not specified. +func functionCompleter(substring string) string { + found := "" + for _, fName := range profileFunctionNames { + if strings.Contains(fName, substring) { + if found != "" { + return substring + } + found = fName + } + } + if found != "" { + return found + } + return substring +} + +// updateAutoComplete enhances autocompletion with information that can be +// retrieved from the profile +func updateAutoComplete(p *profile.Profile) { + profileFunctionNames = nil // remove function names retrieved previously + for _, fn := range p.Function { + profileFunctionNames = append(profileFunctionNames, fn.Name) + } +} + +// splitCommand splits the command line input into tokens separated by +// spaces. Takes care to separate commands of the form 'top10' into +// two tokens: 'top' and '10' +func splitCommand(input string) []string { + fields := strings.Fields(input) + if num := strings.IndexAny(fields[0], "0123456789"); num != -1 { + inputNumber := fields[0][num:] + fields[0] = fields[0][:num] + fields = append([]string{fields[0], inputNumber}, fields[1:]...) + } + return fields +} + +// interactive displays a prompt and reads commands for profile +// manipulation/visualization. +func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error { + updateAutoComplete(p) + + // Enter command processing loop. + ui.Print("Entering interactive mode (type \"help\" for commands)") + ui.SetAutoComplete(commands.NewCompleter(f.commands)) + + for { + input, err := readCommand(p, ui, f) + if err != nil { + if err != io.EOF { + return err + } + if input == "" { + return nil + } + } + // Process simple commands. + switch input { + case "": + continue + case ":": + f.flagFocus = newString("") + f.flagIgnore = newString("") + f.flagTagFocus = newString("") + f.flagTagIgnore = newString("") + f.flagHide = newString("") + continue + } + + fields := splitCommand(input) + // Process report generation commands. + if _, ok := f.commands[fields[0]]; ok { + if err := generateReport(p, fields, obj, ui, f); err != nil { + if err == io.EOF { + return nil + } + ui.PrintErr(err) + } + continue + } + + switch cmd := fields[0]; cmd { + case "help": + commandHelp(fields, ui, f) + continue + case "exit", "quit": + return nil + } + + // Process option settings. + if of, err := optFlags(p, input, f); err == nil { + f = of + } else { + ui.PrintErr("Error: ", err.Error()) + } + } +} + +func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plugin.UI, f *flags) error { + prof := p.Copy() + + cf, err := cmdFlags(prof, cmd, ui, f) + if err != nil { + return err + } + + return generate(true, prof, obj, ui, cf) +} + +// validateRegex checks if a string is a valid regular expression. +func validateRegex(v string) error { + _, err := regexp.Compile(v) + return err +} + +// readCommand prompts for and reads the next command. +func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) { + //ui.Print("Options:\n", f.String(p)) + s, err := ui.ReadLine() + return strings.TrimSpace(s), err +} + +func commandHelp(_ []string, ui plugin.UI, f *flags) error { + help := ` + Commands: + cmd [n] [--cum] [focus_regex]* [-ignore_regex]* + Produce a text report with the top n entries. + Include samples matching focus_regex, and exclude ignore_regex. + Add --cum to sort using cumulative data. + Available commands: +` + var commands []string + for name, cmd := range f.commands { + commands = append(commands, fmt.Sprintf(" %-12s %s", name, cmd.Usage)) + } + sort.Strings(commands) + + help = help + strings.Join(commands, "\n") + ` + peek func_regex + Display callers and callees of functions matching func_regex. + + dot [n] [focus_regex]* [-ignore_regex]* [>file] + Produce an annotated callgraph with the top n entries. + Include samples matching focus_regex, and exclude ignore_regex. + For other outputs, replace dot with: + - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file) + - Graph viewer: gv, web, evince, eog + + callgrind [n] [focus_regex]* [-ignore_regex]* [>file] + Produce a file in callgrind-compatible format. + Include samples matching focus_regex, and exclude ignore_regex. + + weblist func_regex [-ignore_regex]* + Show annotated source with interspersed assembly in a web browser. + + list func_regex [-ignore_regex]* + Print source for routines matching func_regex, and exclude ignore_regex. + + disasm func_regex [-ignore_regex]* + Disassemble routines matching func_regex, and exclude ignore_regex. + + tags tag_regex [-ignore_regex]* + List tags with key:value matching tag_regex and exclude ignore_regex. + + quit/exit/^D + Exit pprof. + + option=value + The following options can be set individually: + cum/flat: Sort entries based on cumulative or flat data + call_tree: Build context-sensitive call trees + nodecount: Max number of entries to display + nodefraction: Min frequency ratio of nodes to display + edgefraction: Min frequency ratio of edges to display + focus/ignore: Regexp to include/exclude samples by name/file + tagfocus/tagignore: Regexp or value range to filter samples by tag + eg "1mb", "1mb:2mb", ":64kb" + + functions: Level of aggregation for sample data + files: + lines: + addresses: + + unit: Measurement unit to use on reports + + Sample value selection by index: + sample_index: Index of sample value to display + mean: Average sample value over first value + + Sample value selection by name: + alloc_space for heap profiles + alloc_objects + inuse_space + inuse_objects + + total_delay for contention profiles + mean_delay + contentions + + : Clear focus/ignore/hide/tagfocus/tagignore` + + ui.Print(help) + return nil +} + +// cmdFlags parses the options of an interactive command and returns +// an updated flags object. +func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*flags, error) { + cf := *f + + var focus, ignore string + output := *cf.flagOutput + nodeCount := *cf.flagNodeCount + cmd := input[0] + + // Update output flags based on parameters. + tokens := input[1:] + for p := 0; p < len(tokens); p++ { + t := tokens[p] + if t == "" { + continue + } + if c, err := strconv.ParseInt(t, 10, 32); err == nil { + nodeCount = int(c) + continue + } + switch t[0] { + case '>': + if len(t) > 1 { + output = t[1:] + continue + } + // find next token + for p++; p < len(tokens); p++ { + if tokens[p] != "" { + output = tokens[p] + break + } + } + case '-': + if t == "--cum" || t == "-cum" { + cf.flagCum = newBool(true) + continue + } + ignore = catRegex(ignore, t[1:]) + default: + focus = catRegex(focus, t) + } + } + + pcmd, ok := f.commands[cmd] + if !ok { + return nil, fmt.Errorf("Unexpected parse failure: %v", input) + } + // Reset flags + cf.flagCommands = make(map[string]*bool) + cf.flagParamCommands = make(map[string]*string) + + if !pcmd.HasParam { + cf.flagCommands[cmd] = newBool(true) + + switch cmd { + case "tags": + cf.flagTagFocus = newString(focus) + cf.flagTagIgnore = newString(ignore) + default: + cf.flagFocus = newString(catRegex(*cf.flagFocus, focus)) + cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore)) + } + } else { + if focus == "" { + focus = "." + } + cf.flagParamCommands[cmd] = newString(focus) + cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore)) + } + + if nodeCount < 0 { + switch cmd { + case "text", "top": + // Default text/top to 10 nodes on interactive mode + nodeCount = 10 + default: + nodeCount = 80 + } + } + + cf.flagNodeCount = newInt(nodeCount) + cf.flagOutput = newString(output) + + // Do regular flags processing + if err := processFlags(prof, ui, &cf); err != nil { + cf.usage(ui) + return nil, err + } + + return &cf, nil +} + +func catRegex(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return a + "|" + b +} + +// optFlags parses an interactive option setting and returns +// an updated flags object. +func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) { + inputs := strings.SplitN(input, "=", 2) + option := strings.ToLower(strings.TrimSpace(inputs[0])) + var value string + if len(inputs) == 2 { + value = strings.TrimSpace(inputs[1]) + } + + of := *f + + var err error + var bv bool + var uv uint64 + var fv float64 + + switch option { + case "cum": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagCum = newBool(bv) + case "flat": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagCum = newBool(!bv) + case "call_tree": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagCallTree = newBool(bv) + case "unit": + of.flagDisplayUnit = newString(value) + case "sample_index": + if uv, err = strconv.ParseUint(value, 10, 32); err != nil { + return nil, err + } + if ix := int(uv); ix < 0 || ix >= len(p.SampleType) { + return nil, fmt.Errorf("sample_index out of range [0..%d]", len(p.SampleType)-1) + } + of.flagSampleIndex = newInt(int(uv)) + case "mean": + if bv, err = parseBool(value); err != nil { + return nil, err + } + of.flagMean = newBool(bv) + case "nodecount": + if uv, err = strconv.ParseUint(value, 10, 32); err != nil { + return nil, err + } + of.flagNodeCount = newInt(int(uv)) + case "nodefraction": + if fv, err = strconv.ParseFloat(value, 64); err != nil { + return nil, err + } + of.flagNodeFraction = newFloat64(fv) + case "edgefraction": + if fv, err = strconv.ParseFloat(value, 64); err != nil { + return nil, err + } + of.flagEdgeFraction = newFloat64(fv) + case "focus": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagFocus = newString(value) + case "ignore": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagIgnore = newString(value) + case "tagfocus": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagTagFocus = newString(value) + case "tagignore": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagTagIgnore = newString(value) + case "hide": + if err = validateRegex(value); err != nil { + return nil, err + } + of.flagHide = newString(value) + case "addresses", "files", "lines", "functions": + if bv, err = parseBool(value); err != nil { + return nil, err + } + if !bv { + return nil, fmt.Errorf("select one of addresses/files/lines/functions") + } + setGranularityToggle(option, &of) + default: + if ix := findSampleIndex(p, "", option); ix >= 0 { + of.flagSampleIndex = newInt(ix) + } else if ix := findSampleIndex(p, "total_", option); ix >= 0 { + of.flagSampleIndex = newInt(ix) + of.flagMean = newBool(false) + } else if ix := findSampleIndex(p, "mean_", option); ix >= 1 { + of.flagSampleIndex = newInt(ix) + of.flagMean = newBool(true) + } else { + return nil, fmt.Errorf("unrecognized command: %s", input) + } + } + return &of, nil +} + +// parseBool parses a string as a boolean value. +func parseBool(v string) (bool, error) { + switch strings.ToLower(v) { + case "true", "t", "yes", "y", "1", "": + return true, nil + case "false", "f", "no", "n", "0": + return false, nil + } + return false, fmt.Errorf(`illegal input "%s" for bool value`, v) +} + +func findSampleIndex(p *profile.Profile, prefix, sampleType string) int { + if !strings.HasPrefix(sampleType, prefix) { + return -1 + } + sampleType = strings.TrimPrefix(sampleType, prefix) + for i, r := range p.SampleType { + if r.Type == sampleType { + return i + } + } + return -1 +} + +// setGranularityToggle manages the set of granularity options. These +// operate as a toggle; turning one on turns the others off. +func setGranularityToggle(o string, fl *flags) { + t, f := newBool(true), newBool(false) + fl.flagFunctions = f + fl.flagFiles = f + fl.flagLines = f + fl.flagAddresses = f + switch o { + case "functions": + fl.flagFunctions = t + case "files": + fl.flagFiles = t + case "lines": + fl.flagLines = t + case "addresses": + fl.flagAddresses = t + default: + panic(fmt.Errorf("unexpected option %s", o)) + } +} |