mirror of
https://github.com/kopia/kopia.git
synced 2025-12-23 22:57:50 -05:00
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.
157 lines
4.0 KiB
Go
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)
|
|
}
|
|
}()
|
|
}
|
|
}
|