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 }