Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions runsc/boot/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,12 @@ const (

// Profiling related commands (see pprof.go for more details).
const (
ProfileCPU = "Profile.CPU"
ProfileHeap = "Profile.Heap"
ProfileBlock = "Profile.Block"
ProfileMutex = "Profile.Mutex"
ProfileTrace = "Profile.Trace"
ProfileCPU = "Profile.CPU"
ProfileHeap = "Profile.Heap"
ProfileGoroutine = "Profile.Goroutine"
ProfileBlock = "Profile.Block"
ProfileMutex = "Profile.Mutex"
ProfileTrace = "Profile.Trace"
)

// Logging related commands (see logging.go for more details).
Expand Down
4 changes: 4 additions & 0 deletions runsc/cmd/profile/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ package(
go_library(
name = "profile",
srcs = [
"block.go",
"cpu.go",
"goroutine.go",
"heap.go",
"mutex.go",
"profile.go",
"trace.go",
],
visibility = [
"//runsc:__subpackages__",
Expand Down
99 changes: 99 additions & 0 deletions runsc/cmd/profile/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package profile

import (
"context"
"os"
"time"

"github.com/google/subcommands"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)

// block implements subcommands.Command for the "block" command.
type block struct {
duration time.Duration
output string
}

// Name implements subcommands.Command.
func (*block) Name() string {
return "block"
}

// Synopsis implements subcommands.Command.
func (*block) Synopsis() string {
return "collects a goroutine blocking profile of a running sandbox"
}

// Usage implements subcommands.Command.
func (*block) Usage() string {
return "block [flags] <container id> - collects a block profile of the sandbox\n"
}

// SetFlags implements subcommands.Command.
func (b *block) SetFlags(f *flag.FlagSet) {
f.DurationVar(&b.duration, "duration", 30*time.Second, "amount of time to collect the block profile")
f.StringVar(&b.output, "output", "", "path to write block profile output (default: stdout)")
}

// Execute implements subcommands.Command.
func (b *block) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
if f.NArg() != 1 {
if f.Usage != nil {
f.Usage()
}
return subcommands.ExitUsageError
}

id := f.Arg(0)
conf := args[0].(*config.Config)

opts := container.LoadOpts{
SkipCheck: true,
RootContainer: true,
}
cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, opts)
if err != nil {
util.Fatalf("loading sandbox: %v", err)
}

if !cont.IsSandboxRunning() {
util.Fatalf("container sandbox is not running")
}

out := os.Stdout
if b.output != "" {
f, err := os.OpenFile(b.output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
util.Fatalf("failed to open output file: %v", err)
}
defer f.Close()
out = f
}

util.Infof("Collecting block profile for sandbox %q (%v)...", cont.Sandbox.ID, b.duration)
if err := cont.Sandbox.BlockProfile(out, b.duration); err != nil {
util.Fatalf("block profiling failed: %v", err)
}
if b.output != "" {
util.Infof("Block profile written to %s", b.output)
}
return subcommands.ExitSuccess
}
96 changes: 96 additions & 0 deletions runsc/cmd/profile/goroutine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package profile

import (
"context"
"os"

"github.com/google/subcommands"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)

// goroutine implements subcommands.Command for the "goroutine" command.
type goroutine struct {
output string
}

// Name implements subcommands.Command.
func (*goroutine) Name() string {
return "goroutine"
}

// Synopsis implements subcommands.Command.
func (*goroutine) Synopsis() string {
return "dumps the current stack traces of all running goroutines in a sandbox"
}

// Usage implements subcommands.Command.
func (*goroutine) Usage() string {
return "goroutine [flags] <container id> - collects a goroutine profile of the sandbox\n"
}

// SetFlags implements subcommands.Command.
func (g *goroutine) SetFlags(f *flag.FlagSet) {
f.StringVar(&g.output, "output", "", "path to write goroutine profile output (default: stdout)")
}

// Execute implements subcommands.Command.
func (g *goroutine) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
if f.NArg() != 1 {
if f.Usage != nil {
f.Usage()
}
return subcommands.ExitUsageError
}

id := f.Arg(0)
conf := args[0].(*config.Config)

opts := container.LoadOpts{
SkipCheck: true,
RootContainer: true,
}
cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, opts)
if err != nil {
util.Fatalf("loading sandbox: %v", err)
}

if !cont.IsSandboxRunning() {
util.Fatalf("container sandbox is not running")
}

out := os.Stdout
if g.output != "" {
f, err := os.OpenFile(g.output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
util.Fatalf("failed to open output file: %v", err)
}
defer f.Close()
out = f
}

util.Infof("Collecting goroutine profile for sandbox %q...", cont.Sandbox.ID)
if err := cont.Sandbox.GoroutineProfile(out); err != nil {
util.Fatalf("goroutine profiling failed: %v", err)
}
if g.output != "" {
util.Infof("Goroutine profile written to %s", g.output)
}
return subcommands.ExitSuccess
}
99 changes: 99 additions & 0 deletions runsc/cmd/profile/mutex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package profile

import (
"context"
"os"
"time"

"github.com/google/subcommands"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)

// mutex implements subcommands.Command for the "mutex" command.
type mutex struct {
duration time.Duration
output string
}

// Name implements subcommands.Command.
func (*mutex) Name() string {
return "mutex"
}

// Synopsis implements subcommands.Command.
func (*mutex) Synopsis() string {
return "collects a mutex contention profile of a running sandbox"
}

// Usage implements subcommands.Command.
func (*mutex) Usage() string {
return "mutex [flags] <container id> - collects a mutex profile of the sandbox\n"
}

// SetFlags implements subcommands.Command.
func (m *mutex) SetFlags(f *flag.FlagSet) {
f.DurationVar(&m.duration, "duration", 30*time.Second, "amount of time to collect the mutex profile")
f.StringVar(&m.output, "output", "", "path to write mutex profile output (default: stdout)")
}

// Execute implements subcommands.Command.
func (m *mutex) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus {
if f.NArg() != 1 {
if f.Usage != nil {
f.Usage()
}
return subcommands.ExitUsageError
}

id := f.Arg(0)
conf := args[0].(*config.Config)

opts := container.LoadOpts{
SkipCheck: true,
RootContainer: true,
}
cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, opts)
if err != nil {
util.Fatalf("loading sandbox: %v", err)
}

if !cont.IsSandboxRunning() {
util.Fatalf("container sandbox is not running")
}

out := os.Stdout
if m.output != "" {
f, err := os.OpenFile(m.output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
util.Fatalf("failed to open output file: %v", err)
}
defer f.Close()
out = f
}

util.Infof("Collecting mutex profile for sandbox %q (%v)...", cont.Sandbox.ID, m.duration)
if err := cont.Sandbox.MutexProfile(out, m.duration); err != nil {
util.Fatalf("mutex profiling failed: %v", err)
}
if m.output != "" {
util.Infof("Mutex profile written to %s", m.output)
}
return subcommands.ExitSuccess
}
4 changes: 4 additions & 0 deletions runsc/cmd/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ func createCommander(f *flag.FlagSet) *subcommands.Commander {
cdr := subcommands.NewCommander(f, "profile")
cdr.Register(cdr.HelpCommand(), "")
cdr.Register(cdr.FlagsCommand(), "")
cdr.Register(new(block), "")
cdr.Register(new(cpu), "")
cdr.Register(new(goroutine), "")
cdr.Register(new(heap), "")
cdr.Register(new(mutex), "")
cdr.Register(new(trace), "")
return cdr
}
Loading
Loading