mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Make profiling commands synchronous.
This allows for a model of profiling when you can start collection, and it will terminate when the sandbox terminates. Without this synchronous call, it is effectively impossible to collect length blocking and mutex profiles. PiperOrigin-RevId: 349483418
This commit is contained in:
committed by
gVisor bot
parent
91c05c609e
commit
85c1c3ed4b
+191
-122
@@ -15,10 +15,10 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/fd"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
@@ -26,184 +26,253 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/urpc"
|
||||
)
|
||||
|
||||
var errNoOutput = errors.New("no output writer provided")
|
||||
// Profile includes profile-related RPC stubs. It provides a way to
|
||||
// control the built-in runtime profiling facilities.
|
||||
//
|
||||
// The profile object must be instantied via NewProfile.
|
||||
type Profile struct {
|
||||
// kernel is the kernel under profile. It's immutable.
|
||||
kernel *kernel.Kernel
|
||||
|
||||
// ProfileOpts contains options for the StartCPUProfile/Goroutine RPC call.
|
||||
type ProfileOpts struct {
|
||||
// File is the filesystem path for the profile.
|
||||
File string `json:"path"`
|
||||
// cpuMu protects CPU profiling.
|
||||
cpuMu sync.Mutex
|
||||
|
||||
// blockMu protects block profiling.
|
||||
blockMu sync.Mutex
|
||||
|
||||
// mutexMu protects mutex profiling.
|
||||
mutexMu sync.Mutex
|
||||
|
||||
// traceMu protects trace profiling.
|
||||
traceMu sync.Mutex
|
||||
|
||||
// done is closed when profiling is done.
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// NewProfile returns a new Profile object, and a stop callback.
|
||||
//
|
||||
// The stop callback should be used at most once.
|
||||
func NewProfile(k *kernel.Kernel) (*Profile, func()) {
|
||||
p := &Profile{
|
||||
kernel: k,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
return p, func() {
|
||||
close(p.done)
|
||||
}
|
||||
}
|
||||
|
||||
// CPUProfileOpts contains options specifically for CPU profiles.
|
||||
type CPUProfileOpts struct {
|
||||
// FilePayload is the destination for the profiling output.
|
||||
urpc.FilePayload
|
||||
|
||||
// Duration is the duration of the profile.
|
||||
Duration time.Duration `json:"duration"`
|
||||
|
||||
// Hz is the rate, which may be zero.
|
||||
Hz int `json:"hz"`
|
||||
}
|
||||
|
||||
// CPU is an RPC stub which collects a CPU profile.
|
||||
func (p *Profile) CPU(o *CPUProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return nil // Allowed.
|
||||
}
|
||||
|
||||
output, err := fd.NewFromFile(o.FilePayload.Files[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
p.cpuMu.Lock()
|
||||
defer p.cpuMu.Unlock()
|
||||
|
||||
// Returns an error if profiling is already started.
|
||||
if o.Hz != 0 {
|
||||
runtime.SetCPUProfileRate(o.Hz)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(output); err != nil {
|
||||
return err
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
|
||||
// Collect the profile.
|
||||
select {
|
||||
case <-time.After(o.Duration):
|
||||
case <-p.done:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HeapProfileOpts contains options specifically for heap profiles.
|
||||
type HeapProfileOpts struct {
|
||||
// FilePayload is the destination for the profiling output.
|
||||
urpc.FilePayload
|
||||
}
|
||||
|
||||
// Profile includes profile-related RPC stubs. It provides a way to
|
||||
// control the built-in pprof facility in sentry via sentryctl.
|
||||
//
|
||||
// The following options to sentryctl are added:
|
||||
//
|
||||
// - collect CPU profile on-demand.
|
||||
// sentryctl -pid <pid> pprof-cpu-start
|
||||
// sentryctl -pid <pid> pprof-cpu-stop
|
||||
//
|
||||
// - dump out the stack trace of current go routines.
|
||||
// sentryctl -pid <pid> pprof-goroutine
|
||||
type Profile struct {
|
||||
// Kernel is the kernel under profile. It's immutable.
|
||||
Kernel *kernel.Kernel
|
||||
|
||||
// mu protects the fields below.
|
||||
mu sync.Mutex
|
||||
|
||||
// cpuFile is the current CPU profile output file.
|
||||
cpuFile *fd.FD
|
||||
|
||||
// traceFile is the current execution trace output file.
|
||||
traceFile *fd.FD
|
||||
}
|
||||
|
||||
// StartCPUProfile is an RPC stub which starts recording the CPU profile in a
|
||||
// file.
|
||||
func (p *Profile) StartCPUProfile(o *ProfileOpts, _ *struct{}) error {
|
||||
// Heap generates a heap profile.
|
||||
func (p *Profile) Heap(o *HeapProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return errNoOutput
|
||||
return nil // Allowed.
|
||||
}
|
||||
|
||||
output, err := fd.NewFromFile(o.FilePayload.Files[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
// Returns an error if profiling is already started.
|
||||
if err := pprof.StartCPUProfile(output); err != nil {
|
||||
output.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
p.cpuFile = output
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCPUProfile is an RPC stub which stops the CPU profiling and flush out the
|
||||
// profile data. It takes no argument.
|
||||
func (p *Profile) StopCPUProfile(_, _ *struct{}) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.cpuFile == nil {
|
||||
return errors.New("CPU profiling not started")
|
||||
}
|
||||
|
||||
pprof.StopCPUProfile()
|
||||
p.cpuFile.Close()
|
||||
p.cpuFile = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// HeapProfile generates a heap profile for the sentry.
|
||||
func (p *Profile) HeapProfile(o *ProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return errNoOutput
|
||||
}
|
||||
output := o.FilePayload.Files[0]
|
||||
defer output.Close()
|
||||
|
||||
runtime.GC() // Get up-to-date statistics.
|
||||
if err := pprof.WriteHeapProfile(output); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return pprof.WriteHeapProfile(output)
|
||||
}
|
||||
|
||||
// GoroutineProfile is an RPC stub which dumps out the stack trace for all
|
||||
// running goroutines.
|
||||
func (p *Profile) GoroutineProfile(o *ProfileOpts, _ *struct{}) error {
|
||||
// GoroutineProfileOpts contains options specifically for goroutine profiles.
|
||||
type GoroutineProfileOpts struct {
|
||||
// FilePayload is the destination for the profiling output.
|
||||
urpc.FilePayload
|
||||
}
|
||||
|
||||
// Goroutine dumps out the stack trace for all running goroutines.
|
||||
func (p *Profile) Goroutine(o *GoroutineProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return errNoOutput
|
||||
return nil // Allowed.
|
||||
}
|
||||
|
||||
output := o.FilePayload.Files[0]
|
||||
defer output.Close()
|
||||
if err := pprof.Lookup("goroutine").WriteTo(output, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
return pprof.Lookup("goroutine").WriteTo(output, 2)
|
||||
}
|
||||
|
||||
// BlockProfile is an RPC stub which dumps out the stack trace that led to
|
||||
// blocking on synchronization primitives.
|
||||
func (p *Profile) BlockProfile(o *ProfileOpts, _ *struct{}) error {
|
||||
// BlockProfileOpts contains options specifically for block profiles.
|
||||
type BlockProfileOpts struct {
|
||||
// FilePayload is the destination for the profiling output.
|
||||
urpc.FilePayload
|
||||
|
||||
// Duration is the duration of the profile.
|
||||
Duration time.Duration `json:"duration"`
|
||||
|
||||
// Rate is the block profile rate.
|
||||
Rate int `json:"rate"`
|
||||
}
|
||||
|
||||
// Block dumps a blocking profile.
|
||||
func (p *Profile) Block(o *BlockProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return errNoOutput
|
||||
return nil // Allowed.
|
||||
}
|
||||
|
||||
output := o.FilePayload.Files[0]
|
||||
defer output.Close()
|
||||
if err := pprof.Lookup("block").WriteTo(output, 0); err != nil {
|
||||
return err
|
||||
|
||||
p.blockMu.Lock()
|
||||
defer p.blockMu.Unlock()
|
||||
|
||||
// Always set the rate. We then wait to collect a profile at this rate,
|
||||
// and disable when we're done.
|
||||
rate := 1
|
||||
if o.Rate != 0 {
|
||||
rate = o.Rate
|
||||
}
|
||||
return nil
|
||||
runtime.SetBlockProfileRate(rate)
|
||||
defer runtime.SetBlockProfileRate(0)
|
||||
|
||||
// Collect the profile.
|
||||
select {
|
||||
case <-time.After(o.Duration):
|
||||
case <-p.done:
|
||||
}
|
||||
|
||||
return pprof.Lookup("block").WriteTo(output, 0)
|
||||
}
|
||||
|
||||
// MutexProfile is an RPC stub which dumps out the stack trace of holders of
|
||||
// contended mutexes.
|
||||
func (p *Profile) MutexProfile(o *ProfileOpts, _ *struct{}) error {
|
||||
// MutexProfileOpts contains options specifically for mutex profiles.
|
||||
type MutexProfileOpts struct {
|
||||
// FilePayload is the destination for the profiling output.
|
||||
urpc.FilePayload
|
||||
|
||||
// Duration is the duration of the profile.
|
||||
Duration time.Duration `json:"duration"`
|
||||
|
||||
// Fraction is the mutex profile fraction.
|
||||
Fraction int `json:"fraction"`
|
||||
}
|
||||
|
||||
// Mutex dumps a mutex profile.
|
||||
func (p *Profile) Mutex(o *MutexProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return errNoOutput
|
||||
return nil // Allowed.
|
||||
}
|
||||
|
||||
output := o.FilePayload.Files[0]
|
||||
defer output.Close()
|
||||
if err := pprof.Lookup("mutex").WriteTo(output, 0); err != nil {
|
||||
return err
|
||||
|
||||
p.mutexMu.Lock()
|
||||
defer p.mutexMu.Unlock()
|
||||
|
||||
// Always set the fraction.
|
||||
fraction := 1
|
||||
if o.Fraction != 0 {
|
||||
fraction = o.Fraction
|
||||
}
|
||||
return nil
|
||||
runtime.SetMutexProfileFraction(fraction)
|
||||
defer runtime.SetMutexProfileFraction(0)
|
||||
|
||||
// Collect the profile.
|
||||
select {
|
||||
case <-time.After(o.Duration):
|
||||
case <-p.done:
|
||||
}
|
||||
|
||||
return pprof.Lookup("mutex").WriteTo(output, 0)
|
||||
}
|
||||
|
||||
// StartTrace is an RPC stub which starts collection of an execution trace.
|
||||
func (p *Profile) StartTrace(o *ProfileOpts, _ *struct{}) error {
|
||||
// TraceProfileOpts contains options specifically for traces.
|
||||
type TraceProfileOpts struct {
|
||||
// FilePayload is the destination for the profiling output.
|
||||
urpc.FilePayload
|
||||
|
||||
// Duration is the duration of the profile.
|
||||
Duration time.Duration `json:"duration"`
|
||||
}
|
||||
|
||||
// Trace is an RPC stub which starts collection of an execution trace.
|
||||
func (p *Profile) Trace(o *TraceProfileOpts, _ *struct{}) error {
|
||||
if len(o.FilePayload.Files) < 1 {
|
||||
return errNoOutput
|
||||
return nil // Allowed.
|
||||
}
|
||||
|
||||
output, err := fd.NewFromFile(o.FilePayload.Files[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.traceMu.Lock()
|
||||
defer p.traceMu.Unlock()
|
||||
|
||||
// Returns an error if profiling is already started.
|
||||
if err := trace.Start(output); err != nil {
|
||||
output.Close()
|
||||
return err
|
||||
}
|
||||
defer trace.Stop()
|
||||
|
||||
// Ensure all trace contexts are registered.
|
||||
p.Kernel.RebuildTraceContexts()
|
||||
p.kernel.RebuildTraceContexts()
|
||||
|
||||
p.traceFile = output
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopTrace is an RPC stub which stops collection of an ongoing execution
|
||||
// trace and flushes the trace data. It takes no argument.
|
||||
func (p *Profile) StopTrace(_, _ *struct{}) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.traceFile == nil {
|
||||
return errors.New("execution tracing not started")
|
||||
// Wait for the trace.
|
||||
select {
|
||||
case <-time.After(o.Duration):
|
||||
case <-p.done:
|
||||
}
|
||||
|
||||
// Similarly to the case above, if tasks have not ended traces, we will
|
||||
// lose information. Thus we need to rebuild the tasks in order to have
|
||||
// complete information. This will not lose information if multiple
|
||||
// traces are overlapping.
|
||||
p.Kernel.RebuildTraceContexts()
|
||||
p.kernel.RebuildTraceContexts()
|
||||
|
||||
trace.Stop()
|
||||
p.traceFile.Close()
|
||||
p.traceFile = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
+3
-5
@@ -283,12 +283,10 @@ func (s *Server) handleOne(client *unet.Socket) error {
|
||||
// Client is dead.
|
||||
return err
|
||||
}
|
||||
if s.afterRPCCallback != nil {
|
||||
defer s.afterRPCCallback()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if s.afterRPCCallback != nil {
|
||||
s.afterRPCCallback()
|
||||
}
|
||||
}()
|
||||
// Explicitly close all these files after the call.
|
||||
//
|
||||
// This is also explicitly a reference to the files after the call,
|
||||
|
||||
+15
-13
@@ -104,13 +104,11 @@ const (
|
||||
|
||||
// Profiling related commands (see pprof.go for more details).
|
||||
const (
|
||||
StartCPUProfile = "Profile.StartCPUProfile"
|
||||
StopCPUProfile = "Profile.StopCPUProfile"
|
||||
HeapProfile = "Profile.HeapProfile"
|
||||
BlockProfile = "Profile.BlockProfile"
|
||||
MutexProfile = "Profile.MutexProfile"
|
||||
StartTrace = "Profile.StartTrace"
|
||||
StopTrace = "Profile.StopTrace"
|
||||
CPUProfile = "Profile.CPU"
|
||||
HeapProfile = "Profile.Heap"
|
||||
BlockProfile = "Profile.Block"
|
||||
MutexProfile = "Profile.Mutex"
|
||||
Trace = "Profile.Trace"
|
||||
)
|
||||
|
||||
// Logging related commands (see logging.go for more details).
|
||||
@@ -132,8 +130,13 @@ type controller struct {
|
||||
// manager holds the containerManager methods.
|
||||
manager *containerManager
|
||||
|
||||
// pprop holds the profile instance if enabled. It may be nil.
|
||||
// pprof holds the profile instance if enabled. It may be nil.
|
||||
pprof *control.Profile
|
||||
|
||||
// stopProfiling has the callback to stop profiling calls. As
|
||||
// this may be executed only once at most, it will be set to nil
|
||||
// after it is executed for the first time.
|
||||
stopProfiling func()
|
||||
}
|
||||
|
||||
// newController creates a new controller. The caller must call
|
||||
@@ -164,7 +167,7 @@ func newController(fd int, l *Loader) (*controller, error) {
|
||||
ctrl.srv.Register(&control.Logging{})
|
||||
|
||||
if l.root.conf.ProfileEnable {
|
||||
ctrl.pprof = &control.Profile{Kernel: l.k}
|
||||
ctrl.pprof, ctrl.stopProfiling = control.NewProfile(l.k)
|
||||
ctrl.srv.Register(ctrl.pprof)
|
||||
}
|
||||
|
||||
@@ -172,10 +175,9 @@ func newController(fd int, l *Loader) (*controller, error) {
|
||||
}
|
||||
|
||||
func (c *controller) stop() {
|
||||
if c.pprof != nil {
|
||||
// These are noop if there is nothing being profiled.
|
||||
_ = c.pprof.StopCPUProfile(nil, nil)
|
||||
_ = c.pprof.StopTrace(nil, nil)
|
||||
if c.stopProfiling != nil {
|
||||
c.stopProfiling()
|
||||
c.stopProfiling = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -598,7 +598,6 @@ func (l *Loader) run() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ep.tg = l.k.GlobalInit()
|
||||
@@ -1045,9 +1044,10 @@ func (l *Loader) WaitExit() kernel.ExitStatus {
|
||||
// Wait for container.
|
||||
l.k.WaitExited()
|
||||
|
||||
// Cleanup
|
||||
// Stop the control server.
|
||||
l.ctrl.stop()
|
||||
|
||||
// Check all references.
|
||||
refs.OnExit()
|
||||
|
||||
return l.k.GlobalInit().ExitStatus()
|
||||
|
||||
+99
-72
@@ -19,6 +19,7 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -70,10 +71,10 @@ func (d *Debug) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&d.profileCPU, "profile-cpu", "", "writes CPU profile to the given file.")
|
||||
f.StringVar(&d.profileBlock, "profile-block", "", "writes block profile to the given file.")
|
||||
f.StringVar(&d.profileMutex, "profile-mutex", "", "writes mutex profile to the given file.")
|
||||
f.DurationVar(&d.duration, "duration", time.Second, "amount of time to wait for CPU and trace profiles")
|
||||
f.DurationVar(&d.duration, "duration", time.Second, "amount of time to wait for CPU and trace profiles.")
|
||||
f.StringVar(&d.trace, "trace", "", "writes an execution trace to the given file.")
|
||||
f.IntVar(&d.signal, "signal", -1, "sends signal to the sandbox")
|
||||
f.StringVar(&d.strace, "strace", "", `A comma separated list of syscalls to trace. "all" enables all traces, "off" disables all`)
|
||||
f.StringVar(&d.strace, "strace", "", `A comma separated list of syscalls to trace. "all" enables all traces, "off" disables all.`)
|
||||
f.StringVar(&d.logLevel, "log-level", "", "The log level to set: warning (0), info (1), or debug (2).")
|
||||
f.StringVar(&d.logPackets, "log-packets", "", "A boolean value to enable or disable packet logging: true or false.")
|
||||
f.BoolVar(&d.ps, "ps", false, "lists processes")
|
||||
@@ -128,6 +129,7 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
}
|
||||
log.Infof("Found sandbox %q, PID: %d", c.Sandbox.ID, c.Sandbox.Pid)
|
||||
|
||||
// Perform synchronous actions.
|
||||
if d.signal > 0 {
|
||||
log.Infof("Sending signal %d to process: %d", d.signal, c.Sandbox.Pid)
|
||||
if err := syscall.Kill(c.Sandbox.Pid, syscall.Signal(d.signal)); err != nil {
|
||||
@@ -143,80 +145,15 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
log.Infof(" *** Stack dump ***\n%s", stacks)
|
||||
}
|
||||
if d.profileHeap != "" {
|
||||
f, err := os.Create(d.profileHeap)
|
||||
f, err := os.OpenFile(d.profileHeap, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return Errorf(err.Error())
|
||||
return Errorf("error opening heap profile output: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := c.Sandbox.HeapProfile(f); err != nil {
|
||||
return Errorf(err.Error())
|
||||
return Errorf("error collecting heap profile: %v", err)
|
||||
}
|
||||
log.Infof("Heap profile written to %q", d.profileHeap)
|
||||
}
|
||||
if d.profileBlock != "" {
|
||||
f, err := os.Create(d.profileBlock)
|
||||
if err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := c.Sandbox.BlockProfile(f); err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
log.Infof("Block profile written to %q", d.profileBlock)
|
||||
}
|
||||
if d.profileMutex != "" {
|
||||
f, err := os.Create(d.profileMutex)
|
||||
if err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := c.Sandbox.MutexProfile(f); err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
log.Infof("Mutex profile written to %q", d.profileMutex)
|
||||
}
|
||||
|
||||
delay := false
|
||||
if d.profileCPU != "" {
|
||||
delay = true
|
||||
f, err := os.Create(d.profileCPU)
|
||||
if err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err := c.Sandbox.StopCPUProfile(); err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
log.Infof("CPU profile written to %q", d.profileCPU)
|
||||
}()
|
||||
if err := c.Sandbox.StartCPUProfile(f); err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
log.Infof("CPU profile started for %v, writing to %q", d.duration, d.profileCPU)
|
||||
}
|
||||
if d.trace != "" {
|
||||
delay = true
|
||||
f, err := os.Create(d.trace)
|
||||
if err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err := c.Sandbox.StopTrace(); err != nil {
|
||||
Fatalf(err.Error())
|
||||
}
|
||||
log.Infof("Trace written to %q", d.trace)
|
||||
}()
|
||||
if err := c.Sandbox.StartTrace(f); err != nil {
|
||||
return Errorf(err.Error())
|
||||
}
|
||||
log.Infof("Tracing started for %v, writing to %q", d.duration, d.trace)
|
||||
}
|
||||
|
||||
if d.strace != "" || len(d.logLevel) != 0 || len(d.logPackets) != 0 {
|
||||
args := control.LoggingArgs{}
|
||||
switch strings.ToLower(d.strace) {
|
||||
@@ -285,8 +222,98 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
|
||||
log.Infof(o)
|
||||
}
|
||||
|
||||
if delay {
|
||||
time.Sleep(d.duration)
|
||||
// Open profiling files.
|
||||
var (
|
||||
cpuFile *os.File
|
||||
traceFile *os.File
|
||||
blockFile *os.File
|
||||
mutexFile *os.File
|
||||
)
|
||||
if d.profileCPU != "" {
|
||||
f, err := os.OpenFile(d.profileCPU, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return Errorf("error opening cpu profile output: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
cpuFile = f
|
||||
}
|
||||
if d.trace != "" {
|
||||
f, err := os.OpenFile(d.trace, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return Errorf("error opening trace profile output: %v", err)
|
||||
}
|
||||
traceFile = f
|
||||
}
|
||||
if d.profileBlock != "" {
|
||||
f, err := os.OpenFile(d.profileBlock, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return Errorf("error opening blocking profile output: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
blockFile = f
|
||||
}
|
||||
if d.profileMutex != "" {
|
||||
f, err := os.OpenFile(d.profileMutex, os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return Errorf("error opening mutex profile output: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
mutexFile = f
|
||||
}
|
||||
|
||||
// Collect profiles.
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
cpuErr error
|
||||
traceErr error
|
||||
blockErr error
|
||||
mutexErr error
|
||||
)
|
||||
if cpuFile != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
cpuErr = c.Sandbox.CPUProfile(cpuFile, d.duration)
|
||||
}()
|
||||
}
|
||||
if traceFile != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
traceErr = c.Sandbox.Trace(traceFile, d.duration)
|
||||
}()
|
||||
}
|
||||
if blockFile != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
blockErr = c.Sandbox.BlockProfile(blockFile, d.duration)
|
||||
}()
|
||||
}
|
||||
if mutexFile != nil {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mutexErr = c.Sandbox.MutexProfile(mutexFile, d.duration)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
errorCount := 0
|
||||
if cpuErr != nil {
|
||||
log.Infof("error collecting cpu profile: %v", cpuErr)
|
||||
}
|
||||
if traceErr != nil {
|
||||
log.Infof("error collecting trace profile: %v", traceErr)
|
||||
}
|
||||
if blockErr != nil {
|
||||
log.Infof("error collecting block profile: %v", blockErr)
|
||||
}
|
||||
if mutexErr != nil {
|
||||
log.Infof("error collecting mutex profile: %v", mutexErr)
|
||||
}
|
||||
if errorCount > 0 {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
|
||||
+27
-78
@@ -999,54 +999,30 @@ func (s *Sandbox) HeapProfile(f *os.File) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
opts := control.ProfileOpts{
|
||||
FilePayload: urpc.FilePayload{
|
||||
Files: []*os.File{f},
|
||||
},
|
||||
opts := control.HeapProfileOpts{
|
||||
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
|
||||
}
|
||||
if err := conn.Call(boot.HeapProfile, &opts, nil); err != nil {
|
||||
return fmt.Errorf("getting sandbox %q heap profile: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
return conn.Call(boot.HeapProfile, &opts, nil)
|
||||
}
|
||||
|
||||
// StartCPUProfile start CPU profile writing to the given file.
|
||||
func (s *Sandbox) StartCPUProfile(f *os.File) error {
|
||||
log.Debugf("CPU profile start %q", s.ID)
|
||||
// CPUProfile collects a CPU profile.
|
||||
func (s *Sandbox) CPUProfile(f *os.File, duration time.Duration) error {
|
||||
log.Debugf("CPU profile %q", s.ID)
|
||||
conn, err := s.sandboxConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
opts := control.ProfileOpts{
|
||||
FilePayload: urpc.FilePayload{
|
||||
Files: []*os.File{f},
|
||||
},
|
||||
opts := control.CPUProfileOpts{
|
||||
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
|
||||
Duration: duration,
|
||||
}
|
||||
if err := conn.Call(boot.StartCPUProfile, &opts, nil); err != nil {
|
||||
return fmt.Errorf("starting sandbox %q CPU profile: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCPUProfile stops a previously started CPU profile.
|
||||
func (s *Sandbox) StopCPUProfile() error {
|
||||
log.Debugf("CPU profile stop %q", s.ID)
|
||||
conn, err := s.sandboxConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.Call(boot.StopCPUProfile, nil, nil); err != nil {
|
||||
return fmt.Errorf("stopping sandbox %q CPU profile: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
return conn.Call(boot.CPUProfile, &opts, nil)
|
||||
}
|
||||
|
||||
// BlockProfile writes a block profile to the given file.
|
||||
func (s *Sandbox) BlockProfile(f *os.File) error {
|
||||
func (s *Sandbox) BlockProfile(f *os.File, duration time.Duration) error {
|
||||
log.Debugf("Block profile %q", s.ID)
|
||||
conn, err := s.sandboxConnect()
|
||||
if err != nil {
|
||||
@@ -1054,19 +1030,15 @@ func (s *Sandbox) BlockProfile(f *os.File) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
opts := control.ProfileOpts{
|
||||
FilePayload: urpc.FilePayload{
|
||||
Files: []*os.File{f},
|
||||
},
|
||||
opts := control.BlockProfileOpts{
|
||||
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
|
||||
Duration: duration,
|
||||
}
|
||||
if err := conn.Call(boot.BlockProfile, &opts, nil); err != nil {
|
||||
return fmt.Errorf("getting sandbox %q block profile: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
return conn.Call(boot.BlockProfile, &opts, nil)
|
||||
}
|
||||
|
||||
// MutexProfile writes a mutex profile to the given file.
|
||||
func (s *Sandbox) MutexProfile(f *os.File) error {
|
||||
func (s *Sandbox) MutexProfile(f *os.File, duration time.Duration) error {
|
||||
log.Debugf("Mutex profile %q", s.ID)
|
||||
conn, err := s.sandboxConnect()
|
||||
if err != nil {
|
||||
@@ -1074,50 +1046,27 @@ func (s *Sandbox) MutexProfile(f *os.File) error {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
opts := control.ProfileOpts{
|
||||
FilePayload: urpc.FilePayload{
|
||||
Files: []*os.File{f},
|
||||
},
|
||||
opts := control.MutexProfileOpts{
|
||||
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
|
||||
Duration: duration,
|
||||
}
|
||||
if err := conn.Call(boot.MutexProfile, &opts, nil); err != nil {
|
||||
return fmt.Errorf("getting sandbox %q mutex profile: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
return conn.Call(boot.MutexProfile, &opts, nil)
|
||||
}
|
||||
|
||||
// StartTrace start trace writing to the given file.
|
||||
func (s *Sandbox) StartTrace(f *os.File) error {
|
||||
log.Debugf("Trace start %q", s.ID)
|
||||
// Trace collects an execution trace.
|
||||
func (s *Sandbox) Trace(f *os.File, duration time.Duration) error {
|
||||
log.Debugf("Trace %q", s.ID)
|
||||
conn, err := s.sandboxConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
opts := control.ProfileOpts{
|
||||
FilePayload: urpc.FilePayload{
|
||||
Files: []*os.File{f},
|
||||
},
|
||||
opts := control.TraceProfileOpts{
|
||||
FilePayload: urpc.FilePayload{Files: []*os.File{f}},
|
||||
Duration: duration,
|
||||
}
|
||||
if err := conn.Call(boot.StartTrace, &opts, nil); err != nil {
|
||||
return fmt.Errorf("starting sandbox %q trace: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopTrace stops a previously started trace.
|
||||
func (s *Sandbox) StopTrace() error {
|
||||
log.Debugf("Trace stop %q", s.ID)
|
||||
conn, err := s.sandboxConnect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
if err := conn.Call(boot.StopTrace, nil, nil); err != nil {
|
||||
return fmt.Errorf("stopping sandbox %q trace: %v", s.ID, err)
|
||||
}
|
||||
return nil
|
||||
return conn.Call(boot.Trace, &opts, nil)
|
||||
}
|
||||
|
||||
// ChangeLogging changes logging options.
|
||||
|
||||
Reference in New Issue
Block a user