github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/src/cmd/internal/pprof/driver/interactive.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package driver
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"regexp"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"cmd/internal/pprof/commands"
    16  	"cmd/internal/pprof/plugin"
    17  	"cmd/internal/pprof/profile"
    18  )
    19  
    20  var profileFunctionNames = []string{}
    21  
    22  // functionCompleter replaces provided substring with a function
    23  // name retrieved from a profile if a single match exists. Otherwise,
    24  // it returns unchanged substring. It defaults to no-op if the profile
    25  // is not specified.
    26  func functionCompleter(substring string) string {
    27  	found := ""
    28  	for _, fName := range profileFunctionNames {
    29  		if strings.Contains(fName, substring) {
    30  			if found != "" {
    31  				return substring
    32  			}
    33  			found = fName
    34  		}
    35  	}
    36  	if found != "" {
    37  		return found
    38  	}
    39  	return substring
    40  }
    41  
    42  // updateAutoComplete enhances autocompletion with information that can be
    43  // retrieved from the profile
    44  func updateAutoComplete(p *profile.Profile) {
    45  	profileFunctionNames = nil // remove function names retrieved previously
    46  	for _, fn := range p.Function {
    47  		profileFunctionNames = append(profileFunctionNames, fn.Name)
    48  	}
    49  }
    50  
    51  // splitCommand splits the command line input into tokens separated by
    52  // spaces. Takes care to separate commands of the form 'top10' into
    53  // two tokens: 'top' and '10'
    54  func splitCommand(input string) []string {
    55  	fields := strings.Fields(input)
    56  	if num := strings.IndexAny(fields[0], "0123456789"); num != -1 {
    57  		inputNumber := fields[0][num:]
    58  		fields[0] = fields[0][:num]
    59  		fields = append([]string{fields[0], inputNumber}, fields[1:]...)
    60  	}
    61  	return fields
    62  }
    63  
    64  // interactive displays a prompt and reads commands for profile
    65  // manipulation/visualization.
    66  func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
    67  	updateAutoComplete(p)
    68  
    69  	// Enter command processing loop.
    70  	ui.Print("Entering interactive mode (type \"help\" for commands)")
    71  	ui.SetAutoComplete(commands.NewCompleter(f.commands))
    72  
    73  	for {
    74  		input, err := readCommand(p, ui, f)
    75  		if err != nil {
    76  			if err != io.EOF {
    77  				return err
    78  			}
    79  			if input == "" {
    80  				return nil
    81  			}
    82  		}
    83  		// Process simple commands.
    84  		switch input {
    85  		case "":
    86  			continue
    87  		case ":":
    88  			f.flagFocus = newString("")
    89  			f.flagIgnore = newString("")
    90  			f.flagTagFocus = newString("")
    91  			f.flagTagIgnore = newString("")
    92  			f.flagHide = newString("")
    93  			continue
    94  		}
    95  
    96  		fields := splitCommand(input)
    97  		// Process report generation commands.
    98  		if _, ok := f.commands[fields[0]]; ok {
    99  			if err := generateReport(p, fields, obj, ui, f); err != nil {
   100  				if err == io.EOF {
   101  					return nil
   102  				}
   103  				ui.PrintErr(err)
   104  			}
   105  			continue
   106  		}
   107  
   108  		switch cmd := fields[0]; cmd {
   109  		case "help":
   110  			commandHelp(fields, ui, f)
   111  			continue
   112  		case "exit", "quit":
   113  			return nil
   114  		}
   115  
   116  		// Process option settings.
   117  		if of, err := optFlags(p, input, f); err == nil {
   118  			f = of
   119  		} else {
   120  			ui.PrintErr("Error: ", err.Error())
   121  		}
   122  	}
   123  }
   124  
   125  func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
   126  	prof := p.Copy()
   127  
   128  	cf, err := cmdFlags(prof, cmd, ui, f)
   129  	if err != nil {
   130  		return err
   131  	}
   132  
   133  	return generate(true, prof, obj, ui, cf)
   134  }
   135  
   136  // validateRegex checks if a string is a valid regular expression.
   137  func validateRegex(v string) error {
   138  	_, err := regexp.Compile(v)
   139  	return err
   140  }
   141  
   142  // readCommand prompts for and reads the next command.
   143  func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) {
   144  	//ui.Print("Options:\n", f.String(p))
   145  	s, err := ui.ReadLine()
   146  	return strings.TrimSpace(s), err
   147  }
   148  
   149  func commandHelp(_ []string, ui plugin.UI, f *flags) error {
   150  	help := `
   151   Commands:
   152     cmd [n] [--cum] [focus_regex]* [-ignore_regex]*
   153         Produce a text report with the top n entries.
   154         Include samples matching focus_regex, and exclude ignore_regex.
   155         Add --cum to sort using cumulative data.
   156         Available commands:
   157  `
   158  	var commands []string
   159  	for name, cmd := range f.commands {
   160  		commands = append(commands, fmt.Sprintf("         %-12s %s", name, cmd.Usage))
   161  	}
   162  	sort.Strings(commands)
   163  
   164  	help = help + strings.Join(commands, "\n") + `
   165     peek func_regex
   166         Display callers and callees of functions matching func_regex.
   167  
   168     dot [n] [focus_regex]* [-ignore_regex]* [>file]
   169         Produce an annotated callgraph with the top n entries.
   170         Include samples matching focus_regex, and exclude ignore_regex.
   171         For other outputs, replace dot with:
   172         - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file)
   173         - Graph viewer:    gv, web, evince, eog
   174  
   175     callgrind [n] [focus_regex]* [-ignore_regex]* [>file]
   176         Produce a file in callgrind-compatible format.
   177         Include samples matching focus_regex, and exclude ignore_regex.
   178  
   179     weblist func_regex [-ignore_regex]*
   180         Show annotated source with interspersed assembly in a web browser.
   181  
   182     list func_regex [-ignore_regex]*
   183         Print source for routines matching func_regex, and exclude ignore_regex.
   184  
   185     disasm func_regex [-ignore_regex]*
   186         Disassemble routines matching func_regex, and exclude ignore_regex.
   187  
   188     tags tag_regex [-ignore_regex]*
   189         List tags with key:value matching tag_regex and exclude ignore_regex.
   190  
   191     quit/exit/^D
   192   	     Exit pprof.
   193  
   194     option=value
   195         The following options can be set individually:
   196             cum/flat:           Sort entries based on cumulative or flat data
   197             call_tree:          Build context-sensitive call trees
   198             nodecount:          Max number of entries to display
   199             nodefraction:       Min frequency ratio of nodes to display
   200             edgefraction:       Min frequency ratio of edges to display
   201             focus/ignore:       Regexp to include/exclude samples by name/file
   202             tagfocus/tagignore: Regexp or value range to filter samples by tag
   203                                 eg "1mb", "1mb:2mb", ":64kb"
   204  
   205             functions:          Level of aggregation for sample data
   206             files:
   207             lines:
   208             addresses:
   209  
   210             unit:               Measurement unit to use on reports
   211  
   212             Sample value selection by index:
   213              sample_index:      Index of sample value to display
   214              mean:              Average sample value over first value
   215  
   216             Sample value selection by name:
   217              alloc_space        for heap profiles
   218              alloc_objects
   219              inuse_space
   220              inuse_objects
   221  
   222              total_delay        for contention profiles
   223              mean_delay
   224              contentions
   225  
   226     :   Clear focus/ignore/hide/tagfocus/tagignore`
   227  
   228  	ui.Print(help)
   229  	return nil
   230  }
   231  
   232  // cmdFlags parses the options of an interactive command and returns
   233  // an updated flags object.
   234  func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*flags, error) {
   235  	cf := *f
   236  
   237  	var focus, ignore string
   238  	output := *cf.flagOutput
   239  	nodeCount := *cf.flagNodeCount
   240  	cmd := input[0]
   241  
   242  	// Update output flags based on parameters.
   243  	tokens := input[1:]
   244  	for p := 0; p < len(tokens); p++ {
   245  		t := tokens[p]
   246  		if t == "" {
   247  			continue
   248  		}
   249  		if c, err := strconv.ParseInt(t, 10, 32); err == nil {
   250  			nodeCount = int(c)
   251  			continue
   252  		}
   253  		switch t[0] {
   254  		case '>':
   255  			if len(t) > 1 {
   256  				output = t[1:]
   257  				continue
   258  			}
   259  			// find next token
   260  			for p++; p < len(tokens); p++ {
   261  				if tokens[p] != "" {
   262  					output = tokens[p]
   263  					break
   264  				}
   265  			}
   266  		case '-':
   267  			if t == "--cum" || t == "-cum" {
   268  				cf.flagCum = newBool(true)
   269  				continue
   270  			}
   271  			ignore = catRegex(ignore, t[1:])
   272  		default:
   273  			focus = catRegex(focus, t)
   274  		}
   275  	}
   276  
   277  	pcmd, ok := f.commands[cmd]
   278  	if !ok {
   279  		return nil, fmt.Errorf("Unexpected parse failure: %v", input)
   280  	}
   281  	// Reset flags
   282  	cf.flagCommands = make(map[string]*bool)
   283  	cf.flagParamCommands = make(map[string]*string)
   284  
   285  	if !pcmd.HasParam {
   286  		cf.flagCommands[cmd] = newBool(true)
   287  
   288  		switch cmd {
   289  		case "tags":
   290  			cf.flagTagFocus = newString(focus)
   291  			cf.flagTagIgnore = newString(ignore)
   292  		default:
   293  			cf.flagFocus = newString(catRegex(*cf.flagFocus, focus))
   294  			cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
   295  		}
   296  	} else {
   297  		if focus == "" {
   298  			focus = "."
   299  		}
   300  		cf.flagParamCommands[cmd] = newString(focus)
   301  		cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
   302  	}
   303  
   304  	if nodeCount < 0 {
   305  		switch cmd {
   306  		case "text", "top":
   307  			// Default text/top to 10 nodes on interactive mode
   308  			nodeCount = 10
   309  		default:
   310  			nodeCount = 80
   311  		}
   312  	}
   313  
   314  	cf.flagNodeCount = newInt(nodeCount)
   315  	cf.flagOutput = newString(output)
   316  
   317  	// Do regular flags processing
   318  	if err := processFlags(prof, ui, &cf); err != nil {
   319  		cf.usage(ui)
   320  		return nil, err
   321  	}
   322  
   323  	return &cf, nil
   324  }
   325  
   326  func catRegex(a, b string) string {
   327  	if a == "" {
   328  		return b
   329  	}
   330  	if b == "" {
   331  		return a
   332  	}
   333  	return a + "|" + b
   334  }
   335  
   336  // optFlags parses an interactive option setting and returns
   337  // an updated flags object.
   338  func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) {
   339  	inputs := strings.SplitN(input, "=", 2)
   340  	option := strings.ToLower(strings.TrimSpace(inputs[0]))
   341  	var value string
   342  	if len(inputs) == 2 {
   343  		value = strings.TrimSpace(inputs[1])
   344  	}
   345  
   346  	of := *f
   347  
   348  	var err error
   349  	var bv bool
   350  	var uv uint64
   351  	var fv float64
   352  
   353  	switch option {
   354  	case "cum":
   355  		if bv, err = parseBool(value); err != nil {
   356  			return nil, err
   357  		}
   358  		of.flagCum = newBool(bv)
   359  	case "flat":
   360  		if bv, err = parseBool(value); err != nil {
   361  			return nil, err
   362  		}
   363  		of.flagCum = newBool(!bv)
   364  	case "call_tree":
   365  		if bv, err = parseBool(value); err != nil {
   366  			return nil, err
   367  		}
   368  		of.flagCallTree = newBool(bv)
   369  	case "unit":
   370  		of.flagDisplayUnit = newString(value)
   371  	case "sample_index":
   372  		if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
   373  			return nil, err
   374  		}
   375  		if ix := int(uv); ix < 0 || ix >= len(p.SampleType) {
   376  			return nil, fmt.Errorf("sample_index out of range [0..%d]", len(p.SampleType)-1)
   377  		}
   378  		of.flagSampleIndex = newInt(int(uv))
   379  	case "mean":
   380  		if bv, err = parseBool(value); err != nil {
   381  			return nil, err
   382  		}
   383  		of.flagMean = newBool(bv)
   384  	case "nodecount":
   385  		if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
   386  			return nil, err
   387  		}
   388  		of.flagNodeCount = newInt(int(uv))
   389  	case "nodefraction":
   390  		if fv, err = strconv.ParseFloat(value, 64); err != nil {
   391  			return nil, err
   392  		}
   393  		of.flagNodeFraction = newFloat64(fv)
   394  	case "edgefraction":
   395  		if fv, err = strconv.ParseFloat(value, 64); err != nil {
   396  			return nil, err
   397  		}
   398  		of.flagEdgeFraction = newFloat64(fv)
   399  	case "focus":
   400  		if err = validateRegex(value); err != nil {
   401  			return nil, err
   402  		}
   403  		of.flagFocus = newString(value)
   404  	case "ignore":
   405  		if err = validateRegex(value); err != nil {
   406  			return nil, err
   407  		}
   408  		of.flagIgnore = newString(value)
   409  	case "tagfocus":
   410  		if err = validateRegex(value); err != nil {
   411  			return nil, err
   412  		}
   413  		of.flagTagFocus = newString(value)
   414  	case "tagignore":
   415  		if err = validateRegex(value); err != nil {
   416  			return nil, err
   417  		}
   418  		of.flagTagIgnore = newString(value)
   419  	case "hide":
   420  		if err = validateRegex(value); err != nil {
   421  			return nil, err
   422  		}
   423  		of.flagHide = newString(value)
   424  	case "addresses", "files", "lines", "functions":
   425  		if bv, err = parseBool(value); err != nil {
   426  			return nil, err
   427  		}
   428  		if !bv {
   429  			return nil, fmt.Errorf("select one of addresses/files/lines/functions")
   430  		}
   431  		setGranularityToggle(option, &of)
   432  	default:
   433  		if ix := findSampleIndex(p, "", option); ix >= 0 {
   434  			of.flagSampleIndex = newInt(ix)
   435  		} else if ix := findSampleIndex(p, "total_", option); ix >= 0 {
   436  			of.flagSampleIndex = newInt(ix)
   437  			of.flagMean = newBool(false)
   438  		} else if ix := findSampleIndex(p, "mean_", option); ix >= 1 {
   439  			of.flagSampleIndex = newInt(ix)
   440  			of.flagMean = newBool(true)
   441  		} else {
   442  			return nil, fmt.Errorf("unrecognized command: %s", input)
   443  		}
   444  	}
   445  	return &of, nil
   446  }
   447  
   448  // parseBool parses a string as a boolean value.
   449  func parseBool(v string) (bool, error) {
   450  	switch strings.ToLower(v) {
   451  	case "true", "t", "yes", "y", "1", "":
   452  		return true, nil
   453  	case "false", "f", "no", "n", "0":
   454  		return false, nil
   455  	}
   456  	return false, fmt.Errorf(`illegal input "%s" for bool value`, v)
   457  }
   458  
   459  func findSampleIndex(p *profile.Profile, prefix, sampleType string) int {
   460  	if !strings.HasPrefix(sampleType, prefix) {
   461  		return -1
   462  	}
   463  	sampleType = strings.TrimPrefix(sampleType, prefix)
   464  	for i, r := range p.SampleType {
   465  		if r.Type == sampleType {
   466  			return i
   467  		}
   468  	}
   469  	return -1
   470  }
   471  
   472  // setGranularityToggle manages the set of granularity options. These
   473  // operate as a toggle; turning one on turns the others off.
   474  func setGranularityToggle(o string, fl *flags) {
   475  	t, f := newBool(true), newBool(false)
   476  	fl.flagFunctions = f
   477  	fl.flagFiles = f
   478  	fl.flagLines = f
   479  	fl.flagAddresses = f
   480  	switch o {
   481  	case "functions":
   482  		fl.flagFunctions = t
   483  	case "files":
   484  		fl.flagFiles = t
   485  	case "lines":
   486  		fl.flagLines = t
   487  	case "addresses":
   488  		fl.flagAddresses = t
   489  	default:
   490  		panic(fmt.Errorf("unexpected option %s", o))
   491  	}
   492  }