Skip to content

Commit

Permalink
add io-related benchmark
Browse files Browse the repository at this point in the history
Signed-off-by: Naoki MATSUMOTO <[email protected]>
  • Loading branch information
naoki9911 committed Apr 18, 2024
1 parent db8eaf7 commit b30aab1
Show file tree
Hide file tree
Showing 16 changed files with 465 additions and 23 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
- name: launch lxc container
run: ./tests/launch_test_lxc.sh test-export
- name: run benchmark (${{ matrix.script }})
run: sudo lxc exec test -- sudo --login --user ubuntu /bin/bash -c "sleep 3 && sudo /home/ubuntu/d4c/tests/bench_single.sh benchmark benchmark/images ${{ matrix.script }} ${{ matrix.thread }} ${{ matrix.sched }} ${{ matrix.comp }}"
run: sudo lxc exec test -- sudo --login --user ubuntu /bin/bash -c "sleep 3 && RUNNER=GHA sudo -E /home/ubuntu/d4c/tests/bench_single.sh benchmark benchmark/images ${{ matrix.script }} ${{ matrix.thread }} ${{ matrix.sched }} ${{ matrix.comp }}"

benchmark-cdimg:
runs-on: ubuntu-22.04
Expand Down Expand Up @@ -328,7 +328,7 @@ jobs:
- name: run test
run: sudo lxc exec test -- sudo --login --user ubuntu /bin/bash -c "sleep 3 && sudo /home/ubuntu/d4c/tests/test_bsdiffx.sh"
- name: run benchmark
run: sudo lxc exec test -- sudo --login --user ubuntu /bin/bash -c "sleep 3 && sudo /home/ubuntu/d4c/tests/bench.sh"
run: sudo lxc exec test -- sudo --login --user ubuntu /bin/bash -c "sleep 3 && RUNNER=GHA sudo -E /home/ubuntu/d4c/tests/bench.sh"
- name: get result
id: get_result
run: |
Expand All @@ -341,6 +341,8 @@ jobs:
sudo lxc file pull test/tmp/benchmark/patch.png /tmp/benchmark-results/.
sudo lxc file pull test/tmp/benchmark/file_diff.png /tmp/benchmark-results/.
sudo lxc file pull test/tmp/benchmark/file_compare.png /tmp/benchmark-results/.
sudo lxc file pull test/tmp/benchmark/file_io.png /tmp/benchmark-results/.
sudo lxc file pull test/tmp/benchmark/file_io_with_type.png /tmp/benchmark-results/.
- uses: actions/upload-artifact@v4
if: steps.get_result.conclusion == 'success'
with:
Expand Down
196 changes: 196 additions & 0 deletions cmd/ctr-cli/stat/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ package stat
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"

"github.com/naoki9911/fuse-diff-containerd/pkg/benchmark"
"github.com/naoki9911/fuse-diff-containerd/pkg/bsdiffx"
"github.com/naoki9911/fuse-diff-containerd/pkg/image"
"github.com/naoki9911/fuse-diff-containerd/pkg/utils"
"github.com/urfave/cli/v2"
Expand All @@ -14,6 +20,7 @@ func Command() *cli.Command {
Name: "stat",
Subcommands: []*cli.Command{
compareCommand(),
diffCommand(),
},
}

Expand Down Expand Up @@ -73,3 +80,192 @@ func compareAction(c *cli.Context) error {

return nil
}

func diffCommand() *cli.Command {
cmd := cli.Command{
Name: "diff",
Usage: "validate files or dirs",
ArgsUsage: "<fileA or dirA> <fileB or dirB>",
Action: diffAction,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "benchmark",
Usage: "enable benchmark",
Required: false,
},
&cli.StringFlag{
Name: "pathALabel",
Required: false,
Value: "pathA",
},
&cli.StringFlag{
Name: "pathBLabel",
Required: false,
Value: "pathB",
},
},
}

return &cmd
}

func diffAction(c *cli.Context) error {
if c.NArg() < 2 {
return fmt.Errorf("invalid argurment")
}

pathA := c.Args().Get(0)
pathB := c.Args().Get(1)

pm, err := bsdiffx.LoadOrDefaultPlugins("")
if err != nil {
return err
}

var b *benchmark.Benchmark = nil
if c.Bool("benchmark") {
b, err = benchmark.NewBenchmark("./benchmark-io.log")
if err != nil {
return err
}
defer b.Close()
b.SetDefaultLabels(utils.ParseLabels(c.StringSlice("labels")))
fmt.Println("benchmarker enabled")
}

defer fmt.Println("done")
return diffImpl(pathA, pathB, pm, b, c.String("pathALabel"), c.String("pathBLabel"), pathA, pathB)
}

func doBoth[T, U any](pathA, pathB T, f func(path T) (U, error)) (U, U, error) {
rA, err := f(pathA)
if err != nil {
return rA, rA, fmt.Errorf("on %v: %v", pathA, err)
}
rB, err := f(pathB)
if err != nil {
return rB, rB, fmt.Errorf("on %v: %v", pathB, err)
}

return rA, rB, nil
}

