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.

163 lines
4.1 KiB

  1. // Copyright 2013 Beego Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. package toolbox
  16. import (
  17. "bytes"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "os"
  22. "path"
  23. "runtime"
  24. "runtime/debug"
  25. "runtime/pprof"
  26. "time"
  27. "github.com/Unknwon/com"
  28. "gopkg.in/macaron.v1"
  29. )
  30. var (
  31. profilePath string
  32. pid int
  33. startTime = time.Now()
  34. inCPUProfile bool
  35. )
  36. // StartCPUProfile starts CPU profile monitor.
  37. func StartCPUProfile() error {
  38. if inCPUProfile {
  39. return errors.New("CPU profile has alreday been started!")
  40. }
  41. inCPUProfile = true
  42. os.MkdirAll(profilePath, os.ModePerm)
  43. f, err := os.Create(path.Join(profilePath, "cpu-"+com.ToStr(pid)+".pprof"))
  44. if err != nil {
  45. panic("fail to record CPU profile: " + err.Error())
  46. }
  47. pprof.StartCPUProfile(f)
  48. return nil
  49. }
  50. // StopCPUProfile stops CPU profile monitor.
  51. func StopCPUProfile() error {
  52. if !inCPUProfile {
  53. return errors.New("CPU profile hasn't been started!")
  54. }
  55. pprof.StopCPUProfile()
  56. inCPUProfile = false
  57. return nil
  58. }
  59. func init() {
  60. pid = os.Getpid()
  61. }
  62. // DumpMemProf dumps memory profile in pprof.
  63. func DumpMemProf(w io.Writer) {
  64. pprof.WriteHeapProfile(w)
  65. }
  66. func dumpMemProf() {
  67. os.MkdirAll(profilePath, os.ModePerm)
  68. f, err := os.Create(path.Join(profilePath, "mem-"+com.ToStr(pid)+".memprof"))
  69. if err != nil {
  70. panic("fail to record memory profile: " + err.Error())
  71. }
  72. runtime.GC()
  73. DumpMemProf(f)
  74. f.Close()
  75. }
  76. func avg(items []time.Duration) time.Duration {
  77. var sum time.Duration
  78. for _, item := range items {
  79. sum += item
  80. }
  81. return time.Duration(int64(sum) / int64(len(items)))
  82. }
  83. func dumpGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) {
  84. if gcstats.NumGC > 0 {
  85. lastPause := gcstats.Pause[0]
  86. elapsed := time.Now().Sub(startTime)
  87. overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100
  88. allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds()
  89. fmt.Fprintf(w, "NumGC:%d Pause:%s Pause(Avg):%s Overhead:%3.2f%% Alloc:%s Sys:%s Alloc(Rate):%s/s Histogram:%s %s %s \n",
  90. gcstats.NumGC,
  91. com.ToStr(lastPause),
  92. com.ToStr(avg(gcstats.Pause)),
  93. overhead,
  94. com.HumaneFileSize(memStats.Alloc),
  95. com.HumaneFileSize(memStats.Sys),
  96. com.HumaneFileSize(uint64(allocatedRate)),
  97. com.ToStr(gcstats.PauseQuantiles[94]),
  98. com.ToStr(gcstats.PauseQuantiles[98]),
  99. com.ToStr(gcstats.PauseQuantiles[99]))
  100. } else {
  101. // while GC has disabled
  102. elapsed := time.Now().Sub(startTime)
  103. allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds()
  104. fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n",
  105. com.HumaneFileSize(memStats.Alloc),
  106. com.HumaneFileSize(memStats.Sys),
  107. com.HumaneFileSize(uint64(allocatedRate)))
  108. }
  109. }
  110. // DumpGCSummary dumps GC information to io.Writer
  111. func DumpGCSummary(w io.Writer) {
  112. memStats := &runtime.MemStats{}
  113. runtime.ReadMemStats(memStats)
  114. gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)}
  115. debug.ReadGCStats(gcstats)
  116. dumpGC(memStats, gcstats, w)
  117. }
  118. func handleProfile(ctx *macaron.Context) string {
  119. switch ctx.Query("op") {
  120. case "startcpu":
  121. if err := StartCPUProfile(); err != nil {
  122. return err.Error()
  123. }
  124. case "stopcpu":
  125. if err := StopCPUProfile(); err != nil {
  126. return err.Error()
  127. }
  128. case "mem":
  129. dumpMemProf()
  130. case "gc":
  131. var buf bytes.Buffer
  132. DumpGCSummary(&buf)
  133. return string(buf.Bytes())
  134. default:
  135. return fmt.Sprintf(`<p>Available operations:</p>
  136. <ol>
  137. <li><a href="%[1]s?op=startcpu">Start CPU profile</a></li>
  138. <li><a href="%[1]s?op=stopcpu">Stop CPU profile</a></li>
  139. <li><a href="%[1]s?op=mem">Dump memory profile</a></li>
  140. <li><a href="%[1]s?op=gc">Dump GC summary</a></li>
  141. </ol>`, opt.ProfileURLPrefix)
  142. }
  143. ctx.Redirect(opt.ProfileURLPrefix)
  144. return ""
  145. }