Merge pull request #11473 from Champ-Goblem:shim-add-cgroup-v2-metrics-support

PiperOrigin-RevId: 730560110
This commit is contained in:
gVisor bot
2025-02-25 14:52:09 -08:00
63 changed files with 301 additions and 136 deletions
+1 -1
View File
@@ -133,7 +133,7 @@ go_path(
# binaries have been factored into a cli package, which is
# a good practice in any case.
"//runsc/cli",
"//shim/cli",
"//shim/v1/cli",
"//webhook/pkg/cli",
"//tools/checklocks",
+2 -2
View File
@@ -84,8 +84,8 @@ global:
- "panic recovered: .*types/sizes.go:82: assertion failed"
exclude:
# Generated: exempt all.
- pkg/shim/runtimeoptions/runtimeoptions_cri.go
- pkg/shim/runtimeoptions/v14/runtimeoptions_cri.go
- pkg/shim/v1/runtimeoptions/runtimeoptions_cri.go
- pkg/shim/v1/runtimeoptions/v14/runtimeoptions_cri.go
analyzers:
asmdecl:
generated: # Enabled.
+1 -14
View File
@@ -18,8 +18,6 @@ import (
"errors"
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/state"
@@ -84,18 +82,7 @@ func (s *State) Save(o *SaveOpts, _ *struct{}) error {
Key: o.Key,
Metadata: o.Metadata,
MemoryFileSaveOpts: o.MemoryFileSaveOpts,
Callback: func(err error) {
if err == nil {
log.Infof("Save succeeded: exiting...")
s.Kernel.SetSaveSuccess(false /* autosave */)
} else {
log.Warningf("Save failed: %v", err)
s.Kernel.SetSaveError(err)
}
if !o.Resume {
s.Kernel.Kill(linux.WaitStatusExit(0))
}
},
Resume: o.Resume,
}
if o.HavePagesFile {
saveOpts.PagesMetadata, err = o.ReleaseFD(1)
+7
View File
@@ -31,6 +31,8 @@ import (
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
var _ vfs.FilesystemImplSaveRestoreExtension = (*filesystem)(nil)
// +stateify savable
type savedDentryRW struct {
read bool
@@ -134,6 +136,11 @@ func (d *dentry) beforeSave() {
}
}
// BeforeResume implements vfs.FilesystemImplSaveRestoreExtension.BeforeResume.
func (fs *filesystem) BeforeResume(ctx context.Context) {
fs.savedDentryRW = nil
}
// afterLoad is invoked by stateify.
func (fs *filesystem) afterLoad(ctx goContext.Context) {
fs.mf = pgalloc.MemoryFileFromContext(ctx)
+10 -1
View File
@@ -22,6 +22,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -68,6 +69,14 @@ var _ kernfs.Inode = (*tasksInode)(nil)
func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns *kernel.PIDNamespace, internalData *InternalData) *tasksInode {
root := auth.NewRootCredentials(pidns.UserNamespace())
// /proc is expected to have inode number
// include/linux/proc_ns.h:PROC_ROOT_INO == 1; ensure that this is the case
// by generating its inode number before any of its descendants.
rootIno := fs.NextIno()
if rootIno != 1 {
log.Traceback("proc root inode has number %d", rootIno)
}
contents := map[string]kernfs.Inode{
"cmdline": fs.newInode(ctx, root, 0444, &cmdLineData{}),
"cpuinfo": fs.newInode(ctx, root, 0444, newStaticFileSetStat(cpuInfoData(k))),
@@ -99,7 +108,7 @@ func (fs *filesystem) newTasksInode(ctx context.Context, k *kernel.Kernel, pidns
fs: fs,
fakeCgroupControllers: internalData.Cgroups,
}
inode.InodeAttrs.Init(ctx, root, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0555)
inode.InodeAttrs.Init(ctx, root, linux.UNNAMED_MAJOR, fs.devMinor, rootIno, linux.ModeDirectory|0555)
inode.InitRefs()
inode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
+5
View File
@@ -23,6 +23,8 @@ import (
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
var _ vfs.FilesystemImplSaveRestoreExtension = (*filesystem)(nil)
// saveMf is called by stateify.
func (fs *filesystem) saveMf() string {
if !fs.mf.IsSavable() {
@@ -75,6 +77,9 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
return nil
}
// BeforeResume implements vfs.FilesystemImplSaveRestoreExtension.BeforeResume.
func (fs *filesystem) BeforeResume(ctx context.Context) {}
// CompleteRestore implements
// vfs.FilesystemImplSaveRestoreExtension.CompleteRestore.
func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRestoreOptions) error {
+13 -6
View File
@@ -26,23 +26,30 @@ import (
"gvisor.dev/gvisor/pkg/log"
)
// GetTransparentHugepageEnum returns the currently selected option for
// ReadTransparentHugepageEnum returns the currently selected option for
// whichever of
// /sys/kernel/mm/transparent_hugepage/{enabled,shmem_enabled,defrag} is
// specified by filename. (Only the basename is required, not the full path.)
func GetTransparentHugepageEnum(filename string) (string, error) {
func ReadTransparentHugepageEnum(filename string) (string, error) {
pathname := path.Join("/sys/kernel/mm/transparent_hugepage/", filename)
data, err := os.ReadFile(pathname)
if err != nil {
return "", err
}
return GetTransparentHugepageEnum(string(data))
}
// GetTransparentHugepageEnum returns the currently selected option given the
// contents of any of
// /sys/kernel/mm/transparent_hugepage/{enabled,shmem_enabled,defrag}.
func GetTransparentHugepageEnum(data string) (string, error) {
// In these files, the selected option is highlighted by square brackets.
m := regexp.MustCompile(`\[.*\]`).Find(data)
if m == nil {
return "", fmt.Errorf("failed to parse %s: %q", pathname, data)
m := regexp.MustCompile(`\[.*\]`).FindString(data)
if m == "" {
return "", fmt.Errorf("failed to find selected option in %q", data)
}
// Remove the square brackets.
return string(m[1 : len(m)-1]), nil
return m[1 : len(m)-1], nil
}
// NotifyCurrentMemcgPressureCallback requests that f is called whenever the
+5
View File
@@ -702,6 +702,11 @@ func (k *Kernel) SaveTo(ctx context.Context, w, pagesMetadata io.Writer, pagesFi
return nil
}
// BeforeResume is called before the kernel is resumed after save.
func (k *Kernel) BeforeResume(ctx context.Context) {
k.vfs.BeforeResume(ctx)
}
func (k *Kernel) saveMemoryFiles(ctx context.Context, w, pagesMetadata io.Writer, pagesFile *fd.FD, mfsToSave map[string]*pgalloc.MemoryFile, mfOpts pgalloc.SaveOpts) error {
// Save the memory files' state.
memoryStart := time.Now()
+23 -4
View File
@@ -20,6 +20,7 @@ import (
"fmt"
"io"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fd"
@@ -68,15 +69,18 @@ type SaveOpts struct {
// MemoryFileSaveOpts is passed to calls to pgalloc.MemoryFile.SaveTo().
MemoryFileSaveOpts pgalloc.SaveOpts
// Callback is called prior to unpause, with any save error.
Callback func(err error)
// Resume indicates if the statefile is used for save-resume.
Resume bool
// Autosave indicates if the statefile is used for autosave.
Autosave bool
}
// Save saves the system state.
func (opts SaveOpts) Save(ctx context.Context, k *kernel.Kernel, w *watchdog.Watchdog) error {
t, _ := CPUTime()
log.Infof("Before save CPU usage: %s", t.String())
log.Infof("Sandbox save started, pausing all tasks.")
k.Pause()
k.ReceiveTaskStates()
@@ -127,7 +131,22 @@ func (opts SaveOpts) Save(ctx context.Context, k *kernel.Kernel, w *watchdog.Wat
}
}
}
opts.Callback(err)
t1, _ := CPUTime()
log.Infof("Save CPU usage: %s", (t1 - t).String())
if err == nil {
log.Infof("Save succeeded: exiting...")
k.SetSaveSuccess(opts.Autosave)
} else {
log.Warningf("Save failed: exiting... %v", err)
k.SetSaveError(err)
}
if opts.Resume {
k.BeforeResume(ctx)
} else {
// Kill the sandbox.
k.Kill(linux.WaitStatusExit(0))
}
return err
}
+15
View File
@@ -54,6 +54,10 @@ type FilesystemImplSaveRestoreExtension interface {
// PrepareSave prepares this filesystem for serialization.
PrepareSave(ctx context.Context) error
// BeforeResume is called before the kernel is resumed after save. It can be
// used to clean up any state that should be discarded after save.
BeforeResume(ctx context.Context)
// CompleteRestore completes restoration from checkpoint for this
// filesystem after deserialization.
CompleteRestore(ctx context.Context, opts CompleteRestoreOptions) error
@@ -73,6 +77,17 @@ func (vfs *VirtualFilesystem) PrepareSave(ctx context.Context) error {
return nil
}
// BeforeResume is called before the kernel is resumed after save and allows
// filesystems to clean up S/R state.
func (vfs *VirtualFilesystem) BeforeResume(ctx context.Context) {
for fs := range vfs.getFilesystems() {
if ext, ok := fs.impl.(FilesystemImplSaveRestoreExtension); ok {
ext.BeforeResume(ctx)
}
fs.DecRef(ctx)
}
}
// CompleteRestore completes restoration from checkpoint for all filesystems
// after deserialization.
func (vfs *VirtualFilesystem) CompleteRestore(ctx context.Context, opts *CompleteRestoreOptions) error {
+3 -3
View File
@@ -6,13 +6,13 @@ package(
)
go_library(
name = "shim",
name = "v1",
srcs = ["service.go"],
visibility = ["//shim:__subpackages__"],
deps = [
"//pkg/cleanup",
"//pkg/shim/extension",
"//pkg/shim/runsc",
"//pkg/shim/v1/extension",
"//pkg/shim/v1/runsc",
"//pkg/sync",
"@com_github_containerd_containerd//namespaces:go_default_library",
"@com_github_containerd_containerd//runtime/v2/shim:go_default_library",
@@ -25,9 +25,9 @@ go_library(
deps = [
"//pkg/atomicbitops",
"//pkg/cleanup",
"//pkg/shim/extension",
"//pkg/shim/runsccmd",
"//pkg/shim/utils",
"//pkg/shim/v1/extension",
"//pkg/shim/v1/runsccmd",
"//pkg/shim/v1/utils",
"@com_github_containerd_console//:go_default_library",
"@com_github_containerd_containerd//mount:go_default_library",
"@com_github_containerd_containerd//pkg/stdio:go_default_library",
@@ -22,7 +22,7 @@ import (
"github.com/containerd/console"
"github.com/containerd/errdefs"
runc "github.com/containerd/go-runc"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
)
type deletedState struct{}
@@ -33,8 +33,8 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/runsccmd"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
"gvisor.dev/gvisor/pkg/shim/v1/runsccmd"
)
type execProcess struct {
@@ -20,7 +20,7 @@ import (
"fmt"
"github.com/containerd/console"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
)
type execState interface {
@@ -36,9 +36,9 @@ import (
runc "github.com/containerd/go-runc"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/runsccmd"
"gvisor.dev/gvisor/pkg/shim/utils"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
"gvisor.dev/gvisor/pkg/shim/v1/runsccmd"
"gvisor.dev/gvisor/pkg/shim/v1/utils"
)
const statusStopped = "stopped"
@@ -23,7 +23,7 @@ import (
runc "github.com/containerd/go-runc"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
)
type stateTransition int
@@ -22,7 +22,7 @@ import (
"strings"
"time"
"gvisor.dev/gvisor/pkg/shim/runsccmd"
"gvisor.dev/gvisor/pkg/shim/v1/runsccmd"
)
const (
@@ -20,17 +20,18 @@ go_library(
visibility = ["//pkg/shim:__subpackages__"],
deps = [
"//pkg/cleanup",
"//pkg/shim/extension",
"//pkg/shim/proc",
"//pkg/shim/runsccmd",
"//pkg/shim/runtimeoptions",
"//pkg/shim/runtimeoptions/v14",
"//pkg/shim/utils",
"//pkg/shim/v1/extension",
"//pkg/shim/v1/proc",
"//pkg/shim/v1/runsccmd",
"//pkg/shim/v1/runtimeoptions",
"//pkg/shim/v1/runtimeoptions/v14",
"//pkg/shim/v1/utils",
"//runsc/specutils",
"@com_github_burntsushi_toml//:go_default_library",
"@com_github_containerd_cgroups//:go_default_library",
"@com_github_containerd_cgroups//stats/v1:go_default_library",
"@com_github_containerd_cgroups//v2:go_default_library",
"@com_github_containerd_cgroups//v2/stats:go_default_library",
"@com_github_containerd_console//:go_default_library",
"@com_github_containerd_containerd//api/events:go_default_library",
"@com_github_containerd_containerd//api/types/task:go_default_library",
@@ -46,6 +47,7 @@ go_library(
"@com_github_containerd_containerd//sys/reaper:go_default_library",
"@com_github_containerd_errdefs//:go_default_library",
"@com_github_containerd_fifo//:go_default_library",
"@com_github_containerd_go_runc//:go_default_library",
"@com_github_containerd_log//:go_default_library",
"@com_github_containerd_typeurl//:go_default_library",
"@com_github_gogo_protobuf//types:go_default_library",
@@ -60,7 +62,7 @@ go_test(
srcs = ["service_test.go"],
library = ":runsc",
deps = [
"//pkg/shim/utils",
"//pkg/shim/v1/utils",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
],
)
@@ -29,6 +29,7 @@ import (
"github.com/containerd/cgroups"
cgroupsstats "github.com/containerd/cgroups/stats/v1"
cgroupsv2 "github.com/containerd/cgroups/v2"
cgroupsv2stats "github.com/containerd/cgroups/v2/stats"
"github.com/containerd/console"
"github.com/containerd/containerd/api/events"
"github.com/containerd/containerd/api/types/task"
@@ -42,6 +43,7 @@ import (
taskAPI "github.com/containerd/containerd/runtime/v2/task"
"github.com/containerd/containerd/sys/reaper"
"github.com/containerd/errdefs"
runc "github.com/containerd/go-runc"
"github.com/containerd/log"
"github.com/containerd/typeurl"
"github.com/gogo/protobuf/types"
@@ -49,13 +51,13 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/shim/runtimeoptions/v14"
"gvisor.dev/gvisor/pkg/shim/v1/runtimeoptions/v14"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/proc"
"gvisor.dev/gvisor/pkg/shim/runsccmd"
"gvisor.dev/gvisor/pkg/shim/runtimeoptions"
"gvisor.dev/gvisor/pkg/shim/utils"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
"gvisor.dev/gvisor/pkg/shim/v1/proc"
"gvisor.dev/gvisor/pkg/shim/v1/runsccmd"
"gvisor.dev/gvisor/pkg/shim/v1/runtimeoptions"
"gvisor.dev/gvisor/pkg/shim/v1/utils"
"gvisor.dev/gvisor/runsc/specutils"
)
@@ -660,6 +662,18 @@ func (s *runscService) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*tas
// as runc.
//
// [0]: https://github.com/google/gvisor/blob/277a0d5a1fbe8272d4729c01ee4c6e374d047ebc/runsc/boot/events.go#L61-L81
return s.getStats(stats, r)
}
func (s *runscService) getStats(stats *runc.Stats, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) {
if s.opts.RunscConfig["systemd-cgroup"] == "true" {
return s.getV2Stats(stats, r)
} else {
return s.getV1Stats(stats, r)
}
}
func (s *runscService) getV1Stats(stats *runc.Stats, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) {
metrics := &cgroupsstats.Metrics{
CPU: &cgroupsstats.CPUStat{
Usage: &cgroupsstats.CPUUsage{
@@ -708,10 +722,45 @@ func (s *runscService) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*tas
}
data, err := typeurl.MarshalAny(metrics)
if err != nil {
log.L.Debugf("Stats error, id: %s: %v", r.ID, err)
log.L.Debugf("Stats error v1, id: %s: %v", r.ID, err)
return nil, err
}
log.L.Debugf("Stats success, id: %s: %+v", r.ID, data)
log.L.Debugf("Stats success v1, id: %s: %+v", r.ID, data)
return &taskAPI.StatsResponse{
Stats: data,
}, nil
}
func (s *runscService) getV2Stats(stats *runc.Stats, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) {
metrics := &cgroupsv2stats.Metrics{
// The CGroup V2 stats are in microseconds instead of nanoseconds so divide by 1000
CPU: &cgroupsv2stats.CPUStat{
UsageUsec: stats.Cpu.Usage.Total / 1000,
UserUsec: stats.Cpu.Usage.User / 1000,
SystemUsec: stats.Cpu.Usage.Kernel / 1000,
NrPeriods: stats.Cpu.Throttling.Periods,
NrThrottled: stats.Cpu.Throttling.ThrottledPeriods,
ThrottledUsec: stats.Cpu.Throttling.ThrottledTime / 1000,
},
Memory: &cgroupsv2stats.MemoryStat{
Usage: stats.Memory.Usage.Usage,
UsageLimit: stats.Memory.Usage.Limit,
SwapUsage: stats.Memory.Swap.Usage,
SwapLimit: stats.Memory.Swap.Limit,
Slab: stats.Memory.Kernel.Usage,
File: stats.Memory.Cache,
},
Pids: &cgroupsv2stats.PidsStat{
Current: stats.Pids.Current,
Limit: stats.Pids.Limit,
},
}
data, err := typeurl.MarshalAny(metrics)
if err != nil {
log.L.Debugf("Stats error v2, id: %s: %v", r.ID, err)
return nil, err
}
log.L.Debugf("Stats success v2, id: %s: %+v", r.ID, data)
return &taskAPI.StatsResponse{
Stats: data,
}, nil
@@ -18,7 +18,7 @@ import (
"testing"
specs "github.com/opencontainers/runtime-spec/specs-go"
"gvisor.dev/gvisor/pkg/shim/utils"
"gvisor.dev/gvisor/pkg/shim/v1/utils"
)
func TestCgroupPath(t *testing.T) {
@@ -18,7 +18,7 @@ go_library(
"runtimeoptions.go",
"runtimeoptions_cri.go",
],
visibility = ["//pkg/shim/runsc:__pkg__"],
visibility = ["//pkg/shim/v1/runsc:__pkg__"],
deps = ["@com_github_gogo_protobuf//proto:go_default_library"],
)
@@ -18,7 +18,7 @@ go_library(
"runtimeoptions.go",
"runtimeoptions_cri.go",
],
visibility = ["//pkg/shim/runsc:__pkg__"],
visibility = ["//pkg/shim/v1/runsc:__pkg__"],
deps = ["@com_github_gogo_protobuf//proto:go_default_library"],
)
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package shim implements Containerd Shim v2 interface.
package shim
// Package v1 implements Containerd Shim v2 interface.
package v1
import (
"context"
@@ -31,8 +31,8 @@ import (
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/cleanup"
"gvisor.dev/gvisor/pkg/shim/extension"
"gvisor.dev/gvisor/pkg/shim/runsc"
"gvisor.dev/gvisor/pkg/shim/v1/extension"
"gvisor.dev/gvisor/pkg/shim/v1/runsc"
"gvisor.dev/gvisor/pkg/sync"
)
+10 -31
View File
@@ -18,7 +18,6 @@ import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/fd"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/arch"
@@ -28,42 +27,19 @@ import (
"gvisor.dev/gvisor/pkg/sync"
)
func getSaveOpts(l *Loader, k *kernel.Kernel, isResume bool) state.SaveOpts {
t, _ := state.CPUTime()
log.Infof("Before save CPU usage: %s", t.String())
saveOpts := state.SaveOpts{
Key: nil,
Resume: isResume,
Callback: func(err error) {
t1, _ := state.CPUTime()
log.Infof("Save CPU usage: %s", (t1 - t).String())
if err == nil {
log.Infof("Save succeeded: exiting...")
k.SetSaveSuccess(true)
} else {
log.Warningf("Save failed: exiting... %v", err)
k.SetSaveError(err)
}
if !isResume {
// Kill the sandbox.
k.Kill(linux.WaitStatusExit(0))
}
},
}
return saveOpts
}
func getTargetForSaveResume(l *Loader) func(k *kernel.Kernel) {
return func(k *kernel.Kernel) {
l.addVersionToCheckpoint()
l.addContainerSpecsToCheckpoint()
saveOpts := getSaveOpts(l, k, true /* isResume */)
// Store the state file contents in a buffer for save-resume.
// There is no need to verify the state file, we just need the
// sandbox to continue running after save.
var buf bytes.Buffer
saveOpts.Destination = &buf
saveOpts := state.SaveOpts{
Autosave: true,
Resume: true,
Destination: &buf,
}
saveOpts.Save(k.SupervisorContext(), k, l.watchdog)
}
}
@@ -78,8 +54,11 @@ func getTargetForSaveRestore(l *Loader, files []*fd.FD) func(k *kernel.Kernel) {
once.Do(func() {
l.addVersionToCheckpoint()
l.addContainerSpecsToCheckpoint()
saveOpts := getSaveOpts(l, k, false /* isResume */)
saveOpts.Destination = files[0]
saveOpts := state.SaveOpts{
Autosave: true,
Resume: false,
Destination: files[0],
}
if len(files) == 3 {
saveOpts.PagesMetadata = files[1]
saveOpts.PagesFile = files[2]
+44 -19
View File
@@ -208,9 +208,7 @@ type Loader struct {
// /sys/devices/virtual/dmi/id/product_name.
productName string
// hostShmemHuge is the host's value of
// /sys/kernel/mm/transparent_hugepage/shmem_enabled.
hostShmemHuge string
hostTHP HostTHP
// mu guards the fields below.
mu sync.Mutex
@@ -354,14 +352,23 @@ type Args struct {
// NvidiaDriverVersion is the NVIDIA driver ABI version to use for
// communicating with NVIDIA devices on the host.
NvidiaDriverVersion string
// HostShmemHuge is the host's value of
// /sys/kernel/mm/transparent_hugepage/shmem_enabled, or empty if this is
// unknown.
HostShmemHuge string
// HostTHP contains host transparent hugepage settings.
HostTHP HostTHP
SaveFDs []*fd.FD
}
// HostTHP holds host transparent hugepage settings.
type HostTHP struct {
// ShmemEnabled is the selected option in
// /sys/kernel/mm/transparent_hugepage/shmem_enabled.
ShmemEnabled string
// Defrag is the selected option in
// /sys/kernel/mm/transparent_hugepage/defrag.
Defrag string
}
const (
// startingStdioFD is the starting stdioFD number used during sandbox
// start and restore. This makes sure the stdioFDs are always the same
@@ -439,7 +446,7 @@ func New(args Args) (*Loader, error) {
sharedMounts: make(map[string]*vfs.Mount),
stopProfiling: stopProfiling,
productName: args.ProductName,
hostShmemHuge: args.HostShmemHuge,
hostTHP: args.HostTHP,
containerIDs: make(map[string]string),
containerSpecs: make(map[string]*specs.Spec),
saveFDs: args.SaveFDs,
@@ -508,7 +515,7 @@ func New(args Args) (*Loader, error) {
l.k = &kernel.Kernel{Platform: p}
// Create memory file.
mf, err := createMemoryFile(args.Conf.AppHugePages, args.HostShmemHuge)
mf, err := createMemoryFile(args.Conf.AppHugePages, args.HostTHP)
if err != nil {
return nil, fmt.Errorf("creating memory file: %w", err)
}
@@ -787,7 +794,7 @@ func createPlatform(conf *config.Config, deviceFile *fd.FD) (platform.Platform,
return p.New(deviceFile)
}
func createMemoryFile(appHugePages bool, hostShmemHuge string) (*pgalloc.MemoryFile, error) {
func createMemoryFile(appHugePages bool, hostTHP HostTHP) (*pgalloc.MemoryFile, error) {
const memfileName = "runsc-memory"
memfd, err := memutil.CreateMemFD(memfileName, 0)
if err != nil {
@@ -801,27 +808,45 @@ func createMemoryFile(appHugePages bool, hostShmemHuge string) (*pgalloc.MemoryF
// in a mount namespace in which the relevant cgroupfs is not visible.
}
if appHugePages {
switch hostShmemHuge {
switch hostTHP.ShmemEnabled {
case "":
log.Infof("Disabling application huge pages: host shmem_huge is unknown")
log.Infof("Disabling application huge pages: host shmem_enabled is unknown")
case "never", "deny":
log.Infof("Disabling application huge pages: host shmem_huge is %q", hostShmemHuge)
log.Infof("Disabling application huge pages: host shmem_enabled is %q", hostTHP.ShmemEnabled)
case "advise":
log.Infof("Enabling application huge pages: host shmem_huge is %q", hostShmemHuge)
mfopts.ExpectHugepages = true
mfopts.AdviseHugepage = true
switch hostTHP.Defrag {
case "":
log.Infof("Disabling application huge pages: host shmem_enabled is %q, host defrag is unknown", hostTHP.ShmemEnabled)
case "always", "defer", "never":
// Allocations on MADV_HUGEPAGE pages will invoke direct
// compaction ("always"), wake kcompactd ("defer"), or do
// neither ("never"), consistent with host private anonymous
// memory mappings, so using MADV_HUGEPAGE shouldn't cause a
// regression vs. running natively.
log.Infof("Enabling application huge pages: host shmem_enabled is %q, host defrag is %q", hostTHP.ShmemEnabled, hostTHP.Defrag)
mfopts.ExpectHugepages = true
mfopts.AdviseHugepage = true
case "defer+madvise", "madvise":
// Allocations on MADV_HUGEPAGE pages may invoke direct
// compaction whereas allocations on host private anonymous
// memory mappings will not, so using MADV_HUGEPAGE may degrade
// performance.
log.Infof("Disabling application huge pages: host shmem_enabled is %q, host defrag is %q", hostTHP.ShmemEnabled, hostTHP.Defrag)
default:
log.Warningf("Disabling application huge pages: host shmem_enabled is %q, host defrag is unknown value %q", hostTHP.ShmemEnabled, hostTHP.Defrag)
}
case "always", "within_size":
log.Infof("Enabling application huge pages: host shmem_huge is %q", hostShmemHuge)
log.Infof("Enabling application huge pages: host shmem_enabled is %q", hostTHP.ShmemEnabled)
// In these cases, memfds will default to using huge pages, and we have to
// explicitly ask for small pages.
mfopts.ExpectHugepages = true
mfopts.AdviseNoHugepage = true
case "force":
log.Infof("Enabling application huge pages: host shmem_huge is %q", hostShmemHuge)
log.Infof("Enabling application huge pages: host shmem_enabled is %q", hostTHP.ShmemEnabled)
// The kernel will ignore MADV_NOHUGEPAGE, so don't bother.
mfopts.ExpectHugepages = true
default:
log.Infof("Disabling application huge pages: host shmem_huge is unknown value %q", hostShmemHuge)
log.Warningf("Disabling application huge pages: host shmem_enabled is unknown value %q", hostTHP.ShmemEnabled)
}
}
+1 -1
View File
@@ -171,7 +171,7 @@ func (r *restorer) restore(l *Loader) error {
Platform: p,
}
mf, err := createMemoryFile(l.root.conf.AppHugePages, l.hostShmemHuge)
mf, err := createMemoryFile(l.root.conf.AppHugePages, l.hostTHP)
if err != nil {
return fmt.Errorf("creating memory file: %v", err)
}
+24 -12
View File
@@ -159,8 +159,7 @@ type Boot struct {
// /sys/devices/virtual/dmi/id/product_name.
productName string
// Value of /sys/kernel/mm/transparent_hugepage/shmem_enabled on the host.
hostShmemHuge string
hostTHP boot.HostTHP
// FDs for profile data.
profileFDs profile.FDArgs
@@ -217,7 +216,8 @@ func (b *Boot) SetFlags(f *flag.FlagSet) {
f.BoolVar(&b.attached, "attached", false, "if attached is true, kills the sandbox process when the parent process terminates")
f.StringVar(&b.productName, "product-name", "", "value to show in /sys/devices/virtual/dmi/id/product_name")
f.StringVar(&b.nvidiaDriverVersion, "nvidia-driver-version", "", "Nvidia driver version on the host")
f.StringVar(&b.hostShmemHuge, "host-shmem-huge", "", "value of /sys/kernel/mm/transparent_hugepage/shmem_enabled on the host")
f.StringVar(&b.hostTHP.ShmemEnabled, "host-thp-shmem-enabled", "", "value of /sys/kernel/mm/transparent_hugepage/shmem_enabled on the host")
f.StringVar(&b.hostTHP.Defrag, "host-thp-defrag", "", "value of /sys/kernel/mm/transparent_hugepage/defrag on the host")
// Open FDs that are donated to the sandbox.
f.IntVar(&b.specFD, "spec-fd", -1, "required fd with the container spec")
@@ -275,14 +275,26 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
argOverride["product-name"] = b.productName
}
}
if conf.AppHugePages && len(b.hostShmemHuge) == 0 {
hostShmemHuge, err := hostmm.GetTransparentHugepageEnum("shmem_enabled")
if err != nil {
log.Warningf("Failed to infer --host-shmem-huge: %v", err)
} else {
b.hostShmemHuge = hostShmemHuge
log.Infof("Setting host-shmem-huge: %q", b.hostShmemHuge)
argOverride["host-shmem-huge"] = b.hostShmemHuge
if conf.AppHugePages {
if len(b.hostTHP.ShmemEnabled) == 0 {
val, err := hostmm.ReadTransparentHugepageEnum("shmem_enabled")
if err != nil {
log.Warningf("Failed to infer --host-thp-shmem-enabled: %v", err)
} else {
b.hostTHP.ShmemEnabled = val
log.Infof("Setting host-thp-shmem-enabled: %q", b.hostTHP.ShmemEnabled)
argOverride["host-thp-shmem-enabled"] = b.hostTHP.ShmemEnabled
}
}
if len(b.hostTHP.Defrag) == 0 {
val, err := hostmm.ReadTransparentHugepageEnum("defrag")
if err != nil {
log.Warningf("Failed to infer --host-thp-defrag", err)
} else {
b.hostTHP.Defrag = val
log.Infof("Setting host-thp-defrag: %q", b.hostTHP.Defrag)
argOverride["host-thp-defrag"] = b.hostTHP.Defrag
}
}
}
@@ -504,7 +516,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcomma
SinkFDs: b.sinkFDs.GetArray(),
ProfileOpts: b.profileFDs.ToOpts(),
NvidiaDriverVersion: b.nvidiaDriverVersion,
HostShmemHuge: b.hostShmemHuge,
HostTHP: b.hostTHP,
SaveFDs: b.saveFDs.GetFDs(),
}
l, err := boot.New(bootArgs)
+1
View File
@@ -13,6 +13,7 @@ go_library(
visibility = ["//:sandbox"],
deps = [
"//pkg/log",
"//pkg/sentry/hostmm",
"//runsc/config",
],
)
+41 -4
View File
@@ -23,6 +23,7 @@ import (
"strings"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/hostmm"
"gvisor.dev/gvisor/runsc/config"
)
@@ -189,12 +190,48 @@ func check(conf *config.Config) ([]*Delta, []error) {
path: "/sys/kernel/mm/transparent_hugepage/shmem_enabled",
purpose: "turning on transparent hugepages support in shmem increases memory allocation performance",
delta: func(conf *config.Config, current string) (string, bool, error) {
// /sys/kernel/mm/transparent_hugepage/shmem_enabled is formatted like:
// `always within_size advise [never] deny force`.
if strings.Contains(current, "[always]") || strings.Contains(current, "[advise]") || strings.Contains(current, "[force]") || strings.Contains(current, "[within_size]") {
cur, err := hostmm.GetTransparentHugepageEnum(current)
if err != nil {
return "", false, err
}
switch cur {
case "always", "advise", "force", "within_size":
return "", false, nil
case "never":
return "advise", false, nil
case "deny":
// This is a non-default setting, so assume that it's
// admin-intended and don't try to change it.
return "", false, nil
default:
// Linux can reasonably add new options, so this shouldn't
// be fatal.
log.Warningf("Unknown value for /sys/kernel/mm/transparent_hugepage/shmem_enabled: %s", cur)
return "", false, nil
}
},
},
{
path: "/sys/kernel/mm/transparent_hugepage/defrag",
purpose: "disabling direct compaction improves page fault latency when hugepages are not immediately available",
delta: func(conf *config.Config, current string) (string, bool, error) {
cur, err := hostmm.GetTransparentHugepageEnum(current)
if err != nil {
return "", false, err
}
switch cur {
case "always", "defer", "never":
return "", false, nil
case "defer+madvise":
return "defer", false, nil
case "madvise":
return "never", false, nil
default:
// Linux can reasonably add new options, so this shouldn't
// be fatal.
log.Warningf("Unknown value for /sys/kernel/mm/transparent_hugepage/defrag: %s", cur)
return "", false, nil
}
return "advise", false, nil
},
},
{
+1 -1
View File
@@ -13,7 +13,7 @@ go_binary(
visibility = [
"//visibility:public",
],
deps = ["//shim/cli"],
deps = ["//shim/v1/cli"],
)
pkg_tar(
+1 -1
View File
@@ -16,7 +16,7 @@
package main
import (
"gvisor.dev/gvisor/shim/cli"
"gvisor.dev/gvisor/shim/v1/cli"
)
func main() {
+1 -1
View File
@@ -13,7 +13,7 @@ go_library(
"//shim:__pkg__",
],
deps = [
"//pkg/shim",
"//pkg/shim/v1",
"@com_github_containerd_containerd//runtime/v2/shim:go_default_library",
],
)
+1 -1
View File
@@ -19,7 +19,7 @@ package cli
import (
containerdshim "github.com/containerd/containerd/runtime/v2/shim"
"gvisor.dev/gvisor/pkg/shim"
shim "gvisor.dev/gvisor/pkg/shim/v1"
)
// Main is the main entrypoint.
+6
View File
@@ -424,6 +424,12 @@ void RemoveUnstableCPUInfoFields(std::vector<std::string>& cpu_info_fields) {
}
}
TEST(ProcTest, RootInodeNumber) {
struct stat s;
ASSERT_THAT(stat("/proc", &s), SyscallSucceeds());
EXPECT_EQ(s.st_ino, 1);
}
TEST(ProcTest, NotFoundInRoot) {
struct stat s;
EXPECT_THAT(stat("/proc/foobar", &s), SyscallFailsWithErrno(ENOENT));