diff --git a/cmd/admin-top-api.go b/cmd/admin-top-api.go index 85f2737e..595e7d46 100644 --- a/cmd/admin-top-api.go +++ b/cmd/admin-top-api.go @@ -18,13 +18,8 @@ package cmd import ( - "context" - "os" - - tea "github.com/charmbracelet/bubbletea" "github.com/minio/cli" - "github.com/minio/madmin-go" - "github.com/minio/mc/pkg/probe" + "github.com/minio/pkg/console" ) var adminTopAPIFlags = []cli.Flag{ @@ -54,83 +49,11 @@ var adminTopAPICmd = cli.Command{ Before: setGlobalsFromContext, Flags: append(adminTopAPIFlags, globalFlags...), HideHelpCommand: true, - CustomHelpTemplate: `NAME: - {{.HelpName}} - {{.Usage}} - -USAGE: - {{.HelpName}} [FLAGS] TARGET - -FLAGS: - {{range .VisibleFlags}}{{.}} - {{end}} -EXAMPLES: - 1. Display current in-progress all S3 API calls. - {{.Prompt}} {{.HelpName}} myminio/ - - 2. Display current in-progress all 's3.PutObject' API calls. - {{.Prompt}} {{.HelpName}} --name s3.PutObject myminio/ + CustomHelpTemplate: `Please use 'mc support top api' `, } -// checkAdminTopAPISyntax - validate all the passed arguments -func checkAdminTopAPISyntax(ctx *cli.Context) { - if len(ctx.Args()) == 0 || len(ctx.Args()) > 1 { - cli.ShowCommandHelpAndExit(ctx, "api", 1) // last argument is exit code - } -} - func mainAdminTopAPI(ctx *cli.Context) error { - checkAdminTopAPISyntax(ctx) - - aliasedURL := ctx.Args().Get(0) - - // Create a new MinIO Admin Client - client, err := newAdminClient(aliasedURL) - if err != nil { - fatalIf(err.Trace(aliasedURL), "Unable to initialize admin client.") - return nil - } - - ctxt, cancel := context.WithCancel(globalContext) - defer cancel() - - opts, e := tracingOpts(ctx, ctx.StringSlice("call")) - fatalIf(probe.NewError(e), "Unable to start tracing") - - mopts := matchOpts{ - funcNames: ctx.StringSlice("name"), - apiPaths: ctx.StringSlice("path"), - nodes: ctx.StringSlice("node"), - } - - // Start listening on all trace activity. - traceCh := client.ServiceTrace(ctxt, opts) - done := make(chan struct{}) - - p := tea.NewProgram(initTraceUI()) - go func() { - if e := p.Start(); e != nil { - os.Exit(1) - } - close(done) - }() - - go func() { - for apiCallInfo := range traceCh { - if apiCallInfo.Err != nil { - fatalIf(probe.NewError(apiCallInfo.Err), "Unable to fetch top API events") - } - if matchTrace(mopts, apiCallInfo) { - p.Send(topAPIResult{ - apiCallInfo: apiCallInfo, - }) - } - p.Send(topAPIResult{ - apiCallInfo: madmin.ServiceTraceInfo{}, - }) - } - }() - - <-done + console.Infoln("Please use 'mc support top api'") return nil } diff --git a/cmd/admin-top-locks.go b/cmd/admin-top-locks.go index 56c5ba99..1a33abcb 100644 --- a/cmd/admin-top-locks.go +++ b/cmd/admin-top-locks.go @@ -18,14 +18,7 @@ package cmd import ( - "fmt" - "time" - - "github.com/fatih/color" "github.com/minio/cli" - json "github.com/minio/colorjson" - "github.com/minio/madmin-go" - "github.com/minio/mc/pkg/probe" "github.com/minio/pkg/console" ) @@ -49,117 +42,11 @@ var adminTopLocksCmd = cli.Command{ Action: mainAdminTopLocks, OnUsageError: onUsageError, Flags: append(globalFlags, topLocksFlag...), - CustomHelpTemplate: `NAME: - {{.HelpName}} - {{.Usage}} - -USAGE: - {{.HelpName}} TARGET - -FLAGS: - {{range .VisibleFlags}}{{.}} - {{end}} -EXAMPLES: - 1. Get a list of the 10 oldest locks on a MinIO cluster. - {{.Prompt}} {{.HelpName}} myminio/ + CustomHelpTemplate: `Please use 'mc support top locks' `, } -// lockMessage struct to list lock information. -type lockMessage struct { - Status string `json:"status"` - Lock madmin.LockEntry `json:"locks"` -} - -func getTimeDiff(timeStamp time.Time) (string, string) { - now := time.Now().UTC() - diff := now.Sub(timeStamp) - hours := int(diff.Hours()) - minutes := int(diff.Minutes()) % 60 - seconds := int(diff.Seconds()) % 60 - if hours == 0 { - if minutes == 0 { - return "Lock", fmt.Sprint(seconds, " seconds") - } - return "Lock", fmt.Sprint(minutes, " minutes") - } - return "StaleLock", fmt.Sprint(hours, " hours") -} - -// String colorized oldest locks message. -func (u lockMessage) String() string { - const ( - timeFieldMaxLen = 20 - resourceFieldMaxLen = -1 - typeFieldMaxLen = 6 - ) - - lockState, timeDiff := getTimeDiff(u.Lock.Timestamp) - return console.Colorize(lockState, newPrettyTable(" ", - Field{"Time", timeFieldMaxLen}, - Field{"Type", typeFieldMaxLen}, - Field{"Resource", resourceFieldMaxLen}, - ).buildRow(timeDiff, u.Lock.Type, u.Lock.Resource)) -} - -// JSON jsonified top oldest locks message. -func (u lockMessage) JSON() string { - u.Status = "success" - statusJSONBytes, e := json.MarshalIndent(u, "", " ") - fatalIf(probe.NewError(e), "Unable to marshal into JSON.") - return string(statusJSONBytes) -} - -// checkAdminTopLocksSyntax - validate all the passed arguments -func checkAdminTopLocksSyntax(ctx *cli.Context) { - if len(ctx.Args()) == 0 || len(ctx.Args()) > 1 { - cli.ShowCommandHelpAndExit(ctx, "locks", 1) // last argument is exit code - } -} - func mainAdminTopLocks(ctx *cli.Context) error { - checkAdminTopLocksSyntax(ctx) - - // Get the alias parameter from cli - args := ctx.Args() - aliasedURL := args.Get(0) - - // Create a new MinIO Admin Client - client, err := newAdminClient(aliasedURL) - fatalIf(err, "Unable to initialize admin connection.") - - // Call top locks API - entries, e := client.TopLocksWithOpts(globalContext, madmin.TopLockOpts{ - Count: ctx.Int("count"), - Stale: ctx.Bool("stale"), - }) - fatalIf(probe.NewError(e), "Unable to get server locks list.") - - console.SetColor("StaleLock", color.New(color.FgRed, color.Bold)) - console.SetColor("Lock", color.New(color.FgBlue, color.Bold)) - console.SetColor("Headers", color.New(color.FgGreen, color.Bold)) - - // Print - printLocks(entries) + console.Infoln("Please use 'mc support top locks'") return nil } - -func printHeaders() { - timeFieldMaxLen := 20 - resourceFieldMaxLen := -1 - typeFieldMaxLen := 6 - console.Println(console.Colorize("Headers", newPrettyTable(" ", - Field{"Time", timeFieldMaxLen}, - Field{"Type", typeFieldMaxLen}, - Field{"Resource", resourceFieldMaxLen}, - ).buildRow("Time", "Type", "Resource"))) -} - -// Prints oldest locks. -func printLocks(locks madmin.LockEntries) { - if !globalJSON { - printHeaders() - } - for _, entry := range locks { - printMsg(lockMessage{Lock: entry}) - } -} diff --git a/cmd/auto-complete.go b/cmd/auto-complete.go index db4ed910..582c8edc 100644 --- a/cmd/auto-complete.go +++ b/cmd/auto-complete.go @@ -434,6 +434,8 @@ var completeCmds = map[string]complete.Predictor{ "/support/perf": aliasCompleter, "/support/metrics": aliasCompleter, "/support/status": aliasCompleter, + "/support/top/locks": aliasCompleter, + "/support/top/api": aliasCompleter, "/license/register": aliasCompleter, "/license/info": aliasCompleter, diff --git a/cmd/support-top-api.go b/cmd/support-top-api.go new file mode 100644 index 00000000..1999f49e --- /dev/null +++ b/cmd/support-top-api.go @@ -0,0 +1,136 @@ +// Copyright (c) 2015-2022 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "context" + "os" + + tea "github.com/charmbracelet/bubbletea" + "github.com/minio/cli" + "github.com/minio/madmin-go" + "github.com/minio/mc/pkg/probe" +) + +var supportTopAPIFlags = []cli.Flag{ + cli.StringSliceFlag{ + Name: "name", + Usage: "summarize current calls for matching API name", + }, + cli.StringSliceFlag{ + Name: "path", + Usage: "summarize current API calls only on matching path", + }, + cli.StringSliceFlag{ + Name: "node", + Usage: "summarize current API calls only on matching servers", + }, + cli.BoolFlag{ + Name: "errors, e", + Usage: "summarize current API calls throwing only errors", + }, +} + +var supportTopAPICmd = cli.Command{ + Name: "api", + Usage: "summarize API events on MinIO server in real-time", + Action: mainSupportTopAPI, + OnUsageError: onUsageError, + Before: setGlobalsFromContext, + Flags: append(supportTopAPIFlags, globalFlags...), + HideHelpCommand: true, + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} [FLAGS] TARGET + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +EXAMPLES: + 1. Display current in-progress all S3 API calls. + {{.Prompt}} {{.HelpName}} myminio/ + + 2. Display current in-progress all 's3.PutObject' API calls. + {{.Prompt}} {{.HelpName}} --name s3.PutObject myminio/ +`, +} + +// checkSupportTopAPISyntax - validate all the passed arguments +func checkSupportTopAPISyntax(ctx *cli.Context) { + if len(ctx.Args()) == 0 || len(ctx.Args()) > 1 { + cli.ShowCommandHelpAndExit(ctx, "api", 1) // last argument is exit code + } +} + +func mainSupportTopAPI(ctx *cli.Context) error { + checkSupportTopAPISyntax(ctx) + + aliasedURL := ctx.Args().Get(0) + + // Create a new MinIO Admin Client + client, err := newAdminClient(aliasedURL) + if err != nil { + fatalIf(err.Trace(aliasedURL), "Unable to initialize admin client.") + return nil + } + + ctxt, cancel := context.WithCancel(globalContext) + defer cancel() + + opts, e := tracingOpts(ctx, ctx.StringSlice("call")) + fatalIf(probe.NewError(e), "Unable to start tracing") + + mopts := matchOpts{ + funcNames: ctx.StringSlice("name"), + apiPaths: ctx.StringSlice("path"), + nodes: ctx.StringSlice("node"), + } + + // Start listening on all trace activity. + traceCh := client.ServiceTrace(ctxt, opts) + done := make(chan struct{}) + + p := tea.NewProgram(initTraceUI()) + go func() { + if e := p.Start(); e != nil { + os.Exit(1) + } + close(done) + }() + + go func() { + for apiCallInfo := range traceCh { + if apiCallInfo.Err != nil { + fatalIf(probe.NewError(apiCallInfo.Err), "Unable to fetch top API events") + } + if matchTrace(mopts, apiCallInfo) { + p.Send(topAPIResult{ + apiCallInfo: apiCallInfo, + }) + } + p.Send(topAPIResult{ + apiCallInfo: madmin.ServiceTraceInfo{}, + }) + } + }() + + <-done + return nil +} diff --git a/cmd/support-top-locks.go b/cmd/support-top-locks.go new file mode 100644 index 00000000..abb1c834 --- /dev/null +++ b/cmd/support-top-locks.go @@ -0,0 +1,164 @@ +// Copyright (c) 2015-2021 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import ( + "fmt" + "time" + + "github.com/fatih/color" + "github.com/minio/cli" + json "github.com/minio/colorjson" + "github.com/minio/madmin-go" + "github.com/minio/mc/pkg/probe" + "github.com/minio/pkg/console" +) + +var supportTopLocksFlag = []cli.Flag{ + cli.BoolFlag{ + Name: "stale", + Usage: "list stale locks ask", + }, + cli.IntFlag{ + Name: "count", + Usage: "number of top locks", + Hidden: true, + Value: 10, + }, +} + +var supportTopLocksCmd = cli.Command{ + Name: "locks", + Usage: "get a list of the 10 oldest locks on a MinIO cluster.", + Before: setGlobalsFromContext, + Action: mainSupportTopLocks, + OnUsageError: onUsageError, + Flags: append(supportTopLocksFlag, globalFlags...), + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} TARGET + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +EXAMPLES: + 1. Get a list of the 10 oldest locks on a MinIO cluster. + {{.Prompt}} {{.HelpName}} myminio/ +`, +} + +// lockMessage struct to list lock information. +type lockMessage struct { + Status string `json:"status"` + Lock madmin.LockEntry `json:"locks"` +} + +func getTimeDiff(timeStamp time.Time) (string, string) { + now := time.Now().UTC() + diff := now.Sub(timeStamp) + hours := int(diff.Hours()) + minutes := int(diff.Minutes()) % 60 + seconds := int(diff.Seconds()) % 60 + if hours == 0 { + if minutes == 0 { + return "Lock", fmt.Sprint(seconds, " seconds") + } + return "Lock", fmt.Sprint(minutes, " minutes") + } + return "StaleLock", fmt.Sprint(hours, " hours") +} + +// String colorized oldest locks message. +func (u lockMessage) String() string { + const ( + timeFieldMaxLen = 20 + resourceFieldMaxLen = -1 + typeFieldMaxLen = 6 + ) + + lockState, timeDiff := getTimeDiff(u.Lock.Timestamp) + return console.Colorize(lockState, newPrettyTable(" ", + Field{"Time", timeFieldMaxLen}, + Field{"Type", typeFieldMaxLen}, + Field{"Resource", resourceFieldMaxLen}, + ).buildRow(timeDiff, u.Lock.Type, u.Lock.Resource)) +} + +// JSON jsonified top oldest locks message. +func (u lockMessage) JSON() string { + u.Status = "success" + statusJSONBytes, e := json.MarshalIndent(u, "", " ") + fatalIf(probe.NewError(e), "Unable to marshal into JSON.") + return string(statusJSONBytes) +} + +// checkAdminTopLocksSyntax - validate all the passed arguments +func checkSupportTopLocksSyntax(ctx *cli.Context) { + if len(ctx.Args()) == 0 || len(ctx.Args()) > 1 { + cli.ShowCommandHelpAndExit(ctx, "locks", 1) // last argument is exit code + } +} + +func mainSupportTopLocks(ctx *cli.Context) error { + checkSupportTopLocksSyntax(ctx) + // Get the alias parameter from cli + args := ctx.Args() + aliasedURL := args.Get(0) + + // Create a new MinIO Admin Client + client, err := newAdminClient(aliasedURL) + fatalIf(err, "Unable to initialize admin connection.") + + // Call top locks API + entries, e := client.TopLocksWithOpts(globalContext, madmin.TopLockOpts{ + Count: ctx.Int("count"), + Stale: ctx.Bool("stale"), + }) + fatalIf(probe.NewError(e), "Unable to get server locks list.") + + console.SetColor("StaleLock", color.New(color.FgRed, color.Bold)) + console.SetColor("Lock", color.New(color.FgBlue, color.Bold)) + console.SetColor("Headers", color.New(color.FgGreen, color.Bold)) + + // Print + printLocks(entries) + return nil +} + +func printHeaders() { + timeFieldMaxLen := 20 + resourceFieldMaxLen := -1 + typeFieldMaxLen := 6 + console.Println(console.Colorize("Headers", newPrettyTable(" ", + Field{"Time", timeFieldMaxLen}, + Field{"Type", typeFieldMaxLen}, + Field{"Resource", resourceFieldMaxLen}, + ).buildRow("Time", "Type", "Resource"))) +} + +// Prints oldest locks. +func printLocks(locks madmin.LockEntries) { + if !globalJSON { + printHeaders() + } + for _, entry := range locks { + printMsg(lockMessage{Lock: entry}) + } +} diff --git a/cmd/support-top.go b/cmd/support-top.go new file mode 100644 index 00000000..92646964 --- /dev/null +++ b/cmd/support-top.go @@ -0,0 +1,42 @@ +// Copyright (c) 2015-2021 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cmd + +import "github.com/minio/cli" + +var supportTopSubcommands = []cli.Command{ + supportTopAPICmd, + supportTopLocksCmd, +} + +var supportTopCmd = cli.Command{ + Name: "top", + Usage: "provide top like statistics for MinIO", + Action: mainSupportTop, + Before: setGlobalsFromContext, + Flags: globalFlags, + Subcommands: supportTopSubcommands, + HideHelpCommand: true, +} + +// mainSupportTop is the handle for "mc support top" command. +func mainSupportTop(ctx *cli.Context) error { + commandNotFound(ctx, supportTopSubcommands) + return nil + // Sub-commands like "locks" have their own main. +} diff --git a/cmd/support.go b/cmd/support.go index 2cc725dd..f0dd33b9 100644 --- a/cmd/support.go +++ b/cmd/support.go @@ -37,6 +37,7 @@ var supportSubcommands = []cli.Command{ supportPerfCmd, supportInspectCmd, supportProfileCmd, + supportTopCmd, } var supportCmd = cli.Command{ diff --git a/docs/minio-admin-complete-guide.md b/docs/minio-admin-complete-guide.md index 7951d7c4..f1365a47 100644 --- a/docs/minio-admin-complete-guide.md +++ b/docs/minio-admin-complete-guide.md @@ -17,7 +17,6 @@ prometheus manages prometheus config kms perform KMS management operations bucket manage buckets defined in the MinIO server tier manage remote tier targets for ILM transition -top provide top like statistics for MinIO trace show http trace for MinIO server console show console logs for MinIO server ``` @@ -647,23 +646,6 @@ mc admin config import myminio < /tmp/my-serverconfig Healing is automatic on server side which runs on a continuous basis on a low priority thread, `mc admin heal` is deprecated and will be removed in future. - -### Command `top` - provide top like statistics for MinIO -NOTE: This command is only applicable for a distributed MinIO setup. It is not supported on single node and gateway deployments. - -``` -NAME: - mc admin top - provide top like statistics for MinIO - -COMMANDS: - locks Get a list of the 10 oldest locks on a MinIO cluster. -``` - -*Example: Get a list of the 10 oldest locks on a distributed MinIO cluster, where 'myminio' is the MinIO cluster alias.* - -``` -mc admin top locks myminio -``` ### Command `trace` - Show http trace for MinIO server diff --git a/docs/minio-client-complete-guide.md b/docs/minio-client-complete-guide.md index b6d4cb51..3b78539c 100644 --- a/docs/minio-client-complete-guide.md +++ b/docs/minio-client-complete-guide.md @@ -1925,6 +1925,7 @@ NAME: mc support inspect upload raw object contents for analysis mc support profile generate profile data for debugging mc support logs configure/display MinIO console logs + mc support top provide top like statistics for MinIO ``` @@ -1968,6 +1969,16 @@ Enable logs for cluster with alias 'play' mc support logs enable play ``` +Get a list of the 10 oldest locks on a distributed MinIO cluster, where 'myminio' is the MinIO cluster alias.* +``` +mc admin top locks myminio +``` + +Display current in-progress all 's3.PutObject' API calls. +``` +mc support top api --name s3.PutObject myminio/ +``` + ### Command `ping` diff --git a/go.sum b/go.sum index 0ea5922d..62e55640 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -272,6 +273,7 @@ github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=