diff options
author | Bhasker Hariharan <bhaskerh@google.com> | 2019-05-29 11:30:59 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-05-30 12:07:11 -0700 |
commit | 035a8fa38ed21da2e06db22d3dfd6122610fb856 (patch) | |
tree | 3650d76b5519f5a3e8efdd0c5df039cacc1502ec | |
parent | b52e571a6188ce90b5a13b002753230780119db9 (diff) |
Add support for collecting execution trace to runsc.
Updates #220
PiperOrigin-RevId: 250532302
-rw-r--r-- | pkg/sentry/control/pprof.go | 44 | ||||
-rw-r--r-- | runsc/boot/controller.go | 2 | ||||
-rw-r--r-- | runsc/cmd/debug.go | 55 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 35 |
4 files changed, 123 insertions, 13 deletions
diff --git a/pkg/sentry/control/pprof.go b/pkg/sentry/control/pprof.go index 94ed149f2..d63916600 100644 --- a/pkg/sentry/control/pprof.go +++ b/pkg/sentry/control/pprof.go @@ -18,6 +18,7 @@ import ( "errors" "runtime" "runtime/pprof" + "runtime/trace" "sync" "gvisor.googlesource.com/gvisor/pkg/fd" @@ -52,6 +53,9 @@ type Profile struct { // 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 @@ -122,3 +126,43 @@ func (p *Profile) Goroutine(o *ProfileOpts, _ *struct{}) error { } return nil } + +// StartTrace is an RPC stub which starts collection of an execution trace. +func (p *Profile) StartTrace(o *ProfileOpts, _ *struct{}) error { + if len(o.FilePayload.Files) < 1 { + return errNoOutput + } + + 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 := trace.Start(output); err != nil { + output.Close() + return err + } + + 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 start") + } + + trace.Stop() + p.traceFile.Close() + p.traceFile = nil + return nil +} diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go index f09c1bd85..72ab9ef86 100644 --- a/runsc/boot/controller.go +++ b/runsc/boot/controller.go @@ -101,6 +101,8 @@ const ( StartCPUProfile = "Profile.StartCPUProfile" StopCPUProfile = "Profile.StopCPUProfile" HeapProfile = "Profile.HeapProfile" + StartTrace = "Profile.StartTrace" + StopTrace = "Profile.StopTrace" ) // ControlSocketAddr generates an abstract unix socket name for the given ID. diff --git a/runsc/cmd/debug.go b/runsc/cmd/debug.go index 000f694c7..27eb51172 100644 --- a/runsc/cmd/debug.go +++ b/runsc/cmd/debug.go @@ -35,6 +35,7 @@ type Debug struct { profileHeap string profileCPU string profileDelay int + trace string } // Name implements subcommands.Command. @@ -59,6 +60,7 @@ func (d *Debug) SetFlags(f *flag.FlagSet) { f.StringVar(&d.profileHeap, "profile-heap", "", "writes heap profile to the given file.") f.StringVar(&d.profileCPU, "profile-cpu", "", "writes CPU profile to the given file.") f.IntVar(&d.profileDelay, "profile-delay", 5, "amount of time to wait before stoping CPU profile") + f.StringVar(&d.trace, "trace", "", "writes an execution trace to the given file.") f.IntVar(&d.signal, "signal", -1, "sends signal to the sandbox") } @@ -122,35 +124,62 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) } log.Infof(" *** Stack dump ***\n%s", stacks) } - if d.profileCPU != "" { - f, err := os.Create(d.profileCPU) + if d.profileHeap != "" { + f, err := os.Create(d.profileHeap) if err != nil { Fatalf(err.Error()) } defer f.Close() - if err := c.Sandbox.StartCPUProfile(f); err != nil { + if err := c.Sandbox.HeapProfile(f); err != nil { Fatalf(err.Error()) } - log.Infof("CPU profile started for %d sec, writing to %q", d.profileDelay, d.profileCPU) - time.Sleep(time.Duration(d.profileDelay) * time.Second) + log.Infof("Heap profile written to %q", d.profileHeap) + } - if err := c.Sandbox.StopCPUProfile(); err != nil { + delay := false + if d.profileCPU != "" { + delay = true + f, err := os.Create(d.profileCPU) + if err != nil { Fatalf(err.Error()) } - log.Infof("CPU profile written to %q", d.profileCPU) + 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 { + Fatalf(err.Error()) + } + log.Infof("CPU profile started for %d sec, writing to %q", d.profileDelay, d.profileCPU) } - if d.profileHeap != "" { - f, err := os.Create(d.profileHeap) + if d.trace != "" { + delay = true + f, err := os.Create(d.trace) if err != nil { Fatalf(err.Error()) } - defer f.Close() - - if err := c.Sandbox.HeapProfile(f); err != nil { + 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 { Fatalf(err.Error()) } - log.Infof("Heap profile written to %q", d.profileHeap) + log.Infof("Tracing started for %d sec, writing to %q", d.profileDelay, d.trace) + } + + if delay { + time.Sleep(time.Duration(d.profileDelay) * time.Second) + + } + return subcommands.ExitSuccess } diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index bc69a9d61..47a66afb2 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -883,6 +883,41 @@ func (s *Sandbox) StopCPUProfile() error { return nil } +// StartTrace start trace writing to the given file. +func (s *Sandbox) StartTrace(f *os.File) error { + log.Debugf("Trace start %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}, + }, + } + 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 +} + // DestroyContainer destroys the given container. If it is the root container, // then the entire sandbox is destroyed. func (s *Sandbox) DestroyContainer(cid string) error { |