mirror of
https://github.com/minio/mc.git
synced 2025-07-31 18:24:21 +03:00
Upload results of perf tests to SUBNET (#4254)
This commit is contained in:
@ -494,15 +494,15 @@ func getSubnetCreds(alias string) (string, string, error) {
|
|||||||
// getSubnetAPIKey - returns the SUBNET API key.
|
// getSubnetAPIKey - returns the SUBNET API key.
|
||||||
// Returns error if the cluster is not registered with SUBNET.
|
// Returns error if the cluster is not registered with SUBNET.
|
||||||
func getSubnetAPIKey(alias string) (string, error) {
|
func getSubnetAPIKey(alias string) (string, error) {
|
||||||
apiKey, _, e := getSubnetCreds(alias)
|
apiKey, lic, e := getSubnetCreds(alias)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return "", e
|
return "", e
|
||||||
}
|
}
|
||||||
if len(apiKey) > 0 {
|
if len(apiKey) == 0 && len(lic) == 0 {
|
||||||
return apiKey, nil
|
e = fmt.Errorf("Please register the cluster first by running 'mc license register %s'", alias)
|
||||||
}
|
|
||||||
e = fmt.Errorf("Please register the cluster first by running 'mc support register %s', or use --airgap flag", alias)
|
|
||||||
return "", e
|
return "", e
|
||||||
|
}
|
||||||
|
return apiKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSubnetAPIKeyUsingLicense(lic string) (string, error) {
|
func getSubnetAPIKeyUsingLicense(lic string) (string, error) {
|
||||||
|
@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/mc/pkg/probe"
|
"github.com/minio/mc/pkg/probe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error {
|
func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string, outCh chan<- PerfTestResult) error {
|
||||||
client, perr := newAdminClient(aliasedURL)
|
client, perr := newAdminClient(aliasedURL)
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
fatalIf(perr.Trace(aliasedURL), "Unable to initialize admin client.")
|
fatalIf(perr.Trace(aliasedURL), "Unable to initialize admin client.")
|
||||||
@ -103,10 +103,15 @@ func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
printMsg(PerfTestResult{
|
r := PerfTestResult{
|
||||||
Type: DrivePerfTest,
|
Type: DrivePerfTest,
|
||||||
Err: e.Error(),
|
Err: e.Error(),
|
||||||
})
|
Final: true,
|
||||||
|
}
|
||||||
|
p.Send(r)
|
||||||
|
if outCh != nil {
|
||||||
|
outCh <- r
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,11 +126,15 @@ func mainAdminSpeedTestDrive(ctx *cli.Context, aliasedURL string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Send(PerfTestResult{
|
r := PerfTestResult{
|
||||||
Type: DrivePerfTest,
|
Type: DrivePerfTest,
|
||||||
DriveResult: results,
|
DriveResult: results,
|
||||||
Final: true,
|
Final: true,
|
||||||
})
|
}
|
||||||
|
p.Send(r)
|
||||||
|
if outCh != nil {
|
||||||
|
outCh <- r
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
|
@ -28,7 +28,7 @@ import (
|
|||||||
"github.com/minio/mc/pkg/probe"
|
"github.com/minio/mc/pkg/probe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string) error {
|
func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string, outCh chan<- PerfTestResult) error {
|
||||||
client, perr := newAdminClient(aliasedURL)
|
client, perr := newAdminClient(aliasedURL)
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
fatalIf(perr.Trace(aliasedURL), "Unable to initialize admin client.")
|
fatalIf(perr.Trace(aliasedURL), "Unable to initialize admin client.")
|
||||||
@ -93,18 +93,26 @@ func mainAdminSpeedTestNetperf(ctx *cli.Context, aliasedURL string) error {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case e := <-errorCh:
|
case e := <-errorCh:
|
||||||
p.Send(PerfTestResult{
|
r := PerfTestResult{
|
||||||
Type: NetPerfTest,
|
Type: NetPerfTest,
|
||||||
Err: e.Error(),
|
Err: e.Error(),
|
||||||
Final: true,
|
Final: true,
|
||||||
})
|
}
|
||||||
|
p.Send(r)
|
||||||
|
if outCh != nil {
|
||||||
|
outCh <- r
|
||||||
|
}
|
||||||
return
|
return
|
||||||
case result := <-resultCh:
|
case result := <-resultCh:
|
||||||
p.Send(PerfTestResult{
|
r := PerfTestResult{
|
||||||
Type: NetPerfTest,
|
Type: NetPerfTest,
|
||||||
NetResult: &result,
|
NetResult: &result,
|
||||||
Final: true,
|
Final: true,
|
||||||
})
|
}
|
||||||
|
p.Send(r)
|
||||||
|
if outCh != nil {
|
||||||
|
outCh <- r
|
||||||
|
}
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
p.Send(PerfTestResult{
|
p.Send(PerfTestResult{
|
||||||
|
@ -46,7 +46,7 @@ func mainAdminSpeedtest(ctx *cli.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string) error {
|
func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string, outCh chan<- PerfTestResult) error {
|
||||||
client, perr := newAdminClient(aliasedURL)
|
client, perr := newAdminClient(aliasedURL)
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
fatalIf(perr.Trace(aliasedURL), "Unable to initialize admin client.")
|
fatalIf(perr.Trace(aliasedURL), "Unable to initialize admin client.")
|
||||||
@ -135,11 +135,15 @@ func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
p.Send(PerfTestResult{
|
r := PerfTestResult{
|
||||||
Type: ObjectPerfTest,
|
Type: ObjectPerfTest,
|
||||||
Err: e.Error(),
|
Err: e.Error(),
|
||||||
Final: true,
|
Final: true,
|
||||||
})
|
}
|
||||||
|
p.Send(r)
|
||||||
|
if outCh != nil {
|
||||||
|
outCh <- r
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,11 +154,15 @@ func mainAdminSpeedTestObject(ctx *cli.Context, aliasedURL string) error {
|
|||||||
ObjectResult: &result,
|
ObjectResult: &result,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
p.Send(PerfTestResult{
|
r := PerfTestResult{
|
||||||
Type: ObjectPerfTest,
|
Type: ObjectPerfTest,
|
||||||
ObjectResult: &result,
|
ObjectResult: &result,
|
||||||
Final: true,
|
Final: true,
|
||||||
})
|
}
|
||||||
|
p.Send(r)
|
||||||
|
if outCh != nil {
|
||||||
|
outCh <- r
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
|
@ -18,16 +18,22 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
gojson "encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
|
"github.com/fatih/color"
|
||||||
"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/madmin-go"
|
||||||
"github.com/minio/mc/pkg/probe"
|
"github.com/minio/mc/pkg/probe"
|
||||||
|
"github.com/minio/pkg/console"
|
||||||
)
|
)
|
||||||
|
|
||||||
var supportPerfFlags = []cli.Flag{
|
var supportPerfFlags = append([]cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "duration",
|
Name: "duration",
|
||||||
Usage: "duration the entire perf tests are run",
|
Usage: "duration the entire perf tests are run",
|
||||||
@ -72,7 +78,7 @@ var supportPerfFlags = []cli.Flag{
|
|||||||
Usage: "run tests on drive(s) one-by-one",
|
Usage: "run tests on drive(s) one-by-one",
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
},
|
},
|
||||||
}
|
}, subnetCommonFlags...)
|
||||||
|
|
||||||
var supportPerfCmd = cli.Command{
|
var supportPerfCmd = cli.Command{
|
||||||
Name: "perf",
|
Name: "perf",
|
||||||
@ -146,6 +152,7 @@ func mainSupportPerf(ctx *cli.Context) error {
|
|||||||
|
|
||||||
// the alias parameter from cli
|
// the alias parameter from cli
|
||||||
aliasedURL := ""
|
aliasedURL := ""
|
||||||
|
perfType := ""
|
||||||
switch len(args) {
|
switch len(args) {
|
||||||
case 1:
|
case 1:
|
||||||
// cannot use alias by the name 'drive' or 'net'
|
// cannot use alias by the name 'drive' or 'net'
|
||||||
@ -154,24 +161,143 @@ func mainSupportPerf(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
aliasedURL = args[0]
|
aliasedURL = args[0]
|
||||||
|
|
||||||
mainAdminSpeedTestNetperf(ctx, aliasedURL)
|
|
||||||
mainAdminSpeedTestDrive(ctx, aliasedURL)
|
|
||||||
mainAdminSpeedTestObject(ctx, aliasedURL)
|
|
||||||
case 2:
|
case 2:
|
||||||
aliasedURL := args[1]
|
perfType = args[0]
|
||||||
switch args[0] {
|
aliasedURL = args[1]
|
||||||
case "drive":
|
|
||||||
return mainAdminSpeedTestDrive(ctx, aliasedURL)
|
|
||||||
case "object":
|
|
||||||
return mainAdminSpeedTestObject(ctx, aliasedURL)
|
|
||||||
case "net":
|
|
||||||
return mainAdminSpeedTestNetperf(ctx, aliasedURL)
|
|
||||||
default:
|
|
||||||
cli.ShowCommandHelpAndExit(ctx, "perf", 1) // last argument is exit code
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
cli.ShowCommandHelpAndExit(ctx, "perf", 1) // last argument is exit code
|
cli.ShowCommandHelpAndExit(ctx, "perf", 1) // last argument is exit code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main execution
|
||||||
|
execSupportPerf(ctx, aliasedURL, perfType)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func execSupportPerf(ctx *cli.Context, aliasedURL string, perfType string) {
|
||||||
|
alias, apiKey := initSubnetConnectivity(ctx, aliasedURL)
|
||||||
|
|
||||||
|
if len(apiKey) == 0 {
|
||||||
|
// api key not passed as flag. check if it's available in the config
|
||||||
|
var e error
|
||||||
|
apiKey, e = getSubnetAPIKey(alias)
|
||||||
|
|
||||||
|
// Non-registered execution allowed only in debug+airgapped mode
|
||||||
|
if !(globalDebug && globalAirgapped) {
|
||||||
|
fatalIf(probe.NewError(e), "Unable to retrieve SUBNET API key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results := runPerfTests(ctx, aliasedURL, perfType)
|
||||||
|
if globalJSON {
|
||||||
|
// No file to be saved or uploaded to SUBNET in case of `--json`
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultFileNamePfx := fmt.Sprintf("%s-perf_%s", filepath.Clean(alias), UTCNow().Format("20060102150405"))
|
||||||
|
resultFileName := resultFileNamePfx + ".json"
|
||||||
|
|
||||||
|
regInfo := getClusterRegInfo(getAdminInfo(aliasedURL), alias)
|
||||||
|
tmpFileName, e := zipPerfResult(results, resultFileName, regInfo)
|
||||||
|
fatalIf(probe.NewError(e), "Error creating zip from perf test results:")
|
||||||
|
|
||||||
|
if globalAirgapped {
|
||||||
|
savePerfResultFile(tmpFileName, resultFileNamePfx, alias)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadURL := subnetUploadURL("perf", tmpFileName)
|
||||||
|
reqURL, headers := prepareSubnetUploadURL(uploadURL, alias, tmpFileName, apiKey)
|
||||||
|
|
||||||
|
_, e = uploadFileToSubnet(alias, tmpFileName, reqURL, headers)
|
||||||
|
if e != nil {
|
||||||
|
console.Errorln("Unable to upload perf test results to SUBNET portal: " + e.Error())
|
||||||
|
savePerfResultFile(tmpFileName, resultFileNamePfx, alias)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clr := color.New(color.FgGreen, color.Bold)
|
||||||
|
clr.Println("uploaded successfully to SUBNET.")
|
||||||
|
|
||||||
|
if len(apiKey) > 0 {
|
||||||
|
setSubnetAPIKey(alias, apiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func savePerfResultFile(tmpFileName string, resultFileNamePfx string, alias string) {
|
||||||
|
zipFileName := resultFileNamePfx + ".zip"
|
||||||
|
e := moveFile(tmpFileName, zipFileName)
|
||||||
|
fatalIf(probe.NewError(e), fmt.Sprintf("Error moving temp file %s to %s:", tmpFileName, zipFileName))
|
||||||
|
console.Infoln("MinIO performance report saved at", zipFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPerfTests(ctx *cli.Context, aliasedURL string, perfType string) []PerfTestResult {
|
||||||
|
resultCh := make(chan PerfTestResult)
|
||||||
|
results := []PerfTestResult{}
|
||||||
|
defer close(resultCh)
|
||||||
|
|
||||||
|
tests := []string{perfType}
|
||||||
|
if len(perfType) == 0 {
|
||||||
|
// by default run all tests
|
||||||
|
tests = []string{"net", "drive", "object"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range tests {
|
||||||
|
switch t {
|
||||||
|
case "drive":
|
||||||
|
mainAdminSpeedTestDrive(ctx, aliasedURL, resultCh)
|
||||||
|
case "object":
|
||||||
|
mainAdminSpeedTestObject(ctx, aliasedURL, resultCh)
|
||||||
|
case "net":
|
||||||
|
mainAdminSpeedTestNetperf(ctx, aliasedURL, resultCh)
|
||||||
|
default:
|
||||||
|
cli.ShowCommandHelpAndExit(ctx, "perf", 1) // last argument is exit code
|
||||||
|
}
|
||||||
|
|
||||||
|
if !globalJSON {
|
||||||
|
results = append(results, <-resultCh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSONObjToZip(zipWriter *zip.Writer, obj interface{}, filename string) error {
|
||||||
|
writer, e := zipWriter.Create(filename)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := gojson.NewEncoder(writer)
|
||||||
|
if e = enc.Encode(obj); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compress MinIO performance output
|
||||||
|
func zipPerfResult(perfResult []PerfTestResult, resultFilename string, regInfo ClusterRegistrationInfo) (string, error) {
|
||||||
|
// Create profile zip file
|
||||||
|
tmpArchive, e := os.CreateTemp("", "mc-perf-")
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
defer tmpArchive.Close()
|
||||||
|
|
||||||
|
zipWriter := zip.NewWriter(tmpArchive)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
|
||||||
|
e = writeJSONObjToZip(zipWriter, perfResult, resultFilename)
|
||||||
|
if e != nil {
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
|
||||||
|
e = writeJSONObjToZip(zipWriter, regInfo, "cluster.info")
|
||||||
|
if e != nil {
|
||||||
|
return "", e
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpArchive.Name(), nil
|
||||||
|
}
|
||||||
|
@ -170,11 +170,6 @@ func mainSupportProfile(ctx *cli.Context) error {
|
|||||||
aliasedURL := ctx.Args().Get(0)
|
aliasedURL := ctx.Args().Get(0)
|
||||||
alias, apiKey := initSubnetConnectivity(ctx, aliasedURL)
|
alias, apiKey := initSubnetConnectivity(ctx, aliasedURL)
|
||||||
|
|
||||||
// if `--airgap` is provided do not try to upload to SUBNET.
|
|
||||||
if !globalAirgapped {
|
|
||||||
fatalIf(checkURLReachable(subnetBaseURL()).Trace(aliasedURL), "Unable to reach %s to upload MinIO profile file, please use --airgap to upload manually", subnetBaseURL())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new MinIO Admin Client
|
// Create a new MinIO Admin Client
|
||||||
client := getClient(aliasedURL)
|
client := getClient(aliasedURL)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user