github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sentry/control/pprof.go (about) 1 // Copyright 2019 The gVisor Authors. 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 control 16 17 import ( 18 "runtime" 19 "runtime/pprof" 20 "runtime/trace" 21 "time" 22 23 "github.com/nicocha30/gvisor-ligolo/pkg/fd" 24 "github.com/nicocha30/gvisor-ligolo/pkg/sentry/kernel" 25 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 26 "github.com/nicocha30/gvisor-ligolo/pkg/urpc" 27 ) 28 29 const ( 30 // DefaultBlockProfileRate is the default profiling rate for block 31 // profiles. 32 // 33 // The default here is 10%, which will record a stacktrace 10% of the 34 // time when blocking occurs. Since these events should not be super 35 // frequent, we expect this to achieve a reasonable balance between 36 // collecting the data we need and imposing a high performance cost 37 // (e.g. skewing even the CPU profile). 38 DefaultBlockProfileRate = 10 39 40 // DefaultMutexProfileRate is the default profiling rate for mutex 41 // profiles. Like the block rate above, we use a default rate of 10% 42 // for the same reasons. 43 DefaultMutexProfileRate = 10 44 ) 45 46 // Profile includes profile-related RPC stubs. It provides a way to 47 // control the built-in runtime profiling facilities. 48 // 49 // The profile object must be instantied via NewProfile. 50 type Profile struct { 51 // kernel is the kernel under profile. It's immutable. 52 kernel *kernel.Kernel 53 54 // cpuMu protects CPU profiling. 55 cpuMu sync.Mutex 56 57 // blockMu protects block profiling. 58 blockMu sync.Mutex 59 60 // mutexMu protects mutex profiling. 61 mutexMu sync.Mutex 62 63 // traceMu protects trace profiling. 64 traceMu sync.Mutex 65 66 // done is closed when profiling is done. 67 done chan struct{} 68 } 69 70 // NewProfile returns a new Profile object. 71 func NewProfile(k *kernel.Kernel) *Profile { 72 return &Profile{ 73 kernel: k, 74 done: make(chan struct{}), 75 } 76 } 77 78 // Stop implements urpc.Stopper.Stop. 79 func (p *Profile) Stop() { 80 close(p.done) 81 } 82 83 // CPUProfileOpts contains options specifically for CPU profiles. 84 type CPUProfileOpts struct { 85 // FilePayload is the destination for the profiling output. 86 urpc.FilePayload 87 88 // Duration is the duration of the profile. 89 Duration time.Duration `json:"duration"` 90 } 91 92 // CPU is an RPC stub which collects a CPU profile. 93 func (p *Profile) CPU(o *CPUProfileOpts, _ *struct{}) error { 94 if len(o.FilePayload.Files) < 1 { 95 return nil // Allowed. 96 } 97 98 output := o.FilePayload.Files[0] 99 defer output.Close() 100 101 p.cpuMu.Lock() 102 defer p.cpuMu.Unlock() 103 104 // Returns an error if profiling is already started. 105 if err := pprof.StartCPUProfile(output); err != nil { 106 return err 107 } 108 defer pprof.StopCPUProfile() 109 110 // Collect the profile. 111 select { 112 case <-time.After(o.Duration): 113 case <-p.done: 114 } 115 116 return nil 117 } 118 119 // HeapProfileOpts contains options specifically for heap profiles. 120 type HeapProfileOpts struct { 121 // FilePayload is the destination for the profiling output. 122 urpc.FilePayload 123 124 // Delay is the sleep time, similar to Duration. This may 125 // not affect the data collected however, as the heap will 126 // continue only the memory associated with the last alloc. 127 Delay time.Duration `json:"delay"` 128 } 129 130 // Heap generates a heap profile. 131 func (p *Profile) Heap(o *HeapProfileOpts, _ *struct{}) error { 132 if len(o.FilePayload.Files) < 1 { 133 return nil // Allowed. 134 } 135 136 output := o.FilePayload.Files[0] 137 defer output.Close() 138 139 // Wait for the given delay. 140 select { 141 case <-time.After(o.Delay): 142 case <-p.done: 143 } 144 145 // Get up-to-date statistics. 146 runtime.GC() 147 148 // Write the given profile. 149 return pprof.WriteHeapProfile(output) 150 } 151 152 // GoroutineProfileOpts contains options specifically for goroutine profiles. 153 type GoroutineProfileOpts struct { 154 // FilePayload is the destination for the profiling output. 155 urpc.FilePayload 156 } 157 158 // Goroutine dumps out the stack trace for all running goroutines. 159 func (p *Profile) Goroutine(o *GoroutineProfileOpts, _ *struct{}) error { 160 if len(o.FilePayload.Files) < 1 { 161 return nil // Allowed. 162 } 163 164 output := o.FilePayload.Files[0] 165 defer output.Close() 166 167 return pprof.Lookup("goroutine").WriteTo(output, 2) 168 } 169 170 // BlockProfileOpts contains options specifically for block profiles. 171 type BlockProfileOpts struct { 172 // FilePayload is the destination for the profiling output. 173 urpc.FilePayload 174 175 // Duration is the duration of the profile. 176 Duration time.Duration `json:"duration"` 177 178 // Rate is the block profile rate. 179 Rate int `json:"rate"` 180 } 181 182 // Block dumps a blocking profile. 183 func (p *Profile) Block(o *BlockProfileOpts, _ *struct{}) error { 184 if len(o.FilePayload.Files) < 1 { 185 return nil // Allowed. 186 } 187 188 output := o.FilePayload.Files[0] 189 defer output.Close() 190 191 p.blockMu.Lock() 192 defer p.blockMu.Unlock() 193 194 // Always set the rate. We then wait to collect a profile at this rate, 195 // and disable when we're done. 196 rate := DefaultBlockProfileRate 197 if o.Rate != 0 { 198 rate = o.Rate 199 } 200 runtime.SetBlockProfileRate(rate) 201 defer runtime.SetBlockProfileRate(0) 202 203 // Collect the profile. 204 select { 205 case <-time.After(o.Duration): 206 case <-p.done: 207 } 208 209 return pprof.Lookup("block").WriteTo(output, 0) 210 } 211 212 // MutexProfileOpts contains options specifically for mutex profiles. 213 type MutexProfileOpts struct { 214 // FilePayload is the destination for the profiling output. 215 urpc.FilePayload 216 217 // Duration is the duration of the profile. 218 Duration time.Duration `json:"duration"` 219 220 // Fraction is the mutex profile fraction. 221 Fraction int `json:"fraction"` 222 } 223 224 // Mutex dumps a mutex profile. 225 func (p *Profile) Mutex(o *MutexProfileOpts, _ *struct{}) error { 226 if len(o.FilePayload.Files) < 1 { 227 return nil // Allowed. 228 } 229 230 output := o.FilePayload.Files[0] 231 defer output.Close() 232 233 p.mutexMu.Lock() 234 defer p.mutexMu.Unlock() 235 236 // Always set the fraction. 237 fraction := DefaultMutexProfileRate 238 if o.Fraction != 0 { 239 fraction = o.Fraction 240 } 241 runtime.SetMutexProfileFraction(fraction) 242 defer runtime.SetMutexProfileFraction(0) 243 244 // Collect the profile. 245 select { 246 case <-time.After(o.Duration): 247 case <-p.done: 248 } 249 250 return pprof.Lookup("mutex").WriteTo(output, 0) 251 } 252 253 // TraceProfileOpts contains options specifically for traces. 254 type TraceProfileOpts struct { 255 // FilePayload is the destination for the profiling output. 256 urpc.FilePayload 257 258 // Duration is the duration of the profile. 259 Duration time.Duration `json:"duration"` 260 } 261 262 // Trace is an RPC stub which starts collection of an execution trace. 263 func (p *Profile) Trace(o *TraceProfileOpts, _ *struct{}) error { 264 if len(o.FilePayload.Files) < 1 { 265 return nil // Allowed. 266 } 267 268 output, err := fd.NewFromFile(o.FilePayload.Files[0]) 269 if err != nil { 270 return err 271 } 272 defer output.Close() 273 274 p.traceMu.Lock() 275 defer p.traceMu.Unlock() 276 277 // Returns an error if profiling is already started. 278 if err := trace.Start(output); err != nil { 279 output.Close() 280 return err 281 } 282 defer trace.Stop() 283 284 // Ensure all trace contexts are registered. 285 p.kernel.RebuildTraceContexts() 286 287 // Wait for the trace. 288 select { 289 case <-time.After(o.Duration): 290 case <-p.done: 291 } 292 293 // Similarly to the case above, if tasks have not ended traces, we will 294 // lose information. Thus we need to rebuild the tasks in order to have 295 // complete information. This will not lose information if multiple 296 // traces are overlapping. 297 p.kernel.RebuildTraceContexts() 298 299 return nil 300 }