func doBothNoError[T, U any](pathA, pathB T, f func(path T) U) (U, U) {
rA := f(pathA)
rB := f(pathB)

return rA, rB
}

func openAndReadAllFile(path string, b *benchmark.Benchmark, pathLabel, rootPath string) ([]byte, error) {
start := time.Now()
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
openEnd := time.Now()
bytes, err := io.ReadAll(f)
if err != nil {
return nil, err
}
readEnd := time.Now()

if b != nil {
stat, err := f.Stat()
if err != nil {
return nil, err
}
m := benchmark.Metric{
TaskName: "open",
ElapsedMicro: openEnd.Sub(start).Microseconds(),
Size: stat.Size(),
Labels: map[string]string{
"path": path,
"pathLabel": pathLabel,
"root": rootPath,
},
}
err = b.AppendResult(m)
if err != nil {
return nil, err
}

m.TaskName = "read"
m.ElapsedMicro = readEnd.Sub(openEnd).Microseconds()
err = b.AppendResult(m)
if err != nil {
return nil, err
}

m.TaskName = "open+read"
m.ElapsedMicro = readEnd.Sub(start).Microseconds()
err = b.AppendResult(m)
if err != nil {
return nil, err
}
}

return bytes, nil
}

func diffImpl(pathA, pathB string, pm *bsdiffx.PluginManager, b *benchmark.Benchmark, pathALabel, pathBLabel, pathARoot, pathBRoot string) error {
statA, statB, err := doBoth(pathA, pathB, os.Lstat)
if err != nil {
return fmt.Errorf("failed to stats: %v", err)
}

typeA, typeB := doBothNoError(statA, statB, func(s os.FileInfo) os.FileMode { return s.Mode().Type() })
if typeA != typeB {
return fmt.Errorf("unmatched type %s is %s but %s is %s", pathA, typeA, pathB, typeB)
}

if typeA == os.ModeSymlink {
linkA, linkB, err := doBoth(pathA, pathB, os.Readlink)
if err != nil {
return fmt.Errorf("failed to readlink: %v", err)
}
if linkA != linkB {
return fmt.Errorf("unmatched link %s is %s but %s is %s", pathA, linkA, pathB, linkB)
}

return nil
}

if typeA.IsRegular() {
fileA, err := openAndReadAllFile(pathA, b, pathALabel, pathARoot)
if err != nil {
return fmt.Errorf("failed to open and readall %s: %v", pathA, err)
}

fileB, err := openAndReadAllFile(pathB, b, pathBLabel, pathBRoot)
if err != nil {
return fmt.Errorf("failed to open and readall %s: %v", pathB, err)
}

p := pm.GetPluginByExt(filepath.Ext(pathA))
if !p.Compare(fileA, fileB) {
return fmt.Errorf("unmatched content %s and %s", fileA, fileB)
}

return nil
}

if typeA.IsDir() {
childs, err := os.ReadDir(pathA)
if err != nil {
return fmt.Errorf("failed to readdir %s: %v", pathA, err)
}
for _, c := range childs {
err := diffImpl(filepath.Join(pathA, c.Name()), filepath.Join(pathB, c.Name()), pm, b, pathALabel, pathBLabel, pathARoot, pathBRoot)
if err != nil {
return err
}
}

return nil
}

// ignore others
return nil
}
5 changes: 5 additions & 0 deletions cmd/plugins/basic/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"bytes"
"io"

"github.com/naoki9911/fuse-diff-containerd/pkg/bsdiffx"
Expand All @@ -22,5 +23,9 @@ func Merge(lowerDiff, upperDiff io.Reader, mergedDiff io.Writer) error {
return bsdiffx.DeltaMergingBytes(lowerDiff, upperDiff, mergedDiff)
}

func Compare(a, b []byte) bool {
return bytes.Equal(a, b)
}

func init() {
}
1 change: 1 addition & 0 deletions pkg/benchmark/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Metric struct {
TaskName string `json:"taskName"`
Timestamp time.Time `json:"timestamp"`
ElapsedMilli int `json:"elapsedMilliseconds"`
ElapsedMicro int64 `json:"elapsedMicroseconds"`
Size int64 `json:"size"`
Labels map[string]string `json:"labels"`
}
Expand Down
31 changes: 24 additions & 7 deletions pkg/bsdiffx/plugin.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bsdiffx

