github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/cmd/puppeth/wizard_netstats.go (about)

     1  // Copyright 2017 The Spectrum Authors
     2  // This file is part of Spectrum.
     3  //
     4  // Spectrum 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  // Spectrum 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 Spectrum. 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/SmartMeshFoundation/Spectrum/core"
    27  	"github.com/SmartMeshFoundation/Spectrum/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.bootFull = w.conf.bootFull[:0]
    41  	w.conf.bootLight = w.conf.bootLight[: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  		bootFull  []string
    80  		bootLight []string
    81  	)
    82  	// Ensure a valid SSH connection to the remote server
    83  	logger := log.New("server", server)
    84  	logger.Info("Starting remote server health-check")
    85  
    86  	stat := &serverStat{
    87  		address:  client.address,
    88  		services: make(map[string]map[string]string),
    89  	}
    90  	if client == nil {
    91  		conn, err := dial(server, pubkey)
    92  		if err != nil {
    93  			logger.Error("Failed to establish remote connection", "err", err)
    94  			stat.failure = err.Error()
    95  			return stat
    96  		}
    97  		client = conn
    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  		bootFull = append(bootFull, infos.enodeFull)
   127  		if infos.enodeLight != "" {
   128  			bootLight = append(bootLight, infos.enodeLight)
   129  		}
   130  	}
   131  	logger.Debug("Checking for sealnode availability")
   132  	if infos, err := checkNode(client, w.network, false); err != nil {
   133  		if err != ErrServiceUnknown {
   134  			stat.services["sealnode"] = map[string]string{"offline": err.Error()}
   135  		}
   136  	} else {
   137  		stat.services["sealnode"] = infos.Report()
   138  		genesis = string(infos.genesis)
   139  	}
   140  	logger.Debug("Checking for explorer availability")
   141  	if infos, err := checkExplorer(client, w.network); err != nil {
   142  		if err != ErrServiceUnknown {
   143  			stat.services["explorer"] = map[string]string{"offline": err.Error()}
   144  		}
   145  	} else {
   146  		stat.services["explorer"] = infos.Report()
   147  	}
   148  	logger.Debug("Checking for wallet availability")
   149  	if infos, err := checkWallet(client, w.network); err != nil {
   150  		if err != ErrServiceUnknown {
   151  			stat.services["wallet"] = map[string]string{"offline": err.Error()}
   152  		}
   153  	} else {
   154  		stat.services["wallet"] = infos.Report()
   155  	}
   156  	logger.Debug("Checking for faucet availability")
   157  	if infos, err := checkFaucet(client, w.network); err != nil {
   158  		if err != ErrServiceUnknown {
   159  			stat.services["faucet"] = map[string]string{"offline": err.Error()}
   160  		}
   161  	} else {
   162  		stat.services["faucet"] = infos.Report()
   163  	}
   164  	logger.Debug("Checking for dashboard availability")
   165  	if infos, err := checkDashboard(client, w.network); err != nil {
   166  		if err != ErrServiceUnknown {
   167  			stat.services["dashboard"] = map[string]string{"offline": err.Error()}
   168  		}
   169  	} else {
   170  		stat.services["dashboard"] = infos.Report()
   171  	}
   172  	// Feed and newly discovered information into the wizard
   173  	w.lock.Lock()
   174  	defer w.lock.Unlock()
   175  
   176  	if genesis != "" && w.conf.Genesis == nil {
   177  		g := new(core.Genesis)
   178  		if err := json.Unmarshal([]byte(genesis), g); err != nil {
   179  			log.Error("Failed to parse remote genesis", "err", err)
   180  		} else {
   181  			w.conf.Genesis = g
   182  		}
   183  	}
   184  	if ethstats != "" {
   185  		w.conf.ethstats = ethstats
   186  	}
   187  	w.conf.bootFull = append(w.conf.bootFull, bootFull...)
   188  	w.conf.bootLight = append(w.conf.bootLight, bootLight...)
   189  
   190  	return stat
   191  }
   192  
   193  // serverStat is a collection of service configuration parameters and health
   194  // check reports to print to the user.
   195  type serverStat struct {
   196  	address  string
   197  	failure  string
   198  	services map[string]map[string]string
   199  }
   200  
   201  // serverStats is a collection of server stats for multiple hosts.
   202  type serverStats map[string]*serverStat
   203  
   204  // render converts the gathered statistics into a user friendly tabular report
   205  // and prints it to the standard output.
   206  func (stats serverStats) render() {
   207  	// Start gathering service statistics and config parameters
   208  	table := tablewriter.NewWriter(os.Stdout)
   209  
   210  	table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
   211  	table.SetAlignment(tablewriter.ALIGN_LEFT)
   212  	table.SetColWidth(100)
   213  
   214  	// Find the longest lines for all columns for the hacked separator
   215  	separator := make([]string, 5)
   216  	for server, stat := range stats {
   217  		if len(server) > len(separator[0]) {
   218  			separator[0] = strings.Repeat("-", len(server))
   219  		}
   220  		if len(stat.address) > len(separator[1]) {
   221  			separator[1] = strings.Repeat("-", len(stat.address))
   222  		}
   223  		for service, configs := range stat.services {
   224  			if len(service) > len(separator[2]) {
   225  				separator[2] = strings.Repeat("-", len(service))
   226  			}
   227  			for config, value := range configs {
   228  				if len(config) > len(separator[3]) {
   229  					separator[3] = strings.Repeat("-", len(config))
   230  				}
   231  				if len(value) > len(separator[4]) {
   232  					separator[4] = strings.Repeat("-", len(value))
   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  			table.Append([]string{server, stats[server].address, "", "", ""})
   258  		}
   259  		for j, service := range services {
   260  			// Add an empty line between all services
   261  			if j > 0 {
   262  				table.Append([]string{"", "", "", separator[3], separator[4]})
   263  			}
   264  			// Fill up the config report in alphabetical order
   265  			configs := make([]string, 0, len(stats[server].services[service]))
   266  			for service := range stats[server].services[service] {
   267  				configs = append(configs, service)
   268  			}
   269  			sort.Strings(configs)
   270  
   271  			for k, config := range configs {
   272  				switch {
   273  				case j == 0 && k == 0:
   274  					table.Append([]string{server, stats[server].address, service, config, stats[server].services[service][config]})
   275  				case k == 0:
   276  					table.Append([]string{"", "", service, config, stats[server].services[service][config]})
   277  				default:
   278  					table.Append([]string{"", "", "", config, stats[server].services[service][config]})
   279  				}
   280  			}
   281  		}
   282  	}
   283  	table.Render()
   284  }
   285  
   286  // protips contains a collection of network infos to report pro-tips
   287  // based on.
   288  type protips struct {
   289  	genesis   string
   290  	network   int64
   291  	bootFull  []string
   292  	bootLight []string
   293  	ethstats  string
   294  }