Нема описа

interactive.go 9.1KB

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