wizard_netstats.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // Copyright 2017 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // go-ethereum is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "encoding/json"
  19. "os"
  20. "sort"
  21. "strings"
  22. "sync"
  23. "github.com/ethereum/go-ethereum/core"
  24. "github.com/ethereum/go-ethereum/log"
  25. "github.com/olekukonko/tablewriter"
  26. )
  27. // networkStats verifies the status of network components and generates a protip
  28. // configuration set to give users hints on how to do various tasks.
  29. func (w *wizard) networkStats() {
  30. if len(w.servers) == 0 {
  31. log.Info("No remote machines to gather stats from")
  32. return
  33. }
  34. // Clear out some previous configs to refill from current scan
  35. w.conf.ethstats = ""
  36. w.conf.bootnodes = w.conf.bootnodes[:0]
  37. // Iterate over all the specified hosts and check their status
  38. var pend sync.WaitGroup
  39. stats := make(serverStats)
  40. for server, pubkey := range w.conf.Servers {
  41. pend.Add(1)
  42. // Gather the service stats for each server concurrently
  43. go func(server string, pubkey []byte) {
  44. defer pend.Done()
  45. stat := w.gatherStats(server, pubkey, w.servers[server])
  46. // All status checks complete, report and check next server
  47. w.lock.Lock()
  48. defer w.lock.Unlock()
  49. delete(w.services, server)
  50. for service := range stat.services {
  51. w.services[server] = append(w.services[server], service)
  52. }
  53. stats[server] = stat
  54. }(server, pubkey)
  55. }
  56. pend.Wait()
  57. // Print any collected stats and return
  58. stats.render()
  59. }
  60. // gatherStats gathers service statistics for a particular remote server.
  61. func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *serverStat {
  62. // Gather some global stats to feed into the wizard
  63. var (
  64. genesis string
  65. ethstats string
  66. bootnodes []string
  67. )
  68. // Ensure a valid SSH connection to the remote server
  69. logger := log.New("server", server)
  70. logger.Info("Starting remote server health-check")
  71. stat := &serverStat{
  72. services: make(map[string]map[string]string),
  73. }
  74. if client == nil {
  75. conn, err := dial(server, pubkey)
  76. if err != nil {
  77. logger.Error("Failed to establish remote connection", "err", err)
  78. stat.failure = err.Error()
  79. return stat
  80. }
  81. client = conn
  82. }
  83. stat.address = client.address
  84. // Client connected one way or another, run health-checks
  85. logger.Debug("Checking for nginx availability")
  86. if infos, err := checkNginx(client, w.network); err != nil {
  87. if err != ErrServiceUnknown {
  88. stat.services["nginx"] = map[string]string{"offline": err.Error()}
  89. }
  90. } else {
  91. stat.services["nginx"] = infos.Report()
  92. }
  93. logger.Debug("Checking for ethstats availability")
  94. if infos, err := checkEthstats(client, w.network); err != nil {
  95. if err != ErrServiceUnknown {
  96. stat.services["ethstats"] = map[string]string{"offline": err.Error()}
  97. }
  98. } else {
  99. stat.services["ethstats"] = infos.Report()
  100. ethstats = infos.config
  101. }
  102. logger.Debug("Checking for bootnode availability")
  103. if infos, err := checkNode(client, w.network, true); err != nil {
  104. if err != ErrServiceUnknown {
  105. stat.services["bootnode"] = map[string]string{"offline": err.Error()}
  106. }
  107. } else {
  108. stat.services["bootnode"] = infos.Report()
  109. genesis = string(infos.genesis)
  110. bootnodes = append(bootnodes, infos.enode)
  111. }
  112. logger.Debug("Checking for sealnode availability")
  113. if infos, err := checkNode(client, w.network, false); err != nil {
  114. if err != ErrServiceUnknown {
  115. stat.services["sealnode"] = map[string]string{"offline": err.Error()}
  116. }
  117. } else {
  118. stat.services["sealnode"] = infos.Report()
  119. genesis = string(infos.genesis)
  120. }
  121. logger.Debug("Checking for explorer availability")
  122. if infos, err := checkExplorer(client, w.network); err != nil {
  123. if err != ErrServiceUnknown {
  124. stat.services["explorer"] = map[string]string{"offline": err.Error()}
  125. }
  126. } else {
  127. stat.services["explorer"] = infos.Report()
  128. }
  129. logger.Debug("Checking for faucet availability")
  130. if infos, err := checkFaucet(client, w.network); err != nil {
  131. if err != ErrServiceUnknown {
  132. stat.services["faucet"] = map[string]string{"offline": err.Error()}
  133. }
  134. } else {
  135. stat.services["faucet"] = infos.Report()
  136. }
  137. logger.Debug("Checking for dashboard availability")
  138. if infos, err := checkDashboard(client, w.network); err != nil {
  139. if err != ErrServiceUnknown {
  140. stat.services["dashboard"] = map[string]string{"offline": err.Error()}
  141. }
  142. } else {
  143. stat.services["dashboard"] = infos.Report()
  144. }
  145. // Feed and newly discovered information into the wizard
  146. w.lock.Lock()
  147. defer w.lock.Unlock()
  148. if genesis != "" && w.conf.Genesis == nil {
  149. g := new(core.Genesis)
  150. if err := json.Unmarshal([]byte(genesis), g); err != nil {
  151. log.Error("Failed to parse remote genesis", "err", err)
  152. } else {
  153. w.conf.Genesis = g
  154. }
  155. }
  156. if ethstats != "" {
  157. w.conf.ethstats = ethstats
  158. }
  159. w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...)
  160. return stat
  161. }
  162. // serverStat is a collection of service configuration parameters and health
  163. // check reports to print to the user.
  164. type serverStat struct {
  165. address string
  166. failure string
  167. services map[string]map[string]string
  168. }
  169. // serverStats is a collection of server stats for multiple hosts.
  170. type serverStats map[string]*serverStat
  171. // render converts the gathered statistics into a user friendly tabular report
  172. // and prints it to the standard output.
  173. func (stats serverStats) render() {
  174. // Start gathering service statistics and config parameters
  175. table := tablewriter.NewWriter(os.Stdout)
  176. table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
  177. table.SetAlignment(tablewriter.ALIGN_LEFT)
  178. table.SetColWidth(40)
  179. // Find the longest lines for all columns for the hacked separator
  180. separator := make([]string, 5)
  181. for server, stat := range stats {
  182. if len(server) > len(separator[0]) {
  183. separator[0] = strings.Repeat("-", len(server))
  184. }
  185. if len(stat.address) > len(separator[1]) {
  186. separator[1] = strings.Repeat("-", len(stat.address))
  187. }
  188. if len(stat.failure) > len(separator[1]) {
  189. separator[1] = strings.Repeat("-", len(stat.failure))
  190. }
  191. for service, configs := range stat.services {
  192. if len(service) > len(separator[2]) {
  193. separator[2] = strings.Repeat("-", len(service))
  194. }
  195. for config, value := range configs {
  196. if len(config) > len(separator[3]) {
  197. separator[3] = strings.Repeat("-", len(config))
  198. }
  199. for _, val := range strings.Split(value, "\n") {
  200. if len(val) > len(separator[4]) {
  201. separator[4] = strings.Repeat("-", len(val))
  202. }
  203. }
  204. }
  205. }
  206. }
  207. // Fill up the server report in alphabetical order
  208. servers := make([]string, 0, len(stats))
  209. for server := range stats {
  210. servers = append(servers, server)
  211. }
  212. sort.Strings(servers)
  213. for i, server := range servers {
  214. // Add a separator between all servers
  215. if i > 0 {
  216. table.Append(separator)
  217. }
  218. // Fill up the service report in alphabetical order
  219. services := make([]string, 0, len(stats[server].services))
  220. for service := range stats[server].services {
  221. services = append(services, service)
  222. }
  223. sort.Strings(services)
  224. if len(services) == 0 {
  225. if stats[server].failure != "" {
  226. table.Append([]string{server, stats[server].failure, "", "", ""})
  227. } else {
  228. table.Append([]string{server, stats[server].address, "", "", ""})
  229. }
  230. }
  231. for j, service := range services {
  232. // Add an empty line between all services
  233. if j > 0 {
  234. table.Append([]string{"", "", "", separator[3], separator[4]})
  235. }
  236. // Fill up the config report in alphabetical order
  237. configs := make([]string, 0, len(stats[server].services[service]))
  238. for service := range stats[server].services[service] {
  239. configs = append(configs, service)
  240. }
  241. sort.Strings(configs)
  242. for k, config := range configs {
  243. for l, value := range strings.Split(stats[server].services[service][config], "\n") {
  244. switch {
  245. case j == 0 && k == 0 && l == 0:
  246. table.Append([]string{server, stats[server].address, service, config, value})
  247. case k == 0 && l == 0:
  248. table.Append([]string{"", "", service, config, value})
  249. case l == 0:
  250. table.Append([]string{"", "", "", config, value})
  251. default:
  252. table.Append([]string{"", "", "", "", value})
  253. }
  254. }
  255. }
  256. }
  257. }
  258. table.Render()
  259. }