You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
202 lines
4.7 KiB
Go
202 lines
4.7 KiB
Go
// +build linux darwin
|
|
|
|
package proc
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"runtime/trace"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/tal-tech/go-zero/core/logx"
|
|
)
|
|
|
|
// DefaultMemProfileRate is the default memory profiling rate.
|
|
// See also http://golang.org/pkg/runtime/#pkg-variables
|
|
const DefaultMemProfileRate = 4096
|
|
|
|
// started is non zero if a profile is running.
|
|
var started uint32
|
|
|
|
// Profile represents an active profiling session.
|
|
type Profile struct {
|
|
// closers holds cleanup functions that run after each profile
|
|
closers []func()
|
|
|
|
// stopped records if a call to profile.Stop has been made
|
|
stopped uint32
|
|
}
|
|
|
|
func (p *Profile) close() {
|
|
for _, closer := range p.closers {
|
|
closer()
|
|
}
|
|
}
|
|
|
|
func (p *Profile) startBlockProfile() {
|
|
fn := createDumpFile("block")
|
|
f, err := os.Create(fn)
|
|
if err != nil {
|
|
logx.Errorf("profile: could not create block profile %q: %v", fn, err)
|
|
return
|
|
}
|
|
|
|
runtime.SetBlockProfileRate(1)
|
|
logx.Infof("profile: block profiling enabled, %s", fn)
|
|
p.closers = append(p.closers, func() {
|
|
pprof.Lookup("block").WriteTo(f, 0)
|
|
f.Close()
|
|
runtime.SetBlockProfileRate(0)
|
|
logx.Infof("profile: block profiling disabled, %s", fn)
|
|
})
|
|
}
|
|
|
|
func (p *Profile) startCpuProfile() {
|
|
fn := createDumpFile("cpu")
|
|
f, err := os.Create(fn)
|
|
if err != nil {
|
|
logx.Errorf("profile: could not create cpu profile %q: %v", fn, err)
|
|
return
|
|
}
|
|
|
|
logx.Infof("profile: cpu profiling enabled, %s", fn)
|
|
pprof.StartCPUProfile(f)
|
|
p.closers = append(p.closers, func() {
|
|
pprof.StopCPUProfile()
|
|
f.Close()
|
|
logx.Infof("profile: cpu profiling disabled, %s", fn)
|
|
})
|
|
}
|
|
|
|
func (p *Profile) startMemProfile() {
|
|
fn := createDumpFile("mem")
|
|
f, err := os.Create(fn)
|
|
if err != nil {
|
|
logx.Errorf("profile: could not create memory profile %q: %v", fn, err)
|
|
return
|
|
}
|
|
|
|
old := runtime.MemProfileRate
|
|
runtime.MemProfileRate = DefaultMemProfileRate
|
|
logx.Infof("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
|
|
p.closers = append(p.closers, func() {
|
|
pprof.Lookup("heap").WriteTo(f, 0)
|
|
f.Close()
|
|
runtime.MemProfileRate = old
|
|
logx.Infof("profile: memory profiling disabled, %s", fn)
|
|
})
|
|
}
|
|
|
|
func (p *Profile) startMutexProfile() {
|
|
fn := createDumpFile("mutex")
|
|
f, err := os.Create(fn)
|
|
if err != nil {
|
|
logx.Errorf("profile: could not create mutex profile %q: %v", fn, err)
|
|
return
|
|
}
|
|
|
|
runtime.SetMutexProfileFraction(1)
|
|
logx.Infof("profile: mutex profiling enabled, %s", fn)
|
|
p.closers = append(p.closers, func() {
|
|
if mp := pprof.Lookup("mutex"); mp != nil {
|
|
mp.WriteTo(f, 0)
|
|
}
|
|
f.Close()
|
|
runtime.SetMutexProfileFraction(0)
|
|
logx.Infof("profile: mutex profiling disabled, %s", fn)
|
|
})
|
|
}
|
|
|
|
func (p *Profile) startThreadCreateProfile() {
|
|
fn := createDumpFile("threadcreate")
|
|
f, err := os.Create(fn)
|
|
if err != nil {
|
|
logx.Errorf("profile: could not create threadcreate profile %q: %v", fn, err)
|
|
return
|
|
}
|
|
|
|
logx.Infof("profile: threadcreate profiling enabled, %s", fn)
|
|
p.closers = append(p.closers, func() {
|
|
if mp := pprof.Lookup("threadcreate"); mp != nil {
|
|
mp.WriteTo(f, 0)
|
|
}
|
|
f.Close()
|
|
logx.Infof("profile: threadcreate profiling disabled, %s", fn)
|
|
})
|
|
}
|
|
|
|
func (p *Profile) startTraceProfile() {
|
|
fn := createDumpFile("trace")
|
|
f, err := os.Create(fn)
|
|
if err != nil {
|
|
logx.Errorf("profile: could not create trace output file %q: %v", fn, err)
|
|
return
|
|
}
|
|
|
|
if err := trace.Start(f); err != nil {
|
|
logx.Errorf("profile: could not start trace: %v", err)
|
|
return
|
|
}
|
|
|
|
logx.Infof("profile: trace enabled, %s", fn)
|
|
p.closers = append(p.closers, func() {
|
|
trace.Stop()
|
|
logx.Infof("profile: trace disabled, %s", fn)
|
|
})
|
|
}
|
|
|
|
// 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
|
|
}
|
|
p.close()
|
|
atomic.StoreUint32(&started, 0)
|
|
}
|
|
|
|
// Start starts a new profiling session.
|
|
// The caller should call the Stop method on the value returned
|
|
// to cleanly stop profiling.
|
|
func StartProfile() Stopper {
|
|
if !atomic.CompareAndSwapUint32(&started, 0, 1) {
|
|
logx.Error("profile: Start() already called")
|
|
return noopStopper
|
|
}
|
|
|
|
var prof Profile
|
|
prof.startCpuProfile()
|
|
prof.startMemProfile()
|
|
prof.startMutexProfile()
|
|
prof.startBlockProfile()
|
|
prof.startTraceProfile()
|
|
prof.startThreadCreateProfile()
|
|
|
|
go func() {
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, syscall.SIGINT)
|
|
<-c
|
|
|
|
logx.Info("profile: caught interrupt, stopping profiles")
|
|
prof.Stop()
|
|
|
|
signal.Reset()
|
|
syscall.Kill(os.Getpid(), syscall.SIGINT)
|
|
}()
|
|
|
|
return &prof
|
|
}
|
|
|
|
func createDumpFile(kind string) string {
|
|
command := path.Base(os.Args[0])
|
|
pid := syscall.Getpid()
|
|
return path.Join(os.TempDir(), fmt.Sprintf("%s-%d-%s-%s.pprof",
|
|
command, pid, kind, time.Now().Format(timeFormat)))
|
|
}
|