LEFT | RIGHT |
(no file at all) | |
| 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 } |
LEFT | RIGHT |