golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package driver
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"regexp"
    21  	"sort"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/google/pprof/internal/plugin"
    26  	"github.com/google/pprof/internal/report"
    27  	"github.com/google/pprof/profile"
    28  )
    29  
    30  var commentStart = "//:" // Sentinel for comments on options
    31  var tailDigitsRE = regexp.MustCompile("[0-9]+$")
    32  
    33  // interactive starts a shell to read pprof commands.
    34  func interactive(p *profile.Profile, o *plugin.Options) error {
    35  	// Enter command processing loop.
    36  	o.UI.SetAutoComplete(newCompleter(functionNames(p)))
    37  	pprofVariables.set("compact_labels", "true")
    38  	pprofVariables["sample_index"].help += fmt.Sprintf("Or use sample_index=name, with name in %v.\n", sampleTypes(p))
    39  
    40  	// Do not wait for the visualizer to complete, to allow multiple
    41  	// graphs to be visualized simultaneously.
    42  	interactiveMode = true
    43  	shortcuts := profileShortcuts(p)
    44  
    45  	greetings(p, o.UI)
    46  	for {
    47  		input, err := o.UI.ReadLine("(pprof) ")
    48  		if err != nil {
    49  			if err != io.EOF {
    50  				return err
    51  			}
    52  			if input == "" {
    53  				return nil
    54  			}
    55  		}
    56  
    57  		for _, input := range shortcuts.expand(input) {
    58  			// Process assignments of the form variable=value
    59  			if s := strings.SplitN(input, "=", 2); len(s) > 0 {
    60  				name := strings.TrimSpace(s[0])
    61  				var value string
    62  				if len(s) == 2 {
    63  					value = s[1]
    64  					if comment := strings.LastIndex(value, commentStart); comment != -1 {
    65  						value = value[:comment]
    66  					}
    67  					value = strings.TrimSpace(value)
    68  				}
    69  				if v := pprofVariables[name]; v != nil {
    70  					if name == "sample_index" {
    71  						// Error check sample_index=xxx to ensure xxx is a valid sample type.
    72  						index, err := p.SampleIndexByName(value)
    73  						if err != nil {
    74  							o.UI.PrintErr(err)
    75  							continue
    76  						}
    77  						value = p.SampleType[index].Type
    78  					}
    79  					if err := pprofVariables.set(name, value); err != nil {
    80  						o.UI.PrintErr(err)
    81  					}
    82  					continue
    83  				}
    84  				// Allow group=variable syntax by converting into variable="".
    85  				if v := pprofVariables[value]; v != nil && v.group == name {
    86  					if err := pprofVariables.set(value, ""); err != nil {
    87  						o.UI.PrintErr(err)
    88  					}
    89  					continue
    90  				}
    91  			}
    92  
    93  			tokens := strings.Fields(input)
    94  			if len(tokens) == 0 {
    95  				continue
    96  			}
    97  
    98  			switch tokens[0] {
    99  			case "o", "options":
   100  				printCurrentOptions(p, o.UI)
   101  				continue
   102  			case "exit", "quit":
   103  				return nil
   104  			case "help":
   105  				commandHelp(strings.Join(tokens[1:], " "), o.UI)
   106  				continue
   107  			}
   108  
   109  			args, vars, err := parseCommandLine(tokens)
   110  			if err == nil {
   111  				err = generateReportWrapper(p, args, vars, o)
   112  			}
   113  
   114  			if err != nil {
   115  				o.UI.PrintErr(err)
   116  			}
   117  		}
   118  	}
   119  }
   120  
   121  var generateReportWrapper = generateReport // For testing purposes.
   122  
   123  // greetings prints a brief welcome and some overall profile
   124  // information before accepting interactive commands.
   125  func greetings(p *profile.Profile, ui plugin.UI) {
   126  	ropt, err := reportOptions(p, pprofVariables)
   127  	if err == nil {
   128  		ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n"))
   129  	}
   130  	ui.Print("Entering interactive mode (type \"help\" for commands, \"o\" for options)")
   131  }
   132  
   133  // shortcuts represents composite commands that expand into a sequence
   134  // of other commands.
   135  type shortcuts map[string][]string
   136  
   137  func (a shortcuts) expand(input string) []string {
   138  	input = strings.TrimSpace(input)
   139  	if a != nil {
   140  		if r, ok := a[input]; ok {
   141  			return r
   142  		}
   143  	}
   144  	return []string{input}
   145  }
   146  
   147  var pprofShortcuts = shortcuts{
   148  	":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="},
   149  }
   150  
   151  // profileShortcuts creates macros for convenience and backward compatibility.
   152  func profileShortcuts(p *profile.Profile) shortcuts {
   153  	s := pprofShortcuts
   154  	// Add shortcuts for sample types
   155  	for _, st := range p.SampleType {
   156  		command := fmt.Sprintf("sample_index=%s", st.Type)
   157  		s[st.Type] = []string{command}
   158  		s["total_"+st.Type] = []string{"mean=0", command}
   159  		s["mean_"+st.Type] = []string{"mean=1", command}
   160  	}
   161  	return s
   162  }
   163  
   164  func sampleTypes(p *profile.Profile) []string {
   165  	types := make([]string, len(p.SampleType))
   166  	for i, t := range p.SampleType {
   167  		types[i] = t.Type
   168  	}
   169  	return types
   170  }
   171  
   172  func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
   173  	var args []string
   174  	type groupInfo struct {
   175  		set    string
   176  		values []string
   177  	}
   178  	groups := make(map[string]*groupInfo)
   179  	for n, o := range pprofVariables {
   180  		v := o.stringValue()
   181  		comment := ""
   182  		if g := o.group; g != "" {
   183  			gi, ok := groups[g]
   184  			if !ok {
   185  				gi = &groupInfo{}
   186  				groups[g] = gi
   187  			}
   188  			if o.boolValue() {
   189  				gi.set = n
   190  			}
   191  			gi.values = append(gi.values, n)
   192  			continue
   193  		}
   194  		switch {
   195  		case n == "sample_index":
   196  			st := sampleTypes(p)
   197  			if v == "" {
   198  				// Apply default (last sample index).
   199  				v = st[len(st)-1]
   200  			}
   201  			// Add comments for all sample types in profile.
   202  			comment = "[" + strings.Join(st, " | ") + "]"
   203  		case n == "source_path":
   204  			continue
   205  		case n == "nodecount" && v == "-1":
   206  			comment = "default"
   207  		case v == "":
   208  			// Add quotes for empty values.
   209  			v = `""`
   210  		}
   211  		if comment != "" {
   212  			comment = commentStart + " " + comment
   213  		}
   214  		args = append(args, fmt.Sprintf("  %-25s = %-20s %s", n, v, comment))
   215  	}
   216  	for g, vars := range groups {
   217  		sort.Strings(vars.values)
   218  		comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]"
   219  		args = append(args, fmt.Sprintf("  %-25s = %-20s %s", g, vars.set, comment))
   220  	}
   221  	sort.Strings(args)
   222  	ui.Print(strings.Join(args, "\n"))
   223  }
   224  
   225  // parseCommandLine parses a command and returns the pprof command to
   226  // execute and a set of variables for the report.
   227  func parseCommandLine(input []string) ([]string, variables, error) {
   228  	cmd, args := input[:1], input[1:]
   229  	name := cmd[0]
   230  
   231  	c := pprofCommands[name]
   232  	if c == nil {
   233  		// Attempt splitting digits on abbreviated commands (eg top10)
   234  		if d := tailDigitsRE.FindString(name); d != "" && d != name {
   235  			name = name[:len(name)-len(d)]
   236  			cmd[0], args = name, append([]string{d}, args...)
   237  			c = pprofCommands[name]
   238  		}
   239  	}
   240  	if c == nil {
   241  		return nil, nil, fmt.Errorf("Unrecognized command: %q", name)
   242  	}
   243  
   244  	if c.hasParam {
   245  		if len(args) == 0 {
   246  			return nil, nil, fmt.Errorf("command %s requires an argument", name)
   247  		}
   248  		cmd = append(cmd, args[0])
   249  		args = args[1:]
   250  	}
   251  
   252  	// Copy the variables as options set in the command line are not persistent.
   253  	vcopy := pprofVariables.makeCopy()
   254  
   255  	var focus, ignore string
   256  	for i := 0; i < len(args); i++ {
   257  		t := args[i]
   258  		if _, err := strconv.ParseInt(t, 10, 32); err == nil {
   259  			vcopy.set("nodecount", t)
   260  			continue
   261  		}
   262  		switch t[0] {
   263  		case '>':
   264  			outputFile := t[1:]
   265  			if outputFile == "" {
   266  				i++
   267  				if i >= len(args) {
   268  					return nil, nil, fmt.Errorf("Unexpected end of line after >")
   269  				}
   270  				outputFile = args[i]
   271  			}
   272  			vcopy.set("output", outputFile)
   273  		case '-':
   274  			if t == "--cum" || t == "-cum" {
   275  				vcopy.set("cum", "t")
   276  				continue
   277  			}
   278  			ignore = catRegex(ignore, t[1:])
   279  		default:
   280  			focus = catRegex(focus, t)
   281  		}
   282  	}
   283  
   284  	if name == "tags" {
   285  		updateFocusIgnore(vcopy, "tag", focus, ignore)
   286  	} else {
   287  		updateFocusIgnore(vcopy, "", focus, ignore)
   288  	}
   289  
   290  	if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") {
   291  		vcopy.set("nodecount", "10")
   292  	}
   293  
   294  	return cmd, vcopy, nil
   295  }
   296  
   297  func updateFocusIgnore(v variables, prefix, f, i string) {
   298  	if f != "" {
   299  		focus := prefix + "focus"
   300  		v.set(focus, catRegex(v[focus].value, f))
   301  	}
   302  
   303  	if i != "" {
   304  		ignore := prefix + "ignore"
   305  		v.set(ignore, catRegex(v[ignore].value, i))
   306  	}
   307  }
   308  
   309  func catRegex(a, b string) string {
   310  	if a != "" && b != "" {
   311  		return a + "|" + b
   312  	}
   313  	return a + b
   314  }
   315  
   316  // commandHelp displays help and usage information for all Commands
   317  // and Variables or a specific Command or Variable.
   318  func commandHelp(args string, ui plugin.UI) {
   319  	if args == "" {
   320  		help := usage(false)
   321  		help = help + `
   322    :   Clear focus/ignore/hide/tagfocus/tagignore
   323  
   324    type "help <cmd|option>" for more information
   325  `
   326  
   327  		ui.Print(help)
   328  		return
   329  	}
   330  
   331  	if c := pprofCommands[args]; c != nil {
   332  		ui.Print(c.help(args))
   333  		return
   334  	}
   335  
   336  	if v := pprofVariables[args]; v != nil {
   337  		ui.Print(v.help + "\n")
   338  		return
   339  	}
   340  
   341  	ui.PrintErr("Unknown command: " + args)
   342  }
   343  
   344  // newCompleter creates an autocompletion function for a set of commands.
   345  func newCompleter(fns []string) func(string) string {
   346  	return func(line string) string {
   347  		v := pprofVariables
   348  		switch tokens := strings.Fields(line); len(tokens) {
   349  		case 0:
   350  			// Nothing to complete
   351  		case 1:
   352  			// Single token -- complete command name
   353  			if match := matchVariableOrCommand(v, tokens[0]); match != "" {
   354  				return match
   355  			}
   356  		case 2:
   357  			if tokens[0] == "help" {
   358  				if match := matchVariableOrCommand(v, tokens[1]); match != "" {
   359  					return tokens[0] + " " + match
   360  				}
   361  				return line
   362  			}
   363  			fallthrough
   364  		default:
   365  			// Multiple tokens -- complete using functions, except for tags
   366  			if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" {
   367  				lastTokenIdx := len(tokens) - 1
   368  				lastToken := tokens[lastTokenIdx]
   369  				if strings.HasPrefix(lastToken, "-") {
   370  					lastToken = "-" + functionCompleter(lastToken[1:], fns)
   371  				} else {
   372  					lastToken = functionCompleter(lastToken, fns)
   373  				}
   374  				return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
   375  			}
   376  		}
   377  		return line
   378  	}
   379  }
   380  
   381  // matchCommand attempts to match a string token to the prefix of a Command.
   382  func matchVariableOrCommand(v variables, token string) string {
   383  	token = strings.ToLower(token)
   384  	found := ""
   385  	for cmd := range pprofCommands {
   386  		if strings.HasPrefix(cmd, token) {
   387  			if found != "" {
   388  				return ""
   389  			}
   390  			found = cmd
   391  		}
   392  	}
   393  	for variable := range v {
   394  		if strings.HasPrefix(variable, token) {
   395  			if found != "" {
   396  				return ""
   397  			}
   398  			found = variable
   399  		}
   400  	}
   401  	return found
   402  }
   403  
   404  // functionCompleter replaces provided substring with a function
   405  // name retrieved from a profile if a single match exists. Otherwise,
   406  // it returns unchanged substring. It defaults to no-op if the profile
   407  // is not specified.
   408  func functionCompleter(substring string, fns []string) string {
   409  	found := ""
   410  	for _, fName := range fns {
   411  		if strings.Contains(fName, substring) {
   412  			if found != "" {
   413  				return substring
   414  			}
   415  			found = fName
   416  		}
   417  	}
   418  	if found != "" {
   419  		return found
   420  	}
   421  	return substring
   422  }
   423  
   424  func functionNames(p *profile.Profile) []string {
   425  	var fns []string
   426  	for _, fn := range p.Function {
   427  		fns = append(fns, fn.Name)
   428  	}
   429  	return fns
   430  }