wizard_netstats.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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. address: client.address,
  73. services: make(map[string]map[string]string),
  74. }
  75. if client == nil {
  76. conn, err := dial(server, pubkey)
  77. if err != nil {
  78. logger.Error("Failed to establish remote connection", "err", err)
  79. stat.failure = err.Error()
  80. return stat
  81. }
  82. client = conn
  83. }
  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(100)
  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. for service, configs := range stat.services {
  197. if len(service) > len(separator[2]) {
  198. separator[2] = strings.Repeat("-", len(service))
  199. }
  200. for config, value := range configs {
  201. if len(config) > len(separator[3]) {
  202. separator[3] = strings.Repeat("-", len(config))
  203. }
  204. if len(value) > len(separator[4]) {
  205. separator[4] = strings.Repeat("-", len(value))
  206. }
  207. }
  208. }
  209. }
  210. // Fill up the server report in alphabetical order
  211. servers := make([]string, 0, len(stats))
  212. for server := range stats {
  213. servers = append(servers, server)
  214. }
  215. sort.Strings(servers)
  216. for i, server := range servers {
  217. // Add a separator between all servers
  218. if i > 0 {
  219. table.Append(separator)
  220. }
  221. // Fill up the service report in alphabetical order
  222. services := make([]string, 0, len(stats[server].services))
  223. for service := range stats[server].services {
  224. services = append(services, service)
  225. }
  226. sort.Strings(services)
  227. if len(services) == 0 {
  228. table.Append([]string{server, stats[server].address, "", "", ""})
  229. }
  230. for j, service := range services {
  231. // Add an empty line between all services
  232. if j > 0 {
  233. table.Append([]string{"", "", "", separator[3], separator[4]})
  234. }
  235. // Fill up the config report in alphabetical order
  236. configs := make([]string, 0, len(stats[server].services[service]))
  237. for service := range stats[server].services[service] {
  238. configs = append(configs, service)
  239. }
  240. sort.Strings(configs)
  241. for k, config := range configs {
  242. switch {
  243. case j == 0 && k == 0:
  244. table.Append([]string{server, stats[server].address, service, config, stats[server].services[service][config]})
  245. case k == 0:
  246. table.Append([]string{"", "", service, config, stats[server].services[service][config]})
  247. default:
  248. table.Append([]string{"", "", "", config, stats[server].services[service][config]})
  249. }
  250. }
  251. }
  252. }
  253. table.Render()
  254. }
  255. // protips contains a collection of network infos to report pro-tips
  256. // based on.
  257. type protips struct {
  258. genesis string
  259. network int64
  260. bootFull []string
  261. bootLight []string
  262. ethstats string
  263. }