golang.org/toolchain@v0.0.1-go1.9rc2.windows-amd64/src/cmd/vendor/github.com/google/pprof/internal/driver/interactive.go (about) 1 // Copyright 2014 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 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 = "//:" // Sentinel for comments on options 31 var tailDigitsRE = regexp.MustCompile("[0-9]+$") 32 33 // interactive starts a shell to read pprof commands. 34 func interactive(p *profile.Profile, o *plugin.Options) error { 35 // Enter command processing loop. 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 // Do not wait for the visualizer to complete, to allow multiple 41 // graphs to be visualized simultaneously. 42 interactiveMode = true 43 shortcuts := profileShortcuts(p) 44 45 greetings(p, o.UI) 46 for { 47 input, err := o.UI.ReadLine("(pprof) ") 48 if err != nil { 49 if err != io.EOF { 50 return err 51 } 52 if input == "" { 53 return nil 54 } 55 } 56 57 for _, input := range shortcuts.expand(input) { 58 // Process assignments of the form variable=value 59 if s := strings.SplitN(input, "=", 2); len(s) > 0 { 60 name := strings.TrimSpace(s[0]) 61 var value string 62 if len(s) == 2 { 63 value = s[1] 64 if comment := strings.LastIndex(value, commentStart); comment != -1 { 65 value = value[:comment] 66 } 67 value = strings.TrimSpace(value) 68 } 69 if v := pprofVariables[name]; v != nil { 70 if name == "sample_index" { 71 // Error check sample_index=xxx to ensure xxx is a valid sample type. 72 index, err := p.SampleIndexByName(value) 73 if err != nil { 74 o.UI.PrintErr(err) 75 continue 76 } 77 value = p.SampleType[index].Type 78 } 79 if err := pprofVariables.set(name, value); err != nil { 80 o.UI.PrintErr(err) 81 } 82 continue 83 } 84 // Allow group=variable syntax by converting into variable="". 85 if v := pprofVariables[value]; v != nil && v.group == name { 86 if err := pprofVariables.set(value, ""); err != nil { 87 o.UI.PrintErr(err) 88 } 89 continue 90 } 91 } 92 93 tokens := strings.Fields(input) 94 if len(tokens) == 0 { 95 continue 96 } 97 98 switch tokens[0] { 99 case "o", "options": 100 printCurrentOptions(p, o.UI) 101 continue 102 case "exit", "quit": 103 return nil 104 case "help": 105 commandHelp(strings.Join(tokens[1:], " "), o.UI) 106 continue 107 } 108 109 args, vars, err := parseCommandLine(tokens) 110 if err == nil { 111 err = generateReportWrapper(p, args, vars, o) 112 } 113 114 if err != nil { 115 o.UI.PrintErr(err) 116 } 117 } 118 } 119 } 120 121 var generateReportWrapper = generateReport // For testing purposes. 122 123 // greetings prints a brief welcome and some overall profile 124 // information before accepting interactive commands. 125 func greetings(p *profile.Profile, ui plugin.UI) { 126 ropt, err := reportOptions(p, pprofVariables) 127 if err == nil { 128 ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n")) 129 } 130 ui.Print("Entering interactive mode (type \"help\" for commands, \"o\" for options)") 131 } 132 133 // shortcuts represents composite commands that expand into a sequence 134 // of other commands. 135 type shortcuts map[string][]string 136 137 func (a shortcuts) expand(input string) []string { 138 input = strings.TrimSpace(input) 139 if a != nil { 140 if r, ok := a[input]; ok { 141 return r 142 } 143 } 144 return []string{input} 145 } 146 147 var pprofShortcuts = shortcuts{ 148 ":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="}, 149 } 150 151 // profileShortcuts creates macros for convenience and backward compatibility. 152 func profileShortcuts(p *profile.Profile) shortcuts { 153 s := pprofShortcuts 154 // Add shortcuts for sample types 155 for _, st := range p.SampleType { 156 command := fmt.Sprintf("sample_index=%s", st.Type) 157 s[st.Type] = []string{command} 158 s["total_"+st.Type] = []string{"mean=0", command} 159 s["mean_"+st.Type] = []string{"mean=1", command} 160 } 161 return s 162 } 163 164 func sampleTypes(p *profile.Profile) []string { 165 types := make([]string, len(p.SampleType)) 166 for i, t := range p.SampleType { 167 types[i] = t.Type 168 } 169 return types 170 } 171 172 func printCurrentOptions(p *profile.Profile, ui plugin.UI) { 173 var args []string 174 type groupInfo struct { 175 set string 176 values []string 177 } 178 groups := make(map[string]*groupInfo) 179 for n, o := range pprofVariables { 180 v := o.stringValue() 181 comment := "" 182 if g := o.group; g != "" { 183 gi, ok := groups[g] 184 if !ok { 185 gi = &groupInfo{} 186 groups[g] = gi 187 } 188 if o.boolValue() { 189 gi.set = n 190 } 191 gi.values = append(gi.values, n) 192 continue 193 } 194 switch { 195 case n == "sample_index": 196 st := sampleTypes(p) 197 if v == "" { 198 // Apply default (last sample index). 199 v = st[len(st)-1] 200 } 201 // Add comments for all sample types in profile. 202 comment = "[" + strings.Join(st, " | ") + "]" 203 case n == "source_path": 204 continue 205 case n == "nodecount" && v == "-1": 206 comment = "default" 207 case v == "": 208 // Add quotes for empty values. 209 v = `""` 210 } 211 if comment != "" { 212 comment = commentStart + " " + comment 213 } 214 args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment)) 215 } 216 for g, vars := range groups { 217 sort.Strings(vars.values) 218 comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]" 219 args = append(args, fmt.Sprintf(" %-25s = %-20s %s", g, vars.set, comment)) 220 } 221 sort.Strings(args) 222 ui.Print(strings.Join(args, "\n")) 223 } 224 225 // parseCommandLine parses a command and returns the pprof command to 226 // execute and a set of variables for the report. 227 func parseCommandLine(input []string) ([]string, variables, error) { 228 cmd, args := input[:1], input[1:] 229 name := cmd[0] 230 231 c := pprofCommands[name] 232 if c == nil { 233 // Attempt splitting digits on abbreviated commands (eg top10) 234 if d := tailDigitsRE.FindString(name); d != "" && d != name { 235 name = name[:len(name)-len(d)] 236 cmd[0], args = name, append([]string{d}, args...) 237 c = pprofCommands[name] 238 } 239 } 240 if c == nil { 241 return nil, nil, fmt.Errorf("Unrecognized command: %q", name) 242 } 243 244 if c.hasParam { 245 if len(args) == 0 { 246 return nil, nil, fmt.Errorf("command %s requires an argument", name) 247 } 248 cmd = append(cmd, args[0]) 249 args = args[1:] 250 } 251 252 // Copy the variables as options set in the command line are not persistent. 253 vcopy := pprofVariables.makeCopy() 254 255 var focus, ignore string 256 for i := 0; i < len(args); i++ { 257 t := args[i] 258 if _, err := strconv.ParseInt(t, 10, 32); err == nil { 259 vcopy.set("nodecount", t) 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, nil, fmt.Errorf("Unexpected end of line after >") 269 } 270 outputFile = args[i] 271 } 272 vcopy.set("output", outputFile) 273 case '-': 274 if t == "--cum" || t == "-cum" { 275 vcopy.set("cum", "t") 276 continue 277 } 278 ignore = catRegex(ignore, t[1:]) 279 default: 280 focus = catRegex(focus, t) 281 } 282 } 283 284 if name == "tags" { 285 updateFocusIgnore(vcopy, "tag", focus, ignore) 286 } else { 287 updateFocusIgnore(vcopy, "", focus, ignore) 288 } 289 290 if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") { 291 vcopy.set("nodecount", "10") 292 } 293 294 return cmd, vcopy, nil 295 } 296 297 func updateFocusIgnore(v variables, prefix, f, i string) { 298 if f != "" { 299 focus := prefix + "focus" 300 v.set(focus, catRegex(v[focus].value, f)) 301 } 302 303 if i != "" { 304 ignore := prefix + "ignore" 305 v.set(ignore, catRegex(v[ignore].value, i)) 306 } 307 } 308 309 func catRegex(a, b string) string { 310 if a != "" && b != "" { 311 return a + "|" + b 312 } 313 return a + b 314 } 315 316 // commandHelp displays help and usage information for all Commands 317 // and Variables or a specific Command or Variable. 318 func commandHelp(args string, ui plugin.UI) { 319 if args == "" { 320 help := usage(false) 321 help = help + ` 322 : Clear focus/ignore/hide/tagfocus/tagignore 323 324 type "help <cmd|option>" for more information 325 ` 326 327 ui.Print(help) 328 return 329 } 330 331 if c := pprofCommands[args]; c != nil { 332 ui.Print(c.help(args)) 333 return 334 } 335 336 if v := pprofVariables[args]; v != nil { 337 ui.Print(v.help + "\n") 338 return 339 } 340 341 ui.PrintErr("Unknown command: " + args) 342 } 343 344 // newCompleter creates an autocompletion function for a set of commands. 345 func newCompleter(fns []string) func(string) string { 346 return func(line string) string { 347 v := pprofVariables 348 switch tokens := strings.Fields(line); len(tokens) { 349 case 0: 350 // Nothing to complete 351 case 1: 352 // Single token -- complete command name 353 if match := matchVariableOrCommand(v, tokens[0]); match != "" { 354 return match 355 } 356 case 2: 357 if tokens[0] == "help" { 358 if match := matchVariableOrCommand(v, tokens[1]); match != "" { 359 return tokens[0] + " " + match 360 } 361 return line 362 } 363 fallthrough 364 default: 365 // Multiple tokens -- complete using functions, except for tags 366 if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" { 367 lastTokenIdx := len(tokens) - 1 368 lastToken := tokens[lastTokenIdx] 369 if strings.HasPrefix(lastToken, "-") { 370 lastToken = "-" + functionCompleter(lastToken[1:], fns) 371 } else { 372 lastToken = functionCompleter(lastToken, fns) 373 } 374 return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ") 375 } 376 } 377 return line 378 } 379 } 380 381 // matchCommand attempts to match a string token to the prefix of a Command. 382 func matchVariableOrCommand(v variables, token string) string { 383 token = strings.ToLower(token) 384 found := "" 385 for cmd := range pprofCommands { 386 if strings.HasPrefix(cmd, token) { 387 if found != "" { 388 return "" 389 } 390 found = cmd 391 } 392 } 393 for variable := range v { 394 if strings.HasPrefix(variable, token) { 395 if found != "" { 396 return "" 397 } 398 found = variable 399 } 400 } 401 return found 402 } 403 404 // functionCompleter replaces provided substring with a function 405 // name retrieved from a profile if a single match exists. Otherwise, 406 // it returns unchanged substring. It defaults to no-op if the profile 407 // is not specified. 408 func functionCompleter(substring string, fns []string) string { 409 found := "" 410 for _, fName := range fns { 411 if strings.Contains(fName, substring) { 412 if found != "" { 413 return substring 414 } 415 found = fName 416 } 417 } 418 if found != "" { 419 return found 420 } 421 return substring 422 } 423 424 func functionNames(p *profile.Profile) []string { 425 var fns []string 426 for _, fn := range p.Function { 427 fns = append(fns, fn.Name) 428 } 429 return fns 430 }