profile.go 4.9 KB

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