1
0
mirror of https://github.com/minio/mc.git synced 2025-07-31 18:24:21 +03:00

Change the JSON output of mc support perf (#4198)

Change the JSON output of mc support perf

The new change will still print multiple JSON lines for each conducted
performance test but now we have a new field called 'type' field to
difference between network, drive and object tests.

This change also adds 'final' field to indicate this is the final json of a
particular performance testing.

Also, errors when conducting those tests will have a field 'err' as well
in the JSON output.
This commit is contained in:
Anis Elleuch
2022-08-10 16:09:05 +01:00
committed by GitHub
parent 73b157e4b1
commit 3e990e3525
5 changed files with 160 additions and 112 deletions

View File

@ -40,12 +40,33 @@ type speedTestUI struct {
result speedTestResult result speedTestResult
} }
type speedTestType byte
const (
netSpeedTest speedTestType = 1 << iota
driveSpeedTest
objectSpeedTest
)
func (s speedTestType) Name() string {
switch s {
case netSpeedTest:
return "NetPerf"
case driveSpeedTest:
return "DrivePerf"
case objectSpeedTest:
return "ObjectPerf"
}
return "<unknown>"
}
type speedTestResult struct { type speedTestResult struct {
err error Type speedTestType `json:"type"`
final bool ObjectResult *madmin.SpeedTestResult `json:"object,omitempty"`
result *madmin.SpeedTestResult NetResult *madmin.NetperfResult `json:"network,omitempty"`
nresult *madmin.NetperfResult DriveResult []madmin.DriveSpeedTestResult `json:"drive,omitempty"`
dresult []madmin.DriveSpeedTestResult Err string `json:"err,omitempty"`
Final bool `json:"final,omitempty"`
} }
func initSpeedTestUI() *speedTestUI { func initSpeedTestUI() *speedTestUI {
@ -73,7 +94,7 @@ func (m *speedTestUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
case speedTestResult: case speedTestResult:
m.result = msg m.result = msg
if msg.final { if msg.Final {
m.quitting = true m.quitting = true
return m, tea.Quit return m, tea.Quit
} }
@ -89,15 +110,8 @@ func (m *speedTestUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m *speedTestUI) View() string { func (m *speedTestUI) View() string {
// Quit when there is an error // Quit when there is an error
if m.result.err != nil { if m.result.Err != "" {
errMsg := "" return fmt.Sprintf("\n%s: ✗ (Err: %s)\n", m.result.Type.Name(), m.result.Err)
errResp := madmin.ToErrorResponse(m.result.err)
if errResp.Code == "NotImplemented" {
errMsg = "Not implemented"
} else {
errMsg = m.result.err.Error()
}
return fmt.Sprintf("\nNetperf: ✗ (Err: %s)\n", errMsg)
} }
var s strings.Builder var s strings.Builder
@ -117,9 +131,9 @@ func (m *speedTestUI) View() string {
table.SetTablePadding("\t") // pad with tabs table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true) table.SetNoWhiteSpace(true)
res := m.result.result ores := m.result.ObjectResult
nres := m.result.nresult nres := m.result.NetResult
dres := m.result.dresult dres := m.result.DriveResult
trailerIfGreaterThan := func(in string, max int) string { trailerIfGreaterThan := func(in string, max int) string {
if len(in) < max { if len(in) < max {
@ -128,11 +142,11 @@ func (m *speedTestUI) View() string {
return in[:max] + "..." return in[:max] + "..."
} }
if res != nil { if ores != nil {
table.SetHeader([]string{"", "Throughput", "IOPS"}) table.SetHeader([]string{"", "Throughput", "IOPS"})
data := make([][]string, 2) data := make([][]string, 2)
if res.Version == "" { if ores.Version == "" {
data[0] = []string{ data[0] = []string{
"PUT", "PUT",
whiteStyle.Render("-- KiB/sec"), whiteStyle.Render("-- KiB/sec"),
@ -146,26 +160,25 @@ func (m *speedTestUI) View() string {
} else { } else {
data[0] = []string{ data[0] = []string{
"PUT", "PUT",
whiteStyle.Render(humanize.IBytes(res.PUTStats.ThroughputPerSec) + "/s"), whiteStyle.Render(humanize.IBytes(ores.PUTStats.ThroughputPerSec) + "/s"),
whiteStyle.Render(humanize.Comma(int64(res.PUTStats.ObjectsPerSec)) + " objs/s"), whiteStyle.Render(humanize.Comma(int64(ores.PUTStats.ObjectsPerSec)) + " objs/s"),
} }
data[1] = []string{ data[1] = []string{
"GET", "GET",
whiteStyle.Render(humanize.IBytes(res.GETStats.ThroughputPerSec) + "/s"), whiteStyle.Render(humanize.IBytes(ores.GETStats.ThroughputPerSec) + "/s"),
whiteStyle.Render(humanize.Comma(int64(res.GETStats.ObjectsPerSec)) + " objs/s"), whiteStyle.Render(humanize.Comma(int64(ores.GETStats.ObjectsPerSec)) + " objs/s"),
} }
} }
table.AppendBulk(data) table.AppendBulk(data)
table.Render() table.Render()
if m.quitting { if m.quitting {
s.WriteString("\n" + m.result.String()) s.WriteString("\n" + objectTestShortResult(ores))
if vstr := m.result.StringVerbose(); vstr != "" { if globalPerfTestVerbose {
s.WriteString(vstr + "\n") s.WriteString("\n\n")
} else { s.WriteString(objectTestVerboseResult(ores))
s.WriteString("\n")
} }
s.WriteString("Objectperf: ✔\n") s.WriteString("\n")
} }
} else if nres != nil { } else if nres != nil {
table.SetHeader([]string{"Node", "RX", "TX", ""}) table.SetHeader([]string{"Node", "RX", "TX", ""})
@ -204,10 +217,6 @@ func (m *speedTestUI) View() string {
table.AppendBulk(data) table.AppendBulk(data)
table.Render() table.Render()
if m.quitting {
s.WriteString("\nNetperf: ✔\n")
}
} else if dres != nil { } else if dres != nil {
table.SetHeader([]string{"Node", "Path", "Read", "Write", ""}) table.SetHeader([]string{"Node", "Path", "Read", "Write", ""})
data := make([][]string, 0, len(dres)) data := make([][]string, 0, len(dres))
@ -245,19 +254,13 @@ func (m *speedTestUI) View() string {
} }
table.AppendBulk(data) table.AppendBulk(data)
table.Render() table.Render()
if m.quitting {
s.WriteString("\nDriveperf: ✔\n")
}
} }
// Print the spinner
if !m.quitting { if !m.quitting {
if nres != nil { s.WriteString(fmt.Sprintf("\n%s: %s", m.result.Type.Name(), m.spinner.View()))
s.WriteString(fmt.Sprintf("\nNetperf: %s", m.spinner.View())) } else {
} else if res != nil { s.WriteString(fmt.Sprintf("\n%s: ✔\n", m.result.Type.Name()))
s.WriteString(fmt.Sprintf("\nObjectperf: %s", m.spinner.View()))
} else if dres != nil {
s.WriteString(fmt.Sprintf("\nDriveperf: %s", m.spinner.View()))
}
} }
return s.String() return s.String()
} }

View File

@ -24,10 +24,8 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/minio/cli" "github.com/minio/cli"
json "github.com/minio/colorjson"
"github.com/minio/madmin-go" "github.com/minio/madmin-go"
"github.com/minio/mc/pkg/probe" "github.com/minio/mc/pkg/probe"
"github.com/minio/pkg/console"
) )
func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error { func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error {
@ -62,22 +60,34 @@ func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error {
serial := ctx.Bool("serial") serial := ctx.Bool("serial")
resultCh, e := client.DriveSpeedtest(ctxt, madmin.DriveSpeedTestOpts{ resultCh, speedTestErr := client.DriveSpeedtest(ctxt, madmin.DriveSpeedTestOpts{
Serial: serial, Serial: serial,
BlockSize: uint64(blocksize), BlockSize: uint64(blocksize),
FileSize: uint64(filesize), FileSize: uint64(filesize),
}) })
fatalIf(probe.NewError(e), "Failed to execute drive speedtest")
if globalJSON { if globalJSON {
if speedTestErr != nil {
printMsg(speedTestResult{
Type: driveSpeedTest,
Err: speedTestErr.Error(),
Final: true,
})
return nil
}
var results []madmin.DriveSpeedTestResult
for result := range resultCh { for result := range resultCh {
if result.Version != "" { if result.Version != "" {
jsonBytes, e := json.MarshalIndent(result, "", " ") results = append(results, result)
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
console.Println(string(jsonBytes))
} }
} }
printMsg(speedTestResult{
Type: driveSpeedTest,
DriveResult: results,
Final: true,
})
return nil return nil
} }
@ -92,19 +102,29 @@ func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error {
}() }()
go func() { go func() {
if speedTestErr != nil {
printMsg(speedTestResult{
Type: driveSpeedTest,
Err: speedTestErr.Error(),
})
return
}
var results []madmin.DriveSpeedTestResult var results []madmin.DriveSpeedTestResult
for result := range resultCh { for result := range resultCh {
if result.Version != "" { if result.Version != "" {
results = append(results, result) results = append(results, result)
} else { } else {
p.Send(speedTestResult{ p.Send(speedTestResult{
dresult: []madmin.DriveSpeedTestResult{}, Type: driveSpeedTest,
DriveResult: []madmin.DriveSpeedTestResult{},
}) })
} }
} }
p.Send(speedTestResult{ p.Send(speedTestResult{
dresult: results, Type: driveSpeedTest,
final: true, DriveResult: results,
Final: true,
}) })
}() }()

View File

@ -24,24 +24,10 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/minio/cli" "github.com/minio/cli"
json "github.com/minio/colorjson"
"github.com/minio/madmin-go" "github.com/minio/madmin-go"
"github.com/minio/mc/pkg/probe" "github.com/minio/mc/pkg/probe"
) )
type netperfResult madmin.NetperfResult
func (m netperfResult) String() (msg string) {
// string version is handled by banner.
return ""
}
func (m netperfResult) JSON() string {
JSONBytes, e := json.MarshalIndent(m, "", " ")
fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
return string(JSONBytes)
}
func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string) error { func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string) error {
client, perr := newAdminClient(aliasedURL) client, perr := newAdminClient(aliasedURL)
if perr != nil { if perr != nil {
@ -76,13 +62,21 @@ func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string) error {
}() }()
if globalJSON { if globalJSON {
for { select {
select { case err := <-errorCh:
case result := <-resultCh: printMsg(speedTestResult{
printMsg(netperfResult(result)) Type: netSpeedTest,
return nil Err: err.Error(),
} Final: true,
})
case result := <-resultCh:
printMsg(speedTestResult{
Type: netSpeedTest,
NetResult: &result,
Final: true,
})
} }
return nil
} }
done := make(chan struct{}) done := make(chan struct{})
@ -100,19 +94,22 @@ func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string) error {
select { select {
case err := <-errorCh: case err := <-errorCh:
p.Send(speedTestResult{ p.Send(speedTestResult{
err: err, Type: netSpeedTest,
final: true, Err: err.Error(),
Final: true,
}) })
return return
case result := <-resultCh: case result := <-resultCh:
p.Send(speedTestResult{ p.Send(speedTestResult{
nresult: &result, Type: netSpeedTest,
final: true, NetResult: &result,
Final: true,
}) })
return return
default: default:
p.Send(speedTestResult{ p.Send(speedTestResult{
nresult: &madmin.NetperfResult{}, Type: netSpeedTest,
NetResult: &madmin.NetperfResult{},
}) })
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }

View File

@ -85,24 +85,41 @@ func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string) error {
// in all other scenarios keep auto-tuning on. // in all other scenarios keep auto-tuning on.
autotune := !ctx.IsSet("concurrent") autotune := !ctx.IsSet("concurrent")
resultCh, err := client.Speedtest(ctxt, madmin.SpeedtestOpts{ resultCh, speedTestErr := client.Speedtest(ctxt, madmin.SpeedtestOpts{
Size: int(size), Size: int(size),
Duration: duration, Duration: duration,
Concurrency: concurrent, Concurrency: concurrent,
Autotune: autotune, Autotune: autotune,
Bucket: ctx.String("bucket"), // This is a hidden flag. Bucket: ctx.String("bucket"), // This is a hidden flag.
}) })
fatalIf(probe.NewError(err), "Failed to execute performance test")
if globalJSON { if globalJSON {
for result := range resultCh { if speedTestErr != nil {
printMsg(speedTestResult{
Type: objectSpeedTest,
Err: speedTestErr.Error(),
Final: true,
})
return nil
}
var result madmin.SpeedTestResult
for result = range resultCh {
if result.Version == "" { if result.Version == "" {
continue continue
} }
printMsg(speedTestResult{ printMsg(speedTestResult{
result: &result, Type: objectSpeedTest,
ObjectResult: &result,
}) })
} }
printMsg(speedTestResult{
Type: objectSpeedTest,
ObjectResult: &result,
Final: true,
})
return nil return nil
} }
@ -117,15 +134,26 @@ func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string) error {
}() }()
go func() { go func() {
if speedTestErr != nil {
p.Send(speedTestResult{
Type: objectSpeedTest,
Err: speedTestErr.Error(),
Final: true,
})
return
}
var result madmin.SpeedTestResult var result madmin.SpeedTestResult
for result = range resultCh { for result = range resultCh {
p.Send(speedTestResult{ p.Send(speedTestResult{
result: &result, Type: objectSpeedTest,
ObjectResult: &result,
}) })
} }
p.Send(speedTestResult{ p.Send(speedTestResult{
result: &result, Type: objectSpeedTest,
final: true, ObjectResult: &result,
Final: true,
}) })
}() }()

View File

@ -23,6 +23,7 @@ import (
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/minio/cli" "github.com/minio/cli"
json "github.com/minio/colorjson" json "github.com/minio/colorjson"
"github.com/minio/madmin-go"
"github.com/minio/mc/pkg/probe" "github.com/minio/mc/pkg/probe"
) )
@ -97,34 +98,29 @@ EXAMPLES:
`, `,
} }
func (s speedTestResult) StringVerbose() (msg string) { func objectTestVerboseResult(result *madmin.SpeedTestResult) (msg string) {
result := s.result msg += "PUT:\n"
if globalPerfTestVerbose { for _, node := range result.PUTStats.Servers {
msg += "\n\n" msg += fmt.Sprintf(" * %s: %s/s %s objs/s", node.Endpoint, humanize.IBytes(node.ThroughputPerSec), humanize.Comma(int64(node.ObjectsPerSec)))
msg += "PUT:\n" if node.Err != "" {
for _, node := range result.PUTStats.Servers { msg += " Err: " + node.Err
msg += fmt.Sprintf(" * %s: %s/s %s objs/s", node.Endpoint, humanize.IBytes(node.ThroughputPerSec), humanize.Comma(int64(node.ObjectsPerSec)))
if node.Err != "" {
msg += " Err: " + node.Err
}
msg += "\n"
} }
msg += "\n"
msg += "GET:\n"
for _, node := range result.GETStats.Servers {
msg += fmt.Sprintf(" * %s: %s/s %s objs/s", node.Endpoint, humanize.IBytes(node.ThroughputPerSec), humanize.Comma(int64(node.ObjectsPerSec)))
if node.Err != "" {
msg += " Err: " + node.Err
}
msg += "\n"
}
} }
msg += "GET:\n"
for _, node := range result.GETStats.Servers {
msg += fmt.Sprintf(" * %s: %s/s %s objs/s", node.Endpoint, humanize.IBytes(node.ThroughputPerSec), humanize.Comma(int64(node.ObjectsPerSec)))
if node.Err != "" {
msg += " Err: " + node.Err
}
msg += "\n"
}
return msg return msg
} }
func (s speedTestResult) String() (msg string) { func objectTestShortResult(result *madmin.SpeedTestResult) (msg string) {
result := s.result
msg += fmt.Sprintf("MinIO %s, %d servers, %d drives, %s objects, %d threads", msg += fmt.Sprintf("MinIO %s, %d servers, %d drives, %s objects, %d threads",
result.Version, result.Servers, result.Disks, result.Version, result.Servers, result.Disks,
humanize.IBytes(uint64(result.Size)), result.Concurrent) humanize.IBytes(uint64(result.Size)), result.Concurrent)
@ -132,8 +128,12 @@ func (s speedTestResult) String() (msg string) {
return msg return msg
} }
func (s speedTestResult) String() string {
return ""
}
func (s speedTestResult) JSON() string { func (s speedTestResult) JSON() string {
JSONBytes, e := json.MarshalIndent(s.result, "", " ") JSONBytes, e := json.MarshalIndent(s, "", " ")
fatalIf(probe.NewError(e), "Unable to marshal into JSON.") fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
return string(JSONBytes) return string(JSONBytes)
} }