github.com/tyler-smith/go-ethereum@v1.9.7/cmd/puppeth/wizard_netstats.go (about) 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 17 package main 18 19 import ( 20 "encoding/json" 21 "os" 22 "sort" 23 "strings" 24 "sync" 25 26 "github.com/ethereum/go-ethereum/core" 27 "github.com/ethereum/go-ethereum/log" 28 "github.com/olekukonko/tablewriter" 29 ) 30 31 // networkStats verifies the status of network components and generates a protip 32 // configuration set to give users hints on how to do various tasks. 33 func (w *wizard) networkStats() { 34 if len(w.servers) == 0 { 35 log.Info("No remote machines to gather stats from") 36 return 37 } 38 // Clear out some previous configs to refill from current scan 39 w.conf.ethstats = "" 40 w.conf.bootnodes = w.conf.bootnodes[:0] 41 42 // Iterate over all the specified hosts and check their status 43 var pend sync.WaitGroup 44 45 stats := make(serverStats) 46 for server, pubkey := range w.conf.Servers { 47 pend.Add(1) 48 49 // Gather the service stats for each server concurrently 50 go func(server string, pubkey []byte) { 51 defer pend.Done() 52 53 stat := w.gatherStats(server, pubkey, w.servers[server]) 54 55 // All status checks complete, report and check next server 56 w.lock.Lock() 57 defer w.lock.Unlock() 58 59 delete(w.services, server) 60 for service := range stat.services { 61 w.services[server] = append(w.services[server], service) 62 } 63 stats[server] = stat 64 }(server, pubkey) 65 } 66 pend.Wait() 67 68 // Print any collected stats and return 69 stats.render() 70 } 71 72 // gatherStats gathers service statistics for a particular remote server. 73 func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *serverStat { 74 // Gather some global stats to feed into the wizard 75 var ( 76 genesis string 77 ethstats string 78 bootnodes []string 79 ) 80 // Ensure a valid SSH connection to the remote server 81 logger := log.New("server", server) 82 logger.Info("Starting remote server health-check") 83 84 stat := &serverStat{ 85 services: make(map[string]map[string]string), 86 } 87 if client == nil { 88 conn, err := dial(server, pubkey) 89 if err != nil { 90 logger.Error("Failed to establish remote connection", "err", err) 91 stat.failure = err.Error() 92 return stat 93 } 94 client = conn 95 } 96 stat.address = client.address 97 98 // Client connected one way or another, run health-checks 99 logger.Debug("Checking for nginx availability") 100 if infos, err := checkNginx(client, w.network); err != nil { 101 if err != ErrServiceUnknown { 102 stat.services["nginx"] = map[string]string{"offline": err.Error()} 103 } 104 } else { 105 stat.services["nginx"] = infos.Report() 106 } 107 logger.Debug("Checking for ethstats availability") 108 if infos, err := checkEthstats(client, w.network); err != nil { 109 if err != ErrServiceUnknown { 110 stat.services["ethstats"] = map[string]string{"offline": err.Error()} 111 } 112 } else { 113 stat.services["ethstats"] = infos.Report() 114 ethstats = infos.config 115 } 116 logger.Debug("Checking for bootnode availability") 117 if infos, err := checkNode(client, w.network, true); err != nil { 118 if err != ErrServiceUnknown { 119 stat.services["bootnode"] = map[string]string{"offline": err.Error()} 120 } 121 } else { 122 stat.services["bootnode"] = infos.Report() 123 124 genesis = string(infos.genesis) 125 bootnodes = append(bootnodes, infos.enode) 126 } 127 logger.Debug("Checking for sealnode availability") 128 if infos, err := checkNode(client, w.network, false); err != nil { 129 if err != ErrServiceUnknown { 130 stat.services["sealnode"] = map[string]string{"offline": err.Error()} 131 } 132 } else { 133 stat.services["sealnode"] = infos.Report() 134 genesis = string(infos.genesis) 135 } 136 logger.Debug("Checking for explorer availability") 137 if infos, err := checkExplorer(client, w.network); err != nil { 138 if err != ErrServiceUnknown { 139 stat.services["explorer"] = map[string]string{"offline": err.Error()} 140 } 141 } else { 142 stat.services["explorer"] = infos.Report() 143 } 144 logger.Debug("Checking for wallet availability") 145 if infos, err := checkWallet(client, w.network); err != nil { 146 if err != ErrServiceUnknown { 147 stat.services["wallet"] = map[string]string{"offline": err.Error()} 148 } 149 } else { 150 stat.services["wallet"] = infos.Report() 151 } 152 logger.Debug("Checking for faucet availability") 153 if infos, err := checkFaucet(client, w.network); err != nil { 154 if err != ErrServiceUnknown { 155 stat.services["faucet"] = map[string]string{"offline": err.Error()} 156 } 157 } else { 158 stat.services["faucet"] = infos.Report() 159 } 160 logger.Debug("Checking for dashboard availability") 161 if infos, err := checkDashboard(client, w.network); err != nil { 162 if err != ErrServiceUnknown { 163 stat.services["dashboard"] = map[string]string{"offline": err.Error()} 164 } 165 } else { 166 stat.services["dashboard"] = infos.Report() 167 } 168 // Feed and newly discovered information into the wizard 169 w.lock.Lock() 170 defer w.lock.Unlock() 171 172 if genesis != "" && w.conf.Genesis == nil { 173 g := new(core.Genesis) 174 if err := json.Unmarshal([]byte(genesis), g); err != nil { 175 log.Error("Failed to parse remote genesis", "err", err) 176 } else { 177 w.conf.Genesis = g 178 } 179 } 180 if ethstats != "" { 181 w.conf.ethstats = ethstats 182 } 183 w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...) 184 185 return stat 186 } 187 188 // serverStat is a collection of service configuration parameters and health 189 // check reports to print to the user. 190 type serverStat struct { 191 address string 192 failure string 193 services map[string]map[string]string 194 } 195 196 // serverStats is a collection of server stats for multiple hosts. 197 type serverStats map[string]*serverStat 198 199 // render converts the gathered statistics into a user friendly tabular report 200 // and prints it to the standard output. 201 func (stats serverStats) render() { 202 // Start gathering service statistics and config parameters 203 table := tablewriter.NewWriter(os.Stdout) 204 205 table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"}) 206 table.SetAlignment(tablewriter.ALIGN_LEFT) 207 table.SetColWidth(40) 208 209 // Find the longest lines for all columns for the hacked separator 210 separator := make([]string, 5) 211 for server, stat := range stats { 212 if len(server) > len(separator[0]) { 213 separator[0] = strings.Repeat("-", len(server)) 214 } 215 if len(stat.address) > len(separator[1]) { 216 separator[1] = strings.Repeat("-", len(stat.address)) 217 } 218 if len(stat.failure) > len(separator[1]) { 219 separator[1] = strings.Repeat("-", len(stat.failure)) 220 } 221 for service, configs := range stat.services { 222 if len(service) > len(separator[2]) { 223 separator[2] = strings.Repeat("-", len(service)) 224 } 225 for config, value := range configs { 226 if len(config) > len(separator[3]) { 227 separator[3] = strings.Repeat("-", len(config)) 228 } 229 for _, val := range strings.Split(value, "\n") { 230 if len(val) > len(separator[4]) { 231 separator[4] = strings.Repeat("-", len(val)) 232 } 233 } 234 } 235 } 236 } 237 // Fill up the server report in alphabetical order 238 servers := make([]string, 0, len(stats)) 239 for server := range stats { 240 servers = append(servers, server) 241 } 242 sort.Strings(servers) 243 244 for i, server := range servers { 245 // Add a separator between all servers 246 if i > 0 { 247 table.Append(separator) 248 } 249 // Fill up the service report in alphabetical order 250 services := make([]string, 0, len(stats[server].services)) 251 for service := range stats[server].services { 252 services = append(services, service) 253 } 254 sort.Strings(services) 255 256 if len(services) == 0 { 257 if stats[server].failure != "" { 258 table.Append([]string{server, stats[server].failure, "", "", ""}) 259 } else { 260 table.Append([]string{server, stats[server].address, "", "", ""}) 261 } 262 } 263 for j, service := range services { 264 // Add an empty line between all services 265 if j > 0 { 266 table.Append([]string{"", "", "", separator[3], separator[4]}) 267 } 268 // Fill up the config report in alphabetical order 269 configs := make([]string, 0, len(stats[server].services[service])) 270 for service := range stats[server].services[service] { 271 configs = append(configs, service) 272 } 273 sort.Strings(configs) 274 275 for k, config := range configs { 276 for l, value := range strings.Split(stats[server].services[service][config], "\n") { 277 switch { 278 case j == 0 && k == 0 && l == 0: 279 table.Append([]string{server, stats[server].address, service, config, value}) 280 case k == 0 && l == 0: 281 table.Append([]string{"", "", service, config, value}) 282 case l == 0: 283 table.Append([]string{"", "", "", config, value}) 284 default: 285 table.Append([]string{"", "", "", "", value}) 286 } 287 } 288 } 289 } 290 } 291 table.Render() 292 }