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