rsc.io/go@v0.0.0-20150416155037-e040fd465409/src/cmd/pprof/internal/driver/driver.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 implements the core pprof functionality. It can be
     6  // parameterized with a flag implementation, fetch and symbolize
     7  // mechanisms.
     8  package driver
     9  
    10  import (
    11  	"bytes"
    12  	"fmt"
    13  	"io"
    14  	"net/url"
    15  	"os"
    16  	"path/filepath"
    17  	"regexp"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  	"time"
    23  
    24  	"cmd/pprof/internal/commands"
    25  	"cmd/pprof/internal/plugin"
    26  	"cmd/pprof/internal/profile"
    27  	"cmd/pprof/internal/report"
    28  	"cmd/pprof/internal/tempfile"
    29  )
    30  
    31  // PProf acquires a profile, and symbolizes it using a profile
    32  // manager. Then it generates a report formatted according to the
    33  // options selected through the flags package.
    34  func PProf(flagset plugin.FlagSet, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, overrides commands.Commands) error {
    35  	// Remove any temporary files created during pprof processing.
    36  	defer tempfile.Cleanup()
    37  
    38  	f, err := getFlags(flagset, overrides, ui)
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	obj.SetConfig(*f.flagTools)
    44  
    45  	sources := f.profileSource
    46  	if len(sources) > 1 {
    47  		source := sources[0]
    48  		// If the first argument is a supported object file, treat as executable.
    49  		if file, err := obj.Open(source, 0); err == nil {
    50  			file.Close()
    51  			f.profileExecName = source
    52  			sources = sources[1:]
    53  		} else if *f.flagBuildID == "" && isBuildID(source) {
    54  			f.flagBuildID = &source
    55  			sources = sources[1:]
    56  		}
    57  	}
    58  
    59  	// errMu protects concurrent accesses to errset and err. errset is set if an
    60  	// error is encountered by one of the goroutines grabbing a profile.
    61  	errMu, errset := sync.Mutex{}, false
    62  
    63  	// Fetch profiles.
    64  	wg := sync.WaitGroup{}
    65  	profs := make([]*profile.Profile, len(sources))
    66  	for i, source := range sources {
    67  		wg.Add(1)
    68  		go func(i int, src string) {
    69  			defer wg.Done()
    70  			p, grabErr := grabProfile(src, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f)
    71  			if grabErr != nil {
    72  				errMu.Lock()
    73  				defer errMu.Unlock()
    74  				errset, err = true, grabErr
    75  				return
    76  			}
    77  			profs[i] = p
    78  		}(i, source)
    79  	}
    80  	wg.Wait()
    81  	if errset {
    82  		return err
    83  	}
    84  
    85  	// Merge profiles.
    86  	prof := profs[0]
    87  	for _, p := range profs[1:] {
    88  		if err = prof.Merge(p, 1); err != nil {
    89  			return err
    90  		}
    91  	}
    92  
    93  	if *f.flagBase != "" {
    94  		// Fetch base profile and subtract from current profile.
    95  		base, err := grabProfile(*f.flagBase, f.profileExecName, *f.flagBuildID, fetch, sym, obj, ui, f)
    96  		if err != nil {
    97  			return err
    98  		}
    99  
   100  		if err = prof.Merge(base, -1); err != nil {
   101  			return err
   102  		}
   103  	}
   104  
   105  	if err := processFlags(prof, ui, f); err != nil {
   106  		return err
   107  	}
   108  
   109  	if !*f.flagRuntime {
   110  		prof.RemoveUninteresting()
   111  	}
   112  
   113  	if *f.flagInteractive {
   114  		return interactive(prof, obj, ui, f)
   115  	}
   116  
   117  	return generate(false, prof, obj, ui, f)
   118  }
   119  
   120  // isBuildID determines if the profile may contain a build ID, by
   121  // checking that it is a string of hex digits.
   122  func isBuildID(id string) bool {
   123  	return strings.Trim(id, "0123456789abcdefABCDEF") == ""
   124  }
   125  
   126  // adjustURL updates the profile source URL based on heuristics. It
   127  // will append ?seconds=sec for CPU profiles if not already
   128  // specified. Returns the hostname if the profile is remote.
   129  func adjustURL(source string, sec int, ui plugin.UI) (adjusted, host string, duration time.Duration) {
   130  	// If there is a local file with this name, just use it.
   131  	if _, err := os.Stat(source); err == nil {
   132  		return source, "", 0
   133  	}
   134  
   135  	url, err := url.Parse(source)
   136  
   137  	// Automatically add http:// to URLs of the form hostname:port/path.
   138  	// url.Parse treats "hostname" as the Scheme.
   139  	if err != nil || (url.Host == "" && url.Scheme != "" && url.Scheme != "file") {
   140  		url, err = url.Parse("http://" + source)
   141  		if err != nil {
   142  			return source, url.Host, time.Duration(30) * time.Second
   143  		}
   144  	}
   145  	if scheme := strings.ToLower(url.Scheme); scheme == "" || scheme == "file" {
   146  		url.Scheme = ""
   147  		return url.String(), "", 0
   148  	}
   149  
   150  	values := url.Query()
   151  	if urlSeconds := values.Get("seconds"); urlSeconds != "" {
   152  		if us, err := strconv.ParseInt(urlSeconds, 10, 32); err == nil {
   153  			if sec >= 0 {
   154  				ui.PrintErr("Overriding -seconds for URL ", source)
   155  			}
   156  			sec = int(us)
   157  		}
   158  	}
   159  
   160  	switch strings.ToLower(url.Path) {
   161  	case "", "/":
   162  		// Apply default /profilez.
   163  		url.Path = "/profilez"
   164  	case "/protoz":
   165  		// Rewrite to /profilez?type=proto
   166  		url.Path = "/profilez"
   167  		values.Set("type", "proto")
   168  	}
   169  
   170  	if hasDuration(url.Path) {
   171  		if sec > 0 {
   172  			duration = time.Duration(sec) * time.Second
   173  			values.Set("seconds", fmt.Sprintf("%d", sec))
   174  		} else {
   175  			// Assume default duration: 30 seconds
   176  			duration = 30 * time.Second
   177  		}
   178  	}
   179  	url.RawQuery = values.Encode()
   180  	return url.String(), url.Host, duration
   181  }
   182  
   183  func hasDuration(path string) bool {
   184  	for _, trigger := range []string{"profilez", "wallz", "/profile"} {
   185  		if strings.Contains(path, trigger) {
   186  			return true
   187  		}
   188  	}
   189  	return false
   190  }
   191  
   192  // preprocess does filtering and aggregation of a profile based on the
   193  // requested options.
   194  func preprocess(prof *profile.Profile, ui plugin.UI, f *flags) error {
   195  	if *f.flagFocus != "" || *f.flagIgnore != "" || *f.flagHide != "" {
   196  		focus, ignore, hide, err := compileFocusIgnore(*f.flagFocus, *f.flagIgnore, *f.flagHide)
   197  		if err != nil {
   198  			return err
   199  		}
   200  		fm, im, hm := prof.FilterSamplesByName(focus, ignore, hide)
   201  
   202  		warnNoMatches(fm, *f.flagFocus, "Focus", ui)
   203  		warnNoMatches(im, *f.flagIgnore, "Ignore", ui)
   204  		warnNoMatches(hm, *f.flagHide, "Hide", ui)
   205  	}
   206  
   207  	if *f.flagTagFocus != "" || *f.flagTagIgnore != "" {
   208  		focus, err := compileTagFilter(*f.flagTagFocus, ui)
   209  		if err != nil {
   210  			return err
   211  		}
   212  		ignore, err := compileTagFilter(*f.flagTagIgnore, ui)
   213  		if err != nil {
   214  			return err
   215  		}
   216  		fm, im := prof.FilterSamplesByTag(focus, ignore)
   217  
   218  		warnNoMatches(fm, *f.flagTagFocus, "TagFocus", ui)
   219  		warnNoMatches(im, *f.flagTagIgnore, "TagIgnore", ui)
   220  	}
   221  
   222  	return aggregate(prof, f)
   223  }
   224  
   225  func compileFocusIgnore(focus, ignore, hide string) (f, i, h *regexp.Regexp, err error) {
   226  	if focus != "" {
   227  		if f, err = regexp.Compile(focus); err != nil {
   228  			return nil, nil, nil, fmt.Errorf("parsing focus regexp: %v", err)
   229  		}
   230  	}
   231  
   232  	if ignore != "" {
   233  		if i, err = regexp.Compile(ignore); err != nil {
   234  			return nil, nil, nil, fmt.Errorf("parsing ignore regexp: %v", err)
   235  		}
   236  	}
   237  
   238  	if hide != "" {
   239  		if h, err = regexp.Compile(hide); err != nil {
   240  			return nil, nil, nil, fmt.Errorf("parsing hide regexp: %v", err)
   241  		}
   242  	}
   243  	return
   244  }
   245  
   246  func compileTagFilter(filter string, ui plugin.UI) (f func(string, string, int64) bool, err error) {
   247  	if filter == "" {
   248  		return nil, nil
   249  	}
   250  	if numFilter := parseTagFilterRange(filter); numFilter != nil {
   251  		ui.PrintErr("Interpreted '", filter, "' as range, not regexp")
   252  		return func(key, val string, num int64) bool {
   253  			if val != "" {
   254  				return false
   255  			}
   256  			return numFilter(num, key)
   257  		}, nil
   258  	}
   259  	fx, err := regexp.Compile(filter)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	return func(key, val string, num int64) bool {
   265  		if val == "" {
   266  			return false
   267  		}
   268  		return fx.MatchString(key + ":" + val)
   269  	}, nil
   270  }
   271  
   272  var tagFilterRangeRx = regexp.MustCompile("([[:digit:]]+)([[:alpha:]]+)")
   273  
   274  // parseTagFilterRange returns a function to checks if a value is
   275  // contained on the range described by a string. It can recognize
   276  // strings of the form:
   277  // "32kb" -- matches values == 32kb
   278  // ":64kb" -- matches values <= 64kb
   279  // "4mb:" -- matches values >= 4mb
   280  // "12kb:64mb" -- matches values between 12kb and 64mb (both included).
   281  func parseTagFilterRange(filter string) func(int64, string) bool {
   282  	ranges := tagFilterRangeRx.FindAllStringSubmatch(filter, 2)
   283  	if len(ranges) == 0 {
   284  		return nil // No ranges were identified
   285  	}
   286  	v, err := strconv.ParseInt(ranges[0][1], 10, 64)
   287  	if err != nil {
   288  		panic(fmt.Errorf("Failed to parse int %s: %v", ranges[0][1], err))
   289  	}
   290  	value, unit := report.ScaleValue(v, ranges[0][2], ranges[0][2])
   291  	if len(ranges) == 1 {
   292  		switch match := ranges[0][0]; filter {
   293  		case match:
   294  			return func(v int64, u string) bool {
   295  				sv, su := report.ScaleValue(v, u, unit)
   296  				return su == unit && sv == value
   297  			}
   298  		case match + ":":
   299  			return func(v int64, u string) bool {
   300  				sv, su := report.ScaleValue(v, u, unit)
   301  				return su == unit && sv >= value
   302  			}
   303  		case ":" + match:
   304  			return func(v int64, u string) bool {
   305  				sv, su := report.ScaleValue(v, u, unit)
   306  				return su == unit && sv <= value
   307  			}
   308  		}
   309  		return nil
   310  	}
   311  	if filter != ranges[0][0]+":"+ranges[1][0] {
   312  		return nil
   313  	}
   314  	if v, err = strconv.ParseInt(ranges[1][1], 10, 64); err != nil {
   315  		panic(fmt.Errorf("Failed to parse int %s: %v", ranges[1][1], err))
   316  	}
   317  	value2, unit2 := report.ScaleValue(v, ranges[1][2], unit)
   318  	if unit != unit2 {
   319  		return nil
   320  	}
   321  	return func(v int64, u string) bool {
   322  		sv, su := report.ScaleValue(v, u, unit)
   323  		return su == unit && sv >= value && sv <= value2
   324  	}
   325  }
   326  
   327  func warnNoMatches(match bool, rx, option string, ui plugin.UI) {
   328  	if !match && rx != "" && rx != "." {
   329  		ui.PrintErr(option + " expression matched no samples: " + rx)
   330  	}
   331  }
   332  
   333  // grabProfile fetches and symbolizes a profile.
   334  func grabProfile(source, exec, buildid string, fetch plugin.Fetcher, sym plugin.Symbolizer, obj plugin.ObjTool, ui plugin.UI, f *flags) (*profile.Profile, error) {
   335  	source, host, duration := adjustURL(source, *f.flagSeconds, ui)
   336  	remote := host != ""
   337  
   338  	if remote {
   339  		ui.Print("Fetching profile from ", source)
   340  		if duration != 0 {
   341  			ui.Print("Please wait... (" + duration.String() + ")")
   342  		}
   343  	}
   344  
   345  	now := time.Now()
   346  	// Fetch profile from source.
   347  	// Give 50% slack on the timeout.
   348  	p, err := fetch(source, duration+duration/2, ui)
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  
   353  	// Update the time/duration if the profile source doesn't include it.
   354  	// TODO(rsilvera): Remove this when we remove support for legacy profiles.
   355  	if remote {
   356  		if p.TimeNanos == 0 {
   357  			p.TimeNanos = now.UnixNano()
   358  		}
   359  		if duration != 0 && p.DurationNanos == 0 {
   360  			p.DurationNanos = int64(duration)
   361  		}
   362  	}
   363  
   364  	// Replace executable/buildID with the options provided in the
   365  	// command line. Assume the executable is the first Mapping entry.
   366  	if exec != "" || buildid != "" {
   367  		if len(p.Mapping) == 0 {
   368  			// Create a fake mapping to hold the user option, and associate
   369  			// all samples to it.
   370  			m := &profile.Mapping{
   371  				ID: 1,
   372  			}
   373  			for _, l := range p.Location {
   374  				l.Mapping = m
   375  			}
   376  			p.Mapping = []*profile.Mapping{m}
   377  		}
   378  		if exec != "" {
   379  			p.Mapping[0].File = exec
   380  		}
   381  		if buildid != "" {
   382  			p.Mapping[0].BuildID = buildid
   383  		}
   384  	}
   385  
   386  	if err := sym(*f.flagSymbolize, source, p, obj, ui); err != nil {
   387  		return nil, err
   388  	}
   389  
   390  	// Save a copy of any remote profiles, unless the user is explicitly
   391  	// saving it.
   392  	if remote && !f.isFormat("proto") {
   393  		prefix := "pprof."
   394  		if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
   395  			prefix = prefix + filepath.Base(p.Mapping[0].File) + "."
   396  		}
   397  		if !strings.ContainsRune(host, os.PathSeparator) {
   398  			prefix = prefix + host + "."
   399  		}
   400  		for _, s := range p.SampleType {
   401  			prefix = prefix + s.Type + "."
   402  		}
   403  
   404  		dir := os.Getenv("PPROF_TMPDIR")
   405  		tempFile, err := tempfile.New(dir, prefix, ".pb.gz")
   406  		if err == nil {
   407  			if err = p.Write(tempFile); err == nil {
   408  				ui.PrintErr("Saved profile in ", tempFile.Name())
   409  			}
   410  		}
   411  		if err != nil {
   412  			ui.PrintErr("Could not save profile: ", err)
   413  		}
   414  	}
   415  
   416  	if err := p.Demangle(obj.Demangle); err != nil {
   417  		ui.PrintErr("Failed to demangle profile: ", err)
   418  	}
   419  
   420  	if err := p.CheckValid(); err != nil {
   421  		return nil, fmt.Errorf("Grab %s: %v", source, err)
   422  	}
   423  
   424  	return p, nil
   425  }
   426  
   427  type flags struct {
   428  	flagInteractive   *bool              // Accept commands interactively
   429  	flagCommands      map[string]*bool   // pprof commands without parameters
   430  	flagParamCommands map[string]*string // pprof commands with parameters
   431  
   432  	flagSVGPan *string // URL to fetch the SVG Pan library
   433  	flagOutput *string // Output file name
   434  
   435  	flagCum      *bool // Sort by cumulative data
   436  	flagCallTree *bool // generate a context-sensitive call tree
   437  
   438  	flagAddresses *bool // Report at address level
   439  	flagLines     *bool // Report at source line level
   440  	flagFiles     *bool // Report at file level
   441  	flagFunctions *bool // Report at function level [default]
   442  
   443  	flagSymbolize *string // Symbolization options (=none to disable)
   444  	flagBuildID   *string // Override build if for first mapping
   445  
   446  	flagNodeCount    *int     // Max number of nodes to show
   447  	flagNodeFraction *float64 // Hide nodes below <f>*total
   448  	flagEdgeFraction *float64 // Hide edges below <f>*total
   449  	flagTrim         *bool    // Set to false to ignore NodeCount/*Fraction
   450  	flagRuntime      *bool    // Show runtime call frames in memory profiles
   451  	flagFocus        *string  // Restricts to paths going through a node matching regexp
   452  	flagIgnore       *string  // Skips paths going through any nodes matching regexp
   453  	flagHide         *string  // Skips sample locations matching regexp
   454  	flagTagFocus     *string  // Restrict to samples tagged with key:value matching regexp
   455  	flagTagIgnore    *string  // Discard samples tagged with key:value matching regexp
   456  	flagDropNegative *bool    // Skip negative values
   457  
   458  	flagBase *string // Source for base profile to user for comparison
   459  
   460  	flagSeconds *int // Length of time for dynamic profiles
   461  
   462  	flagTotalDelay  *bool // Display total delay at each region
   463  	flagContentions *bool // Display number of delays at each region
   464  	flagMeanDelay   *bool // Display mean delay at each region
   465  
   466  	flagInUseSpace   *bool    // Display in-use memory size
   467  	flagInUseObjects *bool    // Display in-use object counts
   468  	flagAllocSpace   *bool    // Display allocated memory size
   469  	flagAllocObjects *bool    // Display allocated object counts
   470  	flagDisplayUnit  *string  // Measurement unit to use on reports
   471  	flagDivideBy     *float64 // Ratio to divide sample values
   472  
   473  	flagSampleIndex *int  // Sample value to use in reports.
   474  	flagMean        *bool // Use mean of sample_index over count
   475  
   476  	flagTools       *string
   477  	profileSource   []string
   478  	profileExecName string
   479  
   480  	extraUsage string
   481  	commands   commands.Commands
   482  }
   483  
   484  func (f *flags) isFormat(format string) bool {
   485  	if fl := f.flagCommands[format]; fl != nil {
   486  		return *fl
   487  	}
   488  	if fl := f.flagParamCommands[format]; fl != nil {
   489  		return *fl != ""
   490  	}
   491  	return false
   492  }
   493  
   494  // String provides a printable representation for the current set of flags.
   495  func (f *flags) String(p *profile.Profile) string {
   496  	var ret string
   497  
   498  	if ix := *f.flagSampleIndex; ix != -1 {
   499  		ret += fmt.Sprintf("  %-25s : %d (%s)\n", "sample_index", ix, p.SampleType[ix].Type)
   500  	}
   501  	if ix := *f.flagMean; ix {
   502  		ret += boolFlagString("mean")
   503  	}
   504  	if *f.flagDisplayUnit != "minimum" {
   505  		ret += stringFlagString("unit", *f.flagDisplayUnit)
   506  	}
   507  
   508  	switch {
   509  	case *f.flagInteractive:
   510  		ret += boolFlagString("interactive")
   511  	}
   512  	for name, fl := range f.flagCommands {
   513  		if *fl {
   514  			ret += boolFlagString(name)
   515  		}
   516  	}
   517  
   518  	if *f.flagCum {
   519  		ret += boolFlagString("cum")
   520  	}
   521  	if *f.flagCallTree {
   522  		ret += boolFlagString("call_tree")
   523  	}
   524  
   525  	switch {
   526  	case *f.flagAddresses:
   527  		ret += boolFlagString("addresses")
   528  	case *f.flagLines:
   529  		ret += boolFlagString("lines")
   530  	case *f.flagFiles:
   531  		ret += boolFlagString("files")
   532  	case *f.flagFunctions:
   533  		ret += boolFlagString("functions")
   534  	}
   535  
   536  	if *f.flagNodeCount != -1 {
   537  		ret += intFlagString("nodecount", *f.flagNodeCount)
   538  	}
   539  
   540  	ret += floatFlagString("nodefraction", *f.flagNodeFraction)
   541  	ret += floatFlagString("edgefraction", *f.flagEdgeFraction)
   542  
   543  	if *f.flagFocus != "" {
   544  		ret += stringFlagString("focus", *f.flagFocus)
   545  	}
   546  	if *f.flagIgnore != "" {
   547  		ret += stringFlagString("ignore", *f.flagIgnore)
   548  	}
   549  	if *f.flagHide != "" {
   550  		ret += stringFlagString("hide", *f.flagHide)
   551  	}
   552  
   553  	if *f.flagTagFocus != "" {
   554  		ret += stringFlagString("tagfocus", *f.flagTagFocus)
   555  	}
   556  	if *f.flagTagIgnore != "" {
   557  		ret += stringFlagString("tagignore", *f.flagTagIgnore)
   558  	}
   559  
   560  	return ret
   561  }
   562  
   563  func boolFlagString(label string) string {
   564  	return fmt.Sprintf("  %-25s : true\n", label)
   565  }
   566  
   567  func stringFlagString(label, value string) string {
   568  	return fmt.Sprintf("  %-25s : %s\n", label, value)
   569  }
   570  
   571  func intFlagString(label string, value int) string {
   572  	return fmt.Sprintf("  %-25s : %d\n", label, value)
   573  }
   574  
   575  func floatFlagString(label string, value float64) string {
   576  	return fmt.Sprintf("  %-25s : %f\n", label, value)
   577  }
   578  
   579  // Utility routines to set flag values.
   580  func newBool(b bool) *bool {
   581  	return &b
   582  }
   583  
   584  func newString(s string) *string {
   585  	return &s
   586  }
   587  
   588  func newFloat64(fl float64) *float64 {
   589  	return &fl
   590  }
   591  
   592  func newInt(i int) *int {
   593  	return &i
   594  }
   595  
   596  func (f *flags) usage(ui plugin.UI) {
   597  	var commandMsg []string
   598  	for name, cmd := range f.commands {
   599  		if cmd.HasParam {
   600  			name = name + "=p"
   601  		}
   602  		commandMsg = append(commandMsg,
   603  			fmt.Sprintf("  -%-16s %s", name, cmd.Usage))
   604  	}
   605  
   606  	sort.Strings(commandMsg)
   607  
   608  	text := usageMsgHdr + strings.Join(commandMsg, "\n") + "\n" + usageMsg + "\n"
   609  	if f.extraUsage != "" {
   610  		text += f.extraUsage + "\n"
   611  	}
   612  	text += usageMsgVars
   613  	ui.Print(text)
   614  }
   615  
   616  func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (*flags, error) {
   617  	f := &flags{
   618  		flagInteractive:   flag.Bool("interactive", false, "Accepts commands interactively"),
   619  		flagCommands:      make(map[string]*bool),
   620  		flagParamCommands: make(map[string]*string),
   621  
   622  		// Filename for file-based output formats, stdout by default.
   623  		flagOutput: flag.String("output", "", "Output filename for file-based outputs "),
   624  		// Comparisons.
   625  		flagBase:         flag.String("base", "", "Source for base profile for comparison"),
   626  		flagDropNegative: flag.Bool("drop_negative", false, "Ignore negative differences"),
   627  
   628  		flagSVGPan: flag.String("svgpan", "https://www.cyberz.org/projects/SVGPan/SVGPan.js", "URL for SVGPan Library"),
   629  		// Data sorting criteria.
   630  		flagCum: flag.Bool("cum", false, "Sort by cumulative data"),
   631  		// Graph handling options.
   632  		flagCallTree: flag.Bool("call_tree", false, "Create a context-sensitive call tree"),
   633  		// Granularity of output resolution.
   634  		flagAddresses: flag.Bool("addresses", false, "Report at address level"),
   635  		flagLines:     flag.Bool("lines", false, "Report at source line level"),
   636  		flagFiles:     flag.Bool("files", false, "Report at source file level"),
   637  		flagFunctions: flag.Bool("functions", false, "Report at function level [default]"),
   638  		// Internal options.
   639  		flagSymbolize: flag.String("symbolize", "", "Options for profile symbolization"),
   640  		flagBuildID:   flag.String("buildid", "", "Override build id for first mapping"),
   641  		// Filtering options
   642  		flagNodeCount:    flag.Int("nodecount", -1, "Max number of nodes to show"),
   643  		flagNodeFraction: flag.Float64("nodefraction", 0.005, "Hide nodes below <f>*total"),
   644  		flagEdgeFraction: flag.Float64("edgefraction", 0.001, "Hide edges below <f>*total"),
   645  		flagTrim:         flag.Bool("trim", true, "Honor nodefraction/edgefraction/nodecount defaults"),
   646  		flagRuntime:      flag.Bool("runtime", false, "Show runtime call frames in memory profiles"),
   647  		flagFocus:        flag.String("focus", "", "Restricts to paths going through a node matching regexp"),
   648  		flagIgnore:       flag.String("ignore", "", "Skips paths going through any nodes matching regexp"),
   649  		flagHide:         flag.String("hide", "", "Skips nodes matching regexp"),
   650  		flagTagFocus:     flag.String("tagfocus", "", "Restrict to samples with tags in range or matched by regexp"),
   651  		flagTagIgnore:    flag.String("tagignore", "", "Discard samples with tags in range or matched by regexp"),
   652  		// CPU profile options
   653  		flagSeconds: flag.Int("seconds", -1, "Length of time for dynamic profiles"),
   654  		// Heap profile options
   655  		flagInUseSpace:   flag.Bool("inuse_space", false, "Display in-use memory size"),
   656  		flagInUseObjects: flag.Bool("inuse_objects", false, "Display in-use object counts"),
   657  		flagAllocSpace:   flag.Bool("alloc_space", false, "Display allocated memory size"),
   658  		flagAllocObjects: flag.Bool("alloc_objects", false, "Display allocated object counts"),
   659  		flagDisplayUnit:  flag.String("unit", "minimum", "Measurement units to display"),
   660  		flagDivideBy:     flag.Float64("divide_by", 1.0, "Ratio to divide all samples before visualization"),
   661  		flagSampleIndex:  flag.Int("sample_index", -1, "Index of sample value to report"),
   662  		flagMean:         flag.Bool("mean", false, "Average sample value over first value (count)"),
   663  		// Contention profile options
   664  		flagTotalDelay:  flag.Bool("total_delay", false, "Display total delay at each region"),
   665  		flagContentions: flag.Bool("contentions", false, "Display number of delays at each region"),
   666  		flagMeanDelay:   flag.Bool("mean_delay", false, "Display mean delay at each region"),
   667  		flagTools:       flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames"),
   668  		extraUsage:      flag.ExtraUsage(),
   669  	}
   670  
   671  	// Flags used during command processing
   672  	interactive := &f.flagInteractive
   673  	svgpan := &f.flagSVGPan
   674  	f.commands = commands.PProf(functionCompleter, interactive, svgpan)
   675  
   676  	// Override commands
   677  	for name, cmd := range overrides {
   678  		f.commands[name] = cmd
   679  	}
   680  
   681  	for name, cmd := range f.commands {
   682  		if cmd.HasParam {
   683  			f.flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp")
   684  		} else {
   685  			f.flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format")
   686  		}
   687  	}
   688  
   689  	args := flag.Parse(func() { f.usage(ui) })
   690  	if len(args) == 0 {
   691  		return nil, fmt.Errorf("no profile source specified")
   692  	}
   693  
   694  	f.profileSource = args
   695  
   696  	// Instruct legacy heapz parsers to grab historical allocation data,
   697  	// instead of the default in-use data. Not available with tcmalloc.
   698  	if *f.flagAllocSpace || *f.flagAllocObjects {
   699  		profile.LegacyHeapAllocated = true
   700  	}
   701  
   702  	if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir == "" {
   703  		profileDir = os.Getenv("HOME") + "/pprof"
   704  		os.Setenv("PPROF_TMPDIR", profileDir)
   705  		if err := os.MkdirAll(profileDir, 0755); err != nil {
   706  			return nil, fmt.Errorf("failed to access temp dir %s: %v", profileDir, err)
   707  		}
   708  	}
   709  
   710  	return f, nil
   711  }
   712  
   713  func processFlags(p *profile.Profile, ui plugin.UI, f *flags) error {
   714  	flagDis := f.isFormat("disasm")
   715  	flagPeek := f.isFormat("peek")
   716  	flagWebList := f.isFormat("weblist")
   717  	flagList := f.isFormat("list")
   718  
   719  	if flagDis || flagWebList {
   720  		// Collect all samples at address granularity for assembly
   721  		// listing.
   722  		f.flagNodeCount = newInt(0)
   723  		f.flagAddresses = newBool(true)
   724  		f.flagLines = newBool(false)
   725  		f.flagFiles = newBool(false)
   726  		f.flagFunctions = newBool(false)
   727  	}
   728  
   729  	if flagPeek {
   730  		// Collect all samples at function granularity for peek command
   731  		f.flagNodeCount = newInt(0)
   732  		f.flagAddresses = newBool(false)
   733  		f.flagLines = newBool(false)
   734  		f.flagFiles = newBool(false)
   735  		f.flagFunctions = newBool(true)
   736  	}
   737  
   738  	if flagList {
   739  		// Collect all samples at fileline granularity for source
   740  		// listing.
   741  		f.flagNodeCount = newInt(0)
   742  		f.flagAddresses = newBool(false)
   743  		f.flagLines = newBool(true)
   744  		f.flagFiles = newBool(false)
   745  		f.flagFunctions = newBool(false)
   746  	}
   747  
   748  	if !*f.flagTrim {
   749  		f.flagNodeCount = newInt(0)
   750  		f.flagNodeFraction = newFloat64(0)
   751  		f.flagEdgeFraction = newFloat64(0)
   752  	}
   753  
   754  	if oc := countFlagMap(f.flagCommands, f.flagParamCommands); oc == 0 {
   755  		f.flagInteractive = newBool(true)
   756  	} else if oc > 1 {
   757  		f.usage(ui)
   758  		return fmt.Errorf("must set at most one output format")
   759  	}
   760  
   761  	// Apply nodecount defaults for non-interactive mode. The
   762  	// interactive shell will apply defaults for the interactive mode.
   763  	if *f.flagNodeCount < 0 && !*f.flagInteractive {
   764  		switch {
   765  		default:
   766  			f.flagNodeCount = newInt(80)
   767  		case f.isFormat("text"):
   768  			f.flagNodeCount = newInt(0)
   769  		}
   770  	}
   771  
   772  	// Apply legacy options and diagnose conflicts.
   773  	if rc := countFlags([]*bool{f.flagAddresses, f.flagLines, f.flagFiles, f.flagFunctions}); rc == 0 {
   774  		f.flagFunctions = newBool(true)
   775  	} else if rc > 1 {
   776  		f.usage(ui)
   777  		return fmt.Errorf("must set at most one granularity option")
   778  	}
   779  
   780  	var err error
   781  	si, sm := *f.flagSampleIndex, *f.flagMean || *f.flagMeanDelay
   782  	si, err = sampleIndex(p, &f.flagTotalDelay, si, 1, "delay", "-total_delay", err)
   783  	si, err = sampleIndex(p, &f.flagMeanDelay, si, 1, "delay", "-mean_delay", err)
   784  	si, err = sampleIndex(p, &f.flagContentions, si, 0, "contentions", "-contentions", err)
   785  
   786  	si, err = sampleIndex(p, &f.flagInUseSpace, si, 1, "inuse_space", "-inuse_space", err)
   787  	si, err = sampleIndex(p, &f.flagInUseObjects, si, 0, "inuse_objects", "-inuse_objects", err)
   788  	si, err = sampleIndex(p, &f.flagAllocSpace, si, 1, "alloc_space", "-alloc_space", err)
   789  	si, err = sampleIndex(p, &f.flagAllocObjects, si, 0, "alloc_objects", "-alloc_objects", err)
   790  
   791  	if si == -1 {
   792  		// Use last value if none is requested.
   793  		si = len(p.SampleType) - 1
   794  	} else if si < 0 || si >= len(p.SampleType) {
   795  		err = fmt.Errorf("sample_index value %d out of range [0..%d]", si, len(p.SampleType)-1)
   796  	}
   797  
   798  	if err != nil {
   799  		f.usage(ui)
   800  		return err
   801  	}
   802  	f.flagSampleIndex, f.flagMean = newInt(si), newBool(sm)
   803  	return nil
   804  }
   805  
   806  func sampleIndex(p *profile.Profile, flag **bool,
   807  	sampleIndex int,
   808  	newSampleIndex int,
   809  	sampleType, option string,
   810  	err error) (int, error) {
   811  	if err != nil || !**flag {
   812  		return sampleIndex, err
   813  	}
   814  	*flag = newBool(false)
   815  	if sampleIndex != -1 {
   816  		return 0, fmt.Errorf("set at most one sample value selection option")
   817  	}
   818  	if newSampleIndex >= len(p.SampleType) ||
   819  		p.SampleType[newSampleIndex].Type != sampleType {
   820  		return 0, fmt.Errorf("option %s not valid for this profile", option)
   821  	}
   822  	return newSampleIndex, nil
   823  }
   824  
   825  func countFlags(bs []*bool) int {
   826  	var c int
   827  	for _, b := range bs {
   828  		if *b {
   829  			c++
   830  		}
   831  	}
   832  	return c
   833  }
   834  
   835  func countFlagMap(bms map[string]*bool, bmrxs map[string]*string) int {
   836  	var c int
   837  	for _, b := range bms {
   838  		if *b {
   839  			c++
   840  		}
   841  	}
   842  	for _, s := range bmrxs {
   843  		if *s != "" {
   844  			c++
   845  		}
   846  	}
   847  	return c
   848  }
   849  
   850  var usageMsgHdr = "usage: pprof [options] [binary] <profile source> ...\n" +
   851  	"Output format (only set one):\n"
   852  
   853  var usageMsg = "Output file parameters (for file-based output formats):\n" +
   854  	"  -output=f         Generate output on file f (stdout by default)\n" +
   855  	"Output granularity (only set one):\n" +
   856  	"  -functions        Report at function level [default]\n" +
   857  	"  -files            Report at source file level\n" +
   858  	"  -lines            Report at source line level\n" +
   859  	"  -addresses        Report at address level\n" +
   860  	"Comparison options:\n" +
   861  	"  -base <profile>   Show delta from this profile\n" +
   862  	"  -drop_negative    Ignore negative differences\n" +
   863  	"Sorting options:\n" +
   864  	"  -cum              Sort by cumulative data\n\n" +
   865  	"Dynamic profile options:\n" +
   866  	"  -seconds=N        Length of time for dynamic profiles\n" +
   867  	"Profile trimming options:\n" +
   868  	"  -nodecount=N      Max number of nodes to show\n" +
   869  	"  -nodefraction=f   Hide nodes below <f>*total\n" +
   870  	"  -edgefraction=f   Hide edges below <f>*total\n" +
   871  	"Sample value selection option (by index):\n" +
   872  	"  -sample_index      Index of sample value to display\n" +
   873  	"  -mean              Average sample value over first value\n" +
   874  	"Sample value selection option (for heap profiles):\n" +
   875  	"  -inuse_space      Display in-use memory size\n" +
   876  	"  -inuse_objects    Display in-use object counts\n" +
   877  	"  -alloc_space      Display allocated memory size\n" +
   878  	"  -alloc_objects    Display allocated object counts\n" +
   879  	"Sample value selection option (for contention profiles):\n" +
   880  	"  -total_delay      Display total delay at each region\n" +
   881  	"  -contentions      Display number of delays at each region\n" +
   882  	"  -mean_delay       Display mean delay at each region\n" +
   883  	"Filtering options:\n" +
   884  	"  -runtime          Show runtime call frames in memory profiles\n" +
   885  	"  -focus=r          Restricts to paths going through a node matching regexp\n" +
   886  	"  -ignore=r         Skips paths going through any nodes matching regexp\n" +
   887  	"  -tagfocus=r       Restrict to samples tagged with key:value matching regexp\n" +
   888  	"                    Restrict to samples with numeric tags in range (eg \"32kb:1mb\")\n" +
   889  	"  -tagignore=r      Discard samples tagged with key:value matching regexp\n" +
   890  	"                    Avoid samples with numeric tags in range (eg \"1mb:\")\n" +
   891  	"Miscellaneous:\n" +
   892  	"  -call_tree        Generate a context-sensitive call tree\n" +
   893  	"  -unit=u           Convert all samples to unit u for display\n" +
   894  	"  -divide_by=f      Scale all samples by dividing them by f\n" +
   895  	"  -buildid=id       Override build id for main binary in profile\n" +
   896  	"  -tools=path       Search path for object-level tools\n" +
   897  	"  -help             This message"
   898  
   899  var usageMsgVars = "Environment Variables:\n" +
   900  	"   PPROF_TMPDIR       Location for temporary files (default $HOME/pprof)\n" +
   901  	"   PPROF_TOOLS        Search path for object-level tools\n" +
   902  	"   PPROF_BINARY_PATH  Search path for local binary files\n" +
   903  	"                      default: $HOME/pprof/binaries\n" +
   904  	"                      finds binaries by $name and $buildid/$name"
   905  
   906  func aggregate(prof *profile.Profile, f *flags) error {
   907  	switch {
   908  	case f.isFormat("proto"), f.isFormat("raw"):
   909  		// No aggregation for raw profiles.
   910  	case f.isFormat("callgrind"):
   911  		// Aggregate to file/line for callgrind.
   912  		fallthrough
   913  	case *f.flagLines:
   914  		return prof.Aggregate(true, true, true, true, false)
   915  	case *f.flagFiles:
   916  		return prof.Aggregate(true, false, true, false, false)
   917  	case *f.flagFunctions:
   918  		return prof.Aggregate(true, true, false, false, false)
   919  	case f.isFormat("weblist"), f.isFormat("disasm"):
   920  		return prof.Aggregate(false, true, true, true, true)
   921  	}
   922  	return nil
   923  }
   924  
   925  // parseOptions parses the options into report.Options
   926  // Returns a function to postprocess the report after generation.
   927  func parseOptions(f *flags) (o *report.Options, p commands.PostProcessor, err error) {
   928  
   929  	if *f.flagDivideBy == 0 {
   930  		return nil, nil, fmt.Errorf("zero divisor specified")
   931  	}
   932  
   933  	o = &report.Options{
   934  		CumSort:        *f.flagCum,
   935  		CallTree:       *f.flagCallTree,
   936  		PrintAddresses: *f.flagAddresses,
   937  		DropNegative:   *f.flagDropNegative,
   938  		Ratio:          1 / *f.flagDivideBy,
   939  
   940  		NodeCount:    *f.flagNodeCount,
   941  		NodeFraction: *f.flagNodeFraction,
   942  		EdgeFraction: *f.flagEdgeFraction,
   943  		OutputUnit:   *f.flagDisplayUnit,
   944  	}
   945  
   946  	for cmd, b := range f.flagCommands {
   947  		if *b {
   948  			pcmd := f.commands[cmd]
   949  			o.OutputFormat = pcmd.Format
   950  			return o, pcmd.PostProcess, nil
   951  		}
   952  	}
   953  
   954  	for cmd, rx := range f.flagParamCommands {
   955  		if *rx != "" {
   956  			pcmd := f.commands[cmd]
   957  			if o.Symbol, err = regexp.Compile(*rx); err != nil {
   958  				return nil, nil, fmt.Errorf("parsing -%s regexp: %v", cmd, err)
   959  			}
   960  			o.OutputFormat = pcmd.Format
   961  			return o, pcmd.PostProcess, nil
   962  		}
   963  	}
   964  
   965  	return nil, nil, fmt.Errorf("no output format selected")
   966  }
   967  
   968  type sampleValueFunc func(*profile.Sample) int64
   969  
   970  // sampleFormat returns a function to extract values out of a profile.Sample,
   971  // and the type/units of those values.
   972  func sampleFormat(p *profile.Profile, f *flags) (sampleValueFunc, string, string) {
   973  	valueIndex := *f.flagSampleIndex
   974  
   975  	if *f.flagMean {
   976  		return meanExtractor(valueIndex), "mean_" + p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit
   977  	}
   978  
   979  	return valueExtractor(valueIndex), p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit
   980  }
   981  
   982  func valueExtractor(ix int) sampleValueFunc {
   983  	return func(s *profile.Sample) int64 {
   984  		return s.Value[ix]
   985  	}
   986  }
   987  
   988  func meanExtractor(ix int) sampleValueFunc {
   989  	return func(s *profile.Sample) int64 {
   990  		if s.Value[0] == 0 {
   991  			return 0
   992  		}
   993  		return s.Value[ix] / s.Value[0]
   994  	}
   995  }
   996  
   997  func generate(interactive bool, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
   998  	o, postProcess, err := parseOptions(f)
   999  	if err != nil {
  1000  		return err
  1001  	}
  1002  
  1003  	var w io.Writer
  1004  	if *f.flagOutput == "" {
  1005  		w = os.Stdout
  1006  	} else {
  1007  		ui.PrintErr("Generating report in ", *f.flagOutput)
  1008  		outputFile, err := os.Create(*f.flagOutput)
  1009  		if err != nil {
  1010  			return err
  1011  		}
  1012  		defer outputFile.Close()
  1013  		w = outputFile
  1014  	}
  1015  
  1016  	value, stype, unit := sampleFormat(prof, f)
  1017  	o.SampleType = stype
  1018  	rpt := report.New(prof, *o, value, unit)
  1019  
  1020  	// Do not apply filters if we're just generating a proto, so we
  1021  	// still have all the data.
  1022  	if o.OutputFormat != report.Proto {
  1023  		// Delay applying focus/ignore until after creating the report so
  1024  		// the report reflects the total number of samples.
  1025  		if err := preprocess(prof, ui, f); err != nil {
  1026  			return err
  1027  		}
  1028  	}
  1029  
  1030  	if postProcess == nil {
  1031  		return report.Generate(w, rpt, obj)
  1032  	}
  1033  
  1034  	var dot bytes.Buffer
  1035  	if err = report.Generate(&dot, rpt, obj); err != nil {
  1036  		return err
  1037  	}
  1038  
  1039  	return postProcess(&dot, w, ui)
  1040  }