123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- // 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"
- "sort"
- "strings"
- "sync"
- "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.
- func (w *wizard) networkStats() {
- if len(w.servers) == 0 {
- log.Info("No remote machines to gather stats from")
- return
- }
- // Clear out some previous configs to refill from current scan
- w.conf.ethstats = ""
- w.conf.bootnodes = w.conf.bootnodes[:0]
- // Iterate over all the specified hosts and check their status
- var pend sync.WaitGroup
- stats := make(serverStats)
- for server, pubkey := range w.conf.Servers {
- pend.Add(1)
- // 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)
- }
- 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
- bootnodes []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
- }
- 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()}
- }
- } 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()}
- }
- } 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()}
- }
- } else {
- stat.services["bootnode"] = infos.Report()
- genesis = string(infos.genesis)
- bootnodes = append(bootnodes, infos.enode)
- }
- 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()}
- }
- } else {
- stat.services["sealnode"] = infos.Report()
- genesis = string(infos.genesis)
- }
- 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()
- }
- 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()
- }
- 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()}
- }
- } 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()}
- }
- } else {
- stat.services["dashboard"] = infos.Report()
- }
- // Feed and newly discovered information into the wizard
- w.lock.Lock()
- defer w.lock.Unlock()
- if genesis != "" && w.conf.Genesis == nil {
- g := new(core.Genesis)
- if err := json.Unmarshal([]byte(genesis), g); err != nil {
- log.Error("Failed to parse remote genesis", "err", err)
- } else {
- w.conf.Genesis = g
- }
- }
- if ethstats != "" {
- w.conf.ethstats = ethstats
- }
- w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...)
- return stat
- }
- // 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
- }
- // serverStats is a collection of server stats for multiple hosts.
- type serverStats map[string]*serverStat
- // 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)
- table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
- table.SetAlignment(tablewriter.ALIGN_LEFT)
- table.SetColWidth(100)
- // 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))
- }
- }
- }
- }
- // 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)
- 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)
- if len(services) == 0 {
- table.Append([]string{server, stats[server].address, "", "", ""})
- }
- 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]})
- }
- }
- }
- }
- 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
- }
|