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 "os"
24 "path/filepath"
25 "regexp"
26 "strings"
27
28 "github.com/google/pprof/internal/plugin"
29 "github.com/google/pprof/internal/report"
30 "github.com/google/pprof/profile"
31 )
32
33
34
35
36 func PProf(eo *plugin.Options) error {
37
38 defer cleanupTempFiles()
39
40 o := setDefaults(eo)
41
42 src, cmd, err := parseFlags(o)
43 if err != nil {
44 return err
45 }
46
47 p, err := fetchProfiles(src, o)
48 if err != nil {
49 return err
50 }
51
52 if cmd != nil {
53 return generateReport(p, cmd, currentConfig(), o)
54 }
55
56 if src.HTTPHostport != "" {
57 return serveWebInterface(src.HTTPHostport, p, o, src.HTTPDisableBrowser)
58 }
59 return interactive(p, o)
60 }
61
62 func generateRawReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) (*command, *report.Report, error) {
63 p = p.Copy()
64
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 relative := cfg.RelativePercentages
78 if relative {
79 if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
80 return nil, nil, err
81 }
82 }
83 ropt, err := reportOptions(p, numLabelUnits, cfg)
84 if err != nil {
85 return nil, nil, err
86 }
87 ropt.OutputFormat = c.format
88 if len(cmd) == 2 {
89 s, err := regexp.Compile(cmd[1])
90 if err != nil {
91 return nil, nil, fmt.Errorf("parsing argument regexp %s: %v", cmd[1], err)
92 }
93 ropt.Symbol = s
94 }
95
96 rpt := report.New(p, ropt)
97 if !relative {
98 if err := applyFocus(p, numLabelUnits, cfg, o.UI); err != nil {
99 return nil, nil, err
100 }
101 }
102 if err := aggregate(p, cfg); err != nil {
103 return nil, nil, err
104 }
105
106 return c, rpt, nil
107 }
108
109 func generateReport(p *profile.Profile, cmd []string, cfg config, o *plugin.Options) error {
110 c, rpt, err := generateRawReport(p, cmd, cfg, o)
111 if err != nil {
112 return err
113 }
114
115
116 dst := new(bytes.Buffer)
117 if err := report.Generate(dst, rpt, o.Obj); err != nil {
118 return err
119 }
120 src := dst
121
122
123 if c.postProcess != nil {
124 dst = new(bytes.Buffer)
125 if err := c.postProcess(src, dst, o.UI); err != nil {
126 return err
127 }
128 src = dst
129 }
130
131
132 output := cfg.Output
133 if output == "" {
134 if c.visualizer != nil {
135 return c.visualizer(src, os.Stdout, o.UI)
136 }
137 _, err := src.WriteTo(os.Stdout)
138 return err
139 }
140
141
142 o.UI.PrintErr("Generating report in ", output)
143 out, err := o.Writer.Open(output)
144 if err != nil {
145 return err
146 }
147 if _, err := src.WriteTo(out); err != nil {
148 out.Close()
149 return err
150 }
151 return out.Close()
152 }
153
154 func applyCommandOverrides(cmd string, outputFormat int, cfg config) config {
155
156
157
158
159
160
161
162
163 trim := cfg.Trim
164
165 switch cmd {
166 case "disasm", "weblist":
167 trim = false
168 cfg.Granularity = "addresses"
169
170
171
172
173
174 cfg.NoInlines = true
175 case "peek":
176 trim = false
177 case "list":
178 trim = false
179 cfg.Granularity = "lines"
180
181
182 case "text", "top", "topproto":
183 if cfg.NodeCount == -1 {
184 cfg.NodeCount = 0
185 }
186 default:
187 if cfg.NodeCount == -1 {
188 cfg.NodeCount = 80
189 }
190 }
191
192 switch outputFormat {
193 case report.Proto, report.Raw, report.Callgrind:
194 trim = false
195 cfg.Granularity = "addresses"
196 cfg.NoInlines = false
197 }
198
199 if !trim {
200 cfg.NodeCount = 0
201 cfg.NodeFraction = 0
202 cfg.EdgeFraction = 0
203 }
204 return cfg
205 }
206
207 func aggregate(prof *profile.Profile, cfg config) error {
208 var function, filename, linenumber, address bool
209 inlines := !cfg.NoInlines
210 switch cfg.Granularity {
211 case "addresses":
212 if inlines {
213 return nil
214 }
215 function = true
216 filename = true
217 linenumber = true
218 address = true
219 case "lines":
220 function = true
221 filename = true
222 linenumber = true
223 case "files":
224 filename = true
225 case "functions":
226 function = true
227 case "filefunctions":
228 function = true
229 filename = true
230 default:
231 return fmt.Errorf("unexpected granularity")
232 }
233 return prof.Aggregate(inlines, function, filename, linenumber, address)
234 }
235
236 func reportOptions(p *profile.Profile, numLabelUnits map[string]string, cfg config) (*report.Options, error) {
237 si, mean := cfg.SampleIndex, cfg.Mean
238 value, meanDiv, sample, err := sampleFormat(p, si, mean)
239 if err != nil {
240 return nil, err
241 }
242
243 stype := sample.Type
244 if mean {
245 stype = "mean_" + stype
246 }
247
248 if cfg.DivideBy == 0 {
249 return nil, fmt.Errorf("zero divisor specified")
250 }
251
252 var filters []string
253 addFilter := func(k string, v string) {
254 if v != "" {
255 filters = append(filters, k+"="+v)
256 }
257 }
258 addFilter("focus", cfg.Focus)
259 addFilter("ignore", cfg.Ignore)
260 addFilter("hide", cfg.Hide)
261 addFilter("show", cfg.Show)
262 addFilter("show_from", cfg.ShowFrom)
263 addFilter("tagfocus", cfg.TagFocus)
264 addFilter("tagignore", cfg.TagIgnore)
265 addFilter("tagshow", cfg.TagShow)
266 addFilter("taghide", cfg.TagHide)
267
268 ropt := &report.Options{
269 CumSort: cfg.Sort == "cum",
270 CallTree: cfg.CallTree,
271 DropNegative: cfg.DropNegative,
272
273 CompactLabels: cfg.CompactLabels,
274 Ratio: 1 / cfg.DivideBy,
275
276 NodeCount: cfg.NodeCount,
277 NodeFraction: cfg.NodeFraction,
278 EdgeFraction: cfg.EdgeFraction,
279
280 ActiveFilters: filters,
281 NumLabelUnits: numLabelUnits,
282
283 SampleValue: value,
284 SampleMeanDivisor: meanDiv,
285 SampleType: stype,
286 SampleUnit: sample.Unit,
287
288 OutputUnit: cfg.Unit,
289
290 SourcePath: cfg.SourcePath,
291 TrimPath: cfg.TrimPath,
292
293 IntelSyntax: cfg.IntelSyntax,
294 }
295
296 if len(p.Mapping) > 0 && p.Mapping[0].File != "" {
297 ropt.Title = filepath.Base(p.Mapping[0].File)
298 }
299
300 return ropt, nil
301 }
302
303
304
305 func identifyNumLabelUnits(p *profile.Profile, ui plugin.UI) map[string]string {
306 numLabelUnits, ignoredUnits := p.NumLabelUnits()
307
308
309
310 for k, units := range ignoredUnits {
311 ui.PrintErr(fmt.Sprintf("For tag %s used unit %s, also encountered unit(s) %s", k, numLabelUnits[k], strings.Join(units, ", ")))
312 }
313 return numLabelUnits
314 }
315
316 type sampleValueFunc func([]int64) int64
317
318
319
320 func sampleFormat(p *profile.Profile, sampleIndex string, mean bool) (value, meanDiv sampleValueFunc, v *profile.ValueType, err error) {
321 if len(p.SampleType) == 0 {
322 return nil, nil, nil, fmt.Errorf("profile has no samples")
323 }
324 index, err := p.SampleIndexByName(sampleIndex)
325 if err != nil {
326 return nil, nil, nil, err
327 }
328 value = valueExtractor(index)
329 if mean {
330 meanDiv = valueExtractor(0)
331 }
332 v = p.SampleType[index]
333 return
334 }
335
336 func valueExtractor(ix int) sampleValueFunc {
337 return func(v []int64) int64 {
338 return v[ix]
339 }
340 }
341
View as plain text