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 configure("compact_labels", "true")
38 configHelp["sample_index"] += 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 copier := makeProfileCopier(p)
46 greetings(p, o.UI)
47 for {
48 input, err := o.UI.ReadLine("(pprof) ")
49 if err != nil {
50 if err != io.EOF {
51 return err
52 }
53 if input == "" {
54 return nil
55 }
56 }
57
58 for _, input := range shortcuts.expand(input) {
59
60 if s := strings.SplitN(input, "=", 2); len(s) > 0 {
61 name := strings.TrimSpace(s[0])
62 var value string
63 if len(s) == 2 {
64 value = s[1]
65 if comment := strings.LastIndex(value, commentStart); comment != -1 {
66 value = value[:comment]
67 }
68 value = strings.TrimSpace(value)
69 }
70 if isConfigurable(name) {
71
72 if len(s) == 1 && !isBoolConfig(name) {
73 o.UI.PrintErr(fmt.Errorf("please specify a value, e.g. %s=<val>", name))
74 continue
75 }
76 if name == "sample_index" {
77
78 index, err := p.SampleIndexByName(value)
79 if err != nil {
80 o.UI.PrintErr(err)
81 continue
82 }
83 if index < 0 || index >= len(p.SampleType) {
84 o.UI.PrintErr(fmt.Errorf("invalid sample_index %q", value))
85 continue
86 }
87 value = p.SampleType[index].Type
88 }
89 if err := configure(name, value); err != nil {
90 o.UI.PrintErr(err)
91 }
92 continue
93 }
94 }
95
96 tokens := strings.Fields(input)
97 if len(tokens) == 0 {
98 continue
99 }
100
101 switch tokens[0] {
102 case "o", "options":
103 printCurrentOptions(p, o.UI)
104 continue
105 case "exit", "quit", "q":
106 return nil
107 case "help":
108 commandHelp(strings.Join(tokens[1:], " "), o.UI)
109 continue
110 }
111
112 args, cfg, err := parseCommandLine(tokens)
113 if err == nil {
114 err = generateReportWrapper(copier.newCopy(), args, cfg, o)
115 }
116
117 if err != nil {
118 o.UI.PrintErr(err)
119 }
120 }
121 }
122 }
123
124 var generateReportWrapper = generateReport
125
126
127
128 func greetings(p *profile.Profile, ui plugin.UI) {
129 numLabelUnits := identifyNumLabelUnits(p, ui)
130 ropt, err := reportOptions(p, numLabelUnits, currentConfig())
131 if err == nil {
132 rpt := report.New(p, ropt)
133 ui.Print(strings.Join(report.ProfileLabels(rpt), "\n"))
134 if rpt.Total() == 0 && len(p.SampleType) > 1 {
135 ui.Print(`No samples were found with the default sample value type.`)
136 ui.Print(`Try "sample_index" command to analyze different sample values.`, "\n")
137 }
138 }
139 ui.Print(`Entering interactive mode (type "help" for commands, "o" for options)`)
140 }
141
142
143
144 type shortcuts map[string][]string
145
146 func (a shortcuts) expand(input string) []string {
147 input = strings.TrimSpace(input)
148 if a != nil {
149 if r, ok := a[input]; ok {
150 return r
151 }
152 }
153 return []string{input}
154 }
155
156 var pprofShortcuts = shortcuts{
157 ":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="},
158 }
159
160
161 func profileShortcuts(p *profile.Profile) shortcuts {
162 s := pprofShortcuts
163
164 for _, st := range p.SampleType {
165 command := fmt.Sprintf("sample_index=%s", st.Type)
166 s[st.Type] = []string{command}
167 s["total_"+st.Type] = []string{"mean=0", command}
168 s["mean_"+st.Type] = []string{"mean=1", command}
169 }
170 return s
171 }
172
173 func sampleTypes(p *profile.Profile) []string {
174 types := make([]string, len(p.SampleType))
175 for i, t := range p.SampleType {
176 types[i] = t.Type
177 }
178 return types
179 }
180
181 func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
182 var args []string
183 current := currentConfig()
184 for _, f := range configFields {
185 n := f.name
186 v := current.get(f)
187 comment := ""
188 switch {
189 case len(f.choices) > 0:
190 values := append([]string{}, f.choices...)
191 sort.Strings(values)
192 comment = "[" + strings.Join(values, " | ") + "]"
193 case n == "sample_index":
194 st := sampleTypes(p)
195 if v == "" {
196
197 v = st[len(st)-1]
198 }
199
200 comment = "[" + strings.Join(st, " | ") + "]"
201 case n == "source_path":
202 continue
203 case n == "nodecount" && v == "-1":
204 comment = "default"
205 case v == "":
206
207 v = `""`
208 }
209 if comment != "" {
210 comment = commentStart + " " + comment
211 }
212 args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment))
213 }
214 sort.Strings(args)
215 ui.Print(strings.Join(args, "\n"))
216 }
217
218
219
220 func parseCommandLine(input []string) ([]string, config, error) {
221 cmd, args := input[:1], input[1:]
222 name := cmd[0]
223
224 c := pprofCommands[name]
225 if c == nil {
226
227 if d := tailDigitsRE.FindString(name); d != "" && d != name {
228 name = name[:len(name)-len(d)]
229 cmd[0], args = name, append([]string{d}, args...)
230 c = pprofCommands[name]
231 }
232 }
233 if c == nil {
234 if _, ok := configHelp[name]; ok {
235 value := "<val>"
236 if len(args) > 0 {
237 value = args[0]
238 }
239 return nil, config{}, fmt.Errorf("did you mean: %s=%s", name, value)
240 }
241 return nil, config{}, fmt.Errorf("unrecognized command: %q", name)
242 }
243
244 if c.hasParam {
245 if len(args) == 0 {
246 return nil, config{}, fmt.Errorf("command %s requires an argument", name)
247 }
248 cmd = append(cmd, args[0])
249 args = args[1:]
250 }
251
252
253 vcopy := currentConfig()
254
255 var focus, ignore string
256 for i := 0; i < len(args); i++ {
257 t := args[i]
258 if n, err := strconv.ParseInt(t, 10, 32); err == nil {
259 vcopy.NodeCount = int(n)
260 continue
261 }
262 switch t[0] {
263 case '>':
264 outputFile := t[1:]
265 if outputFile == "" {
266 i++
267 if i >= len(args) {
268 return nil, config{}, fmt.Errorf("unexpected end of line after >")
269 }
270 outputFile = args[i]
271 }
272 vcopy.Output = outputFile
273 case '-':
274 if t == "--cum" || t == "-cum" {
275 vcopy.Sort = "cum"
276 continue
277 }
278 ignore = catRegex(ignore, t[1:])
279 default:
280 focus = catRegex(focus, t)
281 }
282 }
283
284 if name == "tags" {
285 if focus != "" {
286 vcopy.TagFocus = focus
287 }
288 if ignore != "" {
289 vcopy.TagIgnore = ignore
290 }
291 } else {
292 if focus != "" {
293 vcopy.Focus = focus
294 }
295 if ignore != "" {
296 vcopy.Ignore = ignore
297 }
298 }
299 if vcopy.NodeCount == -1 && (name == "text" || name == "top") {
300 vcopy.NodeCount = 10
301 }
302
303 return cmd, vcopy, nil
304 }
305
306 func catRegex(a, b string) string {
307 if a != "" && b != "" {
308 return a + "|" + b
309 }
310 return a + b
311 }
312
313
314
315 func commandHelp(args string, ui plugin.UI) {
316 if args == "" {
317 help := usage(false)
318 help = help + `
319 : Clear focus/ignore/hide/tagfocus/tagignore
320
321 type "help <cmd|option>" for more information
322 `
323
324 ui.Print(help)
325 return
326 }
327
328 if c := pprofCommands[args]; c != nil {
329 ui.Print(c.help(args))
330 return
331 }
332
333 if help, ok := configHelp[args]; ok {
334 ui.Print(help + "\n")
335 return
336 }
337
338 ui.PrintErr("Unknown command: " + args)
339 }
340
341
342 func newCompleter(fns []string) func(string) string {
343 return func(line string) string {
344 switch tokens := strings.Fields(line); len(tokens) {
345 case 0:
346
347 case 1:
348
349 if match := matchVariableOrCommand(tokens[0]); match != "" {
350 return match
351 }
352 case 2:
353 if tokens[0] == "help" {
354 if match := matchVariableOrCommand(tokens[1]); match != "" {
355 return tokens[0] + " " + match
356 }
357 return line
358 }
359 fallthrough
360 default:
361
362 if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" {
363 lastTokenIdx := len(tokens) - 1
364 lastToken := tokens[lastTokenIdx]
365 if strings.HasPrefix(lastToken, "-") {
366 lastToken = "-" + functionCompleter(lastToken[1:], fns)
367 } else {
368 lastToken = functionCompleter(lastToken, fns)
369 }
370 return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
371 }
372 }
373 return line
374 }
375 }
376
377
378 func matchVariableOrCommand(token string) string {
379 token = strings.ToLower(token)
380 var matches []string
381 for cmd := range pprofCommands {
382 if strings.HasPrefix(cmd, token) {
383 matches = append(matches, cmd)
384 }
385 }
386 matches = append(matches, completeConfig(token)...)
387 if len(matches) == 1 {
388 return matches[0]
389 }
390 return ""
391 }
392
393
394
395
396
397 func functionCompleter(substring string, fns []string) string {
398 found := ""
399 for _, fName := range fns {
400 if strings.Contains(fName, substring) {
401 if found != "" {
402 return substring
403 }
404 found = fName
405 }
406 }
407 if found != "" {
408 return found
409 }
410 return substring
411 }
412
413 func functionNames(p *profile.Profile) []string {
414 var fns []string
415 for _, fn := range p.Function {
416 fns = append(fns, fn.Name)
417 }
418 return fns
419 }
420
View as plain text