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