github.com/astaxie/beego@v1.12.3/toolbox/profile.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package toolbox 16 17 import ( 18 "fmt" 19 "io" 20 "log" 21 "os" 22 "path" 23 "runtime" 24 "runtime/debug" 25 "runtime/pprof" 26 "strconv" 27 "time" 28 ) 29 30 var startTime = time.Now() 31 var pid int 32 33 func init() { 34 pid = os.Getpid() 35 } 36 37 // ProcessInput parse input command string 38 func ProcessInput(input string, w io.Writer) { 39 switch input { 40 case "lookup goroutine": 41 p := pprof.Lookup("goroutine") 42 p.WriteTo(w, 2) 43 case "lookup heap": 44 p := pprof.Lookup("heap") 45 p.WriteTo(w, 2) 46 case "lookup threadcreate": 47 p := pprof.Lookup("threadcreate") 48 p.WriteTo(w, 2) 49 case "lookup block": 50 p := pprof.Lookup("block") 51 p.WriteTo(w, 2) 52 case "get cpuprof": 53 GetCPUProfile(w) 54 case "get memprof": 55 MemProf(w) 56 case "gc summary": 57 PrintGCSummary(w) 58 } 59 } 60 61 // MemProf record memory profile in pprof 62 func MemProf(w io.Writer) { 63 filename := "mem-" + strconv.Itoa(pid) + ".memprof" 64 if f, err := os.Create(filename); err != nil { 65 fmt.Fprintf(w, "create file %s error %s\n", filename, err.Error()) 66 log.Fatal("record heap profile failed: ", err) 67 } else { 68 runtime.GC() 69 pprof.WriteHeapProfile(f) 70 f.Close() 71 fmt.Fprintf(w, "create heap profile %s \n", filename) 72 _, fl := path.Split(os.Args[0]) 73 fmt.Fprintf(w, "Now you can use this to check it: go tool pprof %s %s\n", fl, filename) 74 } 75 } 76 77 // GetCPUProfile start cpu profile monitor 78 func GetCPUProfile(w io.Writer) { 79 sec := 30 80 filename := "cpu-" + strconv.Itoa(pid) + ".pprof" 81 f, err := os.Create(filename) 82 if err != nil { 83 fmt.Fprintf(w, "Could not enable CPU profiling: %s\n", err) 84 log.Fatal("record cpu profile failed: ", err) 85 } 86 pprof.StartCPUProfile(f) 87 time.Sleep(time.Duration(sec) * time.Second) 88 pprof.StopCPUProfile() 89 90 fmt.Fprintf(w, "create cpu profile %s \n", filename) 91 _, fl := path.Split(os.Args[0]) 92 fmt.Fprintf(w, "Now you can use this to check it: go tool pprof %s %s\n", fl, filename) 93 } 94 95 // PrintGCSummary print gc information to io.Writer 96 func PrintGCSummary(w io.Writer) { 97 memStats := &runtime.MemStats{} 98 runtime.ReadMemStats(memStats) 99 gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} 100 debug.ReadGCStats(gcstats) 101 102 printGC(memStats, gcstats, w) 103 } 104 105 func printGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { 106 107 if gcstats.NumGC > 0 { 108 lastPause := gcstats.Pause[0] 109 elapsed := time.Now().Sub(startTime) 110 overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100 111 allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 112 113 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", 114 gcstats.NumGC, 115 toS(lastPause), 116 toS(avg(gcstats.Pause)), 117 overhead, 118 toH(memStats.Alloc), 119 toH(memStats.Sys), 120 toH(uint64(allocatedRate)), 121 toS(gcstats.PauseQuantiles[94]), 122 toS(gcstats.PauseQuantiles[98]), 123 toS(gcstats.PauseQuantiles[99])) 124 } else { 125 // while GC has disabled 126 elapsed := time.Now().Sub(startTime) 127 allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 128 129 fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n", 130 toH(memStats.Alloc), 131 toH(memStats.Sys), 132 toH(uint64(allocatedRate))) 133 } 134 } 135 136 func avg(items []time.Duration) time.Duration { 137 var sum time.Duration 138 for _, item := range items { 139 sum += item 140 } 141 return time.Duration(int64(sum) / int64(len(items))) 142 } 143 144 // format bytes number friendly 145 func toH(bytes uint64) string { 146 switch { 147 case bytes < 1024: 148 return fmt.Sprintf("%dB", bytes) 149 case bytes < 1024*1024: 150 return fmt.Sprintf("%.2fK", float64(bytes)/1024) 151 case bytes < 1024*1024*1024: 152 return fmt.Sprintf("%.2fM", float64(bytes)/1024/1024) 153 default: 154 return fmt.Sprintf("%.2fG", float64(bytes)/1024/1024/1024) 155 } 156 } 157 158 // short string format 159 func toS(d time.Duration) string { 160 161 u := uint64(d) 162 if u < uint64(time.Second) { 163 switch { 164 case u == 0: 165 return "0" 166 case u < uint64(time.Microsecond): 167 return fmt.Sprintf("%.2fns", float64(u)) 168 case u < uint64(time.Millisecond): 169 return fmt.Sprintf("%.2fus", float64(u)/1000) 170 default: 171 return fmt.Sprintf("%.2fms", float64(u)/1000/1000) 172 } 173 } else { 174 switch { 175 case u < uint64(time.Minute): 176 return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000) 177 case u < uint64(time.Hour): 178 return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60) 179 default: 180 return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60) 181 } 182 } 183 184 }