1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package driver
19
20 import (
21 "bytes"
22 "fmt"
23 "io"
24 "os"
25 "path/filepath"
26 "regexp"
27 "strings"
28
29 "github.com/google/pprof/internal/plugin"
30 "github.com/google/pprof/internal/report"
31 "github.com/google/pprof/profile"
32 )
33
34
35
36
37 func PProf(eo *plugin.Options) error {
38
39 defer cleanupTempFiles()
40
41 o := setDefaults(eo)
42
43 src, cmd, err := parseFlags(o)
44 if err != nil {
45 return err
46 }
47
48 p, err := fetchProfiles(src, o)
49 if err != nil {
50 return err
51 }
52
53 if cmd != nil {
54 return generateReport(p, cmd, currentConfig(), o)
55 }
56
57 if src.HTTPHostport != "" {
58 return serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser)
59 }
60 return interactive(p, o)
61 }
62
63
64 func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {
65
66 numLabelUnits := identifyNumLabelUnits(p, o.UI)
67
68
69 c := pprofCommands[cmd[0]]
70 if c == nil {
71 panic("unexpected nil command")
72 }
73
74 cfg = applyCommandOverrides(cmd[0], c.format, cfg)
75
76
77
78 generateTagRootsLeaves(p, cfg, o.UI)
79
80
81 relative := cfg.RelativePercentages
82 if relative {
83 if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
84 return nil, nil, err
85 }
86 }
87 ropt, err := reportOptions(p, numLabelUnits, cfg)
88 if err != nil {
89 return nil, nil, err
90 }
91 ropt.OutputFormat = c.format
92 if len(cmd) == 2 {
93 s, err := regexp.Compile(cmd[1])
94 if err != nil {
95 return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
96 }
97 ropt.Symbol = s
98 }
99
100 rpt := report.New(p, ropt)
101 if !relative {
102 if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
103 return nil, nil, err
104 }
105 }
106 if err := aggregate(p, cfg); err != nil {
107 return nil, nil, err
108 }
109
110 return c, rpt, nil
111 }
112
113
114 func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
115 c, rpt, err := generateRawReport(p, cmd, cfg, o)
116 if err != nil {
117 return err
118 }
119
120
121 dst := new(bytes.Buffer)
122 switch rpt.OutputFormat() {
123 case report.WebList:
124
125 err = printWebList(dst, rpt, o.Obj)
126 default:
127 err = report.Generate(dst, rpt, o.Obj)
128 }
129 if err != nil {
130 return err
131 }
132 src := dst
133
134
135 if c.postProcess != nil {
136 dst = new(bytes.Buffer)
137 if err := c.postProcess(src, dst, o.UI); err != nil {
138 return err
139 }
140 src = dst
141 }
142
143
144 output := cfg.Output
145 if output == "" {
146 if c.visualizer != nil {
147 return c.visualizer(src, os.Stdout, o.UI)
148 }
149 _, err := src.WriteTo(os.Stdout)
150 return err
151 }
152
153
154 o.UI.PrintErr("Generating report in ", output)
155 out, err := o.Writer.Open(output)
156 if err != nil {
157 return err
158 }
159 if _, err := src.WriteTo(out); err != nil {
160 out.Close()
161 return err
162 }
163 return out.Close()
164 }
165
166 func printWebList(dst io.Writer, rpt *report.Report, obj plugin.ObjTool) error {
167 listing, err := report.MakeWebList(rpt, obj, -1)
168 if err != nil {
169 return err
170 }
171 legend := report.ProfileLabels(rpt)
172 return renderHTML(dst, "sourcelisting", rpt, nil, legend, webArgs{
173 Standalone: true,
174 Listing: listing,
175 })
176 }
177
178 func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
179
180
181
182
183
184
185
186
187 trim := cfg.Trim
188
189 switch cmd {
190 case "disasm":
191 trim = false
192 cfg.Granularity = "addresses"
193
194
195
196
197
198 cfg.NoInlines = true
199 case "weblist":
200 trim = false
201 cfg.Granularity = "addresses"
202 cfg.NoInlines = false
203 case "peek":
204 trim = false
205 case "list":
206 trim = false
207 cfg.Granularity = "lines"
208
209
210 case "text", "top", "topproto":
211 if cfg.NodeCount == -1 {
212 cfg.NodeCount = 0
213 }
214 default:
215 if cfg.NodeCount == -1 {
216 cfg.NodeCount = 80
217 }
218 }
219
220 switch outputFormat {
221 case report.Proto, report.Raw, report.Callgrind:
222 trim = false
223 cfg.Granularity = "addresses"
224 }
225
226 if !trim {
227 cfg.NodeCount = 0
228 cfg.NodeFraction = 0
229 cfg.EdgeFraction = 0
230 }
231 return cfg
232 }
233
234
235 func generateTagRootsLeaves(prof *profile.Profile, cfg config, ui plugin.UI) {
236 tagRootLabelKeys := dropEmptyStrings(strings.Split(cfg.TagRoot, ","))
237 tagLeafLabelKeys := dropEmptyStrings(strings.Split(cfg.TagLeaf, ","))
238 rootm, leafm := addLabelNodes(prof, tagRootLabelKeys, tagLeafLabelKeys, cfg.Unit)
239 warnNoMatches(cfg.TagRoot == "" || rootm, "TagRoot", ui)
240 warnNoMatches(cfg.TagLeaf == "" || leafm, "TagLeaf", ui)
241 }
242
243
244 func dropEmptyStrings(in []string) (out []string) {
245 for _, s := range in {
246 if s != "" {
247 out = append(out, s)
248 }
249 }
250 return
251 }
252
253 func aggregate(prof *profile.Profile, cfg config) error {
254 var function, filename, linenumber, address bool
255 inlines := !cfg.NoInlines
256 switch cfg.Granularity {
257 case "addresses":
258 if inlines {
259 return nil
260 }
261 function = true
262 filename = true
263 linenumber = true
264 address = true
265 case "lines":
266 function = true
267 filename = true
268 linenumber = true
269 case "files":
270 filename = true
271 case "functions":
272 function = true
273 case "filefunctions":
274 function = true
275 filename = true
276 default:
277 return fmt.Errorf("unexpected granularity")
278 }
279 return prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address)
280 }
281
282 func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
283 si, mean := cfg.SampleIndex, cfg.Mean
284 value, meanDiv, sample, err := sampleFormat(p, si, mean)
285 if err != nil {
286 return nil, err
287 }
288
289 stype := sample.Type
290 if mean {
291 stype = "mean_" + stype
292 }
293
294 if cfg.DivideBy == 0 {
295 return nil, fmt.Errorf("zero divisor specified")
296 }
297
298 var filters []string
299 addFilter := func(k string, v string) {
300 if v != "" {
301 filters = append(filters, k+"="+v)
302 }
303 }
304 addFilter("focus", cfg.Focus)
305 addFilter("ignore", cfg.Ignore)
306 addFilter("hide", cfg.Hide)
307 addFilter("show", cfg.Show)
308 addFilter("show_from", cfg.ShowFrom)
309 addFilter("tagfocus", cfg.TagFocus)
310 addFilter("tagignore", cfg.TagIgnore)
311 addFilter("tagshow", cfg.TagShow)
312 addFilter("taghide", cfg.TagHide)
313
314 ropt := &report.Options{
315 CumSort: cfg.Sort == "cum",
316 CallTree: cfg.CallTree,
317 DropNegative: cfg.DropNegative,
318
319 CompactLabels: cfg.CompactLabels,
320 Ratio: 1 / cfg.DivideBy,
321
322 NodeCount: cfg.NodeCount,
323 NodeFraction: cfg.NodeFraction,
324 EdgeFraction: cfg.EdgeFraction,
325
326 ActiveFilters: filters,
327 NumLabelUnits: numLabelUnits,
328
329 SampleValue: value,
330 SampleMeanDivisor: meanDiv,
331 SampleType: stype,
332 SampleUnit: sample.Unit,
333
334 OutputUnit: cfg.Unit,
335
336 SourcePath: cfg.SourcePath,
337 TrimPath: cfg.TrimPath,
338
339 IntelSyntax: cfg.IntelSyntax,
340 }
341
342 if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
343 ropt.Title = filepath.Base(p.Mapping[0].File)
344 }
345
346 return ropt, nil
347 }
348
349
350
351 func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
352 numLabelUnits, ignoredUnits := p.NumLabelUnits()
353
354
355
356 for k, units := range ignoredUnits {
357 ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
358 }
359 return numLabelUnits
360 }
361
362 type sampleValueFunc func([]int64) int64
363
364
365
366 func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
367 if len(p.SampleType) == 0 {
368 return nil, nil, nil, fmt.Errorf("profile has no samples")
369 }
370 index, err := p.SampleIndexByName(sampleIndex)
371 if err != nil {
372 return nil, nil, nil, err
373 }
374 value = valueExtractor(index)
375 if mean {
376 meanDiv = valueExtractor(0)
377 }
378 v = p.SampleType[index]
379 return
380 }
381
382 func valueExtractor(ix int) sampleValueFunc {
383 return func(v []int64) int64 {
384 return v[ix]
385 }
386 }
387
388
389
390 type profileCopier []byte
391
392 func makeProfileCopier(src *profile.Profile) profileCopier {
393
394 var buf bytes.Buffer
395 src.WriteUncompressed(&buf)
396 return profileCopier(buf.Bytes())
397 }
398
399
400 func (c profileCopier) newCopy() *profile.Profile {
401 p, err := profile.ParseUncompressed([]byte(c))
402 if err != nil {
403 panic(err)
404 }
405 return p
406 }
407
View as plain text