Files
kopia/cli/profile.go
Julio López 39fb62970f refactor(cli): refactor diagnosis flags (#5026)
Refactor `--profile-*` flags:
- Multiple profile types can be enabled at once, before only
  a single type profiling could be done during a process execution.
- The new `--profiles-store-on-exit` enables all available profile
  types, except for CPU profiling which needs to be explicitly enabled.
- Profiling parameters can now be set via new flags. This allows setting
  the profile parameters for the pprof endpoint, as well as when saving
  profiles to files on exit.
- Group profiling flags with other observability flags
- Adds a `--diagnostics-output-directory` flag that unifies and
  supersedes the `--profile-dir` and `--metrics-directory` flags

Enhancements and behavior changes:
- Profile flags now have effect for all kopia commands, including
  `server start`. Before these flags did not have any effect
  in a few commands.
- Multiple profile types can be enabled at once, before only
  a single type profiling could be done during a process execution.
- The new `--profiles-store-on-exit` enables all available profile
  types, except for CPU profiling which needs to be explicitly enabled.
- Profiling parameters can now be set via new flags. This allows setting
  the profile parameters for the pprof endpoint, as well as when saving
  profiles to files on exit.

The following flags have been removed:
- `--profile-dir`: superseded by the `--diagnostics-output-directory` flag
- `--profile-blocking`: the `--profile-store-on-exit` flag enables blocking
  profiling. Use `--profile-blocking-rate=0` to explicitly disable it.
- `--profile-memory`: the `--profile-store-on-exit` flag enables memory
  profiling. Use `--profile-memory-rate=0` to explicitly disable it.
- `--profile-mutex`: the `--profile-store-on-exit` flag enables mutex
  profiling. Use `--profile-mutex-fraction=0` to explicitly disable it.

Add CLI test for profile flags.
2025-11-26 09:40:01 -08:00

157 lines
4.0 KiB
Go

package cli
import (
"context"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"github.com/alecthomas/kingpin/v2"
"github.com/pkg/errors"
)
const profDirName = "profiles"
type profileFlags struct {
profileGCBeforeSaving bool
profileCPU bool
profileBlockingRate int
profileMemoryRate int
profileMutexFraction int
saveProfiles bool
outputDirectory string
cpuProfileCloser func()
}
func (c *profileFlags) setup(app *kingpin.Application) {
c.profileBlockingRate = -1
c.profileMemoryRate = -1
c.profileMutexFraction = -1
app.Flag("profile-store-on-exit", "Writes profiling data on exit. It writes a file per profile type (heap, goroutine, threadcreate, block, mutex) in a sub-directory in the directory specified with the --diagnostics-output-directory").Hidden().BoolVar(&c.saveProfiles) //nolint:lll
app.Flag("profile-go-gc-before-dump", "Perform a Go GC before writing out memory profiles").Hidden().BoolVar(&c.profileGCBeforeSaving)
app.Flag("profile-blocking-rate", "Blocking profiling rate, a value of 0 turns off block profiling").Hidden().IntVar(&c.profileBlockingRate)
app.Flag("profile-cpu", "Enable CPU profiling").Hidden().BoolVar(&c.profileCPU)
app.Flag("profile-memory-rate", "Memory profiling rate").Hidden().IntVar(&c.profileMemoryRate)
app.Flag("profile-mutex-fraction", "Mutex profiling, a value of 0 turns off mutex profiling").Hidden().IntVar(&c.profileMutexFraction)
}
func (c *profileFlags) start(ctx context.Context, outputDirectory string) error {
pBlockingRate := c.profileBlockingRate
pMemoryRate := c.profileMemoryRate
pMutexFraction := c.profileMutexFraction
if c.saveProfiles {
// when saving profiles ensure profiling parameters have sensible values
// unless explicitly modified.
// runtime.MemProfileRate has a default value, no need to reset it.
if pBlockingRate == -1 {
pBlockingRate = 1
}
if pMutexFraction == -1 {
pMutexFraction = 1
}
}
// set profiling parameters if they have been changed from defaults
if pBlockingRate != -1 {
runtime.SetBlockProfileRate(pBlockingRate)
}
if pMemoryRate != -1 {
runtime.MemProfileRate = pMemoryRate
}
if pMutexFraction != -1 {
runtime.SetMutexProfileFraction(pMutexFraction)
}
if !c.profileCPU && !c.saveProfiles {
return nil
}
c.outputDirectory = outputDirectory
// ensure upfront that the pprof output dir can be created.
profDir, err := mkSubdirectories(c.outputDirectory, profDirName)
if err != nil {
return err
}
if !c.profileCPU {
return nil
}
// start CPU profile dumper
f, err := os.Create(filepath.Join(profDir, "cpu.pprof")) //nolint:gosec
if err != nil {
return errors.Wrap(err, "could not create CPU profile output file")
}
// CPU profile closer
closer := func() {
pprof.StopCPUProfile()
if err := f.Close(); err != nil {
log(ctx).Warn("error closing CPU profile output file:", err)
}
}
if err := pprof.StartCPUProfile(f); err != nil {
closer()
return errors.Wrap(err, "could not start CPU profile")
}
c.cpuProfileCloser = closer
return nil
}
func (c *profileFlags) stop(ctx context.Context) {
if c.cpuProfileCloser != nil {
c.cpuProfileCloser()
c.cpuProfileCloser = nil
}
if !c.saveProfiles {
return
}
if c.profileGCBeforeSaving {
// update profiles, otherwise they may not include activity after the last GC
runtime.GC()
}
profDir, err := mkSubdirectories(c.outputDirectory, profDirName)
if err != nil {
log(ctx).Warn("cannot create directory to save profiles:", err)
}
for _, p := range pprof.Profiles() {
func() {
fname := filepath.Join(profDir, p.Name()+".pprof")
f, err := os.Create(fname) //nolint:gosec
if err != nil {
log(ctx).Warnf("unable to create profile output file '%s': %v", fname, err)
return
}
defer func() {
if err := f.Close(); err != nil {
log(ctx).Warnf("unable to close profile output file '%s': %v", fname, err)
}
}()
if err := p.WriteTo(f, 0); err != nil {
log(ctx).Warnf("unable to write profile to file '%s': %v", fname, err)
}
}()
}
}