gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/benchmarks/fs/fio_test.go (about)

     1  // Copyright 2020 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  package fio_test
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  	"testing"
    23  
    24  	"gvisor.dev/gvisor/pkg/cleanup"
    25  	"gvisor.dev/gvisor/pkg/test/dockerutil"
    26  	"gvisor.dev/gvisor/test/benchmarks/harness"
    27  	"gvisor.dev/gvisor/test/benchmarks/tools"
    28  	"gvisor.dev/gvisor/test/metricsviz"
    29  )
    30  
    31  // Fio benchmarks run fio on the runtime under test. There are 4 basic test
    32  // cases each run on a tmpfs mount and a bind mount. Fio requires root so that
    33  // caches can be dropped.
    34  
    35  // BenchmarkFioWrite runs write operation benchmark cases.
    36  func BenchmarkFioWrite(b *testing.B) {
    37  	testCases := []tools.Fio{
    38  		{
    39  			Test:        "write",
    40  			IOEngine:    tools.EngineSync,
    41  			BlockSizeKB: 4,
    42  			IODepth:     1,
    43  		},
    44  		{
    45  			Test:        "write",
    46  			IOEngine:    tools.EngineSync,
    47  			BlockSizeKB: 64,
    48  			IODepth:     1,
    49  		},
    50  		{
    51  			Test:        "write",
    52  			IOEngine:    tools.EngineLibAIO,
    53  			BlockSizeKB: 1024,
    54  			IODepth:     4,
    55  		},
    56  		{
    57  			Test:        "write",
    58  			IOEngine:    tools.EngineLibAIO,
    59  			Jobs:        8,
    60  			BlockSizeKB: 4,
    61  			IODepth:     4,
    62  			Direct:      true,
    63  		},
    64  		{
    65  			Test:        "write",
    66  			IOEngine:    tools.EngineLibAIO,
    67  			Jobs:        8,
    68  			BlockSizeKB: 64,
    69  			IODepth:     4,
    70  			Direct:      true,
    71  		},
    72  		{
    73  			Test:        "write",
    74  			IOEngine:    tools.EngineLibAIO,
    75  			Jobs:        8,
    76  			BlockSizeKB: 1024,
    77  			IODepth:     4,
    78  			Direct:      true,
    79  		},
    80  	}
    81  	doFioBenchmark(b, testCases)
    82  }
    83  
    84  // BenchmarkFioRead runs read operation test cases.
    85  func BenchmarkFioRead(b *testing.B) {
    86  	testCases := []tools.Fio{
    87  		{
    88  			Test:        "read",
    89  			IOEngine:    tools.EngineLibAIO,
    90  			BlockSizeKB: 4,
    91  			IODepth:     4,
    92  		},
    93  		{
    94  			Test:        "read",
    95  			IOEngine:    tools.EngineLibAIO,
    96  			BlockSizeKB: 64,
    97  			IODepth:     4,
    98  		},
    99  		{
   100  			Test:        "read",
   101  			IOEngine:    tools.EngineLibAIO,
   102  			BlockSizeKB: 1024,
   103  			IODepth:     4,
   104  		},
   105  		{
   106  			Test:        "read",
   107  			IOEngine:    tools.EngineLibAIO,
   108  			Jobs:        8,
   109  			BlockSizeKB: 4,
   110  			IODepth:     4,
   111  			Direct:      true,
   112  		},
   113  		{
   114  			Test:        "read",
   115  			IOEngine:    tools.EngineLibAIO,
   116  			Jobs:        8,
   117  			BlockSizeKB: 64,
   118  			IODepth:     4,
   119  			Direct:      true,
   120  		},
   121  		{
   122  			Test:        "read",
   123  			IOEngine:    tools.EngineLibAIO,
   124  			Jobs:        8,
   125  			BlockSizeKB: 1024,
   126  			IODepth:     4,
   127  			Direct:      true,
   128  		},
   129  	}
   130  	doFioBenchmark(b, testCases)
   131  }
   132  
   133  // BenchmarkFioRandWrite runs randwrite test cases.
   134  func BenchmarkFioRandWrite(b *testing.B) {
   135  	testCases := []tools.Fio{
   136  		{
   137  			Test:        "randwrite",
   138  			IOEngine:    tools.EngineLibAIO,
   139  			BlockSizeKB: 4,
   140  			IODepth:     4,
   141  		},
   142  		{
   143  			Test:        "randwrite",
   144  			IOEngine:    tools.EngineLibAIO,
   145  			Jobs:        8,
   146  			BlockSizeKB: 4,
   147  			IODepth:     4,
   148  			Direct:      true,
   149  		},
   150  	}
   151  	doFioBenchmark(b, testCases)
   152  }
   153  
   154  // BenchmarkFioRandRead runs randread test cases.
   155  func BenchmarkFioRandRead(b *testing.B) {
   156  	testCases := []tools.Fio{
   157  		{
   158  			Test:        "randread",
   159  			IOEngine:    tools.EngineLibAIO,
   160  			BlockSizeKB: 4,
   161  			IODepth:     4,
   162  		},
   163  		{
   164  			Test:        "randread",
   165  			IOEngine:    tools.EngineLibAIO,
   166  			Jobs:        8,
   167  			BlockSizeKB: 4,
   168  			IODepth:     4,
   169  			Direct:      true,
   170  		},
   171  	}
   172  	doFioBenchmark(b, testCases)
   173  }
   174  
   175  func doFioBenchmark(b *testing.B, testCases []tools.Fio) {
   176  	machine, err := harness.GetMachine()
   177  	if err != nil {
   178  		b.Fatalf("failed to get machine with: %v", err)
   179  	}
   180  	defer machine.CleanUp()
   181  
   182  	for _, fsType := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS} {
   183  		for _, tc := range testCases {
   184  			filesystem := tools.Parameter{
   185  				Name:  "filesystem",
   186  				Value: string(fsType),
   187  			}
   188  			_, name := tc.Parameters(b, filesystem)
   189  			b.Run(name, func(b *testing.B) {
   190  				b.StopTimer()
   191  				tc.SizeMB = b.N
   192  
   193  				ctx := context.Background()
   194  				container := machine.GetContainer(ctx, b)
   195  				cu := cleanup.Make(func() {
   196  					metricsviz.FromContainerLogs(ctx, b, container)
   197  					container.CleanUp(ctx)
   198  				})
   199  				defer cu.Clean()
   200  
   201  				mnts, outdir, err := harness.MakeMount(machine, fsType, &cu)
   202  				if err != nil {
   203  					b.Fatalf("failed to make mount: %v", err)
   204  				}
   205  
   206  				runOpts := dockerutil.RunOpts{
   207  					Image:  "benchmarks/fio",
   208  					Mounts: mnts,
   209  				}
   210  				// Start the container with the mount.
   211  				if err := container.Spawn(
   212  					ctx, runOpts,
   213  					// Sleep on the order of b.N.
   214  					"sleep", fmt.Sprintf("%d", 1000*b.N),
   215  				); err != nil {
   216  					b.Fatalf("failed to start fio container with: %v", err)
   217  				}
   218  
   219  				if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
   220  					"mkdir", "-p", outdir); err != nil {
   221  					b.Fatalf("failed to copy directory: %v (%s)", err, out)
   222  				}
   223  
   224  				if fsType == harness.FuseFS {
   225  					container.CopyFiles(&runOpts, "/fusebin", "test/runner/fuse/fuse")
   226  					_, err := container.ExecProcess(ctx, dockerutil.ExecOpts{
   227  						Privileged: true,
   228  					}, "/fusebin/fuse", "--dir="+outdir, "--debug=false")
   229  					if err != nil {
   230  						b.Fatalf("starting fuse server failed with: %v", err)
   231  					}
   232  				}
   233  
   234  				// Directory and filename inside container where fio will read/write.
   235  				outfile := filepath.Join(outdir, "test.txt")
   236  
   237  				// For reads, we need a file to read so make one inside the container.
   238  				if strings.Contains(tc.Test, "read") {
   239  					fallocateCmd := fmt.Sprintf("fallocate -l %dM %s", tc.SizeMB, outfile)
   240  					if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
   241  						strings.Split(fallocateCmd, " ")...); err != nil {
   242  						b.Fatalf("failed to create readable file on mount: %v, %s", err, out)
   243  					}
   244  				}
   245  
   246  				// Drop caches just before running.
   247  				if err := harness.DropCaches(machine); err != nil {
   248  					b.Skipf("failed to drop caches with %v. You probably need root.", err)
   249  				}
   250  
   251  				cmd := tc.MakeCmd(outfile)
   252  				if err := harness.DropCaches(machine); err != nil {
   253  					b.Fatalf("failed to drop caches: %v", err)
   254  				}
   255  
   256  				// Run fio.
   257  				b.StartTimer()
   258  				data, err := container.Exec(ctx, dockerutil.ExecOpts{}, cmd...)
   259  				if err != nil {
   260  					b.Fatalf("failed to run cmd %v: %v", cmd, err)
   261  				}
   262  				b.StopTimer()
   263  				tc.Report(b, data)
   264  			})
   265  		}
   266  	}
   267  }
   268  
   269  // TestMain is the main method for package fs.
   270  func TestMain(m *testing.M) {
   271  	harness.Init()
   272  	os.Exit(m.Run())
   273  }