diff --git a/cmd/support-perf-drive.go b/cmd/support-perf-drive.go index cfefd513..9c643bb0 100644 --- a/cmd/support-perf-drive.go +++ b/cmd/support-perf-drive.go @@ -68,11 +68,12 @@ func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string, outCh chan<- P if globalJSON { if e != nil { - printMsg(PerfTestResult{ + printMsg(convertPerfResult(PerfTestResult{ Type: DrivePerfTest, Err: e.Error(), Final: true, - }) + })) + return nil } @@ -82,11 +83,11 @@ func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string, outCh chan<- P results = append(results, result) } } - printMsg(PerfTestResult{ + printMsg(convertPerfResult(PerfTestResult{ Type: DrivePerfTest, DriveResult: results, Final: true, - }) + })) return nil } diff --git a/cmd/support-perf-net.go b/cmd/support-perf-net.go index 706af681..1bba21f6 100644 --- a/cmd/support-perf-net.go +++ b/cmd/support-perf-net.go @@ -64,17 +64,17 @@ func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string, outCh chan<- if globalJSON { select { case e := <-errorCh: - printMsg(PerfTestResult{ + printMsg(convertPerfResult(PerfTestResult{ Type: NetPerfTest, Err: e.Error(), Final: true, - }) + })) case result := <-resultCh: - printMsg(PerfTestResult{ + printMsg(convertPerfResult(PerfTestResult{ Type: NetPerfTest, NetResult: &result, Final: true, - }) + })) } return nil } diff --git a/cmd/support-perf-object.go b/cmd/support-perf-object.go index 8f96d229..bb48d36d 100644 --- a/cmd/support-perf-object.go +++ b/cmd/support-perf-object.go @@ -95,11 +95,11 @@ func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string, outCh chan<- if globalJSON { if e != nil { - printMsg(PerfTestResult{ + printMsg(convertPerfResult(PerfTestResult{ Type: ObjectPerfTest, Err: e.Error(), Final: true, - }) + })) return nil } @@ -108,17 +108,13 @@ func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string, outCh chan<- if result.Version == "" { continue } - printMsg(PerfTestResult{ - Type: ObjectPerfTest, - ObjectResult: &result, - }) } - printMsg(PerfTestResult{ + printMsg(convertPerfResult(PerfTestResult{ Type: ObjectPerfTest, ObjectResult: &result, Final: true, - }) + })) return nil } diff --git a/cmd/support-perf.go b/cmd/support-perf.go index e53485c4..56091d9b 100644 --- a/cmd/support-perf.go +++ b/cmd/support-perf.go @@ -106,6 +106,53 @@ EXAMPLES: `, } +// PerfTestOutput - stores the final output of performance test(s) +type PerfTestOutput struct { + ObjectResult *ObjTestResult `json:"object,omitempty"` + NetResult *madmin.NetperfResult `json:"network,omitempty"` + DriveResult []DriveTestResult `json:"drive,omitempty"` + Error string `json:"error,omitempty"` +} + +// DriveTestResult - result of the drive performance test +type DriveTestResult struct { + Endpoint string `json:"endpoint"` + DrivePerf []madmin.DrivePerf `json:"drivePerf,omitempty"` + Error string `json:"error,omitempty"` +} + +// ObjTestResult - result of the object performance test +type ObjTestResult struct { + Servers int `json:"servers"` + Drives int `json:"drives"` + Size int `json:"size"` + Concurrent int `json:"concurrent"` + PUTStats ObjPUTStats `json:"PUTStats"` + GETStats ObjGETStats `json:"GETStats"` +} + +// ObjStatServer - Server level object performance stats +type ObjStatServer struct { + Endpoint string `json:"endpoint"` + Throughput uint64 `json:"throughput"` + ObjectsPerSec uint64 `json:"objectsPerSec"` + Error string `json:"error,omitempty"` +} + +// ObjPUTStats - PUT stats of all the servers +type ObjPUTStats struct { + Throughput uint64 `json:"throughput"` + ObjectsPerSec uint64 `json:"objectsPerSec"` + Response madmin.Timings `json:"responseTime"` + Servers []ObjStatServer `json:"servers"` +} + +// ObjGETStats - GET stats of all the servers +type ObjGETStats struct { + ObjPUTStats + TTFB madmin.Timings `json:"ttfb,omitempty"` +} + func objectTestVerboseResult(result *madmin.SpeedTestResult) (msg string) { msg += "PUT:\n" for _, node := range result.PUTStats.Servers { @@ -136,12 +183,13 @@ func objectTestShortResult(result *madmin.SpeedTestResult) (msg string) { return msg } -func (p PerfTestResult) String() string { +// String - dummy function to confirm to the 'message' interface. Not used. +func (p PerfTestOutput) String() string { return "" } -// JSON - jsonified update message. -func (p PerfTestResult) JSON() string { +// JSON - jsonified output of the perf tests +func (p PerfTestOutput) JSON() string { JSONBytes, e := json.MarshalIndent(p, "", " ") fatalIf(probe.NewError(e), "Unable to marshal into JSON.") return string(JSONBytes) @@ -176,6 +224,94 @@ func mainSupportPerf(ctx *cli.Context) error { return nil } +func convertDriveTestResult(dr madmin.DriveSpeedTestResult) DriveTestResult { + return DriveTestResult{ + Endpoint: dr.Endpoint, + DrivePerf: dr.DrivePerf, + Error: dr.Error, + } +} + +func convertDriveTestResults(driveResults []madmin.DriveSpeedTestResult) []DriveTestResult { + results := []DriveTestResult{} + for _, dr := range driveResults { + results = append(results, convertDriveTestResult(dr)) + } + return results +} + +func convertObjStatServers(ss []madmin.SpeedTestStatServer) []ObjStatServer { + out := []ObjStatServer{} + for _, s := range ss { + out = append(out, ObjStatServer{ + Endpoint: s.Endpoint, + Throughput: s.ThroughputPerSec, + ObjectsPerSec: s.ObjectsPerSec, + Error: s.Err, + }) + } + return out +} + +func convertPUTStats(stats madmin.SpeedTestStats) ObjPUTStats { + return ObjPUTStats{ + Throughput: stats.ThroughputPerSec, + ObjectsPerSec: stats.ObjectsPerSec, + Response: stats.Response, + Servers: convertObjStatServers(stats.Servers), + } +} + +func convertGETStats(stats madmin.SpeedTestStats) ObjGETStats { + return ObjGETStats{ + ObjPUTStats: convertPUTStats(stats), + TTFB: stats.TTFB, + } +} + +func convertObjTestResult(objResult *madmin.SpeedTestResult) ObjTestResult { + if objResult == nil { + return ObjTestResult{} + } + result := ObjTestResult{ + Servers: objResult.Servers, + Drives: objResult.Disks, + Size: objResult.Size, + Concurrent: objResult.Concurrent, + } + result.PUTStats = convertPUTStats(objResult.PUTStats) + result.GETStats = convertGETStats(objResult.GETStats) + return result +} + +func updatePerfOutput(r PerfTestResult, out *PerfTestOutput) { + switch r.Type { + case DrivePerfTest: + out.DriveResult = convertDriveTestResults(r.DriveResult) + case ObjectPerfTest: + or := convertObjTestResult(r.ObjectResult) + out.ObjectResult = &or + case NetPerfTest: + out.NetResult = r.NetResult + default: + fatalIf(errDummy().Trace(), fmt.Sprintf("Invalid test type %d", r.Type)) + } +} + +func convertPerfResult(r PerfTestResult) PerfTestOutput { + out := PerfTestOutput{} + updatePerfOutput(r, &out) + return out +} + +func convertPerfResults(results []PerfTestResult) PerfTestOutput { + out := PerfTestOutput{} + for _, r := range results { + updatePerfOutput(r, &out) + } + return out +} + func execSupportPerf(ctx *cli.Context, aliasedURL string, perfType string) { alias, apiKey := initSubnetConnectivity(ctx, aliasedURL, true) if len(apiKey) == 0 { @@ -193,7 +329,7 @@ func execSupportPerf(ctx *cli.Context, aliasedURL string, perfType string) { resultFileName := resultFileNamePfx + ".json" regInfo := getClusterRegInfo(getAdminInfo(aliasedURL), alias) - tmpFileName, e := zipPerfResult(results, resultFileName, regInfo) + tmpFileName, e := zipPerfResult(convertPerfResults(results), resultFileName, regInfo) fatalIf(probe.NewError(e), "Error creating zip from perf test results:") if globalAirgapped { @@ -268,7 +404,7 @@ func writeJSONObjToZip(zipWriter *zip.Writer, obj interface{}, filename string) } // compress MinIO performance output -func zipPerfResult(perfResult []PerfTestResult, resultFilename string, regInfo ClusterRegistrationInfo) (string, error) { +func zipPerfResult(perfOutput PerfTestOutput, resultFilename string, regInfo ClusterRegistrationInfo) (string, error) { // Create profile zip file tmpArchive, e := os.CreateTemp("", "mc-perf-") @@ -280,7 +416,7 @@ func zipPerfResult(perfResult []PerfTestResult, resultFilename string, regInfo C zipWriter := zip.NewWriter(tmpArchive) defer zipWriter.Close() - e = writeJSONObjToZip(zipWriter, perfResult, resultFilename) + e = writeJSONObjToZip(zipWriter, perfOutput, resultFilename) if e != nil { return "", e }