diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c2f88453..aa67b5aef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -276,12 +276,24 @@ Like any good open source project, we use Pull Requests (PRs) to track code chan or explicitly request another OWNER do that for them. - If the owner of a PR is _not_ listed in `OWNERS`, any core maintainer may merge the PR. -#### Documentation PRs +### Documentation PRs Documentation PRs should be made on the docs repo: . Keeping Helm's documentation up to date is highly desirable, and is recommended for all user facing changes. Accurate and helpful documentation is critical for effectively communicating Helm's behavior to a wide audience. Small, ad-hoc changes/PRs to Helm which introduce user facing changes, which would benefit from documentation changes, should apply the `docs needed` label. Larger changes associated with a HIP should track docs via that HIP. The `docs needed` label doesn't block PRs, and maintainers/PR reviewers should apply discretion judging in whether the `docs needed` label should be applied. +### Testing PRs + +During development, you need to add automated tests where possible. There are a few `make test*` targets that you can use to execute your unit or integration tests. If your contribution requires profiling to check memory and/or CPU usage, you can use `--cpuprofile` and/or `--memprofile` global cli flags to run collect runtime profiling data for analysis. You can use Golang's [pprof](https://github.com/google/pprof/blob/main/doc/README.md) tool to inspect the profiling data. + +Example invocation that collects profiling data +``` +helm show all bitnami/nginx --memprofile mem.prof --cpuprofile cpu.prof + +# Visualize graphs. You need to have installed graphviz in you system +go tool pprof -http=":8000" cpu.prof +``` + ## The Triager Each week, one of the core maintainers will serve as the designated "triager" starting after the diff --git a/cmd/helm/profiling.go b/cmd/helm/profiling.go new file mode 100644 index 000000000..090532416 --- /dev/null +++ b/cmd/helm/profiling.go @@ -0,0 +1,76 @@ +// Profile CPU and memory usage of Helm commands + +package main + +import ( + "fmt" + "os" + "runtime" + "runtime/pprof" + "strings" + + "github.com/spf13/cobra" +) + +var ( + cpuProfileFile *os.File +) + +// startProfiling starts profiling CPU usage +func startProfiling(cpuprofile string) error { + if cpuprofile != "" { + var err error + cpuProfileFile, err = os.Create(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(memprofile string) error { + 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 memprofile != "" { + f, err := os.Create(memprofile) + if err != nil { + errs = append(errs, err.Error()) + } + defer f.Close() + + runtime.GC() // get up-to-date statistics + if err := pprof.WriteHeapProfile(f); err != nil { + errs = append(errs, err.Error()) + } + } + + 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") +} diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 2ba8a882e..fa1e6c6d0 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -95,6 +95,26 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string Short: "The Helm package manager for Kubernetes.", Long: globalUsage, SilenceUsage: true, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + cpuprof, err := cmd.Flags().GetString("cpuprofile") + if err != nil { + log.Printf("Warning: Failed to get cpuprofile flag: %v", err) + } + + if err := startProfiling(cpuprof); err != nil { + log.Printf("Warning: Failed to start profiling: %v", err) + } + }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + memprof, err := cmd.Flags().GetString("memprofile") + if err != nil { + log.Printf("Warning: Failed to get memprofile flag: %v", err) + } + + if err := stopProfiling(memprof); err != nil { + log.Printf("Warning: Failed to stop profiling: %v", err) + } + }, } flags := cmd.PersistentFlags() @@ -206,6 +226,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string // Check for expired repositories checkForExpiredRepos(settings.RepositoryConfig) + // CPU and memory profiling flags that are available to all commands + addProfilingFlags(cmd) + return cmd, nil }