github.com/lingyao2333/mo-zero@v1.4.1/core/proc/profile.go (about) 1 //go:build linux || darwin 2 // +build linux darwin 3 4 package proc 5 6 import ( 7 "fmt" 8 "os" 9 "os/signal" 10 "path" 11 "runtime" 12 "runtime/pprof" 13 "runtime/trace" 14 "sync/atomic" 15 "syscall" 16 "time" 17 18 "github.com/lingyao2333/mo-zero/core/logx" 19 ) 20 21 // DefaultMemProfileRate is the default memory profiling rate. 22 // See also http://golang.org/pkg/runtime/#pkg-variables 23 const DefaultMemProfileRate = 4096 24 25 // started is non zero if a profile is running. 26 var started uint32 27 28 // Profile represents an active profiling session. 29 type Profile struct { 30 // closers holds cleanup functions that run after each profile 31 closers []func() 32 33 // stopped records if a call to profile.Stop has been made 34 stopped uint32 35 } 36 37 func (p *Profile) close() { 38 for _, closer := range p.closers { 39 closer() 40 } 41 } 42 43 func (p *Profile) startBlockProfile() { 44 fn := createDumpFile("block") 45 f, err := os.Create(fn) 46 if err != nil { 47 logx.Errorf("profile: could not create block profile %q: %v", fn, err) 48 return 49 } 50 51 runtime.SetBlockProfileRate(1) 52 logx.Infof("profile: block profiling enabled, %s", fn) 53 p.closers = append(p.closers, func() { 54 pprof.Lookup("block").WriteTo(f, 0) 55 f.Close() 56 runtime.SetBlockProfileRate(0) 57 logx.Infof("profile: block profiling disabled, %s", fn) 58 }) 59 } 60 61 func (p *Profile) startCpuProfile() { 62 fn := createDumpFile("cpu") 63 f, err := os.Create(fn) 64 if err != nil { 65 logx.Errorf("profile: could not create cpu profile %q: %v", fn, err) 66 return 67 } 68 69 logx.Infof("profile: cpu profiling enabled, %s", fn) 70 pprof.StartCPUProfile(f) 71 p.closers = append(p.closers, func() { 72 pprof.StopCPUProfile() 73 f.Close() 74 logx.Infof("profile: cpu profiling disabled, %s", fn) 75 }) 76 } 77 78 func (p *Profile) startMemProfile() { 79 fn := createDumpFile("mem") 80 f, err := os.Create(fn) 81 if err != nil { 82 logx.Errorf("profile: could not create memory profile %q: %v", fn, err) 83 return 84 } 85 86 old := runtime.MemProfileRate 87 runtime.MemProfileRate = DefaultMemProfileRate 88 logx.Infof("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn) 89 p.closers = append(p.closers, func() { 90 pprof.Lookup("heap").WriteTo(f, 0) 91 f.Close() 92 runtime.MemProfileRate = old 93 logx.Infof("profile: memory profiling disabled, %s", fn) 94 }) 95 } 96 97 func (p *Profile) startMutexProfile() { 98 fn := createDumpFile("mutex") 99 f, err := os.Create(fn) 100 if err != nil { 101 logx.Errorf("profile: could not create mutex profile %q: %v", fn, err) 102 return 103 } 104 105 runtime.SetMutexProfileFraction(1) 106 logx.Infof("profile: mutex profiling enabled, %s", fn) 107 p.closers = append(p.closers, func() { 108 if mp := pprof.Lookup("mutex"); mp != nil { 109 mp.WriteTo(f, 0) 110 } 111 f.Close() 112 runtime.SetMutexProfileFraction(0) 113 logx.Infof("profile: mutex profiling disabled, %s", fn) 114 }) 115 } 116 117 func (p *Profile) startThreadCreateProfile() { 118 fn := createDumpFile("threadcreate") 119 f, err := os.Create(fn) 120 if err != nil { 121 logx.Errorf("profile: could not create threadcreate profile %q: %v", fn, err) 122 return 123 } 124 125 logx.Infof("profile: threadcreate profiling enabled, %s", fn) 126 p.closers = append(p.closers, func() { 127 if mp := pprof.Lookup("threadcreate"); mp != nil { 128 mp.WriteTo(f, 0) 129 } 130 f.Close() 131 logx.Infof("profile: threadcreate profiling disabled, %s", fn) 132 }) 133 } 134 135 func (p *Profile) startTraceProfile() { 136 fn := createDumpFile("trace") 137 f, err := os.Create(fn) 138 if err != nil { 139 logx.Errorf("profile: could not create trace output file %q: %v", fn, err) 140 return 141 } 142 143 if err := trace.Start(f); err != nil { 144 logx.Errorf("profile: could not start trace: %v", err) 145 return 146 } 147 148 logx.Infof("profile: trace enabled, %s", fn) 149 p.closers = append(p.closers, func() { 150 trace.Stop() 151 logx.Infof("profile: trace disabled, %s", fn) 152 }) 153 } 154 155 // Stop stops the profile and flushes any unwritten data. 156 func (p *Profile) Stop() { 157 if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) { 158 // someone has already called close 159 return 160 } 161 p.close() 162 atomic.StoreUint32(&started, 0) 163 } 164 165 // StartProfile starts a new profiling session. 166 // The caller should call the Stop method on the value returned 167 // to cleanly stop profiling. 168 func StartProfile() Stopper { 169 if !atomic.CompareAndSwapUint32(&started, 0, 1) { 170 logx.Error("profile: Start() already called") 171 return noopStopper 172 } 173 174 var prof Profile 175 prof.startCpuProfile() 176 prof.startMemProfile() 177 prof.startMutexProfile() 178 prof.startBlockProfile() 179 prof.startTraceProfile() 180 prof.startThreadCreateProfile() 181 182 go func() { 183 c := make(chan os.Signal, 1) 184 signal.Notify(c, syscall.SIGINT) 185 <-c 186 187 logx.Info("profile: caught interrupt, stopping profiles") 188 prof.Stop() 189 190 signal.Reset() 191 syscall.Kill(os.Getpid(), syscall.SIGINT) 192 }() 193 194 return &prof 195 } 196 197 func createDumpFile(kind string) string { 198 command := path.Base(os.Args[0]) 199 pid := syscall.Getpid() 200 return path.Join(os.TempDir(), fmt.Sprintf("%s-%d-%s-%s.pprof", 201 command, pid, kind, time.Now().Format(timeFormat))) 202 }