diff options
-rw-r--r-- | pkg/sentry/fs/ext/benchmark/BUILD | 16 | ||||
-rw-r--r-- | pkg/sentry/fs/ext/benchmark/benchmark_test.go | 193 | ||||
-rwxr-xr-x | pkg/sentry/fs/ext/benchmark/make_deep_ext4.sh | 72 | ||||
-rw-r--r-- | pkg/sentry/fs/ext/ext.go | 10 | ||||
-rw-r--r-- | pkg/sentry/fs/ext/ext_test.go | 2 |
5 files changed, 287 insertions, 6 deletions
diff --git a/pkg/sentry/fs/ext/benchmark/BUILD b/pkg/sentry/fs/ext/benchmark/BUILD new file mode 100644 index 000000000..73999770a --- /dev/null +++ b/pkg/sentry/fs/ext/benchmark/BUILD @@ -0,0 +1,16 @@ +load("//tools/go_stateify:defs.bzl", "go_test") + +package(licenses = ["notice"]) + +go_test( + name = "benchmark_test", + size = "small", + srcs = ["benchmark_test.go"], + deps = [ + "//pkg/sentry/context", + "//pkg/sentry/context/contexttest", + "//pkg/sentry/fs/ext", + "//pkg/sentry/kernel/auth", + "//pkg/sentry/vfs", + ], +) diff --git a/pkg/sentry/fs/ext/benchmark/benchmark_test.go b/pkg/sentry/fs/ext/benchmark/benchmark_test.go new file mode 100644 index 000000000..08f4dd251 --- /dev/null +++ b/pkg/sentry/fs/ext/benchmark/benchmark_test.go @@ -0,0 +1,193 @@ +// Copyright 2019 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. + +// These benchmarks emulate memfs benchmarks. Ext4 images must be created +// before this benchmark is run using the `make_deep_ext4.sh` script at +// /tmp/image-{depth}.ext4 for all the depths tested below. +package benchmark_test + +import ( + "fmt" + "os" + "runtime" + "strings" + "testing" + + "gvisor.dev/gvisor/pkg/sentry/context" + "gvisor.dev/gvisor/pkg/sentry/context/contexttest" + "gvisor.dev/gvisor/pkg/sentry/fs/ext" + "gvisor.dev/gvisor/pkg/sentry/kernel/auth" + "gvisor.dev/gvisor/pkg/sentry/vfs" +) + +var depths = []int{1, 2, 3, 8, 64, 100} + +const filename = "file.txt" + +// setUp opens imagePath as an ext Filesystem and returns all necessary +// elements required to run tests. If error is nil, it also returns a tear +// down function which must be called after the test is run for clean up. +func setUp(b *testing.B, imagePath string) (context.Context, *vfs.VirtualFilesystem, *vfs.VirtualDentry, func(), error) { + f, err := os.Open(imagePath) + if err != nil { + return nil, nil, nil, nil, err + } + + ctx := contexttest.Context(b) + creds := auth.CredentialsFromContext(ctx) + + // Create VFS. + vfsObj := vfs.New() + vfsObj.MustRegisterFilesystemType("extfs", ext.FilesystemType{}) + mntns, err := vfsObj.NewMountNamespace(ctx, creds, imagePath, "extfs", &vfs.NewFilesystemOptions{InternalData: int(f.Fd())}) + if err != nil { + f.Close() + return nil, nil, nil, nil, err + } + + root := mntns.Root() + + tearDown := func() { + root.DecRef() + + if err := f.Close(); err != nil { + b.Fatalf("tearDown failed: %v", err) + } + } + return ctx, vfsObj, &root, tearDown, nil +} + +// mount mounts extfs at the path operation passed. Returns a tear down +// function which must be called after the test is run for clean up. +func mount(b *testing.B, imagePath string, vfsfs *vfs.VirtualFilesystem, pop *vfs.PathOperation) func() { + b.Helper() + + f, err := os.Open(imagePath) + if err != nil { + b.Fatalf("could not open image at %s: %v", imagePath, err) + } + + ctx := contexttest.Context(b) + creds := auth.CredentialsFromContext(ctx) + + if err := vfsfs.NewMount(ctx, creds, imagePath, pop, "extfs", &vfs.NewFilesystemOptions{InternalData: int(f.Fd())}); err != nil { + b.Fatalf("failed to mount tmpfs submount: %v", err) + } + return func() { + if err := f.Close(); err != nil { + b.Fatalf("tearDown failed: %v", err) + } + } +} + +// BenchmarkVFS2Ext4fsStat emulates BenchmarkVFS2MemfsStat. +func BenchmarkVFS2Ext4fsStat(b *testing.B) { + for _, depth := range depths { + b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { + ctx, vfsfs, root, tearDown, err := setUp(b, fmt.Sprintf("/tmp/image-%d.ext4", depth)) + if err != nil { + b.Fatalf("setUp failed: %v", err) + } + defer tearDown() + + creds := auth.CredentialsFromContext(ctx) + var filePathBuilder strings.Builder + filePathBuilder.WriteByte('/') + for i := 1; i <= depth; i++ { + filePathBuilder.WriteString(fmt.Sprintf("%d", i)) + filePathBuilder.WriteByte('/') + } + filePathBuilder.WriteString(filename) + filePath := filePathBuilder.String() + + runtime.GC() + b.ResetTimer() + for i := 0; i < b.N; i++ { + stat, err := vfsfs.StatAt(ctx, creds, &vfs.PathOperation{ + Root: *root, + Start: *root, + Pathname: filePath, + FollowFinalSymlink: true, + }, &vfs.StatOptions{}) + if err != nil { + b.Fatalf("stat(%q) failed: %v", filePath, err) + } + // Sanity check. + if stat.Size > 0 { + b.Fatalf("got wrong file size (%d)", stat.Size) + } + } + }) + } +} + +// BenchmarkVFS2ExtfsMountStat emulates BenchmarkVFS2MemfsMountStat. +func BenchmarkVFS2ExtfsMountStat(b *testing.B) { + for _, depth := range depths { + b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) { + // Create root extfs with depth 1 so we can mount extfs again at /1/. + ctx, vfsfs, root, tearDown, err := setUp(b, fmt.Sprintf("/tmp/image-%d.ext4", 1)) + if err != nil { + b.Fatalf("setUp failed: %v", err) + } + defer tearDown() + + creds := auth.CredentialsFromContext(ctx) + mountPointName := "/1/" + pop := vfs.PathOperation{ + Root: *root, + Start: *root, + Pathname: mountPointName, + } + + // Save the mount point for later use. + mountPoint, err := vfsfs.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{}) + if err != nil { + b.Fatalf("failed to walk to mount point: %v", err) + } + defer mountPoint.DecRef() + + // Create extfs submount. + mountTearDown := mount(b, fmt.Sprintf("/tmp/image-%d.ext4", depth), vfsfs, &pop) + defer mountTearDown() + + var filePathBuilder strings.Builder + filePathBuilder.WriteString(mountPointName) + for i := 1; i <= depth; i++ { + filePathBuilder.WriteString(fmt.Sprintf("%d", i)) + filePathBuilder.WriteByte('/') + } + filePathBuilder.WriteString(filename) + filePath := filePathBuilder.String() + + runtime.GC() + b.ResetTimer() + for i := 0; i < b.N; i++ { + stat, err := vfsfs.StatAt(ctx, creds, &vfs.PathOperation{ + Root: *root, + Start: *root, + Pathname: filePath, + FollowFinalSymlink: true, + }, &vfs.StatOptions{}) + if err != nil { + b.Fatalf("stat(%q) failed: %v", filePath, err) + } + // Sanity check. touch(1) always creates files of size 0 (empty). + if stat.Size > 0 { + b.Fatalf("got wrong file size (%d)", stat.Size) + } + } + }) + } +} diff --git a/pkg/sentry/fs/ext/benchmark/make_deep_ext4.sh b/pkg/sentry/fs/ext/benchmark/make_deep_ext4.sh new file mode 100755 index 000000000..d0910da1f --- /dev/null +++ b/pkg/sentry/fs/ext/benchmark/make_deep_ext4.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Copyright 2019 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. + +# This script creates an ext4 image with $1 depth of directories and a file in +# the inner most directory. The created file is at path /1/2/.../depth/file.txt. +# The ext4 image is written to $2. The image is temporarily mounted at +# /tmp/mountpoint. This script must be run with sudo privileges. + +# Usage: +# sudo bash make_deep_ext4.sh {depth} {output path} + +# Check positional arguments. +if [ "$#" -ne 2 ]; then + echo "Usage: sudo bash make_deep_ext4.sh {depth} {output path}" + exit 1 +fi + +# Make sure depth is a non-negative number. +if ! [[ "$1" =~ ^[0-9]+$ ]]; then + echo "Depth must be a non-negative number." + exit 1 +fi + +# Create a 1 MB filesystem image at the requested output path. +rm -f $2 +fallocate -l 1M $2 +if [ $? -ne 0 ]; then + echo "fallocate failed" + exit $? +fi + +# Convert that blank into an ext4 image. +mkfs.ext4 -j $2 +if [ $? -ne 0 ]; then + echo "mkfs.ext4 failed" + exit $? +fi + +# Mount the image. +MOUNTPOINT=/tmp/mountpoint +mkdir -p $MOUNTPOINT +mount -o loop $2 $MOUNTPOINT +if [ $? -ne 0 ]; then + echo "mount failed" + exit $? +fi + +# Create nested directories and the file. +if [ "$1" -eq 0 ]; then + FILEPATH=$MOUNTPOINT/file.txt +else + FILEPATH=$MOUNTPOINT/$(seq -s '/' 1 $1)/file.txt +fi +mkdir -p $(dirname $FILEPATH) || exit +touch $FILEPATH + +# Clean up. +umount $MOUNTPOINT +rm -rf $MOUNTPOINT diff --git a/pkg/sentry/fs/ext/ext.go b/pkg/sentry/fs/ext/ext.go index c3e2c9efb..632ba97f8 100644 --- a/pkg/sentry/fs/ext/ext.go +++ b/pkg/sentry/fs/ext/ext.go @@ -30,11 +30,11 @@ import ( "gvisor.dev/gvisor/pkg/syserror" ) -// filesystemType implements vfs.FilesystemType. -type filesystemType struct{} +// FilesystemType implements vfs.FilesystemType. +type FilesystemType struct{} -// Compiles only if filesystemType implements vfs.FilesystemType. -var _ vfs.FilesystemType = (*filesystemType)(nil) +// Compiles only if FilesystemType implements vfs.FilesystemType. +var _ vfs.FilesystemType = (*FilesystemType)(nil) // getDeviceFd returns an io.ReaderAt to the underlying device. // Currently there are two ways of mounting an ext(2/3/4) fs: @@ -92,7 +92,7 @@ func isCompatible(sb disklayout.SuperBlock) bool { } // NewFilesystem implements vfs.FilesystemType.NewFilesystem. -func (fstype filesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts vfs.NewFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { +func (FilesystemType) NewFilesystem(ctx context.Context, creds *auth.Credentials, source string, opts vfs.NewFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) { // TODO(b/134676337): Ensure that the user is mounting readonly. If not, // EACCESS should be returned according to mount(2). Filesystem independent // flags (like readonly) are currently not available in pkg/sentry/vfs. diff --git a/pkg/sentry/fs/ext/ext_test.go b/pkg/sentry/fs/ext/ext_test.go index 270f38074..31da80ed9 100644 --- a/pkg/sentry/fs/ext/ext_test.go +++ b/pkg/sentry/fs/ext/ext_test.go @@ -65,7 +65,7 @@ func setUp(t *testing.T, imagePath string) (context.Context, *vfs.VirtualFilesys // Create VFS. vfsObj := vfs.New() - vfsObj.MustRegisterFilesystemType("extfs", filesystemType{}) + vfsObj.MustRegisterFilesystemType("extfs", FilesystemType{}) mntns, err := vfsObj.NewMountNamespace(ctx, creds, localImagePath, "extfs", &vfs.NewFilesystemOptions{InternalData: int(f.Fd())}) if err != nil { f.Close() |