Source file src/pkg/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package driver
16
17 import (
18 "fmt"
19 "io"
20 "regexp"
21 "sort"
22 "strconv"
23 "strings"
24
25 "github.com/google/pprof/internal/plugin"
26 "github.com/google/pprof/internal/report"
27 "github.com/google/pprof/profile"
28 )
29
30 var commentStart = "//:"
31 var tailDigitsRE = regexp.MustCompile("[0-9]+$")
32
33
34 func interactive(p *profile.Profile, o *plugin.Options) error {
35
36 o.UI.SetAutoComplete(newCompleter(functionNames(p)))
37 pprofVariables.set("compact_labels", "true")
38 pprofVariables["sample_index"].help += fmt.Sprintf("Or use sample_index=name, with name in %v.\n", sampleTypes(p))
39
40
41
42 interactiveMode = true
43 shortcuts := profileShortcuts(p)
44
45
46 groups := groupOptions(pprofVariables)
47
48 greetings(p, o.UI)
49 for {
50 input, err := o.UI.ReadLine("(pprof) ")
51 if err != nil {
52 if err != io.EOF {
53 return err
54 }
55 if input == "" {
56 return nil
57 }
58 }
59
60 for _, input := range shortcuts.expand(input) {
61
62 if s := strings.SplitN(input, "=", 2); len(s) > 0 {
63 name := strings.TrimSpace(s[0])
64 var value string
65 if len(s) == 2 {
66 value = s[1]
67 if comment := strings.LastIndex(value, commentStart); comment != -1 {
68 value = value[:comment]
69 }
70 value = strings.TrimSpace(value)
71 }
72 if v := pprofVariables[name]; v != nil {
73 if name == "sample_index" {
74
75 index, err := p.SampleIndexByName(value)
76 if err != nil {
77 o.UI.PrintErr(err)
78 continue
79 }
80 value = p.SampleType[index].Type
81 }
82 if err := pprofVariables.set(name, value); err != nil {
83 o.UI.PrintErr(err)
84 }
85 continue
86 }
87
88 if v := pprofVariables[value]; v != nil && v.group == name {
89 if err := pprofVariables.set(value, ""); err != nil {
90 o.UI.PrintErr(err)
91 }
92 continue
93 } else if okValues := groups[name]; okValues != nil {
94 o.UI.PrintErr(fmt.Errorf("unrecognized value for %s: %q. Use one of %s", name, value, strings.Join(okValues, ", ")))
95 continue
96 }
97 }
98
99 tokens := strings.Fields(input)
100 if len(tokens) == 0 {
101 continue
102 }
103
104 switch tokens[0] {
105 case "o", "options":
106 printCurrentOptions(p, o.UI)
107 continue
108 case "exit", "quit":
109 return nil
110 case "help":
111 commandHelp(strings.Join(tokens[1:], " "), o.UI)
112 continue
113 }
114
115 args, vars, err := parseCommandLine(tokens)
116 if err == nil {
117 err = generateReportWrapper(p, args, vars, o)
118 }
119
120 if err != nil {
121 o.UI.PrintErr(err)
122 }
123 }
124 }
125 }
126
127
128
129
130 func groupOptions(vars variables) map[string][]string {
131 groups := make(map[string][]string)
132 for name, option := range vars {
133 group := option.group
134 if group != "" {
135 groups[group] = append(groups[group], name)
136 }
137 }
138 for _, names := range groups {
139 sort.Strings(names)
140 }
141 return groups
142 }
143
144 var generateReportWrapper = generateReport
145
146
147
148 func greetings(p *profile.Profile, ui plugin.UI) {
149 numLabelUnits := identifyNumLabelUnits(p, ui)
150 ropt, err := reportOptions(p, numLabelUnits, pprofVariables)
151 if err == nil {
152 rpt := report.New(p, ropt)
153 ui.Print(strings.Join(report.ProfileLabels(rpt), "\n"))
154 if rpt.Total() == 0 && len(p.SampleType) > 1 {
155 ui.Print(`No samples were found with the default sample value type.`)
156 ui.Print(`Try "sample_index" command to analyze different sample values.`, "\n")
157 }
158 }
159 ui.Print(`Entering interactive mode (type "help" for commands, "o" for options)`)
160 }
161
162
163
164 type shortcuts map[string][]string
165
166 func (a shortcuts) expand(input string) []string {
167 input = strings.TrimSpace(input)
168 if a != nil {
169 if r, ok := a[input]; ok {
170 return r
171 }
172 }
173 return []string{input}
174 }
175
176 var pprofShortcuts = shortcuts{
177 ":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="},
178 }
179
180
181 func profileShortcuts(p *profile.Profile) shortcuts {
182 s := pprofShortcuts
183
184 for _, st := range p.SampleType {
185 command := fmt.Sprintf("sample_index=%s", st.Type)
186 s[st.Type] = []string{command}
187 s["total_"+st.Type] = []string{"mean=0", command}
188 s["mean_"+st.Type] = []string{"mean=1", command}
189 }
190 return s
191 }
192
193 func sampleTypes(p *profile.Profile) []string {
194 types := make([]string, len(p.SampleType))
195 for i, t := range p.SampleType {
196 types[i] = t.Type
197 }
198 return types
199 }
200
201 func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
202 var args []string
203 type groupInfo struct {
204 set string
205 values []string
206 }
207 groups := make(map[string]*groupInfo)
208 for n, o := range pprofVariables {
209 v := o.stringValue()
210 comment := ""
211 if g := o.group; g != "" {
212 gi, ok := groups[g]
213 if !ok {
214 gi = &groupInfo{}
215 groups[g] = gi
216 }
217 if o.boolValue() {
218 gi.set = n
219 }
220 gi.values = append(gi.values, n)
221 continue
222 }
223 switch {
224 case n == "sample_index":
225 st := sampleTypes(p)
226 if v == "" {
227
228 v = st[len(st)-1]
229 }
230
231 comment = "[" + strings.Join(st, " | ") + "]"
232 case n == "source_path":
233 continue
234 case n == "nodecount" && v == "-1":
235 comment = "default"
236 case v == "":
237
238 v = `""`
239 }
240 if comment != "" {
241 comment = commentStart + " " + comment
242 }
243 args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment))
244 }
245 for g, vars := range groups {
246 sort.Strings(vars.values)
247 comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]"
248 args = append(args, fmt.Sprintf(" %-25s = %-20s %s", g, vars.set, comment))
249 }
250 sort.Strings(args)
251 ui.Print(strings.Join(args, "\n"))
252 }
253
254
255
256 func parseCommandLine(input []string) ([]string, variables, error) {
257 cmd, args := input[:1], input[1:]
258 name := cmd[0]
259
260 c := pprofCommands[name]
261 if c == nil {
262
263 if d := tailDigitsRE.FindString(name); d != "" && d != name {
264 name = name[:len(name)-len(d)]
265 cmd[0], args = name, append([]string{d}, args...)
266 c = pprofCommands[name]
267 }
268 }
269 if c == nil {
270 return nil, nil, fmt.Errorf("unrecognized command: %q", name)
271 }
272
273 if c.hasParam {
274 if len(args) == 0 {
275 return nil, nil, fmt.Errorf("command %s requires an argument", name)
276 }
277 cmd = append(cmd, args[0])
278 args = args[1:]
279 }
280
281
282 vcopy := pprofVariables.makeCopy()
283
284 var focus, ignore string
285 for i := 0; i < len(args); i++ {
286 t := args[i]
287 if _, err := strconv.ParseInt(t, 10, 32); err == nil {
288 vcopy.set("nodecount", t)
289 continue
290 }
291 switch t[0] {
292 case '>':
293 outputFile := t[1:]
294 if outputFile == "" {
295 i++
296 if i >= len(args) {
297 return nil, nil, fmt.Errorf("unexpected end of line after >")
298 }
299 outputFile = args[i]
300 }
301 vcopy.set("output", outputFile)
302 case '-':
303 if t == "--cum" || t == "-cum" {
304 vcopy.set("cum", "t")
305 continue
306 }
307 ignore = catRegex(ignore, t[1:])
308 default:
309 focus = catRegex(focus, t)
310 }
311 }
312
313 if name == "tags" {
314 updateFocusIgnore(vcopy, "tag", focus, ignore)
315 } else {
316 updateFocusIgnore(vcopy, "", focus, ignore)
317 }
318
319 if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") {
320 vcopy.set("nodecount", "10")
321 }
322
323 return cmd, vcopy, nil
324 }
325
326 func updateFocusIgnore(v variables, prefix, f, i string) {
327 if f != "" {
328 focus := prefix + "focus"
329 v.set(focus, catRegex(v[focus].value, f))
330 }
331
332 if i != "" {
333 ignore := prefix + "ignore"
334 v.set(ignore, catRegex(v[ignore].value, i))
335 }
336 }
337
338 func catRegex(a, b string) string {
339 if a != "" && b != "" {
340 return a + "|" + b
341 }
342 return a + b
343 }
344
345
346
347 func commandHelp(args string, ui plugin.UI) {
348 if args == "" {
349 help := usage(false)
350 help = help + `
351 : Clear focus/ignore/hide/tagfocus/tagignore
352
353 type "help <cmd|option>" for more information
354 `
355
356 ui.Print(help)
357 return
358 }
359
360 if c := pprofCommands[args]; c != nil {
361 ui.Print(c.help(args))
362 return
363 }
364
365 if v := pprofVariables[args]; v != nil {
366 ui.Print(v.help + "\n")
367 return
368 }
369
370 ui.PrintErr("Unknown command: " + args)
371 }
372
373
374 func newCompleter(fns []string) func(string) string {
375 return func(line string) string {
376 v := pprofVariables
377 switch tokens := strings.Fields(line); len(tokens) {
378 case 0:
379
380 case 1:
381
382 if match := matchVariableOrCommand(v, tokens[0]); match != "" {
383 return match
384 }
385 case 2:
386 if tokens[0] == "help" {
387 if match := matchVariableOrCommand(v, tokens[1]); match != "" {
388 return tokens[0] + " " + match
389 }
390 return line
391 }
392 fallthrough
393 default:
394
395 if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" {
396 lastTokenIdx := len(tokens) - 1
397 lastToken := tokens[lastTokenIdx]
398 if strings.HasPrefix(lastToken, "-") {
399 lastToken = "-" + functionCompleter(lastToken[1:], fns)
400 } else {
401 lastToken = functionCompleter(lastToken, fns)
402 }
403 return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
404 }
405 }
406 return line
407 }
408 }
409
410
411 func matchVariableOrCommand(v variables, token string) string {
412 token = strings.ToLower(token)
413 found := ""
414 for cmd := range pprofCommands {
415 if strings.HasPrefix(cmd, token) {
416 if found != "" {
417 return ""
418 }
419 found = cmd
420 }
421 }
422 for variable := range v {
423 if strings.HasPrefix(variable, token) {
424 if found != "" {
425 return ""
426 }
427 found = variable
428 }
429 }
430 return found
431 }
432
433
434
435
436
437 func functionCompleter(substring string, fns []string) string {
438 found := ""
439 for _, fName := range fns {
440 if strings.Contains(fName, substring) {
441 if found != "" {
442 return substring
443 }
444 found = fName
445 }
446 }
447 if found != "" {
448 return found
449 }
450 return substring
451 }
452
453 func functionNames(p *profile.Profile) []string {
454 var fns []string
455 for _, fn := range p.Function {
456 fns = append(fns, fn.Name)
457 }
458 return fns
459 }
460
View as plain text