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  }