| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | // Copyright 2017 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of go-ethereum. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/core" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							|  |  |  | 	"github.com/olekukonko/tablewriter" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // networkStats verifies the status of network components and generates a protip | 
					
						
							|  |  |  | // configuration set to give users hints on how to do various tasks. | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | func (w *wizard) networkStats() { | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	if len(w.servers) == 0 { | 
					
						
							| 
									
										
										
										
											2017-10-26 12:39:03 +03:00
										 |  |  | 		log.Info("No remote machines to gather stats from") | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	// Clear out some previous configs to refill from current scan | 
					
						
							|  |  |  | 	w.conf.ethstats = "" | 
					
						
							|  |  |  | 	w.conf.bootFull = w.conf.bootFull[:0] | 
					
						
							|  |  |  | 	w.conf.bootLight = w.conf.bootLight[:0] | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Iterate over all the specified hosts and check their status | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	var pend sync.WaitGroup | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	stats := make(serverStats) | 
					
						
							| 
									
										
										
										
											2017-05-03 10:09:34 +03:00
										 |  |  | 	for server, pubkey := range w.conf.Servers { | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 		pend.Add(1) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 		// Gather the service stats for each server concurrently | 
					
						
							|  |  |  | 		go func(server string, pubkey []byte) { | 
					
						
							|  |  |  | 			defer pend.Done() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			stat := w.gatherStats(server, pubkey, w.servers[server]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// All status checks complete, report and check next server | 
					
						
							|  |  |  | 			w.lock.Lock() | 
					
						
							|  |  |  | 			defer w.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			delete(w.services, server) | 
					
						
							|  |  |  | 			for service := range stat.services { | 
					
						
							|  |  |  | 				w.services[server] = append(w.services[server], service) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 			stats[server] = stat | 
					
						
							|  |  |  | 		}(server, pubkey) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	pend.Wait() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Print any collected stats and return | 
					
						
							|  |  |  | 	stats.render() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // gatherStats gathers service statistics for a particular remote server. | 
					
						
							|  |  |  | func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *serverStat { | 
					
						
							|  |  |  | 	// Gather some global stats to feed into the wizard | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		genesis   string | 
					
						
							|  |  |  | 		ethstats  string | 
					
						
							|  |  |  | 		bootFull  []string | 
					
						
							|  |  |  | 		bootLight []string | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	// Ensure a valid SSH connection to the remote server | 
					
						
							|  |  |  | 	logger := log.New("server", server) | 
					
						
							|  |  |  | 	logger.Info("Starting remote server health-check") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	stat := &serverStat{ | 
					
						
							|  |  |  | 		address:  client.address, | 
					
						
							|  |  |  | 		services: make(map[string]map[string]string), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if client == nil { | 
					
						
							|  |  |  | 		conn, err := dial(server, pubkey) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			logger.Error("Failed to establish remote connection", "err", err) | 
					
						
							|  |  |  | 			stat.failure = err.Error() | 
					
						
							|  |  |  | 			return stat | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 		client = conn | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	// Client connected one way or another, run health-checks | 
					
						
							|  |  |  | 	logger.Debug("Checking for nginx availability") | 
					
						
							|  |  |  | 	if infos, err := checkNginx(client, w.network); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["nginx"] = map[string]string{"offline": err.Error()} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["nginx"] = infos.Report() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	logger.Debug("Checking for ethstats availability") | 
					
						
							|  |  |  | 	if infos, err := checkEthstats(client, w.network); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["ethstats"] = map[string]string{"offline": err.Error()} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["ethstats"] = infos.Report() | 
					
						
							|  |  |  | 		ethstats = infos.config | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	logger.Debug("Checking for bootnode availability") | 
					
						
							|  |  |  | 	if infos, err := checkNode(client, w.network, true); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["bootnode"] = map[string]string{"offline": err.Error()} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["bootnode"] = infos.Report() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		genesis = string(infos.genesis) | 
					
						
							|  |  |  | 		bootFull = append(bootFull, infos.enodeFull) | 
					
						
							|  |  |  | 		if infos.enodeLight != "" { | 
					
						
							|  |  |  | 			bootLight = append(bootLight, infos.enodeLight) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	logger.Debug("Checking for sealnode availability") | 
					
						
							|  |  |  | 	if infos, err := checkNode(client, w.network, false); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["sealnode"] = map[string]string{"offline": err.Error()} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["sealnode"] = infos.Report() | 
					
						
							|  |  |  | 		genesis = string(infos.genesis) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-20 11:14:10 +03:00
										 |  |  | 	logger.Debug("Checking for explorer availability") | 
					
						
							|  |  |  | 	if infos, err := checkExplorer(client, w.network); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["explorer"] = map[string]string{"offline": err.Error()} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["explorer"] = infos.Report() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-23 09:58:33 +03:00
										 |  |  | 	logger.Debug("Checking for wallet availability") | 
					
						
							|  |  |  | 	if infos, err := checkWallet(client, w.network); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["wallet"] = map[string]string{"offline": err.Error()} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["wallet"] = infos.Report() | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	logger.Debug("Checking for faucet availability") | 
					
						
							|  |  |  | 	if infos, err := checkFaucet(client, w.network); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["faucet"] = map[string]string{"offline": err.Error()} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["faucet"] = infos.Report() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	logger.Debug("Checking for dashboard availability") | 
					
						
							|  |  |  | 	if infos, err := checkDashboard(client, w.network); err != nil { | 
					
						
							|  |  |  | 		if err != ErrServiceUnknown { | 
					
						
							|  |  |  | 			stat.services["dashboard"] = map[string]string{"offline": err.Error()} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	} else { | 
					
						
							|  |  |  | 		stat.services["dashboard"] = infos.Report() | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	// Feed and newly discovered information into the wizard | 
					
						
							|  |  |  | 	w.lock.Lock() | 
					
						
							|  |  |  | 	defer w.lock.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-26 12:39:03 +03:00
										 |  |  | 	if genesis != "" && w.conf.Genesis == nil { | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 		g := new(core.Genesis) | 
					
						
							|  |  |  | 		if err := json.Unmarshal([]byte(genesis), g); err != nil { | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 			log.Error("Failed to parse remote genesis", "err", err) | 
					
						
							|  |  |  | 		} else { | 
					
						
							| 
									
										
										
										
											2017-10-26 12:39:03 +03:00
										 |  |  | 			w.conf.Genesis = g | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	if ethstats != "" { | 
					
						
							|  |  |  | 		w.conf.ethstats = ethstats | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	w.conf.bootFull = append(w.conf.bootFull, bootFull...) | 
					
						
							|  |  |  | 	w.conf.bootLight = append(w.conf.bootLight, bootLight...) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	return stat | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | // serverStat is a collection of service configuration parameters and health | 
					
						
							|  |  |  | // check reports to print to the user. | 
					
						
							|  |  |  | type serverStat struct { | 
					
						
							|  |  |  | 	address  string | 
					
						
							|  |  |  | 	failure  string | 
					
						
							|  |  |  | 	services map[string]map[string]string | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | // serverStats is a collection of server stats for multiple hosts. | 
					
						
							|  |  |  | type serverStats map[string]*serverStat | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | // render converts the gathered statistics into a user friendly tabular report | 
					
						
							|  |  |  | // and prints it to the standard output. | 
					
						
							|  |  |  | func (stats serverStats) render() { | 
					
						
							|  |  |  | 	// Start gathering service statistics and config parameters | 
					
						
							|  |  |  | 	table := tablewriter.NewWriter(os.Stdout) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"}) | 
					
						
							|  |  |  | 	table.SetAlignment(tablewriter.ALIGN_LEFT) | 
					
						
							|  |  |  | 	table.SetColWidth(100) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	// Find the longest lines for all columns for the hacked separator | 
					
						
							|  |  |  | 	separator := make([]string, 5) | 
					
						
							|  |  |  | 	for server, stat := range stats { | 
					
						
							|  |  |  | 		if len(server) > len(separator[0]) { | 
					
						
							|  |  |  | 			separator[0] = strings.Repeat("-", len(server)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(stat.address) > len(separator[1]) { | 
					
						
							|  |  |  | 			separator[1] = strings.Repeat("-", len(stat.address)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for service, configs := range stat.services { | 
					
						
							|  |  |  | 			if len(service) > len(separator[2]) { | 
					
						
							|  |  |  | 				separator[2] = strings.Repeat("-", len(service)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			for config, value := range configs { | 
					
						
							|  |  |  | 				if len(config) > len(separator[3]) { | 
					
						
							|  |  |  | 					separator[3] = strings.Repeat("-", len(config)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if len(value) > len(separator[4]) { | 
					
						
							|  |  |  | 					separator[4] = strings.Repeat("-", len(value)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	// Fill up the server report in alphabetical order | 
					
						
							|  |  |  | 	servers := make([]string, 0, len(stats)) | 
					
						
							|  |  |  | 	for server := range stats { | 
					
						
							|  |  |  | 		servers = append(servers, server) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sort.Strings(servers) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	for i, server := range servers { | 
					
						
							|  |  |  | 		// Add a separator between all servers | 
					
						
							|  |  |  | 		if i > 0 { | 
					
						
							|  |  |  | 			table.Append(separator) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Fill up the service report in alphabetical order | 
					
						
							|  |  |  | 		services := make([]string, 0, len(stats[server].services)) | 
					
						
							|  |  |  | 		for service := range stats[server].services { | 
					
						
							|  |  |  | 			services = append(services, service) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		sort.Strings(services) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 		if len(services) == 0 { | 
					
						
							|  |  |  | 			table.Append([]string{server, stats[server].address, "", "", ""}) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 		for j, service := range services { | 
					
						
							|  |  |  | 			// Add an empty line between all services | 
					
						
							|  |  |  | 			if j > 0 { | 
					
						
							|  |  |  | 				table.Append([]string{"", "", "", separator[3], separator[4]}) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Fill up the config report in alphabetical order | 
					
						
							|  |  |  | 			configs := make([]string, 0, len(stats[server].services[service])) | 
					
						
							|  |  |  | 			for service := range stats[server].services[service] { | 
					
						
							|  |  |  | 				configs = append(configs, service) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			sort.Strings(configs) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for k, config := range configs { | 
					
						
							|  |  |  | 				switch { | 
					
						
							|  |  |  | 				case j == 0 && k == 0: | 
					
						
							|  |  |  | 					table.Append([]string{server, stats[server].address, service, config, stats[server].services[service][config]}) | 
					
						
							|  |  |  | 				case k == 0: | 
					
						
							|  |  |  | 					table.Append([]string{"", "", service, config, stats[server].services[service][config]}) | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					table.Append([]string{"", "", "", config, stats[server].services[service][config]}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-10-19 13:59:02 +03:00
										 |  |  | 	table.Render() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // protips contains a collection of network infos to report pro-tips | 
					
						
							|  |  |  | // based on. | 
					
						
							|  |  |  | type protips struct { | 
					
						
							|  |  |  | 	genesis   string | 
					
						
							|  |  |  | 	network   int64 | 
					
						
							|  |  |  | 	bootFull  []string | 
					
						
							|  |  |  | 	bootLight []string | 
					
						
							|  |  |  | 	ethstats  string | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } |