Skip to content

Commit

Permalink
OBD Cli Implementation
Browse files Browse the repository at this point in the history
- Periodically get updates from minio as OBD tests are completed on the backend
- Implement a spinner while waiting for data
  • Loading branch information
wlan0 committed Mar 23, 2020
1 parent 18c5074 commit a690c79
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 122 deletions.
181 changes: 160 additions & 21 deletions cmd/admin-obd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,30 @@
package cmd

import (
"compress/gzip"
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"syscall"
"time"

"github.com/fatih/color"
"github.com/minio/cli"
json "github.com/minio/mc/pkg/colorjson"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio/pkg/console"
"github.com/minio/minio/pkg/madmin"
)

var adminOBDFlags = []cli.Flag{
OBDDataTypeFlag{
Name: "data",
Usage: "diagnostics type, possible values are " + options.String() + " (default $all)",
Name: "tests",
Usage: "choose OBD tests to run [" + options.String() + "]",
Value: nil,
EnvVar: "MINIO_OBD_DATA",
Hidden: true,
},
}

Expand All @@ -59,12 +66,13 @@ EXAMPLES:
}

type clusterOBDStruct struct {
Status string `json:"status"`
Status string `json:"status,omitempty"`
Error string `json:"error,omitempty"`
Info madmin.OBDInfo `json:"obdInfo,omitempty"`
}

