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