Skip to content

Commit

Permalink
Add admin profiling command (#2557)
Browse files Browse the repository at this point in the history
Implement profiling command to start & download profiling data of
a standalone or all nodes of a cluster.

```
1. Start CPU profiling
   $ mc admin profiling start --type "cpu" myminio/

2. Download latest profiling data under save under profiling.zip
   $ mc admin profiling stop myminio/
```
  • Loading branch information
vadmeste authored and kannappanr committed Oct 18, 2018
1 parent c707a17 commit 0dd32c1
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 72 deletions.
1 change: 1 addition & 0 deletions cmd/admin-main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var adminCmd = cli.Command{
adminCredsCmd,
adminConfigCmd,
adminHealCmd,
adminProfilingCmd,
},
}

Expand Down
111 changes: 111 additions & 0 deletions cmd/admin-profiling-start.go
Original file line number Diff line number Diff line change
@@ -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
}
106 changes: 106 additions & 0 deletions cmd/admin-profiling-stop.go
Original file line number Diff line number Diff line change
@@ -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
}
40 changes: 40 additions & 0 deletions cmd/admin-profiling.go
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 4 additions & 0 deletions cmd/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading

0 comments on commit 0dd32c1

Please sign in to comment.