vitess.io/vitess@v0.16.2/go/vt/servenv/pprof.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain 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, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package servenv 18 19 import ( 20 "fmt" 21 "io" 22 "os" 23 "os/signal" 24 "path/filepath" 25 "runtime" 26 "runtime/pprof" 27 "runtime/trace" 28 "strconv" 29 "strings" 30 "sync/atomic" 31 "syscall" 32 33 "github.com/spf13/pflag" 34 35 "vitess.io/vitess/go/vt/log" 36 ) 37 38 var ( 39 pprofFlag []string 40 ) 41 42 type profmode string 43 44 const ( 45 profileCPU profmode = "cpu" 46 profileMemHeap profmode = "mem_heap" 47 profileMemAllocs profmode = "mem_allocs" 48 profileMutex profmode = "mutex" 49 profileBlock profmode = "block" 50 profileTrace profmode = "trace" 51 profileThreads profmode = "threads" 52 profileGoroutine profmode = "goroutine" 53 ) 54 55 func (p profmode) filename() string { 56 return fmt.Sprintf("%s.pprof", string(p)) 57 } 58 59 type profile struct { 60 mode profmode 61 rate int 62 path string 63 quiet bool 64 waitSig bool 65 } 66 67 func parseProfileFlag(pf []string) (*profile, error) { 68 if len(pf) == 0 { 69 return nil, nil 70 } 71 72 var p profile 73 74 switch pf[0] { 75 case "cpu": 76 p.mode = profileCPU 77 case "mem", "mem=heap": 78 p.mode = profileMemHeap 79 p.rate = 4096 80 case "mem=allocs": 81 p.mode = profileMemAllocs 82 p.rate = 4096 83 case "mutex": 84 p.mode = profileMutex 85 p.rate = 1 86 case "block": 87 p.mode = profileBlock 88 p.rate = 1 89 case "trace": 90 p.mode = profileTrace 91 case "threads": 92 p.mode = profileThreads 93 case "goroutine": 94 p.mode = profileGoroutine 95 default: 96 return nil, fmt.Errorf("unknown profile mode: %q", pf[0]) 97 } 98 99 for _, kv := range pf[1:] { 100 var err error 101 fields := strings.SplitN(kv, "=", 2) 102 103 switch fields[0] { 104 case "rate": 105 if len(fields) == 1 { 106 return nil, fmt.Errorf("missing value for 'rate'") 107 } 108 p.rate, err = strconv.Atoi(fields[1]) 109 if err != nil { 110 return nil, fmt.Errorf("invalid profile rate %q: %v", fields[1], err) 111 } 112 113 case "path": 114 if len(fields) == 1 { 115 return nil, fmt.Errorf("missing value for 'path'") 116 } 117 p.path = fields[1] 118 119 case "quiet": 120 if len(fields) == 1 { 121 p.quiet = true 122 continue 123 } 124 125 p.quiet, err = strconv.ParseBool(fields[1]) 126 if err != nil { 127 return nil, fmt.Errorf("invalid quiet flag %q: %v", fields[1], err) 128 } 129 case "waitSig": 130 if len(fields) == 1 { 131 p.waitSig = true 132 continue 133 } 134 p.waitSig, err = strconv.ParseBool(fields[1]) 135 if err != nil { 136 return nil, fmt.Errorf("invalid waitSig flag %q: %v", fields[1], err) 137 } 138 default: 139 return nil, fmt.Errorf("unknown flag: %q", fields[0]) 140 } 141 } 142 143 return &p, nil 144 } 145 146 var profileStarted uint32 147 148 func startCallback(start func()) func() { 149 return func() { 150 if atomic.CompareAndSwapUint32(&profileStarted, 0, 1) { 151 start() 152 } else { 153 log.Fatal("profile: Start() already called") 154 } 155 } 156 } 157 158 func stopCallback(stop func()) func() { 159 return func() { 160 if atomic.CompareAndSwapUint32(&profileStarted, 1, 0) { 161 stop() 162 } 163 } 164 } 165 166 func (prof *profile) mkprofile() io.WriteCloser { 167 var ( 168 path string 169 err error 170 logf = func(format string, args ...any) {} 171 ) 172 173 if prof.path != "" { 174 path = prof.path 175 err = os.MkdirAll(path, 0777) 176 } else { 177 path, err = os.MkdirTemp("", "profile") 178 } 179 if err != nil { 180 log.Fatalf("pprof: could not create initial output directory: %v", err) 181 } 182 183 if !prof.quiet { 184 logf = log.Infof 185 } 186 187 fn := filepath.Join(path, prof.mode.filename()) 188 f, err := os.Create(fn) 189 if err != nil { 190 log.Fatalf("pprof: could not create profile %q: %v", fn, err) 191 } 192 logf("pprof: %s profiling enabled, %s", string(prof.mode), fn) 193 194 return f 195 } 196 197 // init returns a start function that begins the configured profiling process and 198 // returns a cleanup function that must be executed before process termination to 199 // flush the profile to disk. 200 // Based on the profiling code in github.com/pkg/profile 201 func (prof *profile) init() (start func(), stop func()) { 202 var pf io.WriteCloser 203 204 switch prof.mode { 205 case profileCPU: 206 start = startCallback(func() { 207 pf = prof.mkprofile() 208 pprof.StartCPUProfile(pf) 209 }) 210 stop = stopCallback(func() { 211 pprof.StopCPUProfile() 212 pf.Close() 213 }) 214 return start, stop 215 216 case profileMemHeap, profileMemAllocs: 217 old := runtime.MemProfileRate 218 start = startCallback(func() { 219 pf = prof.mkprofile() 220 runtime.MemProfileRate = prof.rate 221 }) 222 stop = stopCallback(func() { 223 tt := "heap" 224 if prof.mode == profileMemAllocs { 225 tt = "allocs" 226 } 227 pprof.Lookup(tt).WriteTo(pf, 0) 228 pf.Close() 229 runtime.MemProfileRate = old 230 }) 231 return start, stop 232 233 case profileMutex: 234 start = startCallback(func() { 235 pf = prof.mkprofile() 236 runtime.SetMutexProfileFraction(prof.rate) 237 }) 238 stop = stopCallback(func() { 239 if mp := pprof.Lookup("mutex"); mp != nil { 240 mp.WriteTo(pf, 0) 241 } 242 pf.Close() 243 runtime.SetMutexProfileFraction(0) 244 }) 245 return start, stop 246 247 case profileBlock: 248 start = startCallback(func() { 249 pf = prof.mkprofile() 250 runtime.SetBlockProfileRate(prof.rate) 251 }) 252 stop = stopCallback(func() { 253 pprof.Lookup("block").WriteTo(pf, 0) 254 pf.Close() 255 runtime.SetBlockProfileRate(0) 256 }) 257 return start, stop 258 259 case profileThreads: 260 start = startCallback(func() { 261 pf = prof.mkprofile() 262 }) 263 stop = stopCallback(func() { 264 if mp := pprof.Lookup("threadcreate"); mp != nil { 265 mp.WriteTo(pf, 0) 266 } 267 pf.Close() 268 }) 269 return start, stop 270 271 case profileTrace: 272 start = startCallback(func() { 273 pf = prof.mkprofile() 274 if err := trace.Start(pf); err != nil { 275 log.Fatalf("pprof: could not start trace: %v", err) 276 } 277 }) 278 stop = stopCallback(func() { 279 trace.Stop() 280 pf.Close() 281 }) 282 return start, stop 283 284 case profileGoroutine: 285 start = startCallback(func() { 286 pf = prof.mkprofile() 287 }) 288 stop = stopCallback(func() { 289 if mp := pprof.Lookup("goroutine"); mp != nil { 290 mp.WriteTo(pf, 0) 291 } 292 pf.Close() 293 }) 294 return start, stop 295 296 default: 297 panic("unsupported profile mode") 298 } 299 } 300 301 func pprofInit() { 302 prof, err := parseProfileFlag(pprofFlag) 303 if err != nil { 304 log.Fatal(err) 305 } 306 if prof != nil { 307 start, stop := prof.init() 308 startSignal := make(chan os.Signal, 1) 309 stopSignal := make(chan os.Signal, 1) 310 311 if prof.waitSig { 312 signal.Notify(startSignal, syscall.SIGUSR1) 313 } else { 314 start() 315 signal.Notify(stopSignal, syscall.SIGUSR1) 316 } 317 318 go func() { 319 for { 320 <-startSignal 321 start() 322 signal.Reset(syscall.SIGUSR1) 323 signal.Notify(stopSignal, syscall.SIGUSR1) 324 } 325 }() 326 327 go func() { 328 for { 329 <-stopSignal 330 stop() 331 signal.Reset(syscall.SIGUSR1) 332 signal.Notify(startSignal, syscall.SIGUSR1) 333 } 334 }() 335 336 OnTerm(stop) 337 } 338 } 339 340 func init() { 341 OnParse(func(fs *pflag.FlagSet) { 342 fs.StringSliceVar(&pprofFlag, "pprof", pprofFlag, "enable profiling") 343 }) 344 OnInit(pprofInit) 345 }