github.com/demonoid81/containerd@v1.3.4/snapshots/benchsuite/benchmark_test.go (about) 1 // +build linux 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package benchsuite 20 21 import ( 22 "context" 23 "crypto/rand" 24 "flag" 25 "fmt" 26 "os" 27 "path/filepath" 28 "sync/atomic" 29 "testing" 30 "time" 31 32 "github.com/containerd/continuity/fs/fstest" 33 "github.com/pkg/errors" 34 "github.com/sirupsen/logrus" 35 "gotest.tools/assert" 36 37 "github.com/containerd/containerd/mount" 38 "github.com/containerd/containerd/snapshots" 39 "github.com/containerd/containerd/snapshots/devmapper" 40 "github.com/containerd/containerd/snapshots/native" 41 "github.com/containerd/containerd/snapshots/overlay" 42 ) 43 44 var ( 45 dmPoolDev string 46 dmRootPath string 47 overlayRootPath string 48 nativeRootPath string 49 ) 50 51 func init() { 52 flag.StringVar(&dmPoolDev, "dm.thinPoolDev", "", "Pool device to run benchmark on") 53 flag.StringVar(&dmRootPath, "dm.rootPath", "", "Root dir for devmapper snapshotter") 54 flag.StringVar(&overlayRootPath, "overlay.rootPath", "", "Root dir for overlay snapshotter") 55 flag.StringVar(&nativeRootPath, "native.rootPath", "", "Root dir for native snapshotter") 56 57 // Avoid mixing benchmark output and INFO messages 58 logrus.SetLevel(logrus.ErrorLevel) 59 } 60 61 func BenchmarkNative(b *testing.B) { 62 if nativeRootPath == "" { 63 b.Skip("native root dir must be provided") 64 } 65 66 snapshotter, err := native.NewSnapshotter(nativeRootPath) 67 assert.NilError(b, err) 68 69 defer func() { 70 err = snapshotter.Close() 71 assert.NilError(b, err) 72 73 err = os.RemoveAll(nativeRootPath) 74 assert.NilError(b, err) 75 }() 76 77 benchmarkSnapshotter(b, snapshotter) 78 } 79 80 func BenchmarkOverlay(b *testing.B) { 81 if overlayRootPath == "" { 82 b.Skip("overlay root dir must be provided") 83 } 84 85 snapshotter, err := overlay.NewSnapshotter(overlayRootPath) 86 assert.NilError(b, err, "failed to create overlay snapshotter") 87 88 defer func() { 89 err = snapshotter.Close() 90 assert.NilError(b, err) 91 92 err = os.RemoveAll(overlayRootPath) 93 assert.NilError(b, err) 94 }() 95 96 benchmarkSnapshotter(b, snapshotter) 97 } 98 99 func BenchmarkDeviceMapper(b *testing.B) { 100 if dmPoolDev == "" { 101 b.Skip("devmapper benchmark requires thin-pool device to be prepared in advance and provided") 102 } 103 104 if dmRootPath == "" { 105 b.Skip("devmapper snapshotter root dir must be provided") 106 } 107 108 config := &devmapper.Config{ 109 PoolName: dmPoolDev, 110 RootPath: dmRootPath, 111 BaseImageSize: "16Mb", 112 } 113 114 ctx := context.Background() 115 116 snapshotter, err := devmapper.NewSnapshotter(ctx, config) 117 assert.NilError(b, err) 118 119 defer func() { 120 err := snapshotter.ResetPool(ctx) 121 assert.NilError(b, err) 122 123 err = snapshotter.Close() 124 assert.NilError(b, err) 125 126 err = os.RemoveAll(dmRootPath) 127 assert.NilError(b, err) 128 }() 129 130 benchmarkSnapshotter(b, snapshotter) 131 } 132 133 // benchmarkSnapshotter tests snapshotter performance. 134 // It writes 16 layers with randomly created, modified, or removed files. 135 // Depending on layer index different sets of files are modified. 136 // In addition to total snapshotter execution time, benchmark outputs a few additional 137 // details - time taken to Prepare layer, mount, write data and unmount time, 138 // and Commit snapshot time. 139 func benchmarkSnapshotter(b *testing.B, snapshotter snapshots.Snapshotter) { 140 const ( 141 layerCount = 16 142 fileSizeBytes = int64(1 * 1024 * 1024) // 1 MB 143 ) 144 145 var ( 146 total = 0 147 layers = make([]fstest.Applier, 0, layerCount) 148 layerIndex = int64(0) 149 ) 150 151 for i := 1; i <= layerCount; i++ { 152 appliers := makeApplier(i, fileSizeBytes) 153 layers = append(layers, fstest.Apply(appliers...)) 154 total += len(appliers) 155 } 156 157 var ( 158 benchN int 159 prepareDuration time.Duration 160 writeDuration time.Duration 161 commitDuration time.Duration 162 ) 163 164 // Wrap test with Run so additional details output will be added right below the benchmark result 165 b.Run("run", func(b *testing.B) { 166 var ( 167 ctx = context.Background() 168 parent string 169 current string 170 ) 171 172 // Reset durations since test might be ran multiple times 173 prepareDuration = 0 174 writeDuration = 0 175 commitDuration = 0 176 benchN = b.N 177 178 b.SetBytes(int64(total) * fileSizeBytes) 179 180 var timer time.Time 181 for i := 0; i < b.N; i++ { 182 for l := 0; l < layerCount; l++ { 183 current = fmt.Sprintf("prepare-layer-%d", atomic.AddInt64(&layerIndex, 1)) 184 185 timer = time.Now() 186 mounts, err := snapshotter.Prepare(ctx, current, parent) 187 assert.NilError(b, err) 188 prepareDuration += time.Since(timer) 189 190 timer = time.Now() 191 err = mount.WithTempMount(ctx, mounts, layers[l].Apply) 192 assert.NilError(b, err) 193 writeDuration += time.Since(timer) 194 195 parent = fmt.Sprintf("committed-%d", atomic.AddInt64(&layerIndex, 1)) 196 197 timer = time.Now() 198 err = snapshotter.Commit(ctx, parent, current) 199 assert.NilError(b, err) 200 commitDuration += time.Since(timer) 201 } 202 } 203 }) 204 205 // Output extra measurements - total time taken to Prepare, mount and write data, and Commit 206 const outputFormat = "%-25s\t%s\n" 207 fmt.Fprintf(os.Stdout, 208 outputFormat, 209 b.Name()+"/prepare", 210 testing.BenchmarkResult{N: benchN, T: prepareDuration}) 211 212 fmt.Fprintf(os.Stdout, 213 outputFormat, 214 b.Name()+"/write", 215 testing.BenchmarkResult{N: benchN, T: writeDuration}) 216 217 fmt.Fprintf(os.Stdout, 218 outputFormat, 219 b.Name()+"/commit", 220 testing.BenchmarkResult{N: benchN, T: commitDuration}) 221 222 fmt.Fprintln(os.Stdout) 223 } 224 225 // makeApplier returns a slice of fstest.Applier where files are written randomly. 226 // Depending on layer index, the returned layers will overwrite some files with the 227 // same generated names with new contents or deletions. 228 func makeApplier(layerIndex int, fileSizeBytes int64) []fstest.Applier { 229 seed := time.Now().UnixNano() 230 231 switch { 232 case layerIndex%3 == 0: 233 return []fstest.Applier{ 234 updateFile("/a"), 235 updateFile("/b"), 236 fstest.CreateRandomFile("/c", seed, fileSizeBytes, 0777), 237 updateFile("/d"), 238 fstest.CreateRandomFile("/f", seed, fileSizeBytes, 0777), 239 updateFile("/e"), 240 fstest.RemoveAll("/g"), 241 fstest.CreateRandomFile("/h", seed, fileSizeBytes, 0777), 242 updateFile("/i"), 243 fstest.CreateRandomFile("/j", seed, fileSizeBytes, 0777), 244 } 245 case layerIndex%2 == 0: 246 return []fstest.Applier{ 247 updateFile("/a"), 248 fstest.CreateRandomFile("/b", seed, fileSizeBytes, 0777), 249 fstest.RemoveAll("/c"), 250 fstest.CreateRandomFile("/d", seed, fileSizeBytes, 0777), 251 updateFile("/e"), 252 fstest.RemoveAll("/f"), 253 fstest.CreateRandomFile("/g", seed, fileSizeBytes, 0777), 254 updateFile("/h"), 255 fstest.CreateRandomFile("/i", seed, fileSizeBytes, 0777), 256 updateFile("/j"), 257 } 258 default: 259 return []fstest.Applier{ 260 fstest.CreateRandomFile("/a", seed, fileSizeBytes, 0777), 261 fstest.CreateRandomFile("/b", seed, fileSizeBytes, 0777), 262 fstest.CreateRandomFile("/c", seed, fileSizeBytes, 0777), 263 fstest.CreateRandomFile("/d", seed, fileSizeBytes, 0777), 264 fstest.CreateRandomFile("/e", seed, fileSizeBytes, 0777), 265 fstest.CreateRandomFile("/f", seed, fileSizeBytes, 0777), 266 fstest.CreateRandomFile("/g", seed, fileSizeBytes, 0777), 267 fstest.CreateRandomFile("/h", seed, fileSizeBytes, 0777), 268 fstest.CreateRandomFile("/i", seed, fileSizeBytes, 0777), 269 fstest.CreateRandomFile("/j", seed, fileSizeBytes, 0777), 270 } 271 } 272 } 273 274 // applierFn represents helper func that implements fstest.Applier 275 type applierFn func(root string) error 276 277 func (fn applierFn) Apply(root string) error { 278 return fn(root) 279 } 280 281 // updateFile modifies a few bytes in the middle in order to demonstrate the difference in performance 282 // for block-based snapshotters (like devicemapper) against file-based snapshotters (like overlay, which need to 283 // perform a copy-up of the full file any time a single bit is modified). 284 func updateFile(name string) applierFn { 285 return func(root string) error { 286 path := filepath.Join(root, name) 287 file, err := os.OpenFile(path, os.O_WRONLY, 0600) 288 if err != nil { 289 return errors.Wrapf(err, "failed to open %q", path) 290 } 291 292 info, err := file.Stat() 293 if err != nil { 294 return err 295 } 296 297 var ( 298 offset = info.Size() / 2 299 buf = make([]byte, 4) 300 ) 301 302 if _, err := rand.Read(buf); err != nil { 303 return err 304 } 305 306 if _, err := file.WriteAt(buf, offset); err != nil { 307 return errors.Wrapf(err, "failed to write %q at offset %d", path, offset) 308 } 309 310 return file.Close() 311 } 312 }