Açıklama Yok

interactive.go 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. package driver
  15. import (
  16. "fmt"
  17. "io"
  18. "sort"
  19. "strconv"
  20. "strings"
  21. "github.com/google/pprof/internal/plugin"
  22. "github.com/google/pprof/internal/report"
  23. "github.com/google/pprof/profile"
  24. )
  25. var commentStart = "//:" // Sentinel for comments on options
  26. // interactive starts a shell to read pprof commands.
  27. func interactive(p *profile.Profile, o *plugin.Options) error {
  28. // Enter command processing loop.
  29. o.UI.SetAutoComplete(newCompleter(functionNames(p)))
  30. pprofVariables.set("compact_labels", "true")
  31. pprofVariables["sample_index"].help += fmt.Sprintf("Or use sample_index=name, with name in %v.\n", sampleTypes(p))
  32. // Do not wait for the visualizer to complete, to allow multiple
  33. // graphs to be visualized simultaneously.
  34. waitForVisualizer = false
  35. shortcuts := profileShortcuts(p)
  36. greetings(p, o.UI)
  37. for {
  38. input, err := o.UI.ReadLine("(pprof) ")
  39. if err != nil {
  40. if err != io.EOF {
  41. return err
  42. }
  43. if input == "" {
  44. return nil
  45. }
  46. }
  47. for _, input := range shortcuts.expand(input) {
  48. // Process assignments of the form variable=value
  49. if s := strings.SplitN(input, "=", 2); len(s) > 0 {
  50. name := strings.TrimSpace(s[0])
  51. var value string
  52. if len(s) == 2 {
  53. value = s[1]
  54. if comment := strings.LastIndex(value, commentStart); comment != -1 {
  55. value = value[:comment]
  56. }
  57. value = strings.TrimSpace(value)
  58. }
  59. if v := pprofVariables[name]; v != nil {
  60. if name == "sample_index" {
  61. // Error check sample_index=xxx to ensure xxx is a valid sample type.
  62. index, err := locateSampleIndex(p, value)
  63. if err != nil {
  64. o.UI.PrintErr(err)
  65. continue
  66. }
  67. value = p.SampleType[index].Type
  68. }
  69. if err := pprofVariables.set(name, value); err != nil {
  70. o.UI.PrintErr(err)
  71. }
  72. continue
  73. }
  74. // Allow group=variable syntax by converting into variable="".
  75. if v := pprofVariables[value]; v != nil && v.group == name {
  76. if err := pprofVariables.set(value, ""); err != nil {
  77. o.UI.PrintErr(err)
  78. }
  79. continue
  80. }
  81. }
  82. tokens := strings.Fields(input)
  83. if len(tokens) == 0 {
  84. continue
  85. }
  86. switch tokens[0] {
  87. case "o", "options":
  88. printCurrentOptions(p, o.UI)
  89. continue
  90. case "exit", "quit":
  91. return nil
  92. case "help":
  93. commandHelp(strings.Join(tokens[1:], " "), o.UI)
  94. continue
  95. }
  96. args, vars, err := parseCommandLine(tokens)
  97. if err == nil {
  98. err = generateReportWrapper(p, args, vars, o)
  99. }
  100. if err != nil {
  101. o.UI.PrintErr(err)
  102. }
  103. }
  104. }
  105. }
  106. var generateReportWrapper = generateReport // For testing purposes.
  107. // greetings prints a brief welcome and some overall profile
  108. // information before accepting interactive commands.
  109. func greetings(p *profile.Profile, ui plugin.UI) {
  110. ropt, err := reportOptions(p, pprofVariables)
  111. if err == nil {
  112. ui.Print(strings.Join(report.ProfileLabels(report.New(p, ropt)), "\n"))
  113. }
  114. ui.Print("Entering interactive mode (type \"help\" for commands, \"o\" for options)")
  115. }
  116. // shortcuts represents composite commands that expand into a sequence
  117. // of other commands.
  118. type shortcuts map[string][]string
  119. func (a shortcuts) expand(input string) []string {
  120. input = strings.TrimSpace(input)
  121. if a != nil {
  122. if r, ok := a[input]; ok {
  123. return r
  124. }
  125. }
  126. return []string{input}
  127. }
  128. var pprofShortcuts = shortcuts{
  129. ":": []string{"focus=", "ignore=", "hide=", "tagfocus=", "tagignore="},
  130. }
  131. // profileShortcuts creates macros for convenience and backward compatibility.
  132. func profileShortcuts(p *profile.Profile) shortcuts {
  133. s := pprofShortcuts
  134. // Add shortcuts for sample types
  135. for _, st := range p.SampleType {
  136. command := fmt.Sprintf("sample_index=%s", st.Type)
  137. s[st.Type] = []string{command}
  138. s["total_"+st.Type] = []string{"mean=0", command}
  139. s["mean_"+st.Type] = []string{"mean=1", command}
  140. }
  141. return s
  142. }
  143. func printCurrentOptions(p *profile.Profile, ui plugin.UI) {
  144. var args []string
  145. type groupInfo struct {
  146. set string
  147. values []string
  148. }
  149. groups := make(map[string]*groupInfo)
  150. for n, o := range pprofVariables {
  151. v := o.stringValue()
  152. comment := ""
  153. if g := o.group; g != "" {
  154. gi, ok := groups[g]
  155. if !ok {
  156. gi = &groupInfo{}
  157. groups[g] = gi
  158. }
  159. if o.boolValue() {
  160. gi.set = n
  161. }
  162. gi.values = append(gi.values, n)
  163. continue
  164. }
  165. switch {
  166. case n == "sample_index":
  167. st := sampleTypes(p)
  168. if v == "" {
  169. // Apply default (last sample index).
  170. v = st[len(st)-1]
  171. }
  172. // Add comments for all sample types in profile.
  173. comment = "[" + strings.Join(st, " | ") + "]"
  174. case n == "source_path":
  175. continue
  176. case n == "nodecount" && v == "-1":
  177. comment = "default"
  178. case v == "":
  179. // Add quotes for empty values.
  180. v = `""`
  181. }
  182. if comment != "" {
  183. comment = commentStart + " " + comment
  184. }
  185. args = append(args, fmt.Sprintf(" %-25s = %-20s %s", n, v, comment))
  186. }
  187. for g, vars := range groups {
  188. sort.Strings(vars.values)
  189. comment := commentStart + " [" + strings.Join(vars.values, " | ") + "]"
  190. args = append(args, fmt.Sprintf(" %-25s = %-20s %s", g, vars.set, comment))
  191. }
  192. sort.Strings(args)
  193. ui.Print(strings.Join(args, "\n"))
  194. }
  195. // parseCommandLine parses a command and returns the pprof command to
  196. // execute and a set of variables for the report.
  197. func parseCommandLine(input []string) ([]string, variables, error) {
  198. cmd, args := input[:1], input[1:]
  199. name := cmd[0]
  200. c := pprofCommands[name]
  201. if c == nil {
  202. // Attempt splitting digits on abbreviated commands (eg top10)
  203. for i := len(name); i > 0; i-- {
  204. if !strings.ContainsAny(name[i-1:i], "0123456789") {
  205. if n, d := name[:i], name[i:]; n != "" && d != "" {
  206. cmd[0], args = n, append([]string{d}, args...)
  207. name = n
  208. c = pprofCommands[n]
  209. }
  210. break
  211. }
  212. }
  213. }
  214. if c == nil {
  215. return nil, nil, fmt.Errorf("Unrecognized command: %q", name)
  216. }
  217. if c.hasParam {
  218. if len(args) == 0 {
  219. return nil, nil, fmt.Errorf("command %s requires an argument", name)
  220. }
  221. cmd = append(cmd, args[0])
  222. args = args[1:]
  223. }
  224. // Copy the variables as options set in the command line are not persistent.
  225. vcopy := pprofVariables.makeCopy()
  226. var focus, ignore string
  227. for i := 0; i < len(args); i++ {
  228. t := args[i]
  229. if _, err := strconv.ParseInt(t, 10, 32); err == nil {
  230. vcopy.set("nodecount", t)
  231. continue
  232. }
  233. switch t[0] {
  234. case '>':
  235. outputFile := t[1:]
  236. if outputFile == "" {
  237. i++
  238. if i >= len(args) {
  239. return nil, nil, fmt.Errorf("Unexpected end of line after >")
  240. }
  241. outputFile = args[i]
  242. }
  243. vcopy.set("output", outputFile)
  244. case '-':
  245. if t == "--cum" || t == "-cum" {
  246. vcopy.set("cum", "t")
  247. continue
  248. }
  249. ignore = catRegex(ignore, t[1:])
  250. default:
  251. focus = catRegex(focus, t)
  252. }
  253. }
  254. if name == "tags" {
  255. updateFocusIgnore(vcopy, "tag", focus, ignore)
  256. } else {
  257. updateFocusIgnore(vcopy, "", focus, ignore)
  258. }
  259. if vcopy["nodecount"].intValue() == -1 && (name == "text" || name == "top") {
  260. vcopy.set("nodecount", "10")
  261. }
  262. return cmd, vcopy, nil
  263. }
  264. func updateFocusIgnore(v variables, prefix, f, i string) {
  265. if f != "" {
  266. focus := prefix + "focus"
  267. v.set(focus, catRegex(v[focus].value, f))
  268. }
  269. if i != "" {
  270. ignore := prefix + "ignore"
  271. v.set(ignore, catRegex(v[ignore].value, i))
  272. }
  273. }
  274. func catRegex(a, b string) string {
  275. if a != "" && b != "" {
  276. return a + "|" + b
  277. }
  278. return a + b
  279. }
  280. // commandHelp displays help and usage information for all Commands
  281. // and Variables or a specific Command or Variable.
  282. func commandHelp(args string, ui plugin.UI) {
  283. if args == "" {
  284. help := usage(false)
  285. help = help + `
  286. : Clear focus/ignore/hide/tagfocus/tagignore
  287. type "help <cmd|option>" for more information
  288. `
  289. ui.Print(help)
  290. return
  291. }
  292. if c := pprofCommands[args]; c != nil {
  293. ui.Print(c.help(args))
  294. return
  295. }
  296. if v := pprofVariables[args]; v != nil {
  297. ui.Print(v.help + "\n")
  298. return
  299. }
  300. ui.PrintErr("Unknown command: " + args)
  301. }
  302. // newCompleter creates an autocompletion function for a set of commands.
  303. func newCompleter(fns []string) func(string) string {
  304. return func(line string) string {
  305. v := pprofVariables
  306. switch tokens := strings.Fields(line); len(tokens) {
  307. case 0:
  308. // Nothing to complete
  309. case 1:
  310. // Single token -- complete command name
  311. if match := matchVariableOrCommand(v, tokens[0]); match != "" {
  312. return match
  313. }
  314. case 2:
  315. if tokens[0] == "help" {
  316. if match := matchVariableOrCommand(v, tokens[1]); match != "" {
  317. return tokens[0] + " " + match
  318. }
  319. return line
  320. }
  321. fallthrough
  322. default:
  323. // Multiple tokens -- complete using functions, except for tags
  324. if cmd := pprofCommands[tokens[0]]; cmd != nil && tokens[0] != "tags" {
  325. lastTokenIdx := len(tokens) - 1
  326. lastToken := tokens[lastTokenIdx]
  327. if strings.HasPrefix(lastToken, "-") {
  328. lastToken = "-" + functionCompleter(lastToken[1:], fns)
  329. } else {
  330. lastToken = functionCompleter(lastToken, fns)
  331. }
  332. return strings.Join(append(tokens[:lastTokenIdx], lastToken), " ")
  333. }
  334. }
  335. return line
  336. }
  337. }
  338. // matchCommand attempts to match a string token to the prefix of a Command.
  339. func matchVariableOrCommand(v variables, token string) string {
  340. token = strings.ToLower(token)
  341. found := ""
  342. for cmd := range pprofCommands {
  343. if strings.HasPrefix(cmd, token) {
  344. if found != "" {
  345. return ""
  346. }
  347. found = cmd
  348. }
  349. }
  350. for variable := range v {
  351. if strings.HasPrefix(variable, token) {
  352. if found != "" {
  353. return ""
  354. }
  355. found = variable
  356. }
  357. }
  358. return found
  359. }
  360. // functionCompleter replaces provided substring with a function
  361. // name retrieved from a profile if a single match exists. Otherwise,
  362. // it returns unchanged substring. It defaults to no-op if the profile
  363. // is not specified.
  364. func functionCompleter(substring string, fns []string) string {
  365. found := ""
  366. for _, fName := range fns {
  367. if strings.Contains(fName, substring) {
  368. if found != "" {
  369. return substring
  370. }
  371. found = fName
  372. }
  373. }
  374. if found != "" {
  375. return found
  376. }
  377. return substring
  378. }
  379. func functionNames(p *profile.Profile) []string {
  380. var fns []string
  381. for _, fn := range p.Function {
  382. fns = append(fns, fn.Name)
  383. }
  384. return fns
  385. }