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