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