chore: Add CLI flags to enable CPU & memory profiling (#926)

Allow collecting of CPU and memory diagnostics when running troubleshoot CLI applications using --memprofile and --cpuprofile flags. These flags accept file paths if where to store the collected runtime data
This commit is contained in:
Evans Mungai
2023-01-04 11:56:07 +00:00
committed by GitHub
parent 87c153cc8c
commit a31e2ff1b9
13 changed files with 196 additions and 31 deletions

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/go-logr/logr"
"github.com/replicatedhq/troubleshoot/cmd/util"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/cobra"
@@ -26,14 +27,22 @@ func RootCmd() *cobra.Command {
if !v.GetBool("debug") {
klog.SetLogger(logr.Discard())
}
logger.SetQuiet(v.GetBool("quiet"))
if err := util.StartProfiling(); err != nil {
logger.Printf("Failed to start profiling: %v", err)
}
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
logger.SetQuiet(v.GetBool("quiet"))
return runAnalyzers(v, args[0])
},
PostRun: func(cmd *cobra.Command, args []string) {
if err := util.StopProfiling(); err != nil {
logger.Printf("Failed to stop profiling: %v", err)
}
},
}
cobra.OnInitialize(initConfig)
@@ -47,6 +56,9 @@ func RootCmd() *cobra.Command {
k8sutil.AddFlags(cmd.Flags())
// CPU and memory profiling flags
util.AddProfilingFlags(cmd)
return cmd
}

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/go-logr/logr"
"github.com/replicatedhq/troubleshoot/cmd/util"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/cobra"
@@ -26,6 +27,10 @@ func RootCmd() *cobra.Command {
if !v.GetBool("debug") {
klog.SetLogger(logr.Discard())
}
if err := util.StartProfiling(); err != nil {
logger.Printf("Failed to start profiling: %v", err)
}
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
@@ -33,6 +38,11 @@ func RootCmd() *cobra.Command {
logger.SetQuiet(v.GetBool("quiet"))
return runCollect(v, args[0])
},
PostRun: func(cmd *cobra.Command, args []string) {
if err := util.StopProfiling(); err != nil {
logger.Printf("Failed to stop profiling: %v", err)
}
},
}
cobra.OnInitialize(initConfig)
@@ -58,6 +68,9 @@ func RootCmd() *cobra.Command {
k8sutil.AddFlags(cmd.Flags())
// CPU and memory profiling flags
util.AddProfilingFlags(cmd)
return cmd
}

View File

