package main import ( "context" "fmt" "os" "os/signal" "path/filepath" "runtime/pprof" "time" "github.com/urfave/cli/v2" ) func takeProfiles(ctx context.Context) (fname string, _err error) { dir, err := os.MkdirTemp(".", ".profiles-temp*") if err != nil { return "", err } if err := writeProfiles(ctx, dir); err != nil { _ = os.RemoveAll(dir) return "", err } fname = fmt.Sprintf("pprof-simulation-%s", time.Now().Format(time.RFC3339)) if err := os.Rename(dir, fname); err != nil { _ = os.RemoveAll(dir) return "", err } return fname, nil } func writeProfiles(ctx context.Context, dir string) error { for _, profile := range pprof.Profiles() { file, err := os.Create(filepath.Join(dir, profile.Name()+".pprof.gz")) if err != nil { return err } if err := profile.WriteTo(file, 0); err != nil { _ = file.Close() return err } if err := file.Close(); err != nil { return err } if err := ctx.Err(); err != nil { return err } } file, err := os.Create(filepath.Join(dir, "cpu.pprof.gz")) if err != nil { return err } if err := pprof.StartCPUProfile(file); err != nil { _ = file.Close() return err } select { case <-time.After(30 * time.Second): case <-ctx.Done(): } pprof.StopCPUProfile() err = file.Close() if err := ctx.Err(); err != nil { return err } return err } func profileOnSignal(cctx *cli.Context, signals ...os.Signal) { ch := make(chan os.Signal, 1) signal.Notify(ch, signals...) defer signal.Stop(ch) for { select { case <-ch: fname, err := takeProfiles(cctx.Context) switch err { case context.Canceled: return case nil: fmt.Fprintf(cctx.App.ErrWriter, "Wrote profile to %q\n", fname) default: fmt.Fprintf(cctx.App.ErrWriter, "ERROR: failed to write profile: %s\n", err) } case <-cctx.Done(): return } } }