You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
4.1 KiB
168 lines
4.1 KiB
package proc |
|
|
|
import ( |
|
"fmt" |
|
"git.diulo.com/mogfee/kit/logx" |
|
"os" |
|
"os/signal" |
|
"path" |
|
"runtime" |
|
"runtime/pprof" |
|
"runtime/trace" |
|
"sync/atomic" |
|
"syscall" |
|
"time" |
|
) |
|
|
|
const DefaultMemProfileRate = 4096 |
|
|
|
var started uint32 |
|
|
|
type Profile struct { |
|
closers []func() |
|
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) |
|
}) |
|
} |
|
func (p *Profile) Stop() { |
|
if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) { |
|
// someone has already called close |
|
return |
|
} |
|
p.close() |
|
atomic.StoreUint32(&started, 0) |
|
} |
|
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))) |
|
}
|
|
|