Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(341)

Side by Side Diff: src/cmd/pprof/internal/driver/interactive.go

Issue 153750043: code review 153750043: cmd/pprof: add Go implementation (Closed)
Patch Set: diff -r 2e467bc60e64def06419194f48fe0d6c8b56765d https://code.google.com/p/go/ Created 9 years, 6 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/cmd/pprof/internal/driver/driver.go ('k') | src/cmd/pprof/internal/fetch/fetch.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
6
7 import (
8 "fmt"
9 "io"
10 "regexp"
11 "sort"
12 "strconv"
13 "strings"
14
15 "cmd/pprof/internal/commands"
16 "cmd/pprof/internal/plugin"
17 "cmd/pprof/internal/profile"
18 )
19
20 var profileFunctionNames = []string{}
21
22 // functionCompleter replaces provided substring with a function
23 // name retrieved from a profile if a single match exists. Otherwise,
24 // it returns unchanged substring. It defaults to no-op if the profile
25 // is not specified.
26 func functionCompleter(substring string) string {
27 found := ""
28 for _, fName := range profileFunctionNames {
29 if strings.Contains(fName, substring) {
30 if found != "" {
31 return substring
32 }
33 found = fName
34 }
35 }
36 if found != "" {
37 return found
38 }
39 return substring
40 }
41
42 // updateAutoComplete enhances autocompletion with information that can be
43 // retrieved from the profile
44 func updateAutoComplete(p *profile.Profile) {
45 profileFunctionNames = nil // remove function names retrieved previously
46 for _, fn := range p.Function {
47 profileFunctionNames = append(profileFunctionNames, fn.Name)
48 }
49 }
50
51 // splitCommand splits the command line input into tokens separated by
52 // spaces. Takes care to separate commands of the form 'top10' into
53 // two tokens: 'top' and '10'
54 func splitCommand(input string) []string {
55 fields := strings.Fields(input)
56 if num := strings.IndexAny(fields[0], "0123456789"); num != -1 {
57 inputNumber := fields[0][num:]
58 fields[0] = fields[0][:num]
59 fields = append([]string{fields[0], inputNumber}, fields[1:]...)
60 }
61 return fields
62 }
63
64 // interactive displays a prompt and reads commands for profile
65 // manipulation/visualization.
66 func interactive(p *profile.Profile, obj plugin.ObjTool, ui plugin.UI, f *flags) error {
67 updateAutoComplete(p)
68
69 // Enter command processing loop.
70 ui.Print("Entering interactive mode (type \"help\" for commands)")
71 ui.SetAutoComplete(commands.NewCompleter(f.commands))
72
73 for {
74 input, err := readCommand(p, ui, f)
75 if err != nil {
76 if err != io.EOF {
77 return err
78 }
79 if input == "" {
80 return nil
81 }
82 }
83 // Process simple commands.
84 switch input {
85 case "":
86 continue
87 case ":":
88 f.flagFocus = newString("")
89 f.flagIgnore = newString("")
90 f.flagTagFocus = newString("")
91 f.flagTagIgnore = newString("")
92 f.flagHide = newString("")
93 continue
94 }
95
96 fields := splitCommand(input)
97 // Process report generation commands.
98 if _, ok := f.commands[fields[0]]; ok {
99 if err := generateReport(p, fields, obj, ui, f); err != nil {
100 if err == io.EOF {
101 return nil
102 }
103 ui.PrintErr(err)
104 }
105 continue
106 }
107
108 switch cmd := fields[0]; cmd {
109 case "help":
110 commandHelp(fields, ui, f)
111 continue
112 case "exit", "quit":
113 return nil
114 }
115
116 // Process option settings.
117 if of, err := optFlags(p, input, f); err == nil {
118 f = of
119 } else {
120 ui.PrintErr("Error: ", err.Error())
121 }
122 }
123 }
124
125 func generateReport(p *profile.Profile, cmd []string, obj plugin.ObjTool, ui plu gin.UI, f *flags) error {
126 prof := p.Copy()
127
128 cf, err := cmdFlags(prof, cmd, ui, f)
129 if err != nil {
130 return err
131 }
132
133 return generate(true, prof, obj, ui, cf)
134 }
135
136 // validateRegex checks if a string is a valid regular expression.
137 func validateRegex(v string) error {
138 _, err := regexp.Compile(v)
139 return err
140 }
141
142 // readCommand prompts for and reads the next command.
143 func readCommand(p *profile.Profile, ui plugin.UI, f *flags) (string, error) {
144 //ui.Print("Options:\n", f.String(p))
145 s, err := ui.ReadLine()
146 return strings.TrimSpace(s), err
147 }
148
149 func commandHelp(_ []string, ui plugin.UI, f *flags) error {
150 help := `
151 Commands:
152 cmd [n] [--cum] [focus_regex]* [-ignore_regex]*
153 Produce a text report with the top n entries.
154 Include samples matching focus_regex, and exclude ignore_regex.
155 Add --cum to sort using cumulative data.
156 Available commands:
157 `
158 var commands []string
159 for name, cmd := range f.commands {
160 commands = append(commands, fmt.Sprintf(" %-12s %s", nam e, cmd.Usage))
161 }
162 sort.Strings(commands)
163
164 help = help + strings.Join(commands, "\n") + `
165 peek func_regex
166 Display callers and callees of functions matching func_regex.
167
168 dot [n] [focus_regex]* [-ignore_regex]* [>file]
169 Produce an annotated callgraph with the top n entries.
170 Include samples matching focus_regex, and exclude ignore_regex.
171 For other outputs, replace dot with:
172 - Graphic formats: dot, svg, pdf, ps, gif, png (use > to name output file )
173 - Graph viewer: gv, web, evince, eog
174
175 callgrind [n] [focus_regex]* [-ignore_regex]* [>file]
176 Produce a file in callgrind-compatible format.
177 Include samples matching focus_regex, and exclude ignore_regex.
178
179 weblist func_regex [-ignore_regex]*
180 Show annotated source with interspersed assembly in a web browser.
181
182 list func_regex [-ignore_regex]*
183 Print source for routines matching func_regex, and exclude ignore_regex.
184
185 disasm func_regex [-ignore_regex]*
186 Disassemble routines matching func_regex, and exclude ignore_regex.
187
188 tags tag_regex [-ignore_regex]*
189 List tags with key:value matching tag_regex and exclude ignore_regex.
190
191 quit/exit/^D
192 Exit pprof.
193
194 option=value
195 The following options can be set individually:
196 cum/flat: Sort entries based on cumulative or flat data
197 call_tree: Build context-sensitive call trees
198 nodecount: Max number of entries to display
199 nodefraction: Min frequency ratio of nodes to display
200 edgefraction: Min frequency ratio of edges to display
201 focus/ignore: Regexp to include/exclude samples by name/file
202 tagfocus/tagignore: Regexp or value range to filter samples by tag
203 eg "1mb", "1mb:2mb", ":64kb"
204
205 functions: Level of aggregation for sample data
206 files:
207 lines:
208 addresses:
209
210 unit: Measurement unit to use on reports
211
212 Sample value selection by index:
213 sample_index: Index of sample value to display
214 mean: Average sample value over first value
215
216 Sample value selection by name:
217 alloc_space for heap profiles
218 alloc_objects
219 inuse_space
220 inuse_objects
221
222 total_delay for contention profiles
223 mean_delay
224 contentions
225
226 : Clear focus/ignore/hide/tagfocus/tagignore`
227
228 ui.Print(help)
229 return nil
230 }
231
232 // cmdFlags parses the options of an interactive command and returns
233 // an updated flags object.
234 func cmdFlags(prof *profile.Profile, input []string, ui plugin.UI, f *flags) (*f lags, error) {
235 cf := *f
236
237 var focus, ignore string
238 output := *cf.flagOutput
239 nodeCount := *cf.flagNodeCount
240 cmd := input[0]
241
242 // Update output flags based on parameters.
243 tokens := input[1:]
244 for p := 0; p < len(tokens); p++ {
245 t := tokens[p]
246 if t == "" {
247 continue
248 }
249 if c, err := strconv.ParseInt(t, 10, 32); err == nil {
250 nodeCount = int(c)
251 continue
252 }
253 switch t[0] {
254 case '>':
255 if len(t) > 1 {
256 output = t[1:]
257 continue
258 }
259 // find next token
260 for p++; p < len(tokens); p++ {
261 if tokens[p] != "" {
262 output = tokens[p]
263 break
264 }
265 }
266 case '-':
267 if t == "--cum" || t == "-cum" {
268 cf.flagCum = newBool(true)
269 continue
270 }
271 ignore = catRegex(ignore, t[1:])
272 default:
273 focus = catRegex(focus, t)
274 }
275 }
276
277 pcmd, ok := f.commands[cmd]
278 if !ok {
279 return nil, fmt.Errorf("Unexpected parse failure: %v", input)
280 }
281 // Reset flags
282 cf.flagCommands = make(map[string]*bool)
283 cf.flagParamCommands = make(map[string]*string)
284
285 if !pcmd.HasParam {
286 cf.flagCommands[cmd] = newBool(true)
287
288 switch cmd {
289 case "tags":
290 cf.flagTagFocus = newString(focus)
291 cf.flagTagIgnore = newString(ignore)
292 default:
293 cf.flagFocus = newString(catRegex(*cf.flagFocus, focus))
294 cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignor e))
295 }
296 } else {
297 if focus == "" {
298 focus = "."
299 }
300 cf.flagParamCommands[cmd] = newString(focus)
301 cf.flagIgnore = newString(catRegex(*cf.flagIgnore, ignore))
302 }
303
304 if nodeCount < 0 {
305 switch cmd {
306 case "text", "top":
307 // Default text/top to 10 nodes on interactive mode
308 nodeCount = 10
309 default:
310 nodeCount = 80
311 }
312 }
313
314 cf.flagNodeCount = newInt(nodeCount)
315 cf.flagOutput = newString(output)
316
317 // Do regular flags processing
318 if err := processFlags(prof, ui, &cf); err != nil {
319 cf.usage(ui)
320 return nil, err
321 }
322
323 return &cf, nil
324 }
325
326 func catRegex(a, b string) string {
327 if a == "" {
328 return b
329 }
330 if b == "" {
331 return a
332 }
333 return a + "|" + b
334 }
335
336 // optFlags parses an interactive option setting and returns
337 // an updated flags object.
338 func optFlags(p *profile.Profile, input string, f *flags) (*flags, error) {
339 inputs := strings.SplitN(input, "=", 2)
340 option := strings.ToLower(strings.TrimSpace(inputs[0]))
341 var value string
342 if len(inputs) == 2 {
343 value = strings.TrimSpace(inputs[1])
344 }
345
346 of := *f
347
348 var err error
349 var bv bool
350 var uv uint64
351 var fv float64
352
353 switch option {
354 case "cum":
355 if bv, err = parseBool(value); err != nil {
356 return nil, err
357 }
358 of.flagCum = newBool(bv)
359 case "flat":
360 if bv, err = parseBool(value); err != nil {
361 return nil, err
362 }
363 of.flagCum = newBool(!bv)
364 case "call_tree":
365 if bv, err = parseBool(value); err != nil {
366 return nil, err
367 }
368 of.flagCallTree = newBool(bv)
369 case "unit":
370 of.flagDisplayUnit = newString(value)
371 case "sample_index":
372 if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
373 return nil, err
374 }
375 if ix := int(uv); ix < 0 || ix >= len(p.SampleType) {
376 return nil, fmt.Errorf("sample_index out of range [0..%d ]", len(p.SampleType)-1)
377 }
378 of.flagSampleIndex = newInt(int(uv))
379 case "mean":
380 if bv, err = parseBool(value); err != nil {
381 return nil, err
382 }
383 of.flagMean = newBool(bv)
384 case "nodecount":
385 if uv, err = strconv.ParseUint(value, 10, 32); err != nil {
386 return nil, err
387 }
388 of.flagNodeCount = newInt(int(uv))
389 case "nodefraction":
390 if fv, err = strconv.ParseFloat(value, 64); err != nil {
391 return nil, err
392 }
393 of.flagNodeFraction = newFloat64(fv)
394 case "edgefraction":
395 if fv, err = strconv.ParseFloat(value, 64); err != nil {
396 return nil, err
397 }
398 of.flagEdgeFraction = newFloat64(fv)
399 case "focus":
400 if err = validateRegex(value); err != nil {
401 return nil, err
402 }
403 of.flagFocus = newString(value)
404 case "ignore":
405 if err = validateRegex(value); err != nil {
406 return nil, err
407 }
408 of.flagIgnore = newString(value)
409 case "tagfocus":
410 if err = validateRegex(value); err != nil {
411 return nil, err
412 }
413 of.flagTagFocus = newString(value)
414 case "tagignore":
415 if err = validateRegex(value); err != nil {
416 return nil, err
417 }
418 of.flagTagIgnore = newString(value)
419 case "hide":
420 if err = validateRegex(value); err != nil {
421 return nil, err
422 }
423 of.flagHide = newString(value)
424 case "addresses", "files", "lines", "functions":
425 if bv, err = parseBool(value); err != nil {
426 return nil, err
427 }
428 if !bv {
429 return nil, fmt.Errorf("select one of addresses/files/li nes/functions")
430 }
431 setGranularityToggle(option, &of)
432 default:
433 if ix := findSampleIndex(p, "", option); ix >= 0 {
434 of.flagSampleIndex = newInt(ix)
435 } else if ix := findSampleIndex(p, "total_", option); ix >= 0 {
436 of.flagSampleIndex = newInt(ix)
437 of.flagMean = newBool(false)
438 } else if ix := findSampleIndex(p, "mean_", option); ix >= 1 {
439 of.flagSampleIndex = newInt(ix)
440 of.flagMean = newBool(true)
441 } else {
442 return nil, fmt.Errorf("unrecognized command: %s", input )
443 }
444 }
445 return &of, nil
446 }
447
448 // parseBool parses a string as a boolean value.
449 func parseBool(v string) (bool, error) {
450 switch strings.ToLower(v) {
451 case "true", "t", "yes", "y", "1", "":
452 return true, nil
453 case "false", "f", "no", "n", "0":
454 return false, nil
455 }
456 return false, fmt.Errorf(`illegal input "%s" for bool value`, v)
457 }
458
459 func findSampleIndex(p *profile.Profile, prefix, sampleType string) int {
460 if !strings.HasPrefix(sampleType, prefix) {
461 return -1
462 }
463 sampleType = strings.TrimPrefix(sampleType, prefix)
464 for i, r := range p.SampleType {
465 if r.Type == sampleType {
466 return i
467 }
468 }
469 return -1
470 }
471
472 // setGranularityToggle manages the set of granularity options. These
473 // operate as a toggle; turning one on turns the others off.
474 func setGranularityToggle(o string, fl *flags) {
475 t, f := newBool(true), newBool(false)
476 fl.flagFunctions = f
477 fl.flagFiles = f
478 fl.flagLines = f
479 fl.flagAddresses = f
480 switch o {
481 case "functions":
482 fl.flagFunctions = t
483 case "files":
484 fl.flagFiles = t
485 case "lines":
486 fl.flagLines = t
487 case "addresses":
488 fl.flagAddresses = t
489 default:
490 panic(fmt.Errorf("unexpected option %s", o))
491 }
492 }
OLDNEW
« no previous file with comments | « src/cmd/pprof/internal/driver/driver.go ('k') | src/cmd/pprof/internal/fetch/fetch.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b