diff --git a/cmd/admin-main.go b/cmd/admin-main.go index 143bbd7951..387722031c 100644 --- a/cmd/admin-main.go +++ b/cmd/admin-main.go @@ -37,6 +37,7 @@ var adminCmd = cli.Command{ adminCredsCmd, adminConfigCmd, adminHealCmd, + adminProfilingCmd, }, } diff --git a/cmd/admin-profiling-start.go b/cmd/admin-profiling-start.go new file mode 100644 index 0000000000..14da04bbc3 --- /dev/null +++ b/cmd/admin-profiling-start.go @@ -0,0 +1,111 @@ +/* + * Minio Client (C) 2018 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "strings" + + "github.com/minio/cli" + "github.com/minio/mc/pkg/console" + "github.com/minio/mc/pkg/probe" + "github.com/minio/minio/pkg/madmin" +) + +var adminProfilingStartFlags = []cli.Flag{ + cli.StringFlag{ + Name: "type", + Usage: "Profiler type, possible values are: `cpu`, `mem`, `block`, `mutex` and `trace`", + Value: "mem", + }, +} + +var adminProfilingStartCmd = cli.Command{ + Name: "start", + Usage: "Start recording profiling data", + Action: mainAdminProfilingStart, + Before: setGlobalsFromContext, + Flags: append(adminProfilingStartFlags, globalFlags...), + HideHelpCommand: true, + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} [FLAGS] TARGET + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +EXAMPLES: + 1. Start CPU profiling + $ {{.HelpName}} --type cpu myminio/ + +`, +} + +func checkAdminProfilingStartSyntax(ctx *cli.Context) { + // Check flags combinations + if len(ctx.Args()) != 1 { + cli.ShowCommandHelpAndExit(ctx, "start", 1) // last argument is exit code + } + + profilerTypes := []madmin.ProfilerType{ + madmin.ProfilerCPU, + madmin.ProfilerMEM, + madmin.ProfilerBlock, + madmin.ProfilerMutex, + madmin.ProfilerTrace, + } + + // Check if the provided profiler type is known and supported + supportedProfiler := false + profilerType := strings.ToLower(ctx.String("type")) + for _, profiler := range profilerTypes { + if profilerType == string(profiler) { + supportedProfiler = true + break + } + } + if !supportedProfiler { + fatalIf(errDummy(), "Profiler type unrecognized. Possible values are: %v.", profilerTypes) + } +} + +// mainAdminProfilingStart - the entry function of profiling command +func mainAdminProfilingStart(ctx *cli.Context) error { + // Check for command syntax + checkAdminProfilingStartSyntax(ctx) + + // Get the alias parameter from cli + args := ctx.Args() + aliasedURL := args.Get(0) + + profilerType := ctx.String("type") + + // Create a new Minio Admin Client + client, err := newAdminClient(aliasedURL) + if err != nil { + fatalIf(err.Trace(aliasedURL), "Cannot initialize admin client.") + return nil + } + + // Start profiling + _, cmdErr := client.StartProfiling(madmin.ProfilerType(profilerType)) + fatalIf(probe.NewError(cmdErr), "Unable to start profiling.") + + console.Infoln("Profiling data successfully started.") + return nil +} diff --git a/cmd/admin-profiling-stop.go b/cmd/admin-profiling-stop.go new file mode 100644 index 0000000000..0ab4b3dbdb --- /dev/null +++ b/cmd/admin-profiling-stop.go @@ -0,0 +1,106 @@ +/* + * Minio Client (C) 2018 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "io" + "io/ioutil" + "os" + "time" + + "github.com/minio/cli" + "github.com/minio/mc/pkg/console" + "github.com/minio/mc/pkg/probe" +) + +var adminProfilingStopCmd = cli.Command{ + Name: "stop", + Usage: "Stop and download profiling data", + Action: mainAdminProfilingStop, + Before: setGlobalsFromContext, + Flags: globalFlags, + HideHelpCommand: true, + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} [FLAGS] TARGET + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +EXAMPLES: + 2. Download latest profiling data in the current directory + $ {{.HelpName}} myminio/ +`, +} + +func checkAdminProfilingStopSyntax(ctx *cli.Context) { + if len(ctx.Args()) != 1 { + cli.ShowCommandHelpAndExit(ctx, "stop", 1) // last argument is exit code + } +} + +// mainAdminProfilingStop - the entry function of profiling stop command +func mainAdminProfilingStop(ctx *cli.Context) error { + // Check for command syntax + checkAdminProfilingStopSyntax(ctx) + + // Get the alias parameter from cli + args := ctx.Args() + aliasedURL := args.Get(0) + + // Create a new Minio Admin Client + client, err := newAdminClient(aliasedURL) + if err != nil { + fatalIf(err.Trace(aliasedURL), "Cannot initialize admin client.") + return nil + } + + // Create profiling zip file + tmpFile, e := ioutil.TempFile("", "mc-profiling-") + fatalIf(probe.NewError(e), "Unable to download profiling data.") + + // Ask for profiling data, which will come compressed with zip format + zippedData, adminErr := client.DownloadProfilingData() + fatalIf(probe.NewError(adminErr), "Unable to download profiling data.") + + // Copy zip content to target download file + _, e = io.Copy(tmpFile, zippedData) + fatalIf(probe.NewError(e), "Unable to download profiling data.") + + // Close everything + zippedData.Close() + tmpFile.Close() + + downloadPath := "profiling.zip" + + fi, e := os.Stat(downloadPath) + if e == nil && !fi.IsDir() { + e = os.Rename(downloadPath, downloadPath+"."+time.Now().Format("2006-01-02T15:04:05.999999-07:00")) + fatalIf(probe.NewError(e), "Unable to create a backup of profiling.zip") + } else { + if !os.IsNotExist(e) { + fatal(probe.NewError(e), "Unable to download profiling data.") + } + } + + fatalIf(probe.NewError(os.Rename(tmpFile.Name(), downloadPath)), "Unable to download profiling data.") + + console.Infof("Profiling data successfully downloaded as %s\n", downloadPath) + return nil +} diff --git a/cmd/admin-profiling.go b/cmd/admin-profiling.go new file mode 100644 index 0000000000..6fe7d59e10 --- /dev/null +++ b/cmd/admin-profiling.go @@ -0,0 +1,40 @@ +/* + * Minio Client (C) 2018 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "github.com/minio/cli" +) + +var adminProfilingCmd = cli.Command{ + Name: "profiling", + Usage: "Generate profiling data for debugging purposes", + Action: mainAdminProfiling, + Before: setGlobalsFromContext, + Flags: globalFlags, + Subcommands: []cli.Command{ + adminProfilingStartCmd, + adminProfilingStopCmd, + }, + HideHelpCommand: true, +} + +// mainAdminProfiling is the handle for "mc admin profiling" command. +func mainAdminProfiling(ctx *cli.Context) error { + cli.ShowCommandHelp(ctx, ctx.Args().First()) + return nil +} diff --git a/cmd/error.go b/cmd/error.go index 30c4976bb2..f4572e42c3 100644 --- a/cmd/error.go +++ b/cmd/error.go @@ -45,6 +45,10 @@ func fatalIf(err *probe.Error, msg string, data ...interface{}) { if err == nil { return } + fatal(err, msg, data...) +} + +func fatal(err *probe.Error, msg string, data ...interface{}) { if globalJSON { errorMsg := errorMessage{ Message: msg, diff --git a/vendor/github.com/minio/minio/pkg/madmin/API.md b/vendor/github.com/minio/minio/pkg/madmin/API.md index 75469df5c5..23aa8e989c 100644 --- a/vendor/github.com/minio/minio/pkg/madmin/API.md +++ b/vendor/github.com/minio/minio/pkg/madmin/API.md @@ -36,12 +36,12 @@ func main() { ``` -| Service operations | Info operations | Healing operations | Config operations | IAM operations | Misc | -|:----------------------------|:----------------------------|:--------------------------------------|:--------------------------|:------------------------------------|:------------------------------------| -| [`ServiceStatus`](#ServiceStatus) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`AddUser`](#AddUser) | [`SetAdminCredentials`](#SetAdminCredentials) | -| [`ServiceSendAction`](#ServiceSendAction) | | | [`SetConfig`](#SetConfig) | [`SetUserPolicy`](#SetUserPolicy) | [`StartProfiling`](#StartProfiling) | -| | | | [`GetConfigKeys`](#GetConfigKeys) | [`ListUsers`](#ListUsers) | [`DownloadProfilingData`](#DownloadProfilingData) | -| | | | [`SetConfigKeys`](#SetConfigKeys) | [`AddCannedPolicy`](#AddCannedPolicy) | | +| Service operations | Info operations | Healing operations | Config operations | Misc | +|:----------------------------|:----------------------------|:--------------------------------------|:--------------------------|:------------------------------------| +| [`ServiceStatus`](#ServiceStatus) | [`ServerInfo`](#ServerInfo) | [`Heal`](#Heal) | [`GetConfig`](#GetConfig) | [`SetCredentials`](#SetCredentials) | +| [`ServiceSendAction`](#ServiceSendAction) | | | [`SetConfig`](#SetConfig) | [`StartProfiling`](#StartProfiling) | +| | | | [`GetConfigKeys`](#GetConfigKeys) | [`DownloadProfilingData`](#DownloadProfilingData) | +| | | | [`SetConfigKeys`](#SetConfigKeys) | | ## 1. Constructor @@ -273,7 +273,7 @@ __Example__ ### GetConfig() ([]byte, error) -Get current `config.json` of a Minio server. +Get config.json of a minio setup. __Example__ @@ -295,17 +295,37 @@ __Example__ -### SetConfig(config io.Reader) error -Set a new `config.json` for a Minio server. +### SetConfig(config io.Reader) (SetConfigResult, error) +Set config.json of a minio setup and restart setup for configuration +change to take effect. + + +| Param | Type | Description | +|---|---|---| +|`st.Status` | _bool_ | true if set-config succeeded, false otherwise. | +|`st.NodeSummary.Name` | _string_ | Network address of the node. | +|`st.NodeSummary.ErrSet` | _bool_ | Bool representation indicating if an error is encountered with the node.| +|`st.NodeSummary.ErrMsg` | _string_ | String representation of the error (if any) on the node.| + __Example__ ``` go config := bytes.NewReader([]byte(`config.json contents go here`)) - if err := madmClnt.SetConfig(config); err != nil { + result, err := madmClnt.SetConfig(config) + if err != nil { log.Fatalf("failed due to: %v", err) } - log.Println("SetConfig was successful") + + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", "\t") + err = enc.Encode(result) + if err != nil { + log.Fatalln(err) + } + log.Println("SetConfig: ", string(buf.Bytes())) ``` @@ -347,72 +367,18 @@ __Example__ log.Println("New configuration successfully set") ``` -## 8. IAM operations - - -### AddCannedPolicy(policyName string, policy string) error -Create a new canned policy on Minio server. - -__Example__ - -``` - policy := `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject"],"Effect": "Allow","Resource": ["arn:aws:s3:::my-bucketname/*"],"Sid": ""}]}` - - if err = madmClnt.AddCannedPolicy("get-only", policy); err != nil { - log.Fatalln(err) - } -``` - - -### AddUser(user string, secret string) error -Add a new user on a Minio server. - -__Example__ -``` go - if err = madmClnt.AddUser("newuser", "newstrongpassword"); err != nil { - log.Fatalln(err) - } -``` - - -### SetUserPolicy(user string, policyName string) error -Enable a canned policy `get-only` for a given user on Minio server. - -__Example__ - -``` go - if err = madmClnt.SetUserPolicy("newuser", "get-only"); err != nil { - log.Fatalln(err) - } -``` - - -### ListUsers() (map[string]UserInfo, error) -Lists all users on Minio server. - -__Example__ - -``` go - users, err := madmClnt.ListUsers(); - if err != nil { - log.Fatalln(err) - } - for k, v := range users { - fmt.Printf("User %s Status %s\n", k, v.Status) - } -``` -## 9. Misc operations +## 8. Misc operations - -### SetAdminCredentials() error + +### SetCredentials() error Set new credentials of a Minio setup. __Example__ ``` go - err = madmClnt.SetAdminCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") + err = madmClnt.SetCredentials("YOUR-NEW-ACCESSKEY", "YOUR-NEW-SECRETKEY") if err != nil { log.Fatalln(err) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 419e9d6f6a..0b4bda1ffb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -134,10 +134,10 @@ "revisionTime": "2018-09-25T09:12:15Z" }, { - "checksumSHA1": "/5zp4gweRyx0NTrMTEwhCqdMaDA=", + "checksumSHA1": "/OdvNthJAc6DQQ7waRjvzAc7Q3c=", "path": "github.com/minio/minio/pkg/madmin", - "revision": "79e0add0e50323873b5d30fb41ef70584a9b6f22", - "revisionTime": "2018-10-16T19:47:06Z" + "revision": "aebfceeafb410452d1c03edfa69b7bc5509cc1bc", + "revisionTime": "2018-09-29T08:17:01Z" }, { "checksumSHA1": "flg07CqTxM9togozKRQiJugao4s=",