Move snapshotters benchmark to a separate package
Signed-off-by: Maksym Pavlenko <makpav@amazon.com>
This commit is contained in:
@@ -1,312 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/containerd/containerd/snapshots/devmapper"
|
||||
"github.com/containerd/containerd/snapshots/native"
|
||||
"github.com/containerd/containerd/snapshots/overlay"
|
||||
)
|
||||
|
||||
var (
|
||||
dmPoolDev string
|
||||
dmRootPath string
|
||||
overlayRootPath string
|
||||
nativeRootPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&dmPoolDev, "dm.thinPoolDev", "", "Pool device to run benchmark on")
|
||||
flag.StringVar(&dmRootPath, "dm.rootPath", "", "Root dir for devmapper snapshotter")
|
||||
flag.StringVar(&overlayRootPath, "overlay.rootPath", "", "Root dir for overlay snapshotter")
|
||||
flag.StringVar(&nativeRootPath, "native.rootPath", "", "Root dir for native snapshotter")
|
||||
|
||||
// Avoid mixing benchmark output and INFO messages
|
||||
logrus.SetLevel(logrus.ErrorLevel)
|
||||
}
|
||||
|
||||
func BenchmarkNative(b *testing.B) {
|
||||
if nativeRootPath == "" {
|
||||
b.Skip("native root dir must be provided")
|
||||
}
|
||||
|
||||
snapshotter, err := native.NewSnapshotter(nativeRootPath)
|
||||
assert.NilError(b, err)
|
||||
|
||||
defer func() {
|
||||
err = snapshotter.Close()
|
||||
assert.NilError(b, err)
|
||||
|
||||
err = os.RemoveAll(nativeRootPath)
|
||||
assert.NilError(b, err)
|
||||
}()
|
||||
|
||||
benchmarkSnapshotter(b, snapshotter)
|
||||
}
|
||||
|
||||
func BenchmarkOverlay(b *testing.B) {
|
||||
if overlayRootPath == "" {
|
||||
b.Skip("overlay root dir must be provided")
|
||||
}
|
||||
|
||||
snapshotter, err := overlay.NewSnapshotter(overlayRootPath)
|
||||
assert.NilError(b, err, "failed to create overlay snapshotter")
|
||||
|
||||
defer func() {
|
||||
err = snapshotter.Close()
|
||||
assert.NilError(b, err)
|
||||
|
||||
err = os.RemoveAll(overlayRootPath)
|
||||
assert.NilError(b, err)
|
||||
}()
|
||||
|
||||
benchmarkSnapshotter(b, snapshotter)
|
||||
}
|
||||
|
||||
func BenchmarkDeviceMapper(b *testing.B) {
|
||||
if dmPoolDev == "" {
|
||||
b.Skip("devmapper benchmark requires thin-pool device to be prepared in advance and provided")
|
||||
}
|
||||
|
||||
if dmRootPath == "" {
|
||||
b.Skip("devmapper snapshotter root dir must be provided")
|
||||
}
|
||||
|
||||
config := &devmapper.Config{
|
||||
PoolName: dmPoolDev,
|
||||
RootPath: dmRootPath,
|
||||
BaseImageSize: "16Mb",
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
snapshotter, err := devmapper.NewSnapshotter(ctx, config)
|
||||
assert.NilError(b, err)
|
||||
|
||||
defer func() {
|
||||
err := snapshotter.ResetPool(ctx)
|
||||
assert.NilError(b, err)
|
||||
|
||||
err = snapshotter.Close()
|
||||
assert.NilError(b, err)
|
||||
|
||||
err = os.RemoveAll(dmRootPath)
|
||||
assert.NilError(b, err)
|
||||
}()
|
||||
|
||||
benchmarkSnapshotter(b, snapshotter)
|
||||
}
|
||||
|
||||
// benchmarkSnapshotter tests snapshotter performance.
|
||||
// It writes 16 layers with randomly created, modified, or removed files.
|
||||
// Depending on layer index different sets of files are modified.
|
||||
// In addition to total snapshotter execution time, benchmark outputs a few additional
|
||||
// details - time taken to Prepare layer, mount, write data and unmount time,
|
||||
// and Commit snapshot time.
|
||||
func benchmarkSnapshotter(b *testing.B, snapshotter snapshots.Snapshotter) {
|
||||
const (
|
||||
layerCount = 16
|
||||
fileSizeBytes = int64(1 * 1024 * 1024) // 1 MB
|
||||
)
|
||||
|
||||
var (
|
||||
total = 0
|
||||
layers = make([]fstest.Applier, 0, layerCount)
|
||||
layerIndex = int64(0)
|
||||
)
|
||||
|
||||
for i := 1; i <= layerCount; i++ {
|
||||
appliers := makeApplier(i, fileSizeBytes)
|
||||
layers = append(layers, fstest.Apply(appliers...))
|
||||
total += len(appliers)
|
||||
}
|
||||
|
||||
var (
|
||||
benchN int
|
||||
prepareDuration time.Duration
|
||||
writeDuration time.Duration
|
||||
commitDuration time.Duration
|
||||
)
|
||||
|
||||
// Wrap test with Run so additional details output will be added right below the benchmark result
|
||||
b.Run("run", func(b *testing.B) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
parent string
|
||||
current string
|
||||
)
|
||||
|
||||
// Reset durations since test might be ran multiple times
|
||||
prepareDuration = 0
|
||||
writeDuration = 0
|
||||
commitDuration = 0
|
||||
benchN = b.N
|
||||
|
||||
b.SetBytes(int64(total) * fileSizeBytes)
|
||||
|
||||
var timer time.Time
|
||||
for i := 0; i < b.N; i++ {
|
||||
for l := 0; l < layerCount; l++ {
|
||||
current = fmt.Sprintf("prepare-layer-%d", atomic.AddInt64(&layerIndex, 1))
|
||||
|
||||
timer = time.Now()
|
||||
mounts, err := snapshotter.Prepare(ctx, current, parent)
|
||||
assert.NilError(b, err)
|
||||
prepareDuration += time.Since(timer)
|
||||
|
||||
timer = time.Now()
|
||||
err = mount.WithTempMount(ctx, mounts, layers[l].Apply)
|
||||
assert.NilError(b, err)
|
||||
writeDuration += time.Since(timer)
|
||||
|
||||
parent = fmt.Sprintf("comitted-%d", atomic.AddInt64(&layerIndex, 1))
|
||||
|
||||
timer = time.Now()
|
||||
err = snapshotter.Commit(ctx, parent, current)
|
||||
assert.NilError(b, err)
|
||||
commitDuration += time.Since(timer)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Output extra measurements - total time taken to Prepare, mount and write data, and Commit
|
||||
const outputFormat = "%-25s\t%s\n"
|
||||
fmt.Fprintf(os.Stdout,
|
||||
outputFormat,
|
||||
b.Name()+"/prepare",
|
||||
testing.BenchmarkResult{N: benchN, T: prepareDuration})
|
||||
|
||||
fmt.Fprintf(os.Stdout,
|
||||
outputFormat,
|
||||
b.Name()+"/write",
|
||||
testing.BenchmarkResult{N: benchN, T: writeDuration})
|
||||
|
||||
fmt.Fprintf(os.Stdout,
|
||||
outputFormat,
|
||||
b.Name()+"/commit",
|
||||
testing.BenchmarkResult{N: benchN, T: commitDuration})
|
||||
|
||||
fmt.Fprintln(os.Stdout)
|
||||
}
|
||||
|
||||
// makeApplier returns a slice of fstest.Applier where files are written randomly.
|
||||
// Depending on layer index, the returned layers will overwrite some files with the
|
||||
// same generated names with new contents or deletions.
|
||||
func makeApplier(layerIndex int, fileSizeBytes int64) []fstest.Applier {
|
||||
seed := time.Now().UnixNano()
|
||||
|
||||
switch {
|
||||
case layerIndex%3 == 0:
|
||||
return []fstest.Applier{
|
||||
updateFile("/a"),
|
||||
updateFile("/b"),
|
||||
fstest.CreateRandomFile("/c", seed, fileSizeBytes, 0777),
|
||||
updateFile("/d"),
|
||||
fstest.CreateRandomFile("/f", seed, fileSizeBytes, 0777),
|
||||
updateFile("/e"),
|
||||
fstest.RemoveAll("/g"),
|
||||
fstest.CreateRandomFile("/h", seed, fileSizeBytes, 0777),
|
||||
updateFile("/i"),
|
||||
fstest.CreateRandomFile("/j", seed, fileSizeBytes, 0777),
|
||||
}
|
||||
case layerIndex%2 == 0:
|
||||
return []fstest.Applier{
|
||||
updateFile("/a"),
|
||||
fstest.CreateRandomFile("/b", seed, fileSizeBytes, 0777),
|
||||
fstest.RemoveAll("/c"),
|
||||
fstest.CreateRandomFile("/d", seed, fileSizeBytes, 0777),
|
||||
updateFile("/e"),
|
||||
fstest.RemoveAll("/f"),
|
||||
fstest.CreateRandomFile("/g", seed, fileSizeBytes, 0777),
|
||||
updateFile("/h"),
|
||||
fstest.CreateRandomFile("/i", seed, fileSizeBytes, 0777),
|
||||
updateFile("/j"),
|
||||
}
|
||||
default:
|
||||
return []fstest.Applier{
|
||||
fstest.CreateRandomFile("/a", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/b", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/c", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/d", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/e", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/f", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/g", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/h", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/i", seed, fileSizeBytes, 0777),
|
||||
fstest.CreateRandomFile("/j", seed, fileSizeBytes, 0777),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applierFn represents helper func that implements fstest.Applier
|
||||
type applierFn func(root string) error
|
||||
|
||||
func (fn applierFn) Apply(root string) error {
|
||||
return fn(root)
|
||||
}
|
||||
|
||||
// updateFile modifies a few bytes in the middle in order to demonstrate the difference in performance
|
||||
// for block-based snapshotters (like devicemapper) against file-based snapshotters (like overlay, which need to
|
||||
// perform a copy-up of the full file any time a single bit is modified).
|
||||
func updateFile(name string) applierFn {
|
||||
return func(root string) error {
|
||||
path := filepath.Join(root, name)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open %q", path)
|
||||
}
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
offset = info.Size() / 2
|
||||
buf = make([]byte, 4)
|
||||
)
|
||||
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := file.WriteAt(buf, offset); err != nil {
|
||||
return errors.Wrapf(err, "failed to write %q at offset %d", path, offset)
|
||||
}
|
||||
|
||||
return file.Close()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user