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