monitorcmd.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package main
  2. import (
  3. "reflect"
  4. "sort"
  5. "strings"
  6. "time"
  7. "github.com/codegangsta/cli"
  8. "github.com/ethereum/go-ethereum/cmd/utils"
  9. "github.com/ethereum/go-ethereum/rpc"
  10. "github.com/ethereum/go-ethereum/rpc/codec"
  11. "github.com/ethereum/go-ethereum/rpc/comms"
  12. "github.com/gizak/termui"
  13. )
  14. // monitor starts a terminal UI based monitoring tool for the requested metrics.
  15. func monitor(ctx *cli.Context) {
  16. var (
  17. client comms.EthereumClient
  18. args []string
  19. err error
  20. )
  21. // Attach to an Ethereum node over IPC or RPC
  22. if ctx.Args().Present() {
  23. // Try to interpret the first parameter as an endpoint
  24. client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON)
  25. if err == nil {
  26. args = ctx.Args().Tail()
  27. }
  28. }
  29. if !ctx.Args().Present() || err != nil {
  30. // Either no args were given, or not endpoint, use defaults
  31. cfg := comms.IpcConfig{
  32. Endpoint: ctx.GlobalString(utils.IPCPathFlag.Name),
  33. }
  34. args = ctx.Args()
  35. client, err = comms.NewIpcClient(cfg, codec.JSON)
  36. }
  37. if err != nil {
  38. utils.Fatalf("Unable to attach to geth node - %v", err)
  39. }
  40. defer client.Close()
  41. xeth := rpc.NewXeth(client)
  42. // Retrieve all the available metrics and resolve the user pattens
  43. metrics, err := xeth.Call("debug_metrics", []interface{}{true})
  44. if err != nil {
  45. utils.Fatalf("Failed to retrieve system metrics: %v", err)
  46. }
  47. monitored := resolveMetrics(metrics, args)
  48. sort.Strings(monitored)
  49. // Create the access function and check that the metric exists
  50. value := func(metrics map[string]interface{}, metric string) float64 {
  51. parts, found := strings.Split(metric, "/"), true
  52. for _, part := range parts[:len(parts)-1] {
  53. metrics, found = metrics[part].(map[string]interface{})
  54. if !found {
  55. utils.Fatalf("Metric not found: %s", metric)
  56. }
  57. }
  58. if v, ok := metrics[parts[len(parts)-1]].(float64); ok {
  59. return v
  60. }
  61. utils.Fatalf("Metric not float64: %s", metric)
  62. return 0
  63. }
  64. // Assemble the terminal UI
  65. if err := termui.Init(); err != nil {
  66. utils.Fatalf("Unable to initialize terminal UI: %v", err)
  67. }
  68. defer termui.Close()
  69. termui.UseTheme("helloworld")
  70. charts := make([]*termui.LineChart, len(monitored))
  71. for i, metric := range monitored {
  72. charts[i] = termui.NewLineChart()
  73. charts[i].Border.Label = metric
  74. charts[i].Data = make([]float64, 512)
  75. charts[i].DataLabels = []string{""}
  76. charts[i].Height = termui.TermHeight() / len(monitored)
  77. charts[i].AxesColor = termui.ColorWhite
  78. charts[i].LineColor = termui.ColorGreen
  79. termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, charts[i])))
  80. }
  81. termui.Body.Align()
  82. termui.Render(termui.Body)
  83. refresh := time.Tick(time.Second)
  84. for {
  85. select {
  86. case event := <-termui.EventCh():
  87. if event.Type == termui.EventKey && event.Ch == 'q' {
  88. return
  89. }
  90. if event.Type == termui.EventResize {
  91. termui.Body.Width = termui.TermWidth()
  92. for _, chart := range charts {
  93. chart.Height = termui.TermHeight() / len(monitored)
  94. }
  95. termui.Body.Align()
  96. termui.Render(termui.Body)
  97. }
  98. case <-refresh:
  99. metrics, err := xeth.Call("debug_metrics", []interface{}{true})
  100. if err != nil {
  101. utils.Fatalf("Failed to retrieve system metrics: %v", err)
  102. }
  103. for i, metric := range monitored {
  104. charts[i].Data = append([]float64{value(metrics, metric)}, charts[i].Data[:len(charts[i].Data)-1]...)
  105. }
  106. termui.Render(termui.Body)
  107. }
  108. }
  109. }
  110. // resolveMetrics takes a list of input metric patterns, and resolves each to one
  111. // or more canonical metric names.
  112. func resolveMetrics(metrics map[string]interface{}, patterns []string) []string {
  113. res := []string{}
  114. for _, pattern := range patterns {
  115. res = append(res, resolveMetric(metrics, pattern, "")...)
  116. }
  117. return res
  118. }
  119. // resolveMetrics takes a single of input metric pattern, and resolves it to one
  120. // or more canonical metric names.
  121. func resolveMetric(metrics map[string]interface{}, pattern string, path string) []string {
  122. var ok bool
  123. // Build up the canonical metric path
  124. parts := strings.Split(pattern, "/")
  125. for len(parts) > 1 {
  126. if metrics, ok = metrics[parts[0]].(map[string]interface{}); !ok {
  127. utils.Fatalf("Failed to retrieve system metrics: %s", path+parts[0])
  128. }
  129. path += parts[0] + "/"
  130. parts = parts[1:]
  131. }
  132. // Depending what the last link is, return or expand
  133. switch metric := metrics[parts[0]].(type) {
  134. case float64:
  135. // Final metric value found, return as singleton
  136. return []string{path + parts[0]}
  137. case map[string]interface{}:
  138. return expandMetrics(metric, path+parts[0]+"/")
  139. default:
  140. utils.Fatalf("Metric pattern resolved to unexpected type: %v", reflect.TypeOf(metric))
  141. return nil
  142. }
  143. }
  144. // expandMetrics expands the entire tree of metrics into a flat list of paths.
  145. func expandMetrics(metrics map[string]interface{}, path string) []string {
  146. // Iterate over all fields and expand individually
  147. list := []string{}
  148. for name, metric := range metrics {
  149. switch metric := metric.(type) {
  150. case float64:
  151. // Final metric value found, append to list
  152. list = append(list, path+name)
  153. case map[string]interface{}:
  154. // Tree of metrics found, expand recursively
  155. list = append(list, expandMetrics(metric, path+name+"/")...)
  156. default:
  157. utils.Fatalf("Metric pattern %s resolved to unexpected type: %v", path+name, reflect.TypeOf(metric))
  158. return nil
  159. }
  160. }
  161. return list
  162. }