github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/src/cmd/pprof/internal/driver/interactive.go (about) 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 plugin.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", name, 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) (*flags, 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, ignore)) 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/lines/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 }