|
|
|
@ -21,6 +21,7 @@ import ( |
|
|
|
|
"os" |
|
|
|
|
"sort" |
|
|
|
|
"strings" |
|
|
|
|
"sync" |
|
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/core" |
|
|
|
|
"github.com/ethereum/go-ethereum/log" |
|
|
|
@ -34,112 +35,143 @@ func (w *wizard) networkStats() { |
|
|
|
|
log.Error("No remote machines to gather stats from") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
protips := new(protips) |
|
|
|
|
// 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
|
|
|
|
|
stats := make(serverStats) |
|
|
|
|
var pend sync.WaitGroup |
|
|
|
|
|
|
|
|
|
stats := make(serverStats) |
|
|
|
|
for server, pubkey := range w.conf.Servers { |
|
|
|
|
client := w.servers[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), |
|
|
|
|
} |
|
|
|
|
stats[client.server] = stat |
|
|
|
|
|
|
|
|
|
if client == nil { |
|
|
|
|
conn, err := dial(server, pubkey) |
|
|
|
|
if err != nil { |
|
|
|
|
logger.Error("Failed to establish remote connection", "err", err) |
|
|
|
|
stat.failure = err.Error() |
|
|
|
|
continue |
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
client = conn |
|
|
|
|
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 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() |
|
|
|
|
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()} |
|
|
|
|
} |
|
|
|
|
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() |
|
|
|
|
protips.ethstats = infos.config |
|
|
|
|
} 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()} |
|
|
|
|
} |
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
protips.genesis = string(infos.genesis) |
|
|
|
|
protips.bootFull = append(protips.bootFull, infos.enodeFull) |
|
|
|
|
if infos.enodeLight != "" { |
|
|
|
|
protips.bootLight = append(protips.bootLight, infos.enodeLight) |
|
|
|
|
} |
|
|
|
|
} 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()} |
|
|
|
|
} |
|
|
|
|
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() |
|
|
|
|
protips.genesis = string(infos.genesis) |
|
|
|
|
} 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 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 sealnode availability") |
|
|
|
|
if infos, err := checkNode(client, w.network, false); err != nil { |
|
|
|
|
if err != ErrServiceUnknown { |
|
|
|
|
stat.services["sealnode"] = map[string]string{"offline": err.Error()} |
|
|
|
|
} |
|
|
|
|
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() |
|
|
|
|
} else { |
|
|
|
|
stat.services["sealnode"] = infos.Report() |
|
|
|
|
genesis = string(infos.genesis) |
|
|
|
|
} |
|
|
|
|
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()} |
|
|
|
|
} |
|
|
|
|
// All status checks complete, report and check next server
|
|
|
|
|
delete(w.services, server) |
|
|
|
|
for service := range stat.services { |
|
|
|
|
w.services[server] = append(w.services[server], service) |
|
|
|
|
} 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() |
|
|
|
|
} |
|
|
|
|
// If a genesis block was found, load it into our configs
|
|
|
|
|
if protips.genesis != "" && w.conf.genesis == nil { |
|
|
|
|
genesis := new(core.Genesis) |
|
|
|
|
if err := json.Unmarshal([]byte(protips.genesis), genesis); err != nil { |
|
|
|
|
// 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 = genesis |
|
|
|
|
protips.network = genesis.Config.ChainId.Int64() |
|
|
|
|
w.conf.genesis = g |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if protips.ethstats != "" { |
|
|
|
|
w.conf.ethstats = protips.ethstats |
|
|
|
|
if ethstats != "" { |
|
|
|
|
w.conf.ethstats = ethstats |
|
|
|
|
} |
|
|
|
|
w.conf.bootFull = protips.bootFull |
|
|
|
|
w.conf.bootLight = protips.bootLight |
|
|
|
|
w.conf.bootFull = append(w.conf.bootFull, bootFull...) |
|
|
|
|
w.conf.bootLight = append(w.conf.bootLight, bootLight...) |
|
|
|
|
|
|
|
|
|
// Print any collected stats and return
|
|
|
|
|
stats.render() |
|
|
|
|
return stat |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// serverStat is a collection of service configuration parameters and health
|
|
|
|
@ -205,6 +237,9 @@ func (stats serverStats) render() { |
|
|
|
|
} |
|
|
|
|
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 { |
|
|
|
|