Skip to content

Commit 7394ce0

Browse files
authored
feat: beautify output for the ping command (#5237)
1 parent b01f0c7 commit 7394ce0

File tree

3 files changed

+156
-50
lines changed

3 files changed

+156
-50
lines changed

cmd/globals.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ var (
8282
// GlobalDevMode is set to true if the program is running in development mode
8383
GlobalDevMode = false
8484

85+
// GlobalTrapSignals is set to true if need to trap the registered signals and cancel the global context.
86+
GlobalTrapSignals = true
87+
8588
// GlobalSubnetProxyURL is the proxy to be used for communication with subnet
8689
GlobalSubnetProxyURL *url.URL
8790

cmd/ping.go

Lines changed: 147 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ import (
2222
"fmt"
2323
"math"
2424
"net/url"
25+
"os"
26+
"os/signal"
2527
"strconv"
2628
"strings"
29+
"syscall"
2730
"text/tabwriter"
2831
"text/template"
2932
"time"
@@ -117,14 +120,15 @@ func (pr PingResult) JSON() string {
117120
var colorMap = template.FuncMap{
118121
"colorWhite": color.New(color.FgWhite).SprintfFunc(),
119122
"colorRed": color.New(color.FgRed).SprintfFunc(),
123+
"colorGreen": color.New(color.FgGreen).SprintfFunc(),
120124
}
121125

122126
// PingDist is the template for ping result in distributed mode
123-
const PingDist = `{{$x := .Counter}}{{range .EndPointsStats}}{{if eq "0 " .CountErr}}{{colorWhite $x}}{{colorWhite ": "}}{{colorWhite .Endpoint.Scheme}}{{colorWhite "://"}}{{colorWhite .Endpoint.Host}}{{"\t"}}{{ colorWhite "min="}}{{colorWhite .Min}}{{"\t"}}{{colorWhite "max="}}{{colorWhite .Max}}{{"\t"}}{{colorWhite "average="}}{{colorWhite .Average}}{{"\t"}}{{colorWhite "errors="}}{{colorWhite .CountErr}}{{" "}}{{colorWhite "roundtrip="}}{{colorWhite .Roundtrip}}{{else}}{{colorRed $x}}{{colorRed ": "}}{{colorRed .Endpoint.Scheme}}{{colorRed "://"}}{{colorRed .Endpoint.Host}}{{"\t"}}{{ colorRed "min="}}{{colorRed .Min}}{{"\t"}}{{colorRed "max="}}{{colorRed .Max}}{{"\t"}}{{colorRed "average="}}{{colorRed .Average}}{{"\t"}}{{colorRed "errors="}}{{colorRed .CountErr}}{{" "}}{{colorRed "roundtrip="}}{{colorRed .Roundtrip}}{{end}}
127+
const PingDist = `{{$x := .Counter}}{{range .EndPointsStats}}{{if eq "ok " .Status}}{{colorWhite $x}}{{colorWhite ": "}}{{colorWhite .Endpoint.Scheme}}{{colorWhite "://"}}{{colorWhite .Endpoint.Host}}{{"\t"}}{{colorWhite "status="}}{{colorGreen .Status}}{{" "}}{{colorWhite "time="}}{{colorWhite .Time}}{{else}}{{colorRed $x}}{{colorRed ": "}}{{colorRed .Endpoint.Scheme}}{{colorRed "://"}}{{colorRed .Endpoint.Host}}{{"\t"}}{{colorRed "status="}}{{colorRed .Status}}{{" "}}{{colorRed "time="}}{{colorRed .Time}}{{end}}
124128
{{end}}`
125129

126130
// Ping is the template for ping result
127-
const Ping = `{{$x := .Counter}}{{range .EndPointsStats}}{{if eq "0 " .CountErr}}{{colorWhite $x}}{{colorWhite ": "}}{{colorWhite .Endpoint.Scheme}}{{colorWhite "://"}}{{colorWhite .Endpoint.Host}}{{"\t"}}{{ colorWhite "min="}}{{colorWhite .Min}}{{"\t"}}{{colorWhite "max="}}{{colorWhite .Max}}{{"\t"}}{{colorWhite "average="}}{{colorWhite .Average}}{{"\t"}}{{colorWhite "errors="}}{{colorWhite .CountErr}}{{" "}}{{colorWhite "roundtrip="}}{{colorWhite .Roundtrip}}{{else}}{{colorRed $x}}{{colorRed ": "}}{{colorRed .Endpoint.Scheme}}{{colorRed "://"}}{{colorRed .Endpoint.Host}}{{"\t"}}{{ colorRed "min="}}{{colorRed .Min}}{{"\t"}}{{colorRed "max="}}{{colorRed .Max}}{{"\t"}}{{colorRed "average="}}{{colorRed .Average}}{{"\t"}}{{colorRed "errors="}}{{colorRed .CountErr}}{{" "}}{{colorRed "roundtrip="}}{{colorRed .Roundtrip}}{{end}}{{end}}`
131+
const Ping = `{{$x := .Counter}}{{range .EndPointsStats}}{{if eq "ok " .Status}}{{colorWhite $x}}{{colorWhite ": "}}{{colorWhite .Endpoint.Scheme}}{{colorWhite "://"}}{{colorWhite .Endpoint.Host}}{{"\t"}}{{colorWhite "status="}}{{colorGreen .Status}}{{" "}}{{colorWhite "time="}}{{colorWhite .Time}}{{else}}{{colorRed $x}}{{colorRed ": "}}{{colorRed .Endpoint.Scheme}}{{colorRed "://"}}{{colorRed .Endpoint.Host}}{{"\t"}}{{colorRed "status="}}{{colorRed .Status}}{{" "}}{{colorRed "time="}}{{colorRed .Time}}{{end}}{{end}}`
128132

129133
// PingTemplateDist - captures ping template
130134
var PingTemplateDist = template.Must(template.New("ping-list").Funcs(colorMap).Parse(PingDist))
@@ -149,14 +153,11 @@ func (pr PingResult) String() string {
149153

150154
// EndPointStats - container to hold server ping stats
151155
type EndPointStats struct {
152-
Endpoint *url.URL `json:"endpoint"`
153-
Min string `json:"min"`
154-
Max string `json:"max"`
155-
Average string `json:"average"`
156-
DNS string `json:"dns"`
157-
CountErr string `json:"error-count,omitempty"`
158-
Error string `json:"error,omitempty"`
159-
Roundtrip string `json:"roundtrip"`
156+
Endpoint *url.URL `json:"endpoint"`
157+
DNS string `json:"dns"`
158+
Status string `json:"status,omitempty"`
159+
Error string `json:"error,omitempty"`
160+
Time string `json:"time"`
160161
}
161162

162163
// PingResult contains ping output
@@ -166,15 +167,71 @@ type PingResult struct {
166167
EndPointsStats []EndPointStats `json:"servers"`
167168
}
168169

169-
type serverStats struct {
170-
min uint64
171-
max uint64
172-
sum uint64
173-
avg uint64
174-
dns uint64 // last DNS resolving time
175-
errorCount int // used to keep a track of consecutive errors
176-
err string
177-
counter int // used to find the average, acts as denominator
170+
// PingSummary Summarizes the results of the ping execution.
171+
type PingSummary struct {
172+
Status string `json:"status"`
173+
// map to contain server stats for all the servers
174+
ServerMap map[string]ServerStats `json:"serverMap"`
175+
}
176+
177+
// JSON jsonified ping summary message.
178+
func (ps PingSummary) JSON() string {
179+
pingJSONBytes, e := json.MarshalIndent(ps, "", " ")
180+
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
181+
182+
return string(pingJSONBytes)
183+
}
184+
185+
// String colorized ping summary message.
186+
func (ps PingSummary) String() string {
187+
dspOrder := []col{colGreen} // Header
188+
for i := 0; i < len(ps.ServerMap); i++ {
189+
dspOrder = append(dspOrder, colGrey)
190+
}
191+
var printColors []*color.Color
192+
for _, c := range dspOrder {
193+
printColors = append(printColors, getPrintCol(c))
194+
}
195+
tbl := console.NewTable(printColors, []bool{false, false, false, false, false, false}, 0)
196+
197+
var builder strings.Builder
198+
cellText := make([][]string, len(ps.ServerMap)+1)
199+
cellText[0] = []string{
200+
"Endpoint",
201+
"Min",
202+
"Avg",
203+
"Max",
204+
"Error",
205+
"Count",
206+
}
207+
index := 0
208+
for endpoint, ping := range ps.ServerMap {
209+
index++
210+
cellText[index] = []string{
211+
ping.Endpoint.Scheme + "://" + endpoint,
212+
trimToTwoDecimal(time.Duration(ping.Min)),
213+
trimToTwoDecimal(time.Duration(ping.Avg)),
214+
trimToTwoDecimal(time.Duration(ping.Max)),
215+
strconv.Itoa(ping.ErrorCount),
216+
strconv.Itoa(ping.Counter),
217+
}
218+
}
219+
e := tbl.PopulateTable(&builder, cellText)
220+
fatalIf(probe.NewError(e), "unable to populate the table")
221+
return builder.String()
222+
}
223+
224+
// ServerStats ping result of each endpoint
225+
type ServerStats struct {
226+
Endpoint *url.URL `json:"endpoint"`
227+
Min uint64 `json:"min"`
228+
Max uint64 `json:"max"`
229+
Sum uint64 `json:"sum"`
230+
Avg uint64 `json:"avg"`
231+
DNS uint64 `json:"dns"` // last DNS resolving time
232+
ErrorCount int `json:"errorCount"` // used to keep a track of consecutive errors
233+
Err string `json:"err"`
234+
Counter int `json:"counter"` // used to find the average, acts as denominator
178235
}
179236

180237
func fetchAdminInfo(admClnt *madmin.AdminClient) (madmin.InfoMessage, error) {
@@ -223,7 +280,7 @@ func filterAdminInfo(admClnt *madmin.AdminClient, nodeName string) (madmin.InfoM
223280
return madmin.InfoMessage{}, e
224281
}
225282

226-
func ping(ctx context.Context, cliCtx *cli.Context, anonClient *madmin.AnonymousClient, admInfo madmin.InfoMessage, endPointMap map[string]serverStats, index int) {
283+
func ping(ctx context.Context, cliCtx *cli.Context, anonClient *madmin.AnonymousClient, admInfo madmin.InfoMessage, pingSummary PingSummary, index int) {
227284
var endPointStats []EndPointStats
228285
var servers []madmin.ServerProperties
229286
if cliCtx.Bool("distributed") || cliCtx.IsSet("node") {
@@ -232,28 +289,29 @@ func ping(ctx context.Context, cliCtx *cli.Context, anonClient *madmin.Anonymous
232289
allOK := true
233290

234291
for result := range anonClient.Alive(ctx, madmin.AliveOpts{}, servers...) {
235-
stat := pingStats(cliCtx, result, endPointMap)
292+
stat := pingStats(cliCtx, result, pingSummary)
293+
status := "ok "
294+
if !result.Online {
295+
status = "failed "
296+
}
236297

237298
allOK = allOK && result.Online
238299
endPointStat := EndPointStats{
239-
Endpoint: result.Endpoint,
240-
Min: trimToTwoDecimal(time.Duration(stat.min)),
241-
Max: trimToTwoDecimal(time.Duration(stat.max)),
242-
Average: trimToTwoDecimal(time.Duration(stat.avg)),
243-
DNS: time.Duration(stat.dns).String(),
244-
CountErr: strconv.Itoa(stat.errorCount) + " ",
245-
Error: stat.err,
246-
Roundtrip: trimToTwoDecimal(result.ResponseTime),
300+
Endpoint: result.Endpoint,
301+
DNS: time.Duration(stat.DNS).String(),
302+
Status: status,
303+
Error: stat.Err,
304+
Time: trimToTwoDecimal(result.ResponseTime),
247305
}
248306
endPointStats = append(endPointStats, endPointStat)
249-
endPointMap[result.Endpoint.Host] = stat
307+
pingSummary.ServerMap[result.Endpoint.Host] = stat
250308

251309
}
252310
stop = stop || cliCtx.Bool("exit") && allOK
253311

254312
printMsg(PingResult{
255313
Status: "success",
256-
Counter: strconv.Itoa(index),
314+
Counter: pad(strconv.Itoa(index), " ", 3-len(strconv.Itoa(index)), true),
257315
EndPointsStats: endPointStats,
258316
})
259317
if !stop {
@@ -302,7 +360,7 @@ func pad(s, p string, count int, left bool) string {
302360
return string(ret)
303361
}
304362

305-
func pingStats(cliCtx *cli.Context, result madmin.AliveResult, serverMap map[string]serverStats) serverStats {
363+
func pingStats(cliCtx *cli.Context, result madmin.AliveResult, ps PingSummary) ServerStats {
306364
var errorString string
307365
var sum, avg, dns uint64
308366
minPing := uint64(math.MaxUint64)
@@ -311,13 +369,13 @@ func pingStats(cliCtx *cli.Context, result madmin.AliveResult, serverMap map[str
311369

312370
if result.Error != nil {
313371
errorString = result.Error.Error()
314-
if stat, ok := serverMap[result.Endpoint.Host]; ok {
315-
minPing = stat.min
316-
maxPing = stat.max
317-
sum = stat.sum
318-
counter = stat.counter
319-
avg = stat.avg
320-
errorCount = stat.errorCount + 1
372+
if stat, ok := ps.ServerMap[result.Endpoint.Host]; ok {
373+
minPing = stat.Min
374+
maxPing = stat.Max
375+
sum = stat.Sum
376+
counter = stat.Counter
377+
avg = stat.Avg
378+
errorCount = stat.ErrorCount + 1
321379

322380
} else {
323381
minPing = 0
@@ -330,17 +388,17 @@ func pingStats(cliCtx *cli.Context, result madmin.AliveResult, serverMap map[str
330388
} else {
331389
// reset consecutive error count
332390
errorCount = 0
333-
if stat, ok := serverMap[result.Endpoint.Host]; ok {
391+
if stat, ok := ps.ServerMap[result.Endpoint.Host]; ok {
334392
var minVal uint64
335-
if stat.min == 0 {
393+
if stat.Min == 0 {
336394
minVal = uint64(result.ResponseTime)
337395
} else {
338-
minVal = stat.min
396+
minVal = stat.Min
339397
}
340398
minPing = uint64(math.Min(float64(minVal), float64(uint64(result.ResponseTime))))
341-
maxPing = uint64(math.Max(float64(stat.max), float64(uint64(result.ResponseTime))))
342-
sum = stat.sum + uint64(result.ResponseTime.Nanoseconds())
343-
counter = stat.counter + 1
399+
maxPing = uint64(math.Max(float64(stat.Max), float64(uint64(result.ResponseTime))))
400+
sum = stat.Sum + uint64(result.ResponseTime.Nanoseconds())
401+
counter = stat.Counter + 1
344402

345403
} else {
346404
minPing = uint64(math.Min(float64(minPing), float64(uint64(result.ResponseTime))))
@@ -351,7 +409,38 @@ func pingStats(cliCtx *cli.Context, result madmin.AliveResult, serverMap map[str
351409
avg = sum / uint64(counter)
352410
dns = uint64(result.DNSResolveTime.Nanoseconds())
353411
}
354-
return serverStats{minPing, maxPing, sum, avg, dns, errorCount, errorString, counter}
412+
return ServerStats{result.Endpoint, minPing, maxPing, sum, avg, dns, errorCount, errorString, counter}
413+
}
414+
415+
func watchSignals(ps PingSummary) {
416+
c := make(chan os.Signal, 1)
417+
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)
418+
go func() {
419+
s := <-c
420+
// Ensure that the table structure is not disrupted when manually canceling.
421+
fmt.Println("")
422+
printMsg(ps)
423+
424+
// Stop profiling if enabled, this needs to be before canceling the
425+
// global context to check for any unusual cpu/mem/goroutines usage
426+
stopProfiling()
427+
428+
// Cancel the global context
429+
globalCancel()
430+
431+
var exitCode int
432+
switch s.String() {
433+
case "interrupt":
434+
exitCode = globalCancelExitStatus
435+
case "killed":
436+
exitCode = globalKillExitStatus
437+
case "terminated":
438+
exitCode = globalTerminatExitStatus
439+
default:
440+
exitCode = globalErrorExitStatus
441+
}
442+
os.Exit(exitCode)
443+
}()
355444
}
356445

357446
// mainPing is entry point for ping command.
@@ -383,9 +472,14 @@ func mainPing(cliCtx *cli.Context) error {
383472
admInfo, e = filterAdminInfo(admClient, cliCtx.String("node"))
384473
fatalIf(probe.NewError(e).Trace(aliasedURL), "Unable to get server info")
385474
}
475+
pingSummary := PingSummary{
476+
ServerMap: make(map[string]ServerStats),
477+
Status: "success",
478+
}
386479

387-
// map to contain server stats for all the servers
388-
serverMap := make(map[string]serverStats)
480+
// stop global signals trap.
481+
GlobalTrapSignals = false
482+
watchSignals(pingSummary)
389483

390484
index := 1
391485
if cliCtx.IsSet("count") {
@@ -396,9 +490,10 @@ func mainPing(cliCtx *cli.Context) error {
396490
for index <= count {
397491
// return if consecutive error count more then specified value
398492
if stop {
493+
printMsg(pingSummary)
399494
return nil
400495
}
401-
ping(ctx, cliCtx, anonClient, admInfo, serverMap, index)
496+
ping(ctx, cliCtx, anonClient, admInfo, pingSummary, index)
402497
index++
403498
}
404499
} else {
@@ -409,12 +504,14 @@ func mainPing(cliCtx *cli.Context) error {
409504
default:
410505
// return if consecutive error count more then specified value
411506
if stop {
507+
printMsg(pingSummary)
412508
return nil
413509
}
414-
ping(ctx, cliCtx, anonClient, admInfo, serverMap, index)
510+
ping(ctx, cliCtx, anonClient, admInfo, pingSummary, index)
415511
index++
416512
}
417513
}
418514
}
515+
printMsg(pingSummary)
419516
return nil
420517
}

cmd/signals.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ func trapSignals(sig ...os.Signal) {
3838
// Once signal has been received stop signal Notify handler.
3939
signal.Stop(sigCh)
4040

41+
// If GlobalTrapSignals is set to false, the global context will not be canceled,
42+
// allowing the method to cancel the context.
43+
if !GlobalTrapSignals {
44+
return
45+
}
46+
4147
// Stop profiling if enabled, this needs to be before canceling the
4248
// global context to check for any unusual cpu/mem/goroutines usage
4349
stopProfiling()

0 commit comments

Comments
 (0)