code.vegaprotocol.io/vega@v0.79.0/libs/pprof/pprof.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package pprof 17 18 import ( 19 "fmt" 20 "net/http" 21 "os" 22 "path/filepath" 23 "runtime" 24 "runtime/pprof" 25 "time" 26 27 "code.vegaprotocol.io/vega/libs/config/encoding" 28 vgfs "code.vegaprotocol.io/vega/libs/fs" 29 "code.vegaprotocol.io/vega/logging" 30 31 "github.com/felixge/fgprof" 32 33 // import pprof globally because it's used to init the package 34 // and this comment is mostly here as well in order to make 35 // golint very many much happy. 36 _ "net/http/pprof" 37 ) 38 39 const ( 40 pprofDir = "pprof" 41 memprofileName = "mem" 42 cpuprofileName = "cpu" 43 profileExt = ".pprof" 44 45 namedLogger = "pprof" 46 ) 47 48 // Config represent the configuration of the pprof package. 49 type Config struct { 50 Level encoding.LogLevel `long:"level"` 51 Enabled bool `long:"enabled"` 52 Port uint16 `long:"port"` 53 ProfilesDir string `long:"profiles-dir"` 54 // To include every blocking event in the profile, pass rate = 1. 55 // To turn off profiling entirely, pass rate <= 0. 56 BlockProfileRate int `long:"block-profile-rate"` 57 // To turn off profiling entirely, pass rate 0. 58 // To just read the current rate, pass rate < 0. 59 // (For n>1 the details of sampling may change.) 60 MutexProfileFraction int `long:"mutex-profile-fraction"` 61 // Write the profiles to disk every WriteEvery interval 62 WriteEvery encoding.Duration `description:"write pprof files at this interval; if 0 only write on shutdown" long:"write-every"` 63 } 64 65 // Pprofhandler is handling pprof profile management. 66 type Pprofhandler struct { 67 Config 68 69 log *logging.Logger 70 stop chan struct{} 71 done chan struct{} 72 memprofilePath string 73 cpuprofilePath string 74 } 75 76 // NewDefaultConfig create a new default configuration for the pprof handler. 77 func NewDefaultConfig() Config { 78 return Config{ 79 Level: encoding.LogLevel{Level: logging.InfoLevel}, 80 Enabled: false, 81 Port: 6060, 82 ProfilesDir: "/tmp", 83 BlockProfileRate: 0, 84 MutexProfileFraction: 0, 85 WriteEvery: encoding.Duration{Duration: 15 * time.Minute}, 86 } 87 } 88 89 // New creates a new pprof handler. 90 func New(log *logging.Logger, config Config) (*Pprofhandler, error) { 91 // setup logger 92 log = log.Named(namedLogger) 93 log.SetLevel(config.Level.Get()) 94 95 runtime.SetBlockProfileRate(config.BlockProfileRate) 96 runtime.SetMutexProfileFraction(config.MutexProfileFraction) 97 98 p := &Pprofhandler{ 99 log: log, 100 Config: config, 101 stop: make(chan struct{}), 102 done: make(chan struct{}), 103 } 104 105 // start the pprof http server 106 http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler()) 107 go func() { 108 p.log.Error("pprof web server closed", logging.Error(http.ListenAndServe(fmt.Sprintf("localhost:%d", config.Port), nil))) 109 }() 110 111 // make sure profile dir exists 112 profDir := filepath.Join(config.ProfilesDir, pprofDir) 113 if err := vgfs.EnsureDir(profDir); err != nil { 114 p.log.Error("Could not create profile dir", 115 logging.String("path", profDir), 116 logging.Error(err), 117 ) 118 return nil, err 119 } 120 121 go p.runProfiling() 122 123 return p, nil 124 } 125 126 func (p *Pprofhandler) runProfiling() error { 127 defer close(p.done) 128 // If WriteEvery is 0, make a ticker that never ticks 129 tick := make(<-chan time.Time) 130 if p.WriteEvery.Duration > 0 { 131 tick = time.NewTicker(p.WriteEvery.Duration).C 132 } 133 134 for { 135 if err := p.startProfiling(); err != nil { 136 return err 137 } 138 139 select { 140 case <-p.stop: 141 return p.stopProfiling() 142 case <-tick: 143 } 144 145 if err := p.stopProfiling(); err != nil { 146 return err 147 } 148 } 149 } 150 151 func (p *Pprofhandler) startProfiling() error { 152 t := time.Now() 153 memprofileFile := fmt.Sprintf("%s-%s%s", memprofileName, t.Format("2006-01-02-15-04-05"), profileExt) 154 cpuprofileFile := fmt.Sprintf("%s-%s%s", cpuprofileName, t.Format("2006-01-02-15-04-05"), profileExt) 155 156 p.memprofilePath = filepath.Join(p.ProfilesDir, pprofDir, memprofileFile) 157 p.cpuprofilePath = filepath.Join(p.ProfilesDir, pprofDir, cpuprofileFile) 158 159 profileFile, err := os.Create(p.cpuprofilePath) 160 if err != nil { 161 p.log.Error("Could not create CPU profile file", 162 logging.String("path", p.cpuprofilePath), 163 logging.Error(err), 164 ) 165 return err 166 } 167 pprof.StartCPUProfile(profileFile) 168 169 return nil 170 } 171 172 // ReloadConf update the configuration of the pprof package. 173 func (p *Pprofhandler) ReloadConf(cfg Config) { 174 p.log.Info("reloading configuration") 175 if p.log.GetLevel() != cfg.Level.Get() { 176 p.log.Info("updating log level", 177 logging.String("old", p.log.GetLevel().String()), 178 logging.String("new", cfg.Level.String()), 179 ) 180 p.log.SetLevel(cfg.Level.Get()) 181 } 182 183 // the config will not be used anyway, just use the log level in here 184 p.Config = cfg 185 runtime.SetBlockProfileRate(cfg.BlockProfileRate) 186 runtime.SetMutexProfileFraction(cfg.MutexProfileFraction) 187 } 188 189 // Stop is meant to be use to stop the pprof profile, should be use with defer probably. 190 func (p *Pprofhandler) Stop() error { 191 close(p.stop) 192 <-p.done 193 return nil 194 } 195 196 func (p *Pprofhandler) stopProfiling() error { 197 // stop cpu profile once the memory profile is written 198 defer pprof.StopCPUProfile() 199 200 p.log.Info("saving pprof memory profile", logging.String("path", p.memprofilePath)) 201 p.log.Info("saving pprof cpu profile", logging.String("path", p.cpuprofilePath)) 202 203 // save memory profile 204 f, err := os.Create(p.memprofilePath) 205 if err != nil { 206 p.log.Error("Could not create memory profile file", 207 logging.String("path", p.memprofilePath), 208 logging.Error(err), 209 ) 210 return err 211 } 212 213 runtime.GC() 214 if err := pprof.WriteHeapProfile(f); err != nil { 215 p.log.Error("Could not write memory profile", 216 logging.Error(err), 217 ) 218 return err 219 } 220 f.Close() 221 222 return nil 223 }