// Copyright 2013 Beego Authors // Copyright 2014 The Macaron Authors // // Licensed under the Apache License, Version 2.0 (the "License"): you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. package toolbox import ( "bytes" "errors" "fmt" "io" "os" "path" "runtime" "runtime/debug" "runtime/pprof" "time" "gitea.com/macaron/macaron" "github.com/unknwon/com" ) var ( profilePath string pid int startTime = time.Now() inCPUProfile bool ) // StartCPUProfile starts CPU profile monitor. func StartCPUProfile() error { if inCPUProfile { return errors.New("CPU profile has alreday been started!") } inCPUProfile = true os.MkdirAll(profilePath, os.ModePerm) f, err := os.Create(path.Join(profilePath, "cpu-"+com.ToStr(pid)+".pprof")) if err != nil { panic("fail to record CPU profile: " + err.Error()) } pprof.StartCPUProfile(f) return nil } // StopCPUProfile stops CPU profile monitor. func StopCPUProfile() error { if !inCPUProfile { return errors.New("CPU profile hasn't been started!") } pprof.StopCPUProfile() inCPUProfile = false return nil } func init() { pid = os.Getpid() } // DumpMemProf dumps memory profile in pprof. func DumpMemProf(w io.Writer) { pprof.WriteHeapProfile(w) } func dumpMemProf() { os.MkdirAll(profilePath, os.ModePerm) f, err := os.Create(path.Join(profilePath, "mem-"+com.ToStr(pid)+".memprof")) if err != nil { panic("fail to record memory profile: " + err.Error()) } runtime.GC() DumpMemProf(f) f.Close() } func avg(items []time.Duration) time.Duration { var sum time.Duration for _, item := range items { sum += item } return time.Duration(int64(sum) / int64(len(items))) } func dumpGC(memStats *runtime.MemStats, gcstats *debug.GCStats, w io.Writer) { if gcstats.NumGC > 0 { lastPause := gcstats.Pause[0] elapsed := time.Now().Sub(startTime) overhead := float64(gcstats.PauseTotal) / float64(elapsed) * 100 allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() 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", gcstats.NumGC, com.ToStr(lastPause), com.ToStr(avg(gcstats.Pause)), overhead, com.HumaneFileSize(memStats.Alloc), com.HumaneFileSize(memStats.Sys), com.HumaneFileSize(uint64(allocatedRate)), com.ToStr(gcstats.PauseQuantiles[94]), com.ToStr(gcstats.PauseQuantiles[98]), com.ToStr(gcstats.PauseQuantiles[99])) } else { // while GC has disabled elapsed := time.Now().Sub(startTime) allocatedRate := float64(memStats.TotalAlloc) / elapsed.Seconds() fmt.Fprintf(w, "Alloc:%s Sys:%s Alloc(Rate):%s/s\n", com.HumaneFileSize(memStats.Alloc), com.HumaneFileSize(memStats.Sys), com.HumaneFileSize(uint64(allocatedRate))) } } // DumpGCSummary dumps GC information to io.Writer func DumpGCSummary(w io.Writer) { memStats := &runtime.MemStats{} runtime.ReadMemStats(memStats) gcstats := &debug.GCStats{PauseQuantiles: make([]time.Duration, 100)} debug.ReadGCStats(gcstats) dumpGC(memStats, gcstats, w) } func handleProfile(ctx *macaron.Context) string { switch ctx.Query("op") { case "startcpu": if err := StartCPUProfile(); err != nil { return err.Error() } case "stopcpu": if err := StopCPUProfile(); err != nil { return err.Error() } case "mem": dumpMemProf() case "gc": var buf bytes.Buffer DumpGCSummary(&buf) return string(buf.Bytes()) default: return fmt.Sprintf(`<p>Available operations:</p> <ol> <li><a href="%[1]s?op=startcpu">Start CPU profile</a></li> <li><a href="%[1]s?op=stopcpu">Stop CPU profile</a></li> <li><a href="%[1]s?op=mem">Dump memory profile</a></li> <li><a href="%[1]s?op=gc">Dump GC summary</a></li> </ol>`, opt.ProfileURLPrefix) } ctx.Redirect(opt.ProfileURLPrefix) return "" }