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