func (u clusterOBDStruct) String() string {
u.Status = ""
data, err := json.Marshal(u)
if err != nil {
fatalIf(probe.NewError(err), "unable to marshal into JSON.")
Expand All @@ -74,7 +82,7 @@ func (u clusterOBDStruct) String() string {

// JSON jsonifies service status message.
func (u clusterOBDStruct) JSON() string {
statusJSONBytes, e := json.MarshalIndent(u, "", " ")
statusJSONBytes, e := json.MarshalIndent(u, " ", " ")
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")

return string(statusJSONBytes)
Expand All @@ -87,6 +95,25 @@ func checkAdminOBDSyntax(ctx *cli.Context) {
}
}

//compress and tar obd output
func tarGZ(c clusterOBDStruct, alias string) error {
filename := fmt.Sprintf("%s-obd_%s.json.gz", alias, time.Now().Format("20060102150405"))
f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return err
}
defer f.Close()
defer func() {
fmt.Println("OBD data saved to", filename)
}()

gzWriter := gzip.NewWriter(f)
defer gzWriter.Close()

enc := json.NewEncoder(gzWriter)
return enc.Encode(c)
}

func mainAdminOBD(ctx *cli.Context) error {
checkAdminOBDSyntax(ctx)

Expand All @@ -100,25 +127,137 @@ func mainAdminOBD(ctx *cli.Context) error {
client, err := newAdminClient(aliasedURL)
fatalIf(err, "Unable to initialize admin connection.")

if len(*types) == 0 {
types = &options
types = &options

dot := "●"
check := "✔"

// Color palette initialization
console.SetColor("INFO", color.New(color.FgGreen, color.Bold))
console.SetColor("WARN", color.New(color.FgRed, color.Bold))
console.SetColor("GREEN", color.New(color.FgGreen))

infoText := func(s string) string {
return console.Colorize("INFO", s)
}
greenText := func(s string) string {
return console.Colorize("GREEN", s)
}
warnText := func(s string) string {
return console.Colorize("WARN", s)
}
spinners := []string{"/", "|", "\\", "--", "|"}

cont, cancel := context.WithCancel(context.Background())
defer cancel()
startSpinner := func(s string) func() {
ctx, cancel := context.WithCancel(cont)
printText := func(t string, sp string, rewind int) {
console.RewindLines(rewind)

dot = infoText(dot)
t = fmt.Sprintf("%s ...", t)
t = greenText(t)
sp = infoText(sp)
toPrint := fmt.Sprintf("%s %s %s ", dot, t, sp)
fmt.Printf("%s\n", toPrint)
}
i := 0
sp := func() string {
i = i + 1
i = i % len(spinners)
return spinners[i]
}

var clusterOBDInfo clusterOBDStruct
// Fetch info of all servers (cluster or single server)
adminOBDInfo, e := client.ServerOBDInfo(*types)
if e != nil {
clusterOBDInfo.Status = "error"
clusterOBDInfo.Error = e.Error()
} else {
clusterOBDInfo.Status = "success"
clusterOBDInfo.Error = ""
}
clusterOBDInfo.Info = adminOBDInfo
done := make(chan bool)
doneToggle := false
go func() {
printText(s, sp(), 0)
for {
<-time.After(500 * time.Millisecond) //8 fps
if ctx.Err() != nil {
printText(s, check, 1)
done <- true
return
}
printText(s, sp(), 1)
}
}()
return func() {
cancel()
if !doneToggle {
<-done
os.Stdout.Sync()
doneToggle = true
}
}
}

spinner := func(resource string) func(bool) bool {
var spinStopper func()
done := false

return func(cond bool) bool {
if done {
return done
}
if spinStopper == nil {
spinStopper = startSpinner(resource)
}
if cond {
spinStopper()
done = true
}
return done
}
}

clusterOBDInfo := clusterOBDStruct{}

admin := spinner("Admin Info")
cpu := spinner("CPU")
diskHw := spinner("Disk Hardware")
osInfo := spinner("Os Info")
mem := spinner("Mem Info")
process := spinner("Process Info")
config := spinner("Config")
drive := spinner("Drive")
net := spinner("Net")

progress := func(info madmin.OBDInfo) bool {
return admin(len(info.Minio.Info.Servers) > 0) &&
cpu(len(info.Sys.CPUInfo) > 0) &&
diskHw(len(info.Sys.DiskHwInfo) > 0) &&
osInfo(len(info.Sys.OsInfo) > 0) &&
mem(len(info.Sys.MemInfo) > 0) &&
process(len(info.Sys.ProcInfo) > 0) &&
config(info.Minio.Config != nil) &&
drive(len(info.Perf.DriveInfo) > 0) &&
net(len(info.Perf.Net) > 1)
}

printMsg(clusterOBDStruct(clusterOBDInfo))
// Fetch info of all servers (cluster or single server)
obdChan := client.ServerOBDInfo(cont, *types)
for adminOBDInfo := range obdChan {
clusterOBDInfo.Info = adminOBDInfo
progress(adminOBDInfo)
}
// If MinIO is not a global distXL cluster, net will never stop spinning.
// add this extra check to ensure that doesn't happen
if !progress(clusterOBDInfo.Info) {
net(true)
}

return nil
warningMsgBoundary := "*********************************************************************************"
warning := warnText(" WARNING!!")
warningContents := infoText(` ** THIS FILE MAY CONTAIN SENSITIVE INFORMATION ABOUT YOUR ENVIRONMENT **
** PLEASE INSPECT CONTENTS BEFORE SHARING IT ON ANY PUBLIC FORUM **`)

warningMsgHeader := infoText(warningMsgBoundary)
warningMsgTrailer := infoText(warningMsgBoundary)
fmt.Printf("%s\n%s\n%s\n%s\n", warningMsgHeader, warning, warningContents, warningMsgTrailer)

return tarGZ(clusterOBDInfo, aliasedURL)
}

type OBDDataTypeSlice []madmin.OBDDataType
Expand Down Expand Up @@ -165,7 +304,7 @@ type OBDDataTypeFlag struct {
}

func (f OBDDataTypeFlag) String() string {
return fmt.Sprintf("--%s %s", f.Name, f.Usage)
return fmt.Sprintf("--%s %s", f.Name, f.Usage)
}

func (f OBDDataTypeFlag) GetName() string {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ require (
go.uber.org/zap v1.11.0 // indirect
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect
golang.org/x/text v0.3.2
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
gopkg.in/h2non/filetype.v1 v1.0.5
gopkg.in/ini.v1 v1.52.0 // indirect
gopkg.in/yaml.v2 v2.2.4
)

replace github.com/minio/minio => ../minio
Loading

0 comments on commit a690c79

Please sign in to comment.