wizard_netstats.go 8.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// 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"
22
	"sort"
23
	"strings"
24
	"sync"
25 26 27 28 29 30 31 32

	"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.
33
func (w *wizard) networkStats() {
34
	if len(w.servers) == 0 {
35
		log.Info("No remote machines to gather stats from")
36 37
		return
	}
38 39
	// Clear out some previous configs to refill from current scan
	w.conf.ethstats = ""
40
	w.conf.bootnodes = w.conf.bootnodes[:0]
41 42

	// Iterate over all the specified hosts and check their status
43
	var pend sync.WaitGroup
44

45
	stats := make(serverStats)
46
	for server, pubkey := range w.conf.Servers {
47
		pend.Add(1)
48

49 50 51 52 53 54 55 56 57 58 59 60 61
		// 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)
62
			}
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
			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
78
		bootnodes []string
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
	)
	// 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
94
		}
95 96 97 98 99 100 101
		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()}
102
		}
103 104 105 106 107 108 109
	} 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()}
110
		}
111 112 113 114 115 116 117 118
	} 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()}
119
		}
120 121 122 123
	} else {
		stat.services["bootnode"] = infos.Report()

		genesis = string(infos.genesis)
124
		bootnodes = append(bootnodes, infos.enode)
125 126 127 128 129
	}
	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()}
130
		}
131 132 133 134
	} else {
		stat.services["sealnode"] = infos.Report()
		genesis = string(infos.genesis)
	}
135 136 137 138 139 140 141 142
	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()
	}
143 144 145 146 147 148 149 150
	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()
	}
151 152 153 154
	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()}
155
		}
156 157 158 159 160 161 162
	} 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()}
163
		}
164 165
	} else {
		stat.services["dashboard"] = infos.Report()
166
	}
167 168 169 170
	// Feed and newly discovered information into the wizard
	w.lock.Lock()
	defer w.lock.Unlock()

171
	if genesis != "" && w.conf.Genesis == nil {
172 173
		g := new(core.Genesis)
		if err := json.Unmarshal([]byte(genesis), g); err != nil {
174 175
			log.Error("Failed to parse remote genesis", "err", err)
		} else {
176
			w.conf.Genesis = g
177 178
		}
	}
179 180
	if ethstats != "" {
		w.conf.ethstats = ethstats
181
	}
182
	w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...)
183

184
	return stat
185 186
}

187 188 189 190 191 192
// 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
193 194
}

195 196
// serverStats is a collection of server stats for multiple hosts.
type serverStats map[string]*serverStat
197

198 199 200 201 202
// 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)
203

204 205
	table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
	table.SetAlignment(tablewriter.ALIGN_LEFT)
206
	table.SetColWidth(40)
207

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
	// 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))
				}
225 226 227 228
				for _, val := range strings.Split(value, "\n") {
					if len(val) > len(separator[4]) {
						separator[4] = strings.Repeat("-", len(val))
					}
229 230
				}
			}
231 232
		}
	}
233 234 235 236 237 238
	// 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)
239

240 241 242 243 244 245 246 247 248 249 250 251
	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)

252 253 254
		if len(services) == 0 {
			table.Append([]string{server, stats[server].address, "", "", ""})
		}
255 256 257 258 259 260 261 262 263 264 265 266 267
		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 {
268 269 270 271 272 273 274 275 276 277 278
				for l, value := range strings.Split(stats[server].services[service][config], "\n") {
					switch {
					case j == 0 && k == 0 && l == 0:
						table.Append([]string{server, stats[server].address, service, config, value})
					case k == 0 && l == 0:
						table.Append([]string{"", "", service, config, value})
					case l == 0:
						table.Append([]string{"", "", "", config, value})
					default:
						table.Append([]string{"", "", "", "", value})
					}
279 280
				}
			}
281 282
		}
	}
283 284
	table.Render()
}