gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/benchmarks/fs/fsbench/fsbench.go (about) 1 // Copyright 2022 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 fsbench provides utility functions for filesystem benchmarks. 16 package fsbench 17 18 import ( 19 "context" 20 "fmt" 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 // FSBenchmark represents a set of work to perform within a container that is instrumented with 32 // different filesystem configurations. 33 type FSBenchmark struct { 34 // Image is the Docker image to load. 35 Image string 36 // WorkDir is where the action takes place. 37 // The commands below are run from a directory that has the same file as what the container image 38 // has at this directory. 39 WorkDir string 40 // RunCmd is the command to run to execute the benchmark. 41 RunCmd []string 42 // WantOutput, if set, is verified to be a substring of the output of RunCmd. 43 WantOutput string 44 // CleanCmd, if set, is run to clean up between benchmarks. 45 CleanCmd []string 46 // Variants is a list of benchmark variants to run. 47 // If unset, the typical set is used. 48 Variants []Variant 49 // Callback is an optional function that is called after each execution of 50 // the workload being benchmarked. It can be used to perform workload 51 // specific metric reporting. 52 Callback func(b *testing.B, output string) 53 } 54 55 // Variant is a specific configuration for a benchmark. 56 // Dimensions here are clean/dirty cache (do or don't drop caches) 57 // and if the mount on which we are compiling is a tmpfs/bind mount. 58 type Variant struct { 59 // clearCache drops caches before running. 60 clearCache bool 61 // fsType is the type of filesystem to use. 62 fsType harness.FileSystemType 63 } 64 65 // TypicalVariants returns the typical full set of benchmark variants. 66 func TypicalVariants() []Variant { 67 variants := make([]Variant, 0, 8) 68 for _, filesys := range []harness.FileSystemType{harness.BindFS, harness.TmpFS, harness.RootFS, harness.FuseFS} { 69 variants = append(variants, Variant{ 70 clearCache: true, 71 fsType: filesys, 72 }) 73 variants = append(variants, Variant{ 74 clearCache: false, 75 fsType: filesys, 76 }) 77 } 78 return variants 79 } 80 81 // RunWithDifferentFilesystems runs a 82 func RunWithDifferentFilesystems(ctx context.Context, b *testing.B, machine harness.Machine, bm FSBenchmark) { 83 b.Helper() 84 85 benchmarkVariants := bm.Variants 86 if len(benchmarkVariants) == 0 { 87 benchmarkVariants = TypicalVariants() 88 } 89 for _, variant := range benchmarkVariants { 90 pageCache := tools.Parameter{ 91 Name: "page_cache", 92 Value: "dirty", 93 } 94 if variant.clearCache { 95 pageCache.Value = "clean" 96 } 97 98 filesystem := tools.Parameter{ 99 Name: "filesystem", 100 Value: string(variant.fsType), 101 } 102 name, err := tools.ParametersToName(pageCache, filesystem) 103 if err != nil { 104 b.Fatalf("Failed to parse parameters: %v", err) 105 } 106 107 b.Run(name, func(b *testing.B) { 108 // Grab a container. 109 container := machine.GetContainer(ctx, b) 110 cu := cleanup.Make(func() { 111 container.CleanUp(ctx) 112 }) 113 defer cu.Clean() 114 mts, prefix, err := harness.MakeMount(machine, variant.fsType, &cu) 115 if err != nil { 116 b.Fatalf("Failed to make mount: %v", err) 117 } 118 119 runOpts := dockerutil.RunOpts{ 120 Image: bm.Image, 121 Mounts: mts, 122 } 123 124 // Start a container and sleep. 125 if err := container.Spawn(ctx, runOpts, "sleep", "24h"); err != nil { 126 b.Fatalf("run failed with: %v", err) 127 } 128 defer metricsviz.FromContainerLogs(ctx, b, container) 129 130 // Ignore safetext/shsprintf linter suggestion. 131 mkdirCmd := fmt.Sprintf("mkdir -p %s", prefix) 132 out, err := container.Exec(ctx, dockerutil.ExecOpts{}, "/bin/sh", "-c", mkdirCmd) 133 if err != nil { 134 b.Fatalf("failed to make directory: %v (%s)", err, out) 135 } 136 137 if variant.fsType == harness.FuseFS { 138 container.CopyFiles(&runOpts, "/fusebin", "test/runner/fuse/fuse") 139 _, err := container.ExecProcess(ctx, dockerutil.ExecOpts{ 140 Privileged: true, 141 }, "/fusebin/fuse", "--dir="+prefix, "--debug=false") 142 if err != nil { 143 b.Fatalf("starting fuse server failed with: %v", err) 144 } 145 } 146 147 cpCmd := fmt.Sprintf("cp -r %s %s/.", bm.WorkDir, prefix) 148 if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, 149 "/bin/sh", "-c", cpCmd); err != nil { 150 b.Fatalf("failed to copy directory: %v (%s)", err, out) 151 } 152 153 b.ResetTimer() 154 b.StopTimer() 155 156 // Drop Caches and bazel clean should happen inside the loop as we may use 157 // time options with b.N. (e.g. Run for an hour.) 158 for i := 0; i < b.N; i++ { 159 // Drop Caches for clear cache runs. 160 if variant.clearCache { 161 if err := harness.DropCaches(machine); err != nil { 162 b.Skipf("failed to drop caches: %v. You probably need root.", err) 163 } 164 } 165 166 b.StartTimer() 167 got, err := container.Exec(ctx, dockerutil.ExecOpts{ 168 WorkDir: prefix + bm.WorkDir, 169 }, bm.RunCmd...) 170 if err != nil { 171 b.Fatalf("Command %v failed with: %v logs: %s", bm.RunCmd, err, got) 172 } 173 b.StopTimer() 174 175 if bm.WantOutput != "" && !strings.Contains(got, bm.WantOutput) { 176 b.Fatalf("string %s not in: %s", bm.WantOutput, got) 177 } 178 179 if bm.Callback != nil { 180 bm.Callback(b, got) 181 } 182 183 // Clean the container in case we are doing another run. 184 if i < b.N-1 && len(bm.CleanCmd) != 0 { 185 if _, err = container.Exec(ctx, dockerutil.ExecOpts{ 186 WorkDir: prefix + bm.WorkDir, 187 }, bm.CleanCmd...); err != nil { 188 b.Fatalf("Cleanup command %v failed with: %v", bm.CleanCmd, err) 189 } 190 } 191 } 192 }) 193 } 194 }