Create gofer profiles when profile flags are passed to runsc.

Previously, when passing --profile and related profile flags to runsc, we only
created profile files for the sentry. The gofer was ignored.

This CL plumbs profile flags to the gofer, and creates profile files for the
gofer as well.

For now, the gofer profiles are put in separate files from the sentry profile.
The gofer profile files are suffixed with "gofer" and the container id, to
avoid collision with the sentry profiles and profiles from other gofers (since
each container has its own gofer).

In a future CL, the gofer profile data will be merged with the sentry profile
data into a single file.

PiperOrigin-RevId: 469794523
This commit is contained in:
Nicolas Lacasse
2022-08-24 12:26:50 -07:00
committed by gVisor bot
parent 2ee98e784b
commit dc3a84baa5
12 changed files with 349 additions and 154 deletions
+1 -1
View File
@@ -15,7 +15,6 @@ go_library(
"loader.go",
"mount_hints.go",
"network.go",
"profile.go",
"seccheck.go",
"strace.go",
"vfs.go",
@@ -116,6 +115,7 @@ go_library(
"//runsc/boot/pprof",
"//runsc/boot/procfs",
"//runsc/config",
"//runsc/profile",
"//runsc/specutils",
"//runsc/specutils/seccomp",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
+5 -16
View File
@@ -74,6 +74,7 @@ import (
_ "gvisor.dev/gvisor/runsc/boot/platforms" // register all platforms.
"gvisor.dev/gvisor/runsc/boot/pprof"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/profile"
"gvisor.dev/gvisor/runsc/specutils"
"gvisor.dev/gvisor/runsc/specutils/seccomp"
@@ -205,21 +206,6 @@ type Args struct {
TotalMem uint64
// UserLogFD is the file descriptor to write user logs to.
UserLogFD int
// ProfileBlockFD is the file descriptor to write a block profile to.
// Valid if >=0.
ProfileBlockFD int
// ProfileCPUFD is the file descriptor to write a CPU profile to.
// Valid if >=0.
ProfileCPUFD int
// ProfileHeapFD is the file descriptor to write a heap profile to.
// Valid if >=0.
ProfileHeapFD int
// ProfileMutexFD is the file descriptor to write a mutex profile to.
// Valid if >=0.
ProfileMutexFD int
// TraceFD is the file descriptor to write a Go execution trace to.
// Valid if >=0.
TraceFD int
// ProductName is the value to show in
// /sys/devices/virtual/dmi/id/product_name.
ProductName string
@@ -229,6 +215,9 @@ type Args struct {
// SinkFDs is an ordered array of file descriptors to be used by seccheck
// sinks configured from the --pod-init-config file.
SinkFDs []int
// ProfileOpts contains the set of profiles to enable and the
// corresponding FDs where profile data will be written.
ProfileOpts profile.Opts
}
// make sure stdioFDs are always the same on initial start and on restore
@@ -237,7 +226,7 @@ const startingStdioFD = 256
// New initializes a new kernel loader configured by spec.
// New also handles setting up a kernel for restoring a container.
func New(args Args) (*Loader, error) {
stopProfiling := startProfiling(args)
stopProfiling := profile.Start(args.ProfileOpts)
// We initialize the rand package now to make sure /dev/urandom is pre-opened
// on kernels that do not support getrandom(2).
-95
View File
@@ -1,95 +0,0 @@
// Copyright 2021 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 boot
import (
"os"
"runtime"
"runtime/pprof"
"runtime/trace"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
)
// startProfiling initiates profiling as defined by the ProfileConfig, and
// returns a function that should be called to stop profiling.
func startProfiling(args Args) func() {
var onStopProfiling []func()
stopProfiling := func() {
for _, f := range onStopProfiling {
f()
}
}
if args.ProfileBlockFD >= 0 {
file := os.NewFile(uintptr(args.ProfileBlockFD), "profile-block")
runtime.SetBlockProfileRate(control.DefaultBlockProfileRate)
onStopProfiling = append(onStopProfiling, func() {
if err := pprof.Lookup("block").WriteTo(file, 0); err != nil {
log.Warningf("Error writing block profile: %v", err)
}
file.Close()
runtime.SetBlockProfileRate(0)
})
}
if args.ProfileCPUFD >= 0 {
file := os.NewFile(uintptr(args.ProfileCPUFD), "profile-cpu")
pprof.StartCPUProfile(file)
onStopProfiling = append(onStopProfiling, func() {
pprof.StopCPUProfile()
file.Close()
})
}
if args.ProfileHeapFD >= 0 {
file := os.NewFile(uintptr(args.ProfileHeapFD), "profile-heap")
onStopProfiling = append(onStopProfiling, func() {
if err := pprof.Lookup("heap").WriteTo(file, 0); err != nil {
log.Warningf("Error writing heap profile: %v", err)
}
file.Close()
})
}
if args.ProfileMutexFD >= 0 {
file := os.NewFile(uintptr(args.ProfileMutexFD), "profile-mutex")
prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate)
onStopProfiling = append(onStopProfiling, func() {
if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil {
log.Warningf("Error writing mutex profile: %v", err)
}
file.Close()
runtime.SetMutexProfileFraction(prev)
})
}
if args.TraceFD >= 0 {
file := os.NewFile(uintptr(args.TraceFD), "trace")
trace.Start(file)
onStopProfiling = append(onStopProfiling, func() {
trace.Stop()
file.Close()
})
}
return stopProfiling
}
+1
View File
@@ -65,6 +65,7 @@ go_library(
"//runsc/fsgofer",
"//runsc/fsgofer/filter",
"//runsc/mitigate",
"//runsc/profile",
"//runsc/specutils",
"@com_github_google_subcommands//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
+8 -30
View File
@@ -31,6 +31,7 @@ import (
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/flag"
"gvisor.dev/gvisor/runsc/profile"
"gvisor.dev/gvisor/runsc/specutils"
)
@@ -82,26 +83,6 @@ type Boot struct {
// sandbox (e.g. gofer) and sent through this FD.
mountsFD int
// profileBlockFD is the file descriptor to write a block profile to.
// Valid if >= 0.
profileBlockFD int
// profileCPUFD is the file descriptor to write a CPU profile to.
// Valid if >= 0.
profileCPUFD int
// profileHeapFD is the file descriptor to write a heap profile to.
// Valid if >= 0.
profileHeapFD int
// profileMutexFD is the file descriptor to write a mutex profile to.
// Valid if >= 0.
profileMutexFD int
// traceFD is the file descriptor to write a Go execution trace to.
// Valid if >= 0.
traceFD int
podInitConfigFD int
sinkFDs intFlags
@@ -117,6 +98,9 @@ type Boot struct {
// productName is the value to show in
// /sys/devices/virtual/dmi/id/product_name.
productName string
// FDs for profile data.
profileFDs profile.FDArgs
}
// Name implements subcommands.Command.Name.
@@ -154,13 +138,11 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
f.IntVar(&b.userLogFD, "user-log-fd", 0, "file descriptor to write user logs to. 0 means no logging.")
f.IntVar(&b.startSyncFD, "start-sync-fd", -1, "required FD to used to synchronize sandbox startup")
f.IntVar(&b.mountsFD, "mounts-fd", -1, "mountsFD is the file descriptor to read list of mounts after they have been resolved (direct paths, no symlinks).")
f.IntVar(&b.profileBlockFD, "profile-block-fd", -1, "file descriptor to write block profile to. -1 disables profiling.")
f.IntVar(&b.profileCPUFD, "profile-cpu-fd", -1, "file descriptor to write CPU profile to. -1 disables profiling.")
f.IntVar(&b.profileHeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.")
f.IntVar(&b.profileMutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.")
f.IntVar(&b.traceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
f.IntVar(&b.podInitConfigFD, "pod-init-config-fd", -1, "file descriptor to the pod init configuration file.")
f.Var(&b.sinkFDs, "sink-fds", "ordered list of file descriptors to be used by the sinks defined in --pod-init-config.")
// Profiling flags.
b.profileFDs.SetFromFlags(f)
}
// Execute implements subcommands.Command.Execute. It starts a sandbox in a
@@ -308,14 +290,10 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
NumCPU: b.cpuNum,
TotalMem: b.totalMem,
UserLogFD: b.userLogFD,
ProfileBlockFD: b.profileBlockFD,
ProfileCPUFD: b.profileCPUFD,
ProfileHeapFD: b.profileHeapFD,
ProfileMutexFD: b.profileMutexFD,
TraceFD: b.traceFD,
ProductName: b.productName,
PodInitConfigFD: b.podInitConfigFD,
SinkFDs: b.sinkFDs.GetArray(),
ProfileOpts: b.profileFDs.ToOpts(),
}
l, err := boot.New(bootArgs)
if err != nil {
+22 -4
View File
@@ -37,6 +37,7 @@ import (
"gvisor.dev/gvisor/runsc/flag"
"gvisor.dev/gvisor/runsc/fsgofer"
"gvisor.dev/gvisor/runsc/fsgofer/filter"
"gvisor.dev/gvisor/runsc/profile"
"gvisor.dev/gvisor/runsc/specutils"
)
@@ -68,6 +69,9 @@ type Gofer struct {
specFD int
mountsFD int
syncUsernsFD int
profileFDs profile.FDArgs
stopProfiling func()
}
// Name implements subcommands.Command.
@@ -96,6 +100,9 @@ func (g *Gofer) SetFlags(f *flag.FlagSet) {
f.IntVar(&g.specFD, "spec-fd", -1, "required fd with the container spec")
f.IntVar(&g.mountsFD, "mounts-fd", -1, "mountsFD is the file descriptor to write list of mounts after they have been resolved (direct paths, no symlinks).")
f.IntVar(&g.syncUsernsFD, "sync-userns-fd", -1, "file descriptor used to synchronize rootless user namespace initialization.")
// Profiling flags.
g.profileFDs.SetFromFlags(f)
}
// Execute implements subcommands.Command.
@@ -156,6 +163,11 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
// We expcec that setCapsAndCallSelfsetCapsAndCallSelf has to be called in this case.
panic("unreachable")
}
// Start profiling. This will be a noop if no profiling arguments were passed.
profileOpts := g.profileFDs.ToOpts()
g.stopProfiling = profile.Start(profileOpts)
// At this point we won't re-execute, so it's safe to limit via rlimits. Any
// limit >= 0 works. If the limit is lower than the current number of open
// files, then Setrlimit will succeed, and the next open will fail.
@@ -214,11 +226,11 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{})
log.Infof("Process chroot'd to %q", root)
// Initialize filters.
if conf.FSGoferHostUDS {
filter.InstallUDSFilters()
opts := filter.Options{
UDSEnabled: conf.FSGoferHostUDS,
ProfileEnabled: len(profileOpts) > 0,
}
if err := filter.Install(); err != nil {
if err := filter.Install(opts); err != nil {
util.Fatalf("installing seccomp filters: %v", err)
}
@@ -295,6 +307,9 @@ func (g *Gofer) serveLisafs(spec *specs.Spec, conf *config.Config, root string)
server.Wait()
server.Destroy()
log.Infof("All lisafs servers exited.")
if g.stopProfiling != nil {
g.stopProfiling()
}
return subcommands.ExitSuccess
}
@@ -353,6 +368,9 @@ func (g *Gofer) serve9P(spec *specs.Spec, conf *config.Config, root string) subc
}
wg.Wait()
log.Infof("All 9P servers exited.")
if g.stopProfiling != nil {
g.stopProfiling()
}
return subcommands.ExitSuccess
}
+44
View File
@@ -927,6 +927,11 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *config.Config, bu
}
donations.DonateAndClose("spec-fd", specFile)
// Donate any profile FDs to the gofer.
if err := c.donateGoferProfileFDs(conf, &donations); err != nil {
return nil, nil, fmt.Errorf("donating gofer profile fds: %w", err)
}
// Create pipe that allows gofer to send mount list to sandbox after all paths
// have been resolved.
mountsSand, mountsGofer, err := os.Pipe()
@@ -1341,6 +1346,45 @@ func (c *Container) setupCgroupForSubcontainer(conf *config.Config, spec *specs.
return cgroupInstall(conf, cg, &specs.LinuxResources{})
}
// donateGoferProfileFDs will open profile files and donate their FDs to the
// gofer.
func (c *Container) donateGoferProfileFDs(conf *config.Config, donations *donation.Agency) error {
// The gofer profile files are named based on the provided flag, but
// suffixed with "gofer" and the container ID to avoid collisions with
// sentry profile files or profile files from other gofers.
//
// TODO(b/243183772): Merge gofer profile data with sentry profile data
// into a single file.
profSuffix := ".gofer." + c.ID
const profFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
if conf.ProfileBlock != "" {
if err := donations.OpenAndDonate("profile-block-fd", conf.ProfileBlock+profSuffix, profFlags); err != nil {
return err
}
}
if conf.ProfileCPU != "" {
if err := donations.OpenAndDonate("profile-cpu-fd", conf.ProfileCPU+profSuffix, profFlags); err != nil {
return err
}
}
if conf.ProfileHeap != "" {
if err := donations.OpenAndDonate("profile-heap-fd", conf.ProfileHeap+profSuffix, profFlags); err != nil {
return err
}
}
if conf.ProfileMutex != "" {
if err := donations.OpenAndDonate("profile-mutex-fd", conf.ProfileMutex+profSuffix, profFlags); err != nil {
return err
}
}
if conf.TraceFile != "" {
if err := donations.OpenAndDonate("trace-fd", conf.TraceFile+profSuffix, profFlags); err != nil {
return err
}
}
return nil
}
// cgroupInstall creates cgroups dir structure and sets their respective
// resources. In case of success, returns the cgroups instance and nil error.
// For rootless, it's possible that cgroups operations fail, in this case the
+1
View File
@@ -8,6 +8,7 @@ go_library(
"config.go",
"config_amd64.go",
"config_arm64.go",
"config_profile.go",
"extra_filters.go",
"extra_filters_msan.go",
"extra_filters_race.go",
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2020 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 filter
import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/seccomp"
)
var profileFilters = seccomp.SyscallRules{
unix.SYS_OPENAT: []seccomp.Rule{
{
seccomp.MatchAny{},
seccomp.MatchAny{},
seccomp.EqualTo(unix.O_RDONLY | unix.O_LARGEFILE | unix.O_CLOEXEC),
},
},
unix.SYS_SETITIMER: {},
unix.SYS_TIMER_CREATE: []seccomp.Rule{
{
seccomp.EqualTo(unix.CLOCK_THREAD_CPUTIME_ID), /* which */
seccomp.MatchAny{}, /* sevp */
seccomp.MatchAny{}, /* timerid */
},
},
unix.SYS_TIMER_DELETE: []seccomp.Rule{},
unix.SYS_TIMER_SETTIME: []seccomp.Rule{
{
seccomp.MatchAny{}, /* timerid */
seccomp.EqualTo(0), /* flags */
seccomp.MatchAny{}, /* new_value */
seccomp.EqualTo(0), /* old_value */
},
},
}
+25 -8
View File
@@ -18,21 +18,38 @@
package filter
import (
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/seccomp"
)
// Options are seccomp filter related options.
type Options struct {
UDSEnabled bool
ProfileEnabled bool
}
// Install installs seccomp filters.
func Install() error {
func Install(opt Options) error {
s := allowedSyscalls
if opt.ProfileEnabled {
report("profile enabled: syscall filters less restrictive!")
s.Merge(profileFilters)
}
if opt.UDSEnabled {
report("host UDS enabled: syscall filters less restrictive!")
s.Merge(udsSyscalls)
}
// Set of additional filters used by -race and -msan. Returns empty
// when not enabled.
allowedSyscalls.Merge(instrumentationFilters())
s.Merge(instrumentationFilters())
return seccomp.Install(allowedSyscalls, seccomp.DenyNewExecMappings)
return seccomp.Install(s, seccomp.DenyNewExecMappings)
}
// InstallUDSFilters extends the allowed syscalls to include those necessary for
// creating and connecting to host UDS.
func InstallUDSFilters() {
// Add additional filters required for connecting to the host's sockets.
allowedSyscalls.Merge(udsSyscalls)
// report writes a warning message to the log.
func report(msg string) {
log.Warningf("*** SECCOMP WARNING: %s", msg)
}
+16
View File
@@ -0,0 +1,16 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "profile",
srcs = ["profile.go"],
visibility = [
"//runsc:__subpackages__",
],
deps = [
"//pkg/log",
"//pkg/sentry/control",
"//runsc/flag",
],
)
+179
View File
@@ -0,0 +1,179 @@
// Copyright 2021 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 contains profiling utils.
package profile
import (
"os"
"runtime"
"runtime/pprof"
"runtime/trace"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/runsc/flag"
)
// Kind is the kind of profiling to perform.
type Kind int
const (
// Block profile.
Block Kind = iota
// CPU profile.
CPU
// Heap profile.
Heap
// Mutex profile.
Mutex
// Trace profile.
Trace
)
// FDArgs are the arguments that describe which profiles to enable and which
// FDs to write them to. Profiling of a given type will only be enabled if the
// corresponding FD is >=0.
type FDArgs struct {
// BlockFD is the file descriptor to write a block profile to.
// Valid if >=0.
BlockFD int
// CPUFD is the file descriptor to write a CPU profile to.
// Valid if >=0.
CPUFD int
// HeapFD is the file descriptor to write a heap profile to.
// Valid if >=0.
HeapFD int
// MutexFD is the file descriptor to write a mutex profile to.
// Valid if >=0.
MutexFD int
// TraceFD is the file descriptor to write a Go execution trace to.
// Valid if >=0.
TraceFD int
}
// SetFromFlags sets the FDArgs from the given flags. The default value for
// each FD is -1.
func (fds *FDArgs) SetFromFlags(f *flag.FlagSet) {
f.IntVar(&fds.BlockFD, "profile-block-fd", -1, "file descriptor to write block profile to. -1 disables profiling.")
f.IntVar(&fds.CPUFD, "profile-cpu-fd", -1, "file descriptor to write CPU profile to. -1 disables profiling.")
f.IntVar(&fds.HeapFD, "profile-heap-fd", -1, "file descriptor to write heap profile to. -1 disables profiling.")
f.IntVar(&fds.MutexFD, "profile-mutex-fd", -1, "file descriptor to write mutex profile to. -1 disables profiling.")
f.IntVar(&fds.TraceFD, "trace-fd", -1, "file descriptor to write Go execution trace to. -1 disables tracing.")
}
// Opts is a map of profile Kind to FD.
type Opts map[Kind]uintptr
// ToOpts turns FDArgs into an Opts struct which can be passed to Start.
func (fds *FDArgs) ToOpts() Opts {
o := Opts{}
if fds.BlockFD >= 0 {
o[Block] = uintptr(fds.BlockFD)
}
if fds.CPUFD >= 0 {
o[CPU] = uintptr(fds.CPUFD)
}
if fds.HeapFD >= 0 {
o[Heap] = uintptr(fds.HeapFD)
}
if fds.MutexFD >= 0 {
o[Mutex] = uintptr(fds.MutexFD)
}
if fds.TraceFD >= 0 {
o[Trace] = uintptr(fds.TraceFD)
}
return o
}
// Start starts profiling for the given Kinds in opts, and writes the profile
// data to the corresponding FDs in opts. It returns a function which will stop
// profiling.
func Start(opts Opts) func() {
var onStopProfiling []func()
stopProfiling := func() {
for _, f := range onStopProfiling {
f()
}
}
if fd, ok := opts[Block]; ok {
log.Infof("Block profiling enabled")
file := os.NewFile(fd, "profile-block")
runtime.SetBlockProfileRate(control.DefaultBlockProfileRate)
onStopProfiling = append(onStopProfiling, func() {
if err := pprof.Lookup("block").WriteTo(file, 0); err != nil {
log.Warningf("Error writing block profile: %v", err)
}
file.Close()
runtime.SetBlockProfileRate(0)
log.Infof("Block profiling stopped")
})
}
if fd, ok := opts[CPU]; ok {
log.Infof("CPU profiling enabled")
file := os.NewFile(fd, "profile-cpu")
pprof.StartCPUProfile(file)
onStopProfiling = append(onStopProfiling, func() {
pprof.StopCPUProfile()
file.Close()
log.Infof("CPU profiling stopped")
})
}
if fd, ok := opts[Heap]; ok {
log.Infof("Heap profiling enabled")
file := os.NewFile(fd, "profile-heap")
onStopProfiling = append(onStopProfiling, func() {
if err := pprof.Lookup("heap").WriteTo(file, 0); err != nil {
log.Warningf("Error writing heap profile: %v", err)
}
file.Close()
log.Infof("Heap profiling stopped")
})
}
if fd, ok := opts[Mutex]; ok {
log.Infof("Mutex profiling enabled")
file := os.NewFile(fd, "profile-mutex")
prev := runtime.SetMutexProfileFraction(control.DefaultMutexProfileRate)
onStopProfiling = append(onStopProfiling, func() {
if err := pprof.Lookup("mutex").WriteTo(file, 0); err != nil {
log.Warningf("Error writing mutex profile: %v", err)
}
file.Close()
runtime.SetMutexProfileFraction(prev)
log.Infof("Mutex profiling stopped")
})
}
if fd, ok := opts[Trace]; ok {
log.Infof("Tracing enabled")
file := os.NewFile(fd, "trace")
trace.Start(file)
onStopProfiling = append(onStopProfiling, func() {
trace.Stop()
file.Close()
log.Infof("Tracing stopped")
})
}
return stopProfiling
}