github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/runsc/profile/profile.go (about)

     1  // Copyright 2021 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 profile contains profiling utils.
    16  package profile
    17  
    18  import (
    19  	"os"
    20  	"runtime"
    21  	"runtime/pprof"
    22  	"runtime/trace"
    23  
    24  	"github.com/nicocha30/gvisor-ligolo/pkg/log"
    25  	"github.com/nicocha30/gvisor-ligolo/pkg/sentry/control"
    26  	"github.com/nicocha30/gvisor-ligolo/runsc/flag"
    27  )
    28  
    29  // Kind is the kind of profiling to perform.
    30  type Kind int
    31  
    32  const (
    33  	// Block profile.
    34  	Block Kind = iota
    35  	// CPU profile.
    36  	CPU
    37  	// Heap profile.
    38  	Heap
    39  	// Mutex profile.
    40  	Mutex
    41  	// Trace profile.
    42  	Trace
    43  )
    44  
    45  // FDArgs are the arguments that describe which profiles to enable and which
    46  // FDs to write them to. Profiling of a given type will only be enabled if the
    47  // corresponding FD is >=0.
    48  type FDArgs struct {
    49  	// BlockFD is the file descriptor to write a block profile to.
    50  	// Valid if >=0.
    51  	BlockFD int
    52  	// CPUFD is the file descriptor to write a CPU profile to.
    53  	// Valid if >=0.
    54  	CPUFD int
    55  	// HeapFD is the file descriptor to write a heap profile to.
    56  	// Valid if >=0.
    57  	HeapFD int
    58  	// MutexFD is the file descriptor to write a mutex profile to.
    59  	// Valid if >=0.
    60  	MutexFD int
    61  	// TraceFD is the file descriptor to write a Go execution trace to.
    62  	// Valid if >=0.
    63  	TraceFD int
    64  }
    65  
    66  // SetFromFlags sets the FDArgs from the given flags. The default value for
    67  // each FD is -1.
    68  func (fds *FDArgs) SetFromFlags(f *flag.FlagSet) {
    69  	f.IntVar(&fds.BlockFD, "profile-block-fd", -1, "file descriptor to write block profile to. -1 disables profiling.")
    70  	f.IntVar(&fds.CPUFD, "profile-cpu-fd", -1, "file descriptor to write CPU profile to. -1 disables profiling.")
    71  	f.IntVar(&fds.HeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.")
    72  	f.IntVar(&fds.MutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.")
    73  	f.IntVar(&fds.TraceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
    74  }
    75  
    76  // Opts is a map of profile Kind to FD.
    77  type Opts map[Kind]uintptr
    78  
    79  // ToOpts turns FDArgs into an Opts struct which can be passed to Start.
    80  func (fds *FDArgs) ToOpts() Opts {
    81  	o := Opts{}
    82  	if fds.BlockFD >= 0 {
    83  		o[Block] = uintptr(fds.BlockFD)
    84  	}
    85  	if fds.CPUFD >= 0 {
    86  		o[CPU] = uintptr(fds.CPUFD)
    87  	}
    88  	if fds.HeapFD >= 0 {
    89  		o[Heap] = uintptr(fds.HeapFD)
    90  	}
    91  	if fds.MutexFD >= 0 {
    92  		o[Mutex] = uintptr(fds.MutexFD)
    93  	}
    94  	if fds.TraceFD >= 0 {
    95  		o[Trace] = uintptr(fds.TraceFD)
    96  	}
    97  	return o
    98  }
    99  
   100  // Start starts profiling for the given Kinds in opts, and writes the profile
   101  // data to the corresponding FDs in opts. It returns a function which will stop
   102  // profiling.
   103  func Start(opts Opts) func() {
   104  	var onStopProfiling []func()
   105  	stopProfiling := func() {
   106  		for _, f := range onStopProfiling {
   107  			f()
   108  		}
   109  	}
   110  
   111  	if fd, ok := opts[Block]; ok {
   112  		log.Infof("Block profiling enabled")
   113  		file := os.NewFile(fd, "profile-block")
   114  
   115  		runtime.SetBlockProfileRate(control.DefaultBlockProfileRate)
   116  		onStopProfiling = append(onStopProfiling, func() {
   117  			if err := pprof.Lookup("block").WriteTo(file, 0); err != nil {
   118  				log.Warningf("Error writing block profile: %v", err)
   119  			}
   120  			file.Close()
   121  			runtime.SetBlockProfileRate(0)
   122  			log.Infof("Block profiling stopped")
   123  		})
   124  	}
   125  
   126  	if fd, ok := opts[CPU]; ok {
   127  		log.Infof("CPU profiling enabled")
   128  		file := os.NewFile(fd, "profile-cpu")
   129  
   130  		pprof.StartCPUProfile(file)
   131  		onStopProfiling = append(onStopProfiling, func() {
   132  			pprof.StopCPUProfile()
   133  			file.Close()
   134  			log.Infof("CPU profiling stopped")
   135  		})
   136  	}
   137  
   138  	if fd, ok := opts[Heap]; ok {
   139  		log.Infof("Heap profiling enabled")
   140  		file := os.NewFile(fd, "profile-heap")
   141  
   142  		onStopProfiling = append(onStopProfiling, func() {
   143  			if err := pprof.Lookup("heap").WriteTo(file, 0); err != nil {
   144  				log.Warningf("Error writing heap profile: %v", err)
   145  			}
   146  			file.Close()
   147  			log.Infof("Heap profiling stopped")
   148  		})
   149  	}
   150  
   151  	if fd, ok := opts[Mutex]; ok {
   152  		log.Infof("Mutex profiling enabled")
   153  		file := os.NewFile(fd, "profile-mutex")
   154  
   155  		prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate)
   156  		onStopProfiling = append(onStopProfiling, func() {
   157  			if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil {
   158  				log.Warningf("Error writing mutex profile: %v", err)
   159  			}
   160  			file.Close()
   161  			runtime.SetMutexProfileFraction(prev)
   162  			log.Infof("Mutex profiling stopped")
   163  		})
   164  	}
   165  
   166  	if fd, ok := opts[Trace]; ok {
   167  		log.Infof("Tracing enabled")
   168  		file := os.NewFile(fd, "trace")
   169  
   170  		trace.Start(file)
   171  		onStopProfiling = append(onStopProfiling, func() {
   172  			trace.Stop()
   173  			file.Close()
   174  			log.Infof("Tracing stopped")
   175  		})
   176  	}
   177  
   178  	return stopProfiling
   179  }