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 "":
258 function = true
259 case "addresses":
260 if inlines {
261 return nil
262 }
263 function = true
264 filename = true
265 linenumber = true
266 address = true
267 case "lines":
268 function = true
269 filename = true
270 linenumber = true
271 case "files":
272 filename = true
273 case "functions":
274 function = true
275 case "filefunctions":
276 function = true
277 filename = true
278 default:
279 return fmt.Errorf("unexpected granularity")
280 }
281 return prof.Aggregate(inlines, function, filename, linenumber, cfg.ShowColumns, address)
282 }
283
284 func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
285 si, mean := cfg.SampleIndex, cfg.Mean
286 value, meanDiv, sample, err := sampleFormat(p, si, mean)
287 if err != nil {
288 return nil, err
289 }
290
291 stype := sample.Type
292 if mean {
293 stype = "mean_" + stype
294 }
295
296 if cfg.DivideBy == 0 {
297 return nil, fmt.Errorf("zero divisor specified")
298 }
299
300 var filters []string
301 addFilter := func(k string, v string) {
302 if v != "" {
303 filters = append(filters, k+"="+v)
304 }
305 }
306 addFilter("focus", cfg.Focus)
307 addFilter("ignore", cfg.Ignore)
308 addFilter("hide", cfg.Hide)
309 addFilter("show", cfg.Show)
310 addFilter("show_from", cfg.ShowFrom)
311 addFilter("tagfocus", cfg.TagFocus)
312 addFilter("tagignore", cfg.TagIgnore)
313 addFilter("tagshow", cfg.TagShow)
314 addFilter("taghide", cfg.TagHide)
315
316 ropt := &report.Options{
317 CumSort: cfg.Sort == "cum",
318 CallTree: cfg.CallTree,
319 DropNegative: cfg.DropNegative,
320
321 CompactLabels: cfg.CompactLabels,
322 Ratio: 1 / cfg.DivideBy,
323
324 NodeCount: cfg.NodeCount,
325 NodeFraction: cfg.NodeFraction,
326 EdgeFraction: cfg.EdgeFraction,
327
328 ActiveFilters: filters,
329 NumLabelUnits: numLabelUnits,
330
331 SampleValue: value,
332 SampleMeanDivisor: meanDiv,
333 SampleType: stype,
334 SampleUnit: sample.Unit,
335
336 OutputUnit: cfg.Unit,
337
338 SourcePath: cfg.SourcePath,
339 TrimPath: cfg.TrimPath,
340
341 IntelSyntax: cfg.IntelSyntax,
342 }
343
344 if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
345 ropt.Title = filepath.Base(p.Mapping[0].File)
346 }
347
348 return ropt, nil
349 }
350
351
352
353 func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
354 numLabelUnits, ignoredUnits := p.NumLabelUnits()
355
356
357
358 for k, units := range ignoredUnits {
359 ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
360 }
361 return numLabelUnits
362 }
363
364 type sampleValueFunc func([]int64) int64
365
366
367
368 func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
369 if len(p.SampleType) == 0 {
370 return nil, nil, nil, fmt.Errorf("profile has no samples")
371 }
372 index, err := p.SampleIndexByName(sampleIndex)
373 if err != nil {
374 return nil, nil, nil, err
375 }
376 value = valueExtractor(index)
377 if mean {
378 meanDiv = valueExtractor(0)
379 }
380 v = p.SampleType[index]
381 return
382 }
383
384 func valueExtractor(ix int) sampleValueFunc {
385 return func(v []int64) int64 {
386 return v[ix]
387 }
388 }
389
390
391
392 type profileCopier []byte
393
394 func makeProfileCopier(src *profile.Profile) profileCopier {
395
396 var buf bytes.Buffer
397 src.WriteUncompressed(&buf)
398 return profileCopier(buf.Bytes())
399 }
400
401
402 func (c profileCopier) newCopy() *profile.Profile {
403 p, err := profile.ParseUncompressed([]byte(c))
404 if err != nil {
405 panic(err)
406 }
407 return p
408 }
409
View as plain text