import (
"bytes"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -83,24 +84,30 @@ func (pm *PluginManager) GetPluginByExt(ext string) *Plugin {
}

type Plugin struct {
p *plugin.Plugin
info func() string
diff func(oldBytes, newBytes []byte, patchWriter io.Writer, mode CompressionMode) error
patch func(oldBytes []byte, patchReader io.Reader) ([]byte, error)
merge func(lowerDiff, upperDiff io.Reader, mergedDiff io.Writer) error
p *plugin.Plugin
info func() string
diff func(oldBytes, newBytes []byte, patchWriter io.Writer, mode CompressionMode) error
patch func(oldBytes []byte, patchReader io.Reader) ([]byte, error)
merge func(lowerDiff, upperDiff io.Reader, mergedDiff io.Writer) error
compare func(a, b []byte) bool
}

func DefaultPluginInfo() string {
func defaultPluginInfo() string {
return "Default plugin with bsdiffx"
}

func defaultCompare(a, b []byte) bool {
return bytes.Equal(a, b)
}

func DefaultPluigin() *Plugin {
p := &Plugin{}

p.info = DefaultPluginInfo
p.info = defaultPluginInfo
p.diff = Diff
p.patch = Patch
p.merge = DeltaMergingBytes
p.compare = defaultCompare

return p
}
Expand Down Expand Up @@ -140,6 +147,12 @@ func OpenPlugin(path string) (*Plugin, error) {
}
plugin.merge = sMerge.(func(lowerDiff, upperDiff io.Reader, mergedDiff io.Writer) error)

sCompare, err := p.Lookup("Compare")
if err != nil {
return nil, err
}
plugin.compare = sCompare.(func(a, b []byte) bool)

return plugin, nil
}

Expand All @@ -158,3 +171,7 @@ func (p *Plugin) Patch(oldBytes []byte, patchReader io.Reader) ([]byte, error) {
func (p *Plugin) Merge(lowerDiff, upperDiff io.Reader, mergedDiff io.Writer) error {
return p.merge(lowerDiff, upperDiff, mergedDiff)
}

func (p *Plugin) Compare(a, b []byte) bool {
return p.compare(a, b)
}
6 changes: 5 additions & 1 deletion pkg/image/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
type FileEntryCompareResult struct {
Path string `json:"path"`
FileSize int `json:"fileSize"`
FileEntryAType EntryType `json:"fileEntryAType"`
FileEntryACompressionSize int `json:"fileEntryACompressionSize"`
FileEntryBType EntryType `json:"fileEntryBType"`
FileEntryBCompressionSize int `json:"fileEntryBCompressionSize"`
Labels map[string]string `json:"labels"`
}
Expand All @@ -34,11 +36,13 @@ func CompareFileEntries(feA, feB *FileEntry, pathPrefix string) ([]FileEntryComp
return nil, fmt.Errorf("invalid FileEntry. %s has both Childs and Body", path)
}

if len(feA.Childs) == 0 && feA.HasBody() {
if len(feA.Childs) == 0 {
fecr := FileEntryCompareResult{
Path: filepath.Join(pathPrefix, feA.Name),
FileSize: feA.Size,
FileEntryAType: feA.Type,
FileEntryACompressionSize: int(feA.CompressedSize),
FileEntryBType: feB.Type,
FileEntryBCompressionSize: int(feB.CompressedSize),
}
return []FileEntryCompareResult{fecr}, nil
Expand Down
11 changes: 8 additions & 3 deletions tests/bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@ mkdir -p /tmp/benchmark

#THREADS=("1" "8")
TESTS=("apache" "mysql" "nginx" "postgres" "redis")
SCHED_MODES=("none" "size-ordered")
COMP_MODES=("bzip2" "zstd")
#SCHED_MODES=("none" "size-ordered")
SCHED_MODES=("size-ordered")
#COMP_MODES=("bzip2" "zstd")
COMP_MODES=("bzip2")
for TEST in "${TESTS[@]}"; do
for COMP_MODE in "${COMP_MODES[@]}"; do
./bench_single.sh $RESULT_DIR $IMAGE_DIR $TEST 1 none $COMP_MODE
./bench_single.sh $RESULT_DIR $IMAGE_DIR $TEST 1 size-ordered $COMP_MODE
done
for SCHED_MODE in "${SCHED_MODES[@]}"; do
for COMP_MODE in "${COMP_MODES[@]}"; do
./bench_single.sh $RESULT_DIR $IMAGE_DIR $TEST 8 $SCHED_MODE $COMP_MODE
done
done
cat $RESULT_DIR/$TEST-benchmark.log >> /tmp/benchmark/benchmark.log
cat $RESULT_DIR/$TEST-benchmark-io.log >> /tmp/benchmark/benchmark-io.log
cat $RESULT_DIR/$TEST-compare.log >> /tmp/benchmark/compare.log
done

Expand All @@ -42,3 +45,5 @@ python3 ./plot_merge.py /tmp/benchmark/benchmark.log /tmp/benchmark/merge.png
python3 ./plot_patch.py /tmp/benchmark/benchmark.log /tmp/benchmark/patch.png
python3 ./plot_file_diff.py /tmp/benchmark/benchmark.log /tmp/benchmark/file_diff.png
python3 ./plot_file_compare.py /tmp/benchmark/compare.log /tmp/benchmark/file_compare.png
python3 ./plot_file_io.py /tmp/benchmark/benchmark-io.log /tmp/benchmark/file_io.png
python3 ./plot_file_io_with_type.py /tmp/benchmark/compare.log /tmp/benchmark/benchmark-io.log /tmp/benchmark/file_io_with_type.png
Loading

0 comments on commit b30aab1

Please sign in to comment.