aboutsummaryrefslogblamecommitdiffstats
path: root/cmd/puppeth/wizard_netstats.go
blob: e19180bb16c345791b468fd5ae71f60af03ad989 (plain) (tree)



















                                                                       
            
              
                 
              







                                                                                
                                 
                                
                                                                   

                      



                                                                      

                                                                      
                               
 
                                  
                                                    
                           
 












                                                                                        
                         































                                                                                           
                 






                                                                                          
                 






                                                                                             
                 







                                                                                             
                 






                                                                       
                 




                                                                                             
                 



                                                          







                                                                                             







                                                                                           



                                                                                           
                 






                                                                                              
                 

                                                           
         



                                                                
                                                   

                                                                          

                                                                               
                                          

                 

                                          
         

                                                                 
 
                   

 





                                                                            

 

                                                                  
 




                                                                              
 


                                                                                    
 




















                                                                                       

                 





                                                          
 











                                                                          


                                                                                         





















                                                                                                                                                       
                 
         










                                                                    
 
// 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.bootFull = w.conf.bootFull[:0]
    w.conf.bootLight = w.conf.bootLight[: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
        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
        }
        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)
        bootFull = append(bootFull, infos.enodeFull)
        if infos.enodeLight != "" {
            bootLight = append(bootLight, infos.enodeLight)
        }
    }
    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.bootFull = append(w.conf.bootFull, bootFull...)
    w.conf.bootLight = append(w.conf.bootLight, bootLight...)

    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
}