github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fsimpl/tmpfs/benchmark_test.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package benchmark_test
    16  
    17  import (
    18  	"fmt"
    19  	"runtime"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    24  	"github.com/SagerNet/gvisor/pkg/context"
    25  	"github.com/SagerNet/gvisor/pkg/errors/linuxerr"
    26  	"github.com/SagerNet/gvisor/pkg/fspath"
    27  	"github.com/SagerNet/gvisor/pkg/refs"
    28  	"github.com/SagerNet/gvisor/pkg/sentry/contexttest"
    29  	"github.com/SagerNet/gvisor/pkg/sentry/fs"
    30  	_ "github.com/SagerNet/gvisor/pkg/sentry/fs/tmpfs"
    31  	"github.com/SagerNet/gvisor/pkg/sentry/fsimpl/tmpfs"
    32  	"github.com/SagerNet/gvisor/pkg/sentry/kernel/auth"
    33  	"github.com/SagerNet/gvisor/pkg/sentry/vfs"
    34  	"github.com/SagerNet/gvisor/pkg/syserror"
    35  )
    36  
    37  // Differences from stat_benchmark:
    38  //
    39  // - Syscall interception, CopyInPath, copyOutStat, and overlayfs overheads are
    40  // not included.
    41  //
    42  // - *MountStat benchmarks use a tmpfs root mount and a tmpfs submount at /tmp.
    43  // Non-MountStat benchmarks use a tmpfs root mount and no submounts.
    44  // stat_benchmark uses a varying root mount, a tmpfs submount at /tmp, and a
    45  // subdirectory /tmp/<top_dir> (assuming TEST_TMPDIR == "/tmp"). Thus
    46  // stat_benchmark at depth 1 does a comparable amount of work to *MountStat
    47  // benchmarks at depth 2, and non-MountStat benchmarks at depth 3.
    48  var depths = []int{1, 2, 3, 8, 64, 100}
    49  
    50  const (
    51  	mountPointName = "tmp"
    52  	filename       = "gvisor_test_temp_0_1557494568"
    53  )
    54  
    55  // This is copied from syscalls/linux/sys_file.go, with the dependency on
    56  // kernel.Task stripped out.
    57  func fileOpOn(ctx context.Context, mntns *fs.MountNamespace, root, wd *fs.Dirent, dirFD int32, path string, resolve bool, fn func(root *fs.Dirent, d *fs.Dirent) error) error {
    58  	var (
    59  		d   *fs.Dirent // The file.
    60  		rel *fs.Dirent // The relative directory for search (if required.)
    61  		err error
    62  	)
    63  
    64  	// Extract the working directory (maybe).
    65  	if len(path) > 0 && path[0] == '/' {
    66  		// Absolute path; rel can be nil.
    67  	} else if dirFD == linux.AT_FDCWD {
    68  		// Need to reference the working directory.
    69  		rel = wd
    70  	} else {
    71  		// Need to extract the given FD.
    72  		return linuxerr.EBADF
    73  	}
    74  
    75  	// Lookup the node.
    76  	remainingTraversals := uint(linux.MaxSymlinkTraversals)
    77  	if resolve {
    78  		d, err = mntns.FindInode(ctx, root, rel, path, &remainingTraversals)
    79  	} else {
    80  		d, err = mntns.FindLink(ctx, root, rel, path, &remainingTraversals)
    81  	}
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	err = fn(root, d)
    87  	d.DecRef(ctx)
    88  	return err
    89  }
    90  
    91  func BenchmarkVFS1TmpfsStat(b *testing.B) {
    92  	for _, depth := range depths {
    93  		b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
    94  			ctx := contexttest.Context(b)
    95  
    96  			// Create VFS.
    97  			tmpfsFS, ok := fs.FindFilesystem("tmpfs")
    98  			if !ok {
    99  				b.Fatalf("failed to find tmpfs filesystem type")
   100  			}
   101  			rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
   102  			if err != nil {
   103  				b.Fatalf("failed to create tmpfs root mount: %v", err)
   104  			}
   105  			mntns, err := fs.NewMountNamespace(ctx, rootInode)
   106  			if err != nil {
   107  				b.Fatalf("failed to create mount namespace: %v", err)
   108  			}
   109  			defer mntns.DecRef(ctx)
   110  
   111  			var filePathBuilder strings.Builder
   112  			filePathBuilder.WriteByte('/')
   113  
   114  			// Create nested directories with given depth.
   115  			root := mntns.Root()
   116  			defer root.DecRef(ctx)
   117  			d := root
   118  			d.IncRef()
   119  			defer d.DecRef(ctx)
   120  			for i := depth; i > 0; i-- {
   121  				name := fmt.Sprintf("%d", i)
   122  				if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
   123  					b.Fatalf("failed to create directory %q: %v", name, err)
   124  				}
   125  				next, err := d.Walk(ctx, root, name)
   126  				if err != nil {
   127  					b.Fatalf("failed to walk to directory %q: %v", name, err)
   128  				}
   129  				d.DecRef(ctx)
   130  				d = next
   131  				filePathBuilder.WriteString(name)
   132  				filePathBuilder.WriteByte('/')
   133  			}
   134  
   135  			// Create the file that will be stat'd.
   136  			file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
   137  			if err != nil {
   138  				b.Fatalf("failed to create file %q: %v", filename, err)
   139  			}
   140  			file.DecRef(ctx)
   141  			filePathBuilder.WriteString(filename)
   142  			filePath := filePathBuilder.String()
   143  
   144  			dirPath := false
   145  			runtime.GC()
   146  			b.ResetTimer()
   147  			for i := 0; i < b.N; i++ {
   148  				err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
   149  					if dirPath && !fs.IsDir(d.Inode.StableAttr) {
   150  						return syserror.ENOTDIR
   151  					}
   152  					uattr, err := d.Inode.UnstableAttr(ctx)
   153  					if err != nil {
   154  						return err
   155  					}
   156  					// Sanity check.
   157  					if uattr.Perms.User.Execute {
   158  						b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
   159  					}
   160  					return nil
   161  				})
   162  				if err != nil {
   163  					b.Fatalf("stat(%q) failed: %v", filePath, err)
   164  				}
   165  			}
   166  			// Don't include deferred cleanup in benchmark time.
   167  			b.StopTimer()
   168  		})
   169  	}
   170  }
   171  
   172  func BenchmarkVFS2TmpfsStat(b *testing.B) {
   173  	for _, depth := range depths {
   174  		b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
   175  			ctx := contexttest.Context(b)
   176  			creds := auth.CredentialsFromContext(ctx)
   177  
   178  			// Create VFS.
   179  			vfsObj := vfs.VirtualFilesystem{}
   180  			if err := vfsObj.Init(ctx); err != nil {
   181  				b.Fatalf("VFS init: %v", err)
   182  			}
   183  			vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
   184  				AllowUserMount: true,
   185  			})
   186  			mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
   187  			if err != nil {
   188  				b.Fatalf("failed to create tmpfs root mount: %v", err)
   189  			}
   190  			defer mntns.DecRef(ctx)
   191  
   192  			var filePathBuilder strings.Builder
   193  			filePathBuilder.WriteByte('/')
   194  
   195  			// Create nested directories with given depth.
   196  			root := mntns.Root()
   197  			root.IncRef()
   198  			defer root.DecRef(ctx)
   199  			vd := root
   200  			vd.IncRef()
   201  			for i := depth; i > 0; i-- {
   202  				name := fmt.Sprintf("%d", i)
   203  				pop := vfs.PathOperation{
   204  					Root:  root,
   205  					Start: vd,
   206  					Path:  fspath.Parse(name),
   207  				}
   208  				if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
   209  					Mode: 0755,
   210  				}); err != nil {
   211  					b.Fatalf("failed to create directory %q: %v", name, err)
   212  				}
   213  				nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
   214  				if err != nil {
   215  					b.Fatalf("failed to walk to directory %q: %v", name, err)
   216  				}
   217  				vd.DecRef(ctx)
   218  				vd = nextVD
   219  				filePathBuilder.WriteString(name)
   220  				filePathBuilder.WriteByte('/')
   221  			}
   222  
   223  			// Create the file that will be stat'd.
   224  			fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
   225  				Root:               root,
   226  				Start:              vd,
   227  				Path:               fspath.Parse(filename),
   228  				FollowFinalSymlink: true,
   229  			}, &vfs.OpenOptions{
   230  				Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
   231  				Mode:  0644,
   232  			})
   233  			vd.DecRef(ctx)
   234  			vd = vfs.VirtualDentry{}
   235  			if err != nil {
   236  				b.Fatalf("failed to create file %q: %v", filename, err)
   237  			}
   238  			defer fd.DecRef(ctx)
   239  			filePathBuilder.WriteString(filename)
   240  			filePath := filePathBuilder.String()
   241  
   242  			runtime.GC()
   243  			b.ResetTimer()
   244  			for i := 0; i < b.N; i++ {
   245  				stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
   246  					Root:               root,
   247  					Start:              root,
   248  					Path:               fspath.Parse(filePath),
   249  					FollowFinalSymlink: true,
   250  				}, &vfs.StatOptions{})
   251  				if err != nil {
   252  					b.Fatalf("stat(%q) failed: %v", filePath, err)
   253  				}
   254  				// Sanity check.
   255  				if stat.Mode&^linux.S_IFMT != 0644 {
   256  					b.Fatalf("got wrong permissions (%0o)", stat.Mode)
   257  				}
   258  			}
   259  			// Don't include deferred cleanup in benchmark time.
   260  			b.StopTimer()
   261  		})
   262  	}
   263  }
   264  
   265  func BenchmarkVFS1TmpfsMountStat(b *testing.B) {
   266  	for _, depth := range depths {
   267  		b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
   268  			ctx := contexttest.Context(b)
   269  
   270  			// Create VFS.
   271  			tmpfsFS, ok := fs.FindFilesystem("tmpfs")
   272  			if !ok {
   273  				b.Fatalf("failed to find tmpfs filesystem type")
   274  			}
   275  			rootInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
   276  			if err != nil {
   277  				b.Fatalf("failed to create tmpfs root mount: %v", err)
   278  			}
   279  			mntns, err := fs.NewMountNamespace(ctx, rootInode)
   280  			if err != nil {
   281  				b.Fatalf("failed to create mount namespace: %v", err)
   282  			}
   283  			defer mntns.DecRef(ctx)
   284  
   285  			var filePathBuilder strings.Builder
   286  			filePathBuilder.WriteByte('/')
   287  
   288  			// Create and mount the submount.
   289  			root := mntns.Root()
   290  			defer root.DecRef(ctx)
   291  			if err := root.Inode.CreateDirectory(ctx, root, mountPointName, fs.FilePermsFromMode(0755)); err != nil {
   292  				b.Fatalf("failed to create mount point: %v", err)
   293  			}
   294  			mountPoint, err := root.Walk(ctx, root, mountPointName)
   295  			if err != nil {
   296  				b.Fatalf("failed to walk to mount point: %v", err)
   297  			}
   298  			defer mountPoint.DecRef(ctx)
   299  			submountInode, err := tmpfsFS.Mount(ctx, "tmpfs", fs.MountSourceFlags{}, "", nil)
   300  			if err != nil {
   301  				b.Fatalf("failed to create tmpfs submount: %v", err)
   302  			}
   303  			if err := mntns.Mount(ctx, mountPoint, submountInode); err != nil {
   304  				b.Fatalf("failed to mount tmpfs submount: %v", err)
   305  			}
   306  			filePathBuilder.WriteString(mountPointName)
   307  			filePathBuilder.WriteByte('/')
   308  
   309  			// Create nested directories with given depth.
   310  			d, err := root.Walk(ctx, root, mountPointName)
   311  			if err != nil {
   312  				b.Fatalf("failed to walk to mount root: %v", err)
   313  			}
   314  			defer d.DecRef(ctx)
   315  			for i := depth; i > 0; i-- {
   316  				name := fmt.Sprintf("%d", i)
   317  				if err := d.Inode.CreateDirectory(ctx, d, name, fs.FilePermsFromMode(0755)); err != nil {
   318  					b.Fatalf("failed to create directory %q: %v", name, err)
   319  				}
   320  				next, err := d.Walk(ctx, root, name)
   321  				if err != nil {
   322  					b.Fatalf("failed to walk to directory %q: %v", name, err)
   323  				}
   324  				d.DecRef(ctx)
   325  				d = next
   326  				filePathBuilder.WriteString(name)
   327  				filePathBuilder.WriteByte('/')
   328  			}
   329  
   330  			// Create the file that will be stat'd.
   331  			file, err := d.Inode.Create(ctx, d, filename, fs.FileFlags{Read: true, Write: true}, fs.FilePermsFromMode(0644))
   332  			if err != nil {
   333  				b.Fatalf("failed to create file %q: %v", filename, err)
   334  			}
   335  			file.DecRef(ctx)
   336  			filePathBuilder.WriteString(filename)
   337  			filePath := filePathBuilder.String()
   338  
   339  			dirPath := false
   340  			runtime.GC()
   341  			b.ResetTimer()
   342  			for i := 0; i < b.N; i++ {
   343  				err := fileOpOn(ctx, mntns, root, root, linux.AT_FDCWD, filePath, true /* resolve */, func(root *fs.Dirent, d *fs.Dirent) error {
   344  					if dirPath && !fs.IsDir(d.Inode.StableAttr) {
   345  						return syserror.ENOTDIR
   346  					}
   347  					uattr, err := d.Inode.UnstableAttr(ctx)
   348  					if err != nil {
   349  						return err
   350  					}
   351  					// Sanity check.
   352  					if uattr.Perms.User.Execute {
   353  						b.Fatalf("got wrong permissions (%0o)", uattr.Perms.LinuxMode())
   354  					}
   355  					return nil
   356  				})
   357  				if err != nil {
   358  					b.Fatalf("stat(%q) failed: %v", filePath, err)
   359  				}
   360  			}
   361  			// Don't include deferred cleanup in benchmark time.
   362  			b.StopTimer()
   363  		})
   364  	}
   365  }
   366  
   367  func BenchmarkVFS2TmpfsMountStat(b *testing.B) {
   368  	for _, depth := range depths {
   369  		b.Run(fmt.Sprintf("%d", depth), func(b *testing.B) {
   370  			ctx := contexttest.Context(b)
   371  			creds := auth.CredentialsFromContext(ctx)
   372  
   373  			// Create VFS.
   374  			vfsObj := vfs.VirtualFilesystem{}
   375  			if err := vfsObj.Init(ctx); err != nil {
   376  				b.Fatalf("VFS init: %v", err)
   377  			}
   378  			vfsObj.MustRegisterFilesystemType("tmpfs", tmpfs.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
   379  				AllowUserMount: true,
   380  			})
   381  			mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
   382  			if err != nil {
   383  				b.Fatalf("failed to create tmpfs root mount: %v", err)
   384  			}
   385  			defer mntns.DecRef(ctx)
   386  
   387  			var filePathBuilder strings.Builder
   388  			filePathBuilder.WriteByte('/')
   389  
   390  			// Create the mount point.
   391  			root := mntns.Root()
   392  			root.IncRef()
   393  			defer root.DecRef(ctx)
   394  			pop := vfs.PathOperation{
   395  				Root:  root,
   396  				Start: root,
   397  				Path:  fspath.Parse(mountPointName),
   398  			}
   399  			if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
   400  				Mode: 0755,
   401  			}); err != nil {
   402  				b.Fatalf("failed to create mount point: %v", err)
   403  			}
   404  			// Save the mount point for later use.
   405  			mountPoint, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
   406  			if err != nil {
   407  				b.Fatalf("failed to walk to mount point: %v", err)
   408  			}
   409  			defer mountPoint.DecRef(ctx)
   410  			// Create and mount the submount.
   411  			if _, err := vfsObj.MountAt(ctx, creds, "", &pop, "tmpfs", &vfs.MountOptions{}); err != nil {
   412  				b.Fatalf("failed to mount tmpfs submount: %v", err)
   413  			}
   414  			filePathBuilder.WriteString(mountPointName)
   415  			filePathBuilder.WriteByte('/')
   416  
   417  			// Create nested directories with given depth.
   418  			vd, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
   419  			if err != nil {
   420  				b.Fatalf("failed to walk to mount root: %v", err)
   421  			}
   422  			for i := depth; i > 0; i-- {
   423  				name := fmt.Sprintf("%d", i)
   424  				pop := vfs.PathOperation{
   425  					Root:  root,
   426  					Start: vd,
   427  					Path:  fspath.Parse(name),
   428  				}
   429  				if err := vfsObj.MkdirAt(ctx, creds, &pop, &vfs.MkdirOptions{
   430  					Mode: 0755,
   431  				}); err != nil {
   432  					b.Fatalf("failed to create directory %q: %v", name, err)
   433  				}
   434  				nextVD, err := vfsObj.GetDentryAt(ctx, creds, &pop, &vfs.GetDentryOptions{})
   435  				if err != nil {
   436  					b.Fatalf("failed to walk to directory %q: %v", name, err)
   437  				}
   438  				vd.DecRef(ctx)
   439  				vd = nextVD
   440  				filePathBuilder.WriteString(name)
   441  				filePathBuilder.WriteByte('/')
   442  			}
   443  
   444  			// Create the file that will be stat'd.
   445  			fd, err := vfsObj.OpenAt(ctx, creds, &vfs.PathOperation{
   446  				Root:               root,
   447  				Start:              vd,
   448  				Path:               fspath.Parse(filename),
   449  				FollowFinalSymlink: true,
   450  			}, &vfs.OpenOptions{
   451  				Flags: linux.O_RDWR | linux.O_CREAT | linux.O_EXCL,
   452  				Mode:  0644,
   453  			})
   454  			vd.DecRef(ctx)
   455  			if err != nil {
   456  				b.Fatalf("failed to create file %q: %v", filename, err)
   457  			}
   458  			fd.DecRef(ctx)
   459  			filePathBuilder.WriteString(filename)
   460  			filePath := filePathBuilder.String()
   461  
   462  			runtime.GC()
   463  			b.ResetTimer()
   464  			for i := 0; i < b.N; i++ {
   465  				stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
   466  					Root:               root,
   467  					Start:              root,
   468  					Path:               fspath.Parse(filePath),
   469  					FollowFinalSymlink: true,
   470  				}, &vfs.StatOptions{})
   471  				if err != nil {
   472  					b.Fatalf("stat(%q) failed: %v", filePath, err)
   473  				}
   474  				// Sanity check.
   475  				if stat.Mode&^linux.S_IFMT != 0644 {
   476  					b.Fatalf("got wrong permissions (%0o)", stat.Mode)
   477  				}
   478  			}
   479  			// Don't include deferred cleanup in benchmark time.
   480  			b.StopTimer()
   481  		})
   482  	}
   483  }
   484  
   485  func init() {
   486  	// Turn off reference leak checking for a fair comparison between vfs1 and
   487  	// vfs2.
   488  	refs.SetLeakMode(refs.NoLeakChecking)
   489  }