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  }