// Package profile provides a simple way to manage runtime/pprof // profiling of your Go application. package profile import ( "io/ioutil" "log" "os" "os/signal" "path/filepath" "runtime" "runtime/pprof" "sync/atomic" ) // started counts the number of times Start has been called var started uint32 const ( cpuMode = iota memMode blockMode ) type profile struct { // quiet suppresses informational messages during profiling. quiet bool // noShutdownHook controls whether the profiling package should // hook SIGINT to write profiles cleanly. noShutdownHook bool // mode holds the type of profiling that will be made mode int // path holds the base path where various profiling files are written. // If blank, the base path will be generated by ioutil.TempDir. path string // memProfileRate holds the rate for the memory profile. memProfileRate int // closers holds the cleanup functions that run after each profile closers []func() // stopped records if a call to profile.Stop has been made stopped uint32 } // NoShutdownHook controls whether the profiling package should // hook SIGINT to write profiles cleanly. // Programs with more sophisticated signal handling should set // this to true and ensure the Stop() function returned from Start() // is called during shutdown. func NoShutdownHook(p *profile) { p.noShutdownHook = true } // Quiet suppresses informational messages during profiling. func Quiet(p *profile) { p.quiet = true } // CPUProfile controls if cpu profiling will be enabled. It disables any previous profiling settings. func CPUProfile(p *profile) { p.mode = cpuMode } // DefaultMemProfileRate is the default memory profiling rate. // See also http://golang.org/pkg/runtime/#pkg-variables const DefaultMemProfileRate = 4096 // MemProfile controls if memory profiling will be enabled. It disables any previous profiling settings. func MemProfile(p *profile) { p.memProfileRate = DefaultMemProfileRate p.mode = memMode } // MemProfileRate controls if memory profiling will be enabled. Additionally, it takes a parameter which // allows the setting of the memory profile rate. func MemProfileRate(rate int) func(*profile) { return func(p *profile) { p.memProfileRate = rate p.mode = memMode } } // BlockProfile controls if block (contention) profiling will be enabled. It disables any previous profiling settings. func BlockProfile(p *profile) { p.mode = blockMode } // ProfilePath controls the base path where various profiling // files are written. If blank, the base path will be generated // by ioutil.TempDir. func ProfilePath(path string) func(*profile) { return func(p *profile) { p.path = path } } // Stop stops the profile and flushes any unwritten data. func (p *profile) Stop() { if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) { // someone has already called close return } for _, c := range p.closers { c() } } // Start starts a new profiling session. // The caller should call the Stop method on the value returned // to cleanly stop profiling. func Start(options ...func(*profile)) interface { Stop() } { if !atomic.CompareAndSwapUint32(&started, 0, 1) { log.Fatal("profile: Start() already called") } var prof profile for _, option := range options { option(&prof) } path, err := func() (string, error) { if p := prof.path; p != "" { return p, os.MkdirAll(p, 0777) } return ioutil.TempDir("", "profile") }() if err != nil { log.Fatalf("profile: could not create initial output directory: %v", err) } switch prof.mode { case cpuMode: fn := filepath.Join(path, "cpu.pprof") f, err := os.Create(fn) if err != nil { log.Fatalf("profile: could not create cpu profile %q: %v", fn, err) } if !prof.quiet { log.Printf("profile: cpu profiling enabled, %s", fn) } pprof.StartCPUProfile(f) prof.closers = append(prof.closers, func() { pprof.StopCPUProfile() f.Close() }) case memMode: fn := filepath.Join(path, "mem.pprof") f, err := os.Create(fn) if err != nil { log.Fatalf("profile: could not create memory profile %q: %v", fn, err) } old := runtime.MemProfileRate runtime.MemProfileRate = prof.memProfileRate if !prof.quiet { log.Printf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn) } prof.closers = append(prof.closers, func() { pprof.Lookup("heap").WriteTo(f, 0) f.Close() runtime.MemProfileRate = old }) case blockMode: fn := filepath.Join(path, "block.pprof") f, err := os.Create(fn) if err != nil { log.Fatalf("profile: could not create block profile %q: %v", fn, err) } runtime.SetBlockProfileRate(1) if !prof.quiet { log.Printf("profile: block profiling enabled, %s", fn) } prof.closers = append(prof.closers, func() { pprof.Lookup("block").WriteTo(f, 0) f.Close() runtime.SetBlockProfileRate(0) }) } if !prof.noShutdownHook { go func() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c log.Println("profile: caught interrupt, stopping profiles") prof.Stop() os.Exit(0) }() } prof.closers = append(prof.closers, func() { atomic.SwapUint32(&started, 0) }) return &prof }