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