| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | package driver |
| |
|
| | import ( |
| | "bytes" |
| | "fmt" |
| | "io" |
| | "os" |
| | "path/filepath" |
| | "regexp" |
| | "strings" |
| |
|
| | "github.com/google/pprof/internal/plugin" |
| | "github.com/google/pprof/internal/report" |
| | "github.com/google/pprof/profile" |
| | ) |
| |
|
| | |
| | |
| | |
| | func PProf(eo *plugin.Options) error { |
| | |
| | defer cleanupTempFiles() |
| |
|
| | o := setDefaults(eo) |
| |
|
| | src, cmd, err := parseFlags(o) |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | p, err := fetchProfiles(src, o) |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | if cmd != nil { |
| | return generateReport(p, cmd, currentConfig(), o) |
| | } |
| |
|
| | if src.HTTPHostport != "" { |
| | return serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser) |
| | } |
| | return interactive(p, o) |
| | } |
| |
|
| | |
| | func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) { |
| | |
| | numLabelUnits := identifyNumLabelUnits(p, o.UI) |
| |
|
| | |
| | c := pprofCommands[cmd[0]] |
| | if c == nil { |
| | panic("unexpected nil command") |
| | } |
| |
|
| | cfg = applyCommandOverrides(cmd[0], c.format, cfg) |
| |
|
| | |
| | |
| | generateTagRootsLeaves(p, cfg, o.UI) |
| |
|
| | |
| | relative := cfg.RelativePercentages |
| | if relative { |
| | if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil { |
| | return nil, nil, err |
| | } |
| | } |
| | ropt, err := reportOptions(p, numLabelUnits, cfg) |
| | if err != nil { |
| | return nil, nil, err |
| | } |
| | ropt.OutputFormat = c.format |
| | if len(cmd) == 2 { |
| | s, err := regexp.Compile(cmd[1]) |
| | if err != nil { |
| | return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err) |
| | } |
| | ropt.Symbol = s |
| | } |
| |
|
| | rpt := report.New(p, ropt) |
| | if !relative { |
| | if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil { |
| | return nil, nil, err |
| | } |
| | } |
| | if err := aggregate(p, cfg); err != nil { |
| | return nil, nil, err |
| | } |
| |
|
| | return c, rpt, nil |
| | } |
| |
|
| | |
| | func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error { |
| | c, rpt, err := generateRawReport(p, cmd, cfg, o) |
| | if err != nil { |
| | return err |
| | } |
| |
|
| | |
| | dst := new(bytes.Buffer) |
| | switch rpt.OutputFormat() { |
| | case report.WebList: |
| | |
| | err = printWebList(dst, rpt, o.Obj) |
| | default: |
| | err = report.Generate(dst, rpt, o.Obj) |
| | } |
| | if err != nil { |
| | return err |
| | } |
| | src := dst |
| |
|
| | |
| | if c.postProcess != nil { |
| | dst = new(bytes.Buffer) |
| | if err := c.postProcess(src, dst, o.UI); err != nil { |
| | return err |
| | } |
| | src = dst |
| | } |
| |
|
| | |
| | output := cfg.Output |
| | if output == "" { |
| | if c.visualizer != nil { |
| | return c.visualizer(src, os.Stdout, o.UI) |
| | } |
| | _, err := src.WriteTo(os.Stdout) |
| | return err |
| | } |
| |
|
| | |
| | o.UI.PrintErr("Generating report in ", output) |
| | out, err := o.Writer.Open(output) |
| | if err != nil { |
| | return err |
| | } |
| | if _, err := src.WriteTo(out); err != nil { |
| | out.Close() |
| | return err |
| | } |
| | return out.Close() |
| | } |
| |
|
| | func printWebList(dst io.Writer, rpt *report.Report, obj plugin.ObjTool) error { |
| | listing, err := report.MakeWebList(rpt, obj, -1) |
| | if err != nil { |
| | return err |
| | } |
| | legend := report.ProfileLabels(rpt) |
| | return renderHTML(dst, "sourcelisting", rpt, nil, legend, webArgs{ |
| | Standalone: true, |
| | Listing: listing, |
| | }) |
| | } |
| |
|
| | func applyCommandOverrides(cmd string, outputFormat int, cfg config) config { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | trim := cfg.Trim |
| |
|
| | switch cmd { |
| | case "disasm": |
| | trim = false |
| | cfg.Granularity = "addresses" |
| | |
| | |
| | |
| | |
| | |
| | cfg.NoInlines = true |
| | case "weblist": |
| | trim = false |
| | cfg.Granularity = "addresses" |
| | cfg.NoInlines = false |
| | case "peek": |
| | trim = false |
| | case "list": |
| | trim = false |
| | cfg.Granularity = "lines" |
| | |
| | |
| | case "text", "top", "topproto": |
| | if cfg.NodeCount == -1 { |
| | cfg.NodeCount = 0 |
| | } |
| | default: |
| | if cfg.NodeCount == -1 { |
| | cfg.NodeCount = 80 |
| | } |
| | } |
| |
|
| | switch outputFormat { |
| | case report.Proto, report.Raw, report.Callgrind: |
| | trim = false |
| | cfg.Granularity = "addresses" |
| | } |
| |
|
| | if !trim { |
| | cfg.NodeCount = 0 |
| | cfg.NodeFraction = 0 |
| | cfg.EdgeFraction = 0 |
| | } |
| | return cfg |
| | } |
| |
|
| | |
| | func generateTagRootsLeaves(prof *profile.Profile, cfg config, ui plugin.UI) { |
| | tagRootLabelKeys := dropEmptyStrings(strings.Split(cfg.TagRoot, ",")) |
| | tagLeafLabelKeys := dropEmptyStrings(strings.Split(cfg.TagLeaf, ",")) |
| | rootm, leafm := addLabelNodes(prof, tagRootLabelKeys, tagLeafLabelKeys, cfg.Unit) |
| | warnNoMatches(cfg.TagRoot == "" || rootm, "TagRoot", ui) |
| | warnNoMatches(cfg.TagLeaf == "" || leafm, "TagLeaf", ui) |
| | } |
| |
|
| | |
| | func dropEmptyStrings(in []string) (out []string) { |
| | for _, s := range in { |
| | if s != "" { |
| | out = append(out, s) |
| | } |
| | } |
| | return |
| | } |
| |
|
| | func aggregate(prof *profile.Profile, cfg config) error { |
| | var function, filename, linenumber, address bool |
| | inlines := !cfg.NoInlines |
| | switch cfg.Granularity { |
| | case "": |
| | function = true |
| | case "addresses": |
| | if inlines { |
| | return nil |
| | } |
| | function = true |
| | filename = true |
| | linenumber = true |
| | address = true |
| | case "lines": |
| | function = true |
| | filename = true |
| | linenumber = true |
| | case "files": |
| | filename = true |
| | case "functions": |
| | function = true |
| | case "filefunctions": |
| | function = true |
| | filename = true |
| | default: |
| | return fmt.Errorf("unexpected granularity") |
| | } |
| | return prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address) |
| | } |
| |
|
| | func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) { |
| | si, mean := cfg.SampleIndex, cfg.Mean |
| | value, meanDiv, sample, err := sampleFormat(p, si, mean) |
| | if err != nil { |
| | return nil, err |
| | } |
| |
|
| | stype := sample.Type |
| | if mean { |
| | stype = "mean_" + stype |
| | } |
| |
|
| | if cfg.DivideBy == 0 { |
| | return nil, fmt.Errorf("zero divisor specified") |
| | } |
| |
|
| | var filters []string |
| | addFilter := func(k string, v string) { |
| | if v != "" { |
| | filters = append(filters, k+"="+v) |
| | } |
| | } |
| | addFilter("focus", cfg.Focus) |
| | addFilter("ignore", cfg.Ignore) |
| | addFilter("hide", cfg.Hide) |
| | addFilter("show", cfg.Show) |
| | addFilter("show_from", cfg.ShowFrom) |
| | addFilter("tagfocus", cfg.TagFocus) |
| | addFilter("tagignore", cfg.TagIgnore) |
| | addFilter("tagshow", cfg.TagShow) |
| | addFilter("taghide", cfg.TagHide) |
| |
|
| | ropt := &report.Options{ |
| | CumSort: cfg.Sort == "cum", |
| | CallTree: cfg.CallTree, |
| | DropNegative: cfg.DropNegative, |
| |
|
| | CompactLabels: cfg.CompactLabels, |
| | Ratio: 1 / cfg.DivideBy, |
| |
|
| | NodeCount: cfg.NodeCount, |
| | NodeFraction: cfg.NodeFraction, |
| | EdgeFraction: cfg.EdgeFraction, |
| |
|
| | ActiveFilters: filters, |
| | NumLabelUnits: numLabelUnits, |
| |
|
| | SampleValue: value, |
| | SampleMeanDivisor: meanDiv, |
| | SampleType: stype, |
| | SampleUnit: sample.Unit, |
| |
|
| | OutputUnit: cfg.Unit, |
| |
|
| | SourcePath: cfg.SourcePath, |
| | TrimPath: cfg.TrimPath, |
| |
|
| | IntelSyntax: cfg.IntelSyntax, |
| | } |
| |
|
| | if len(p.Mapping) > 0 && p.Mapping[0].File != "" { |
| | ropt.Title = filepath.Base(p.Mapping[0].File) |
| | } |
| |
|
| | return ropt, nil |
| | } |
| |
|
| | |
| | |
| | func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string { |
| | numLabelUnits, ignoredUnits := p.NumLabelUnits() |
| |
|
| | |
| | |
| | for k, units := range ignoredUnits { |
| | ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", "))) |
| | } |
| | return numLabelUnits |
| | } |
| |
|
| | type sampleValueFunc func([]int64) int64 |
| |
|
| | |
| | |
| | func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) { |
| | if len(p.SampleType) == 0 { |
| | return nil, nil, nil, fmt.Errorf("profile has no samples") |
| | } |
| | index, err := p.SampleIndexByName(sampleIndex) |
| | if err != nil { |
| | return nil, nil, nil, err |
| | } |
| | value = valueExtractor(index) |
| | if mean { |
| | meanDiv = valueExtractor(0) |
| | } |
| | v = p.SampleType[index] |
| | return |
| | } |
| |
|
| | func valueExtractor(ix int) sampleValueFunc { |
| | return func(v []int64) int64 { |
| | return v[ix] |
| | } |
| | } |
| |
|
| | |
| | |
| | type profileCopier []byte |
| |
|
| | func makeProfileCopier(src *profile.Profile) profileCopier { |
| | |
| | var buf bytes.Buffer |
| | src.WriteUncompressed(&buf) |
| | return profileCopier(buf.Bytes()) |
| | } |
| |
|
| | |
| | func (c profileCopier) newCopy() *profile.Profile { |
| | p, err := profile.ParseUncompressed([]byte(c)) |
| | if err != nil { |
| | panic(err) |
| | } |
| | return p |
| | } |
| |
|