@@ -5,7 +5,9 @@ import (
"strings"
"github.com/go-logr/logr"
"github.com/replicatedhq/troubleshoot/cmd/util"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/replicatedhq/troubleshoot/pkg/preflight"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -22,16 +24,26 @@ that a cluster meets the requirements to run an application.`,
SilenceUsage: true,
PreRun: func(cmd *cobra.Command, args []string) {
v := viper.GetViper()
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
v.BindPFlags(cmd.Flags())
if !v.GetBool("debug") {
klog.SetLogger(logr.Discard())
}
if err := util.StartProfiling(); err != nil {
logger.Printf("Failed to start profiling: %v", err)
}
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
},
PostRun: func(cmd *cobra.Command, args []string) {
if err := util.StopProfiling(); err != nil {
logger.Printf("Failed to stop profiling: %v", err)
}
},
}
cobra.OnInitialize(initConfig)
@@ -39,10 +51,11 @@ that a cluster meets the requirements to run an application.`,
cmd.AddCommand(VersionCmd())
preflight.AddFlags(cmd.PersistentFlags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
k8sutil.AddFlags(cmd.Flags())
// CPU and memory profiling flags
util.AddProfilingFlags(cmd)
return cmd
}

View File

@@ -23,9 +23,7 @@ func Analyze() *cobra.Command {
Short: "analyze a support bundle",
Long: `Analyze a support bundle using the Analyzer definitions provided`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("bundle", cmd.Flags().Lookup("bundle"))
viper.BindPFlag("output", cmd.Flags().Lookup("output"))
viper.BindPFlag("quiet", cmd.Flags().Lookup("quiet"))
viper.BindPFlags(cmd.Flags())
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
@@ -77,8 +75,6 @@ func Analyze() *cobra.Command {
cmd.Flags().MarkHidden("compatibility")
cmd.Flags().Bool("quiet", false, "enable/disable error messaging and only show parseable output")
viper.BindPFlags(cmd.Flags())
return cmd
}

View File

@@ -1,11 +1,11 @@
package cli
import (
"io/ioutil"
"os"
"strings"
"github.com/go-logr/logr"
"github.com/replicatedhq/troubleshoot/cmd/util"
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
"github.com/replicatedhq/troubleshoot/pkg/logger"
"github.com/spf13/cobra"
@@ -21,20 +21,32 @@ func RootCmd() *cobra.Command {
Long: `A support bundle is an archive of files, output, metrics and state
from a server that can be used to assist when troubleshooting a Kubernetes cluster.`,
SilenceUsage: true,
PreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRun: func(cmd *cobra.Command, args []string) {
v := viper.GetViper()
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
v.BindPFlags(cmd.Flags())
if err := util.StartProfiling(); err != nil {
logger.Printf("Failed to start profiling: %v", err)
}
},
PreRun: func(cmd *cobra.Command, args []string) {
v := viper.GetViper()
if !v.GetBool("debug") {
klog.SetLogger(logr.Discard())
}
logger.SetQuiet(v.GetBool("quiet"))
},
RunE: func(cmd *cobra.Command, args []string) error {
v := viper.GetViper()
logger.SetQuiet(v.GetBool("quiet"))
return runTroubleshoot(v, args)
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
if err := util.StopProfiling(); err != nil {
logger.Printf("Failed to stop profiling: %v", err)
}
},
}
cobra.OnInitialize(initConfig)
@@ -62,12 +74,11 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust
// This flag makes sure we can also disable this and fall back to the default spec.
cmd.Flags().Bool("no-uri", false, "When this flag is used, Troubleshoot does not attempt to retrieve the bundle referenced by the uri: field in the spec.`")
viper.BindPFlags(cmd.Flags())
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
k8sutil.AddFlags(cmd.Flags())
// CPU and memory profiling flags
util.AddProfilingFlags(cmd)
return cmd
}
@@ -81,11 +92,3 @@ func initConfig() {
viper.SetEnvPrefix("TROUBLESHOOT")
viper.AutomaticEnv()
}
func writeFile(filename string, contents []byte) error {
if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
return err
}
return nil
}

83
cmd/util/profilers.go Normal file
View File

@@ -0,0 +1,83 @@
package util
import (
"fmt"
"os"
"runtime"
"runtime/pprof"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cpuProfileFile *os.File
)
// StartProfiling starts profiling CPU and memory usage if either --cpuprofile or
// --memprofile flags were set and bound to viper configurations respectively.
func StartProfiling() error {
v := viper.GetViper()
if v.GetString("cpuprofile") != "" {
var err error
cpuProfileFile, err = os.Create(v.GetString("cpuprofile"))
if err != nil {
return fmt.Errorf("could not create CPU profile: %w", err)
}
if err := pprof.StartCPUProfile(cpuProfileFile); err != nil {
cpuProfileFile.Close()
cpuProfileFile = nil
return fmt.Errorf("could not start CPU profile: %w", err)
}
}
return nil
}
// StopProfiling stops profiling CPU and memory usage and writes the results to
// the files specified by --cpuprofile and --memprofile flags respectively.
func StopProfiling() error {
v := viper.GetViper()
errs := []string{}
// Stop CPU profiling if it was started
if cpuProfileFile != nil {
pprof.StopCPUProfile()
err := cpuProfileFile.Close()
if err != nil {
errs = append(errs, err.Error())
}
cpuProfileFile = nil
}
if v.GetString("memprofile") != "" {
f, err := os.Create(v.GetString("memprofile"))
if err != nil {
errs = append(errs, err.Error())
goto BREAK_FROM_MEMPROFILE_IF
}
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
errs = append(errs, err.Error())
goto BREAK_FROM_MEMPROFILE_IF
}
err = f.Close()
if err != nil {
errs = append(errs, err.Error())
}
}
BREAK_FROM_MEMPROFILE_IF:
if len(errs) > 0 {
return fmt.Errorf("errors while stopping profiling: [%s]", strings.Join(errs, ", "))
}
return nil
}
// AddProfilingFlags adds the --cpuprofile and --memprofile flags to the given command.
func AddProfilingFlags(cmd *cobra.Command) {
// Persistent flags to make available to subcommands
cmd.PersistentFlags().String("cpuprofile", "", "File path to write cpu profiling data")
cmd.PersistentFlags().String("memprofile", "", "File path to write memory profiling data")
}