Home | History | Annotate | Download | only in driver
      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 	flagOutput *string // Output file name
    433 
    434 	flagCum      *bool // Sort by cumulative data
    435 	flagCallTree *bool // generate a context-sensitive call tree
    436 
    437 	flagAddresses *bool // Report at address level
    438 	flagLines     *bool // Report at source line level
    439 	flagFiles     *bool // Report at file level
    440 	flagFunctions *bool // Report at function level [default]
    441 
    442 	flagSymbolize *string // Symbolization options (=none to disable)
    443 	flagBuildID   *string // Override build if for first mapping
    444 
    445 	flagNodeCount    *int     // Max number of nodes to show
    446 	flagNodeFraction *float64 // Hide nodes below <f>*total
    447 	flagEdgeFraction *float64 // Hide edges below <f>*total
    448 	flagTrim         *bool    // Set to false to ignore NodeCount/*Fraction
    449 	flagRuntime      *bool    // Show runtime call frames in memory profiles
    450 	flagFocus        *string  // Restricts to paths going through a node matching regexp
    451 	flagIgnore       *string  // Skips paths going through any nodes matching regexp
    452 	flagHide         *string  // Skips sample locations matching regexp
    453 	flagTagFocus     *string  // Restrict to samples tagged with key:value matching regexp
    454 	flagTagIgnore    *string  // Discard samples tagged with key:value matching regexp
    455 	flagDropNegative *bool    // Skip negative values
    456 
    457 	flagBase *string // Source for base profile to user for comparison
    458 
    459 	flagSeconds *int // Length of time for dynamic profiles
    460 
    461 	flagTotalDelay  *bool // Display total delay at each region
    462 	flagContentions *bool // Display number of delays at each region
    463 	flagMeanDelay   *bool // Display mean delay at each region
    464 
    465 	flagInUseSpace   *bool    // Display in-use memory size
    466 	flagInUseObjects *bool    // Display in-use object counts
    467 	flagAllocSpace   *bool    // Display allocated memory size
    468 	flagAllocObjects *bool    // Display allocated object counts
    469 	flagDisplayUnit  *string  // Measurement unit to use on reports
    470 	flagDivideBy     *float64 // Ratio to divide sample values
    471 
    472 	flagSampleIndex *int  // Sample value to use in reports.
    473 	flagMean        *bool // Use mean of sample_index over count
    474 
    475 	flagTools       *string
    476 	profileSource   []string
    477 	profileExecName string
    478 
    479 	extraUsage string
    480 	commands   commands.Commands
    481 }
    482 
    483 func (f *flags) isFormat(format string) bool {
    484 	if fl := f.flagCommands[format]; fl != nil {
    485 		return *fl
    486 	}
    487 	if fl := f.flagParamCommands[format]; fl != nil {
    488 		return *fl != ""
    489 	}
    490 	return false
    491 }
    492 
    493 // String provides a printable representation for the current set of flags.
    494 func (f *flags) String(p *profile.Profile) string {
    495 	var ret string
    496 
    497 	if ix := *f.flagSampleIndex; ix != -1 {
    498 		ret += fmt.Sprintf("  %-25s : %d (%s)\n", "sample_index", ix, p.SampleType[ix].Type)
    499 	}
    500 	if ix := *f.flagMean; ix {
    501 		ret += boolFlagString("mean")
    502 	}
    503 	if *f.flagDisplayUnit != "minimum" {
    504 		ret += stringFlagString("unit", *f.flagDisplayUnit)
    505 	}
    506 
    507 	switch {
    508 	case *f.flagInteractive:
    509 		ret += boolFlagString("interactive")
    510 	}
    511 	for name, fl := range f.flagCommands {
    512 		if *fl {
    513 			ret += boolFlagString(name)
    514 		}
    515 	}
    516 
    517 	if *f.flagCum {
    518 		ret += boolFlagString("cum")
    519 	}
    520 	if *f.flagCallTree {
    521 		ret += boolFlagString("call_tree")
    522 	}
    523 
    524 	switch {
    525 	case *f.flagAddresses:
    526 		ret += boolFlagString("addresses")
    527 	case *f.flagLines:
    528 		ret += boolFlagString("lines")
    529 	case *f.flagFiles:
    530 		ret += boolFlagString("files")
    531 	case *f.flagFunctions:
    532 		ret += boolFlagString("functions")
    533 	}
    534 
    535 	if *f.flagNodeCount != -1 {
    536 		ret += intFlagString("nodecount", *f.flagNodeCount)
    537 	}
    538 
    539 	ret += floatFlagString("nodefraction", *f.flagNodeFraction)
    540 	ret += floatFlagString("edgefraction", *f.flagEdgeFraction)
    541 
    542 	if *f.flagFocus != "" {
    543 		ret += stringFlagString("focus", *f.flagFocus)
    544 	}
    545 	if *f.flagIgnore != "" {
    546 		ret += stringFlagString("ignore", *f.flagIgnore)
    547 	}
    548 	if *f.flagHide != "" {
    549 		ret += stringFlagString("hide", *f.flagHide)
    550 	}
    551 
    552 	if *f.flagTagFocus != "" {
    553 		ret += stringFlagString("tagfocus", *f.flagTagFocus)
    554 	}
    555 	if *f.flagTagIgnore != "" {
    556 		ret += stringFlagString("tagignore", *f.flagTagIgnore)
    557 	}
    558 
    559 	return ret
    560 }
    561 
    562 func boolFlagString(label string) string {
    563 	return fmt.Sprintf("  %-25s : true\n", label)
    564 }
    565 
    566 func stringFlagString(label, value string) string {
    567 	return fmt.Sprintf("  %-25s : %s\n", label, value)
    568 }
    569 
    570 func intFlagString(label string, value int) string {
    571 	return fmt.Sprintf("  %-25s : %d\n", label, value)
    572 }
    573 
    574 func floatFlagString(label string, value float64) string {
    575 	return fmt.Sprintf("  %-25s : %f\n", label, value)
    576 }
    577 
    578 // Utility routines to set flag values.
    579 func newBool(b bool) *bool {
    580 	return &b
    581 }
    582 
    583 func newString(s string) *string {
    584 	return &s
    585 }
    586 
    587 func newFloat64(fl float64) *float64 {
    588 	return &fl
    589 }
    590 
    591 func newInt(i int) *int {
    592 	return &i
    593 }
    594 
    595 func (f *flags) usage(ui plugin.UI) {
    596 	var commandMsg []string
    597 	for name, cmd := range f.commands {
    598 		if cmd.HasParam {
    599 			name = name + "=p"
    600 		}
    601 		commandMsg = append(commandMsg,
    602 			fmt.Sprintf("  -%-16s %s", name, cmd.Usage))
    603 	}
    604 
    605 	sort.Strings(commandMsg)
    606 
    607 	text := usageMsgHdr + strings.Join(commandMsg, "\n") + "\n" + usageMsg + "\n"
    608 	if f.extraUsage != "" {
    609 		text += f.extraUsage + "\n"
    610 	}
    611 	text += usageMsgVars
    612 	ui.Print(text)
    613 }
    614 
    615 func getFlags(flag plugin.FlagSet, overrides commands.Commands, ui plugin.UI) (*flags, error) {
    616 	f := &flags{
    617 		flagInteractive:   flag.Bool("interactive", false, "Accepts commands interactively"),
    618 		flagCommands:      make(map[string]*bool),
    619 		flagParamCommands: make(map[string]*string),
    620 
    621 		// Filename for file-based output formats, stdout by default.
    622 		flagOutput: flag.String("output", "", "Output filename for file-based outputs "),
    623 		// Comparisons.
    624 		flagBase:         flag.String("base", "", "Source for base profile for comparison"),
    625 		flagDropNegative: flag.Bool("drop_negative", false, "Ignore negative differences"),
    626 
    627 		// Data sorting criteria.
    628 		flagCum: flag.Bool("cum", false, "Sort by cumulative data"),
    629 		// Graph handling options.
    630 		flagCallTree: flag.Bool("call_tree", false, "Create a context-sensitive call tree"),
    631 		// Granularity of output resolution.
    632 		flagAddresses: flag.Bool("addresses", false, "Report at address level"),
    633 		flagLines:     flag.Bool("lines", false, "Report at source line level"),
    634 		flagFiles:     flag.Bool("files", false, "Report at source file level"),
    635 		flagFunctions: flag.Bool("functions", false, "Report at function level [default]"),
    636 		// Internal options.
    637 		flagSymbolize: flag.String("symbolize", "", "Options for profile symbolization"),
    638 		flagBuildID:   flag.String("buildid", "", "Override build id for first mapping"),
    639 		// Filtering options
    640 		flagNodeCount:    flag.Int("nodecount", -1, "Max number of nodes to show"),
    641 		flagNodeFraction: flag.Float64("nodefraction", 0.005, "Hide nodes below <f>*total"),
    642 		flagEdgeFraction: flag.Float64("edgefraction", 0.001, "Hide edges below <f>*total"),
    643 		flagTrim:         flag.Bool("trim", true, "Honor nodefraction/edgefraction/nodecount defaults"),
    644 		flagRuntime:      flag.Bool("runtime", false, "Show runtime call frames in memory profiles"),
    645 		flagFocus:        flag.String("focus", "", "Restricts to paths going through a node matching regexp"),
    646 		flagIgnore:       flag.String("ignore", "", "Skips paths going through any nodes matching regexp"),
    647 		flagHide:         flag.String("hide", "", "Skips nodes matching regexp"),
    648 		flagTagFocus:     flag.String("tagfocus", "", "Restrict to samples with tags in range or matched by regexp"),
    649 		flagTagIgnore:    flag.String("tagignore", "", "Discard samples with tags in range or matched by regexp"),
    650 		// CPU profile options
    651 		flagSeconds: flag.Int("seconds", -1, "Length of time for dynamic profiles"),
    652 		// Heap profile options
    653 		flagInUseSpace:   flag.Bool("inuse_space", false, "Display in-use memory size"),
    654 		flagInUseObjects: flag.Bool("inuse_objects", false, "Display in-use object counts"),
    655 		flagAllocSpace:   flag.Bool("alloc_space", false, "Display allocated memory size"),
    656 		flagAllocObjects: flag.Bool("alloc_objects", false, "Display allocated object counts"),
    657 		flagDisplayUnit:  flag.String("unit", "minimum", "Measurement units to display"),
    658 		flagDivideBy:     flag.Float64("divide_by", 1.0, "Ratio to divide all samples before visualization"),
    659 		flagSampleIndex:  flag.Int("sample_index", -1, "Index of sample value to report"),
    660 		flagMean:         flag.Bool("mean", false, "Average sample value over first value (count)"),
    661 		// Contention profile options
    662 		flagTotalDelay:  flag.Bool("total_delay", false, "Display total delay at each region"),
    663 		flagContentions: flag.Bool("contentions", false, "Display number of delays at each region"),
    664 		flagMeanDelay:   flag.Bool("mean_delay", false, "Display mean delay at each region"),
    665 		flagTools:       flag.String("tools", os.Getenv("PPROF_TOOLS"), "Path for object tool pathnames"),
    666 		extraUsage:      flag.ExtraUsage(),
    667 	}
    668 
    669 	// Flags used during command processing
    670 	interactive := &f.flagInteractive
    671 	f.commands = commands.PProf(functionCompleter, interactive)
    672 
    673 	// Override commands
    674 	for name, cmd := range overrides {
    675 		f.commands[name] = cmd
    676 	}
    677 
    678 	for name, cmd := range f.commands {
    679 		if cmd.HasParam {
    680 			f.flagParamCommands[name] = flag.String(name, "", "Generate a report in "+name+" format, matching regexp")
    681 		} else {
    682 			f.flagCommands[name] = flag.Bool(name, false, "Generate a report in "+name+" format")
    683 		}
    684 	}
    685 
    686 	args := flag.Parse(func() { f.usage(ui) })
    687 	if len(args) == 0 {
    688 		return nil, fmt.Errorf("no profile source specified")
    689 	}
    690 
    691 	f.profileSource = args
    692 
    693 	// Instruct legacy heapz parsers to grab historical allocation data,
    694 	// instead of the default in-use data. Not available with tcmalloc.
    695 	if *f.flagAllocSpace || *f.flagAllocObjects {
    696 		profile.LegacyHeapAllocated = true
    697 	}
    698 
    699 	if profileDir := os.Getenv("PPROF_TMPDIR"); profileDir == "" {
    700 		profileDir = os.Getenv("HOME") + "/pprof"
    701 		os.Setenv("PPROF_TMPDIR", profileDir)
    702 		if err := os.MkdirAll(profileDir, 0755); err != nil {
    703 			return nil, fmt.Errorf("failed to access temp dir %s: %v", profileDir, err)
    704 		}
    705 	}
    706 
    707 	return f, nil
    708 }
    709 
    710 func processFlags(p *profile.Profile, ui plugin.UI, f *flags) error {
    711 	flagDis := f.isFormat("disasm")
    712 	flagPeek := f.isFormat("peek")
    713 	flagWebList := f.isFormat("weblist")
    714 	flagList := f.isFormat("list")
    715 
    716 	if flagDis || flagWebList {
    717 		// Collect all samples at address granularity for assembly
    718 		// listing.
    719 		f.flagNodeCount = newInt(0)
    720 		f.flagAddresses = newBool(true)
    721 		f.flagLines = newBool(false)
    722 		f.flagFiles = newBool(false)
    723 		f.flagFunctions = newBool(false)
    724 	}
    725 
    726 	if flagPeek {
    727 		// Collect all samples at function granularity for peek command
    728 		f.flagNodeCount = newInt(0)
    729 		f.flagAddresses = newBool(false)
    730 		f.flagLines = newBool(false)
    731 		f.flagFiles = newBool(false)
    732 		f.flagFunctions = newBool(true)
    733 	}
    734 
    735 	if flagList {
    736 		// Collect all samples at fileline granularity for source
    737 		// listing.
    738 		f.flagNodeCount = newInt(0)
    739 		f.flagAddresses = newBool(false)
    740 		f.flagLines = newBool(true)
    741 		f.flagFiles = newBool(false)
    742 		f.flagFunctions = newBool(false)
    743 	}
    744 
    745 	if !*f.flagTrim {
    746 		f.flagNodeCount = newInt(0)
    747 		f.flagNodeFraction = newFloat64(0)
    748 		f.flagEdgeFraction = newFloat64(0)
    749 	}
    750 
    751 	if oc := countFlagMap(f.flagCommands, f.flagParamCommands); oc == 0 {
    752 		f.flagInteractive = newBool(true)
    753 	} else if oc > 1 {
    754 		f.usage(ui)
    755 		return fmt.Errorf("must set at most one output format")
    756 	}
    757 
    758 	// Apply nodecount defaults for non-interactive mode. The
    759 	// interactive shell will apply defaults for the interactive mode.
    760 	if *f.flagNodeCount < 0 && !*f.flagInteractive {
    761 		switch {
    762 		default:
    763 			f.flagNodeCount = newInt(80)
    764 		case f.isFormat("text"):
    765 			f.flagNodeCount = newInt(0)
    766 		}
    767 	}
    768 
    769 	// Apply legacy options and diagnose conflicts.
    770 	if rc := countFlags([]*bool{f.flagAddresses, f.flagLines, f.flagFiles, f.flagFunctions}); rc == 0 {
    771 		f.flagFunctions = newBool(true)
    772 	} else if rc > 1 {
    773 		f.usage(ui)
    774 		return fmt.Errorf("must set at most one granularity option")
    775 	}
    776 
    777 	var err error
    778 	si, sm := *f.flagSampleIndex, *f.flagMean || *f.flagMeanDelay
    779 	si, err = sampleIndex(p, &f.flagTotalDelay, si, 1, "delay", "-total_delay", err)
    780 	si, err = sampleIndex(p, &f.flagMeanDelay, si, 1, "delay", "-mean_delay", err)
    781 	si, err = sampleIndex(p, &f.flagContentions, si, 0, "contentions", "-contentions", err)
    782 
    783 	si, err = sampleIndex(p, &f.flagInUseSpace, si, 1, "inuse_space", "-inuse_space", err)
    784 	si, err = sampleIndex(p, &f.flagInUseObjects, si, 0, "inuse_objects", "-inuse_objects", err)
    785 	si, err = sampleIndex(p, &f.flagAllocSpace, si, 1, "alloc_space", "-alloc_space", err)
    786 	si, err = sampleIndex(p, &f.flagAllocObjects, si, 0, "alloc_objects", "-alloc_objects", err)
    787 
    788 	if si == -1 {
    789 		// Use last value if none is requested.
    790 		si = len(p.SampleType) - 1
    791 	} else if si < 0 || si >= len(p.SampleType) {
    792 		err = fmt.Errorf("sample_index value %d out of range [0..%d]", si, len(p.SampleType)-1)
    793 	}
    794 
    795 	if err != nil {
    796 		f.usage(ui)
    797 		return err
    798 	}
    799 	f.flagSampleIndex, f.flagMean = newInt(si), newBool(sm)
    800 	return nil
    801 }
    802 
    803 func sampleIndex(p *profile.Profile, flag **bool,
    804 	sampleIndex int,
    805 	newSampleIndex int,
    806 	sampleType, option string,
    807 	err error) (int, error) {
    808 	if err != nil || !**flag {
    809 		return sampleIndex, err
    810 	}
    811 	*flag = newBool(false)
    812 	if sampleIndex != -1 {
    813 		return 0, fmt.Errorf("set at most one sample value selection option")
    814 	}
    815 	if newSampleIndex >= len(p.SampleType) ||
    816 		p.SampleType[newSampleIndex].Type != sampleType {
    817 		return 0, fmt.Errorf("option %s not valid for this profile", option)
    818 	}
    819 	return newSampleIndex, nil
    820 }
    821 
    822 func countFlags(bs []*bool) int {
    823 	var c int
    824 	for _, b := range bs {
    825 		if *b {
    826 			c++
    827 		}
    828 	}
    829 	return c
    830 }
    831 
    832 func countFlagMap(bms map[string]*bool, bmrxs map[string]*string) int {
    833 	var c int
    834 	for _, b := range bms {
    835 		if *b {
    836 			c++
    837 		}
    838 	}
    839 	for _, s := range bmrxs {
    840 		if *s != "" {
    841 			c++
    842 		}
    843 	}
    844 	return c
    845 }
    846 
    847 var usageMsgHdr = "usage: pprof [options] [binary] <profile source> ...\n" +
    848 	"Output format (only set one):\n"
    849 
    850 var usageMsg = "Output file parameters (for file-based output formats):\n" +
    851 	"  -output=f         Generate output on file f (stdout by default)\n" +
    852 	"Output granularity (only set one):\n" +
    853 	"  -functions        Report at function level [default]\n" +
    854 	"  -files            Report at source file level\n" +
    855 	"  -lines            Report at source line level\n" +
    856 	"  -addresses        Report at address level\n" +
    857 	"Comparison options:\n" +
    858 	"  -base <profile>   Show delta from this profile\n" +
    859 	"  -drop_negative    Ignore negative differences\n" +
    860 	"Sorting options:\n" +
    861 	"  -cum              Sort by cumulative data\n\n" +
    862 	"Dynamic profile options:\n" +
    863 	"  -seconds=N        Length of time for dynamic profiles\n" +
    864 	"Profile trimming options:\n" +
    865 	"  -nodecount=N      Max number of nodes to show\n" +
    866 	"  -nodefraction=f   Hide nodes below <f>*total\n" +
    867 	"  -edgefraction=f   Hide edges below <f>*total\n" +
    868 	"Sample value selection option (by index):\n" +
    869 	"  -sample_index      Index of sample value to display\n" +
    870 	"  -mean              Average sample value over first value\n" +
    871 	"Sample value selection option (for heap profiles):\n" +
    872 	"  -inuse_space      Display in-use memory size\n" +
    873 	"  -inuse_objects    Display in-use object counts\n" +
    874 	"  -alloc_space      Display allocated memory size\n" +
    875 	"  -alloc_objects    Display allocated object counts\n" +
    876 	"Sample value selection option (for contention profiles):\n" +
    877 	"  -total_delay      Display total delay at each region\n" +
    878 	"  -contentions      Display number of delays at each region\n" +
    879 	"  -mean_delay       Display mean delay at each region\n" +
    880 	"Filtering options:\n" +
    881 	"  -runtime          Show runtime call frames in memory profiles\n" +
    882 	"  -focus=r          Restricts to paths going through a node matching regexp\n" +
    883 	"  -ignore=r         Skips paths going through any nodes matching regexp\n" +
    884 	"  -tagfocus=r       Restrict to samples tagged with key:value matching regexp\n" +
    885 	"                    Restrict to samples with numeric tags in range (eg \"32kb:1mb\")\n" +
    886 	"  -tagignore=r      Discard samples tagged with key:value matching regexp\n" +
    887 	"                    Avoid samples with numeric tags in range (eg \"1mb:\")\n" +
    888 	"Miscellaneous:\n" +
    889 	"  -call_tree        Generate a context-sensitive call tree\n" +
    890 	"  -unit=u           Convert all samples to unit u for display\n" +
    891 	"  -divide_by=f      Scale all samples by dividing them by f\n" +
    892 	"  -buildid=id       Override build id for main binary in profile\n" +
    893 	"  -tools=path       Search path for object-level tools\n" +
    894 	"  -help             This message"
    895 
    896 var usageMsgVars = "Environment Variables:\n" +
    897 	"   PPROF_TMPDIR       Location for saved profiles (default $HOME/pprof)\n" +
    898 	"   PPROF_TOOLS        Search path for object-level tools\n" +
    899 	"   PPROF_BINARY_PATH  Search path for local binary files\n" +
    900 	"                      default: $HOME/pprof/binaries\n" +
    901 	"                      finds binaries by $name and $buildid/$name"
    902 
    903 func aggregate(prof *profile.Profile, f *flags) error {
    904 	switch {
    905 	case f.isFormat("proto"), f.isFormat("raw"):
    906 		// No aggregation for raw profiles.
    907 	case f.isFormat("callgrind"):
    908 		// Aggregate to file/line for callgrind.
    909 		fallthrough
    910 	case *f.flagLines:
    911 		return prof.Aggregate(true, true, true, true, false)
    912 	case *f.flagFiles:
    913 		return prof.Aggregate(true, false, true, false, false)
    914 	case *f.flagFunctions:
    915 		return prof.Aggregate(true, true, false, false, false)
    916 	case f.isFormat("weblist"), f.isFormat("disasm"):
    917 		return prof.Aggregate(false, true, true, true, true)
    918 	}
    919 	return nil
    920 }
    921 
    922 // parseOptions parses the options into report.Options
    923 // Returns a function to postprocess the report after generation.
    924 func parseOptions(f *flags) (o *report.Options, p commands.PostProcessor, err error) {
    925 
    926 	if *f.flagDivideBy == 0 {
    927 		return nil, nil, fmt.Errorf("zero divisor specified")
    928 	}
    929 
    930 	o = &report.Options{
    931 		CumSort:        *f.flagCum,
    932 		CallTree:       *f.flagCallTree,
    933 		PrintAddresses: *f.flagAddresses,
    934 		DropNegative:   *f.flagDropNegative,
    935 		Ratio:          1 / *f.flagDivideBy,
    936 
    937 		NodeCount:    *f.flagNodeCount,
    938 		NodeFraction: *f.flagNodeFraction,
    939 		EdgeFraction: *f.flagEdgeFraction,
    940 		OutputUnit:   *f.flagDisplayUnit,
    941 	}
    942 
    943 	for cmd, b := range f.flagCommands {
    944 		if *b {
    945 			pcmd := f.commands[cmd]
    946 			o.OutputFormat = pcmd.Format
    947 			return o, pcmd.PostProcess, nil
    948 		}
    949 	}
    950 
    951 	for cmd, rx := range f.flagParamCommands {
    952 		if *rx != "" {
    953 			pcmd := f.commands[cmd]
    954 			if o.Symbol, err = regexp.Compile(*rx); err != nil {
    955 				return nil, nil, fmt.Errorf("parsing -%s regexp: %v", cmd, err)
    956 			}
    957 			o.OutputFormat = pcmd.Format
    958 			return o, pcmd.PostProcess, nil
    959 		}
    960 	}
    961 
    962 	return nil, nil, fmt.Errorf("no output format selected")
    963 }
    964 
    965 type sampleValueFunc func(*profile.Sample) int64
    966 
    967 // sampleFormat returns a function to extract values out of a profile.Sample,
    968 // and the type/units of those values.
    969 func sampleFormat(p *profile.Profile, f *flags) (sampleValueFunc, string, string) {
    970 	valueIndex := *f.flagSampleIndex
    971 
    972 	if *f.flagMean {
    973 		return meanExtractor(valueIndex), "mean_" + p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit
    974 	}
    975 
    976 	return valueExtractor(valueIndex), p.SampleType[valueIndex].Type, p.SampleType[valueIndex].Unit
    977 }
    978 
    979 func valueExtractor(ix int) sampleValueFunc {
    980 	return func(s *profile.Sample) int64 {
    981 		return s.Value[ix]
    982 	}
    983 }
    984 
    985 func meanExtractor(ix int) sampleValueFunc {
    986 	return func(s *profile.Sample) int64 {
    987 		if s.Value[0] == 0 {
    988 			return 0
    989 		}
    990 		return s.Value[ix] / s.Value[0]
    991 	}
    992 }
    993 
    994 func generate(interactive bool, prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
    995 	o, postProcess, err := parseOptions(f)
    996 	if err != nil {
    997 		return err
    998 	}
    999 
   1000 	var w io.Writer
   1001 	if *f.flagOutput == "" {
   1002 		w = os.Stdout
   1003 	} else {
   1004 		ui.PrintErr("Generating report in ", *f.flagOutput)
   1005 		outputFile, err := os.Create(*f.flagOutput)
   1006 		if err != nil {
   1007 			return err
   1008 		}
   1009 		defer outputFile.Close()
   1010 		w = outputFile
   1011 	}
   1012 
   1013 	if prof.Empty() {
   1014 		return fmt.Errorf("profile is empty")
   1015 	}
   1016 
   1017 	value, stype, unit := sampleFormat(prof, f)
   1018 	o.SampleType = stype
   1019 	rpt := report.New(prof, *o, value, unit)
   1020 
   1021 	// Do not apply filters if we're just generating a proto, so we
   1022 	// still have all the data.
   1023 	if o.OutputFormat != report.Proto {
   1024 		// Delay applying focus/ignore until after creating the report so
   1025 		// the report reflects the total number of samples.
   1026 		if err := preprocess(prof, ui, f); err != nil {
   1027 			return err
   1028 		}
   1029 	}
   1030 
   1031 	if postProcess == nil {
   1032 		return report.Generate(w, rpt, obj)
   1033 	}
   1034 
   1035 	var dot bytes.Buffer
   1036 	if err = report.Generate(&dot, rpt, obj); err != nil {
   1037 		return err
   1038 	}
   1039 
   1040 	return postProcess(&dot, w, ui)
   1041 }
   1042