wizard_netstats.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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 wallet availability")
  130. if infos, err := checkWallet(client, w.network); err != nil {
  131. if err != ErrServiceUnknown {
  132. stat.services["wallet"] = map[string]string{"offline": err.Error()}
  133. }
  134. } else {
  135. stat.services["wallet"] = infos.Report()
  136. }
  137. logger.Debug("Checking for faucet availability")
  138. if infos, err := checkFaucet(client, w.network); err != nil {
  139. if err != ErrServiceUnknown {
  140. stat.services["faucet"] = map[string]string{"offline": err.Error()}
  141. }
  142. } else {
  143. stat.services["faucet"] = infos.Report()
  144. }
  145. logger.Debug("Checking for dashboard availability")
  146. if infos, err := checkDashboard(client, w.network); err != nil {
  147. if err != ErrServiceUnknown {
  148. stat.services["dashboard"] = map[string]string{"offline": err.Error()}
  149. }
  150. } else {
  151. stat.services["dashboard"] = infos.Report()
  152. }
  153. // Feed and newly discovered information into the wizard
  154. w.lock.Lock()
  155. defer w.lock.Unlock()
  156. if genesis != "" && w.conf.Genesis == nil {
  157. g := new(core.Genesis)
  158. if err := json.Unmarshal([]byte(genesis), g); err != nil {
  159. log.Error("Failed to parse remote genesis", "err", err)
  160. } else {
  161. w.conf.Genesis = g
  162. }
  163. }
  164. if ethstats != "" {
  165. w.conf.ethstats = ethstats
  166. }
  167. w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...)
  168. return stat
  169. }
  170. // serverStat is a collection of service configuration parameters and health
  171. // check reports to print to the user.
  172. type serverStat struct {
  173. address string
  174. failure string
  175. services map[string]map[string]string
  176. }
  177. // serverStats is a collection of server stats for multiple hosts.
  178. type serverStats map[string]*serverStat
  179. // render converts the gathered statistics into a user friendly tabular report
  180. // and prints it to the standard output.
  181. func (stats serverStats) render() {
  182. // Start gathering service statistics and config parameters
  183. table := tablewriter.NewWriter(os.Stdout)
  184. table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
  185. table.SetAlignment(tablewriter.ALIGN_LEFT)
  186. table.SetColWidth(40)
  187. // Find the longest lines for all columns for the hacked separator
  188. separator := make([]string, 5)
  189. for server, stat := range stats {
  190. if len(server) > len(separator[0]) {
  191. separator[0] = strings.Repeat("-", len(server))
  192. }
  193. if len(stat.address) > len(separator[1]) {
  194. separator[1] = strings.Repeat("-", len(stat.address))
  195. }
  196. if len(stat.failure) > len(separator[1]) {
  197. separator[1] = strings.Repeat("-", len(stat.failure))
  198. }
  199. for service, configs := range stat.services {
  200. if len(service) > len(separator[2]) {
  201. separator[2] = strings.Repeat("-", len(service))
  202. }
  203. for config, value := range configs {
  204. if len(config) > len(separator[3]) {
  205. separator[3] = strings.Repeat("-", len(config))
  206. }
  207. for _, val := range strings.Split(value, "\n") {
  208. if len(val) > len(separator[4]) {
  209. separator[4] = strings.Repeat("-", len(val))
  210. }
  211. }
  212. }
  213. }
  214. }
  215. // Fill up the server report in alphabetical order
  216. servers := make([]string, 0, len(stats))
  217. for server := range stats {
  218. servers = append(servers, server)
  219. }
  220. sort.Strings(servers)
  221. for i, server := range servers {
  222. // Add a separator between all servers
  223. if i > 0 {
  224. table.Append(separator)
  225. }
  226. // Fill up the service report in alphabetical order
  227. services := make([]string, 0, len(stats[server].services))
  228. for service := range stats[server].services {
  229. services = append(services, service)
  230. }
  231. sort.Strings(services)
  232. if len(services) == 0 {
  233. if stats[server].failure != "" {
  234. table.Append([]string{server, stats[server].failure, "", "", ""})
  235. } else {
  236. table.Append([]string{server, stats[server].address, "", "", ""})
  237. }
  238. }
  239. for j, service := range services {
  240. // Add an empty line between all services
  241. if j > 0 {
  242. table.Append([]string{"", "", "", separator[3], separator[4]})
  243. }
  244. // Fill up the config report in alphabetical order
  245. configs := make([]string, 0, len(stats[server].services[service]))
  246. for service := range stats[server].services[service] {
  247. configs = append(configs, service)
  248. }
  249. sort.Strings(configs)
  250. for k, config := range configs {
  251. for l, value := range strings.Split(stats[server].services[service][config], "\n") {
  252. switch {
  253. case j == 0 && k == 0 && l == 0:
  254. table.Append([]string{server, stats[server].address, service, config, value})
  255. case k == 0 && l == 0:
  256. table.Append([]string{"", "", service, config, value})
  257. case l == 0:
  258. table.Append([]string{"", "", "", config, value})
  259. default:
  260. table.Append([]string{"", "", "", "", value})
  261. }
  262. }
  263. }
  264. }
  265. }
  266. table.Render()
  267. }