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