github.com/grailbio/base@v0.0.11/pprof/pprof.go (about) 1 // Copyright 2018 GRAIL, Inc. All rights reserved. 2 // Use of this source code is governed by the Apache-2.0 3 // license that can be found in the LICENSE file. 4 5 package pprof 6 7 import ( 8 "flag" 9 "fmt" 10 "net" 11 "net/http" 12 "os" 13 "runtime" 14 rtpprof "runtime/pprof" 15 "sync" 16 "sync/atomic" 17 "time" 18 19 "github.com/grailbio/base/grail/go/net/http/pprof" 20 "v.io/x/lib/vlog" 21 ) 22 23 // Profiling provides access to the various profiling options provided 24 // by runtime/pprof. 25 type profiling struct { 26 started int32 // # calls to Start(). 27 28 cpuName string 29 heapName string 30 threadName string 31 blockName string 32 mutexName string 33 34 blockRate int 35 mutexRate int 36 profileInterval float64 37 38 mu *sync.Mutex 39 // Following fields are guarded by mu. 40 cpuFile *os.File 41 generation int // guarded by mu 42 43 // HTTP Listen address, in form ":12345" 44 httpAddr string 45 // Actual listen address of the debug HTTP server. 46 pprofAddr net.Addr 47 } 48 49 func newProfiling() *profiling { 50 pr := &profiling{ 51 mu: &sync.Mutex{}, 52 } 53 for _, p := range []struct { 54 fl interface{} 55 n string 56 dv interface{} 57 d string 58 }{ 59 {&pr.httpAddr, "pprof", "", "address for pprof server"}, 60 {&pr.cpuName, "cpu-profile", "", "filename for cpu profile"}, 61 {&pr.heapName, "heap-profile", "", "filename prefix for heap profiles"}, 62 {&pr.threadName, "thread-create-profile", "", "filename prefix for thread create profiles"}, 63 {&pr.blockName, "block-profile", "", "filename prefix for block profiles"}, 64 {&pr.mutexName, "mutex-profile", "", "filename prefix for mutex profiles"}, 65 {&pr.mutexRate, "mutex-profile-rate", 200, "rate for runtime.SetMutexProfileFraction"}, 66 {&pr.blockRate, "block-profile-rate", 200, "rate for runtime.SetBlockProfileRate"}, 67 {&pr.profileInterval, "profile-interval-s", 0.0, "If >0, output new profiles at this interval (seconds). If <=0, profiles are written only when Write() is called"}, 68 } { 69 fn := p.n 70 switch flt := p.fl.(type) { 71 case *string: 72 flag.StringVar(flt, fn, p.dv.(string), p.d) 73 case *int: 74 flag.IntVar(flt, fn, p.dv.(int), p.d) 75 case *float64: 76 flag.Float64Var(flt, fn, p.dv.(float64), p.d) 77 } 78 } 79 return pr 80 } 81 82 func generationSuffix(generation int) string { 83 return fmt.Sprintf("-%05v.pprof", generation) 84 } 85 86 // Write writes the profile information to new files. Each call results 87 // in a new file name of the for <command-line-prefix>-<number> where 88 // number is incremented each time Write is called. All of the profiles 89 // enabled on the command line are written. 90 func (p *profiling) Write(debug int) { 91 p.mu.Lock() 92 defer p.mu.Unlock() 93 suffix := generationSuffix(p.generation) 94 p.generation++ 95 for _, n := range []struct { 96 fl string 97 pn string 98 }{ 99 {p.cpuName, "cpu"}, 100 {p.heapName, "heap"}, 101 {p.threadName, "threadcreate"}, 102 {p.blockName, "block"}, 103 {p.mutexName, "mutex"}, 104 } { 105 if len(n.fl) == 0 { 106 continue 107 } 108 if n.fl == p.cpuName { 109 p.stopCPU() 110 p.startCPU() 111 } else { 112 pr := rtpprof.Lookup(n.pn) 113 if pr == nil { 114 vlog.Errorf("failed to lookup profile: %v", n.pn) 115 } 116 fn := n.fl + suffix 117 f, err := os.Create(fn) 118 if err != nil { 119 vlog.Errorf("failed to create %v for %v profile", fn, n.pn) 120 continue 121 } 122 defer f.Close() 123 if err := pr.WriteTo(f, debug); err != nil { 124 vlog.Errorf("failed to write Profiling for %v to %v: %v", n.pn, fn, err) 125 } 126 vlog.VI(1).Infof("%v: Wrote profile", f.Name()) 127 } 128 } 129 130 } 131 132 // Start starts CPU and all other profiling that has been specified on the 133 // command line. Calling this method multiple times is a noop. 134 func (p *profiling) Start() { 135 if atomic.AddInt32(&p.started, 1) > 1 { 136 return 137 } 138 if len(p.blockName) > 0 || len(p.httpAddr) > 0 { 139 runtime.SetBlockProfileRate(p.blockRate) 140 } 141 if len(p.mutexName) > 0 || len(p.httpAddr) > 0 { 142 runtime.SetMutexProfileFraction(p.mutexRate) 143 } 144 if len(p.cpuName) > 0 { 145 p.startCPU() 146 } 147 if p.profileInterval > 0 { 148 go func() { 149 for { 150 time.Sleep(time.Duration(p.profileInterval * float64(time.Second))) 151 p.Write(1) 152 } 153 }() 154 } 155 if len(p.httpAddr) > 0 { 156 mux := http.NewServeMux() 157 mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 158 w.Header().Set("Content-Type", "text/html; charset=utf-8") 159 w.Write([]byte("<ul><li> <a href=\"/debug/pprof/goroutine?debug=1\">threadz</a></ul>")) 160 })) 161 mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) 162 mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 163 mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 164 mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 165 mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) 166 167 l, err := net.Listen("tcp", p.httpAddr) 168 if err != nil { 169 vlog.Error(err) 170 return 171 } 172 173 p.pprofAddr = l.Addr() 174 vlog.Infof("Pprof server listening on %s", p.pprofAddr.String()) 175 go func() { 176 if err := http.Serve(l, mux); err != nil { 177 vlog.Error(err) 178 } 179 }() 180 } 181 182 } 183 184 // startCPU starts CPU profiling. The CPU profiler must not be already active. 185 func (p *profiling) startCPU() { 186 f, err := os.Create(p.cpuName + generationSuffix(p.generation)) 187 if err != nil { 188 vlog.Fatal("could not create CPU profile: ", err) 189 } 190 p.cpuFile = f 191 if err := rtpprof.StartCPUProfile(f); err != nil { 192 f.Close() 193 vlog.Fatal("could not start CPU profile: ", err) 194 } 195 } 196 197 // stopCPU stops cpu profiling and must be called to ensure that the CPU 198 // Profiling information is written to its output file. 199 func (p *profiling) stopCPU() { 200 rtpprof.StopCPUProfile() 201 p.cpuFile.Close() 202 p.cpuFile = nil 203 } 204 205 var singleton *profiling 206 207 func init() { 208 singleton = newProfiling() 209 } 210 211 // Start starts the cpu, memory, and other profilers specified in the 212 // commandline. This function has no effect if none of the following flags is 213 // set. 214 // 215 // -cpu-profile 216 // -heap-profile 217 // -thread-create-profile 218 // -block-profile 219 // -mutex-profile 220 // -profile-interval-s 221 // -pprof 222 // 223 // This function should be called at the start of a process. Calling it multiple 224 // times a noop. 225 func Start() { 226 singleton.Start() 227 } 228 229 // PprofAddr returns the listen address of the HTTP httpserver. It is useful 230 // when the process was started with flag -pprof=:0. 231 func HTTPAddr() net.Addr { 232 return singleton.pprofAddr 233 } 234 235 // WritePprof writes the profile information to new files. Each call results in 236 // a new file name of the for <command-line-prefix>-<number> where number is 237 // incremented each time Write is called. All of the profiles enabled on the 238 // command line are written. 239 func Write(debug int) { 240 singleton.Write(debug) 241 }