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 }