profile.go 4.7 KB

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