Skip to content

Commit 9ffd6e7

Browse files
committed
Add metadata reader benchmarks
Add benchmarks functions that benchmark sequential and concurrent writes to the underlying metadata db. Signed-off-by: Yasin Turan <turyasin@amazon.com>
1 parent db7df3a commit 9ffd6e7

5 files changed

Lines changed: 467 additions & 188 deletions

File tree

metadata/metadata.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import (
4141
"github.com/awslabs/soci-snapshotter/ztoc/compression"
4242
)
4343

44-
// Attr reprensents the attributes of a node.
44+
// Attr represents the attributes of a node.
4545
type Attr struct {
4646
// Size, for regular files, is the logical size of the file.
4747
Size int64
@@ -102,7 +102,7 @@ type Options struct {
102102
Telemetry *Telemetry
103103
}
104104

105-
// Option is an option to configure the behaviour of reader.
105+
// Option is an option to configure the behavior of reader.
106106
type Option func(o *Options) error
107107

108108
// WithTelemetry option specifies the telemetry hooks
@@ -113,10 +113,10 @@ func WithTelemetry(telemetry *Telemetry) Option {
113113
}
114114
}
115115

116-
// A func which takes start time and records the diff
116+
// MeasureLatencyHook is a func which takes start time and records the diff
117117
type MeasureLatencyHook func(time.Time)
118118

119-
// A struct which defines telemetry hooks. By implementing these hooks you should be able to record
119+
// Telemetry defines telemetry hooks. By implementing these hooks you should be able to record
120120
// the latency metrics of the respective steps of SOCI open operation.
121121
type Telemetry struct {
122122
InitMetadataStoreLatency MeasureLatencyHook // measure time to initialize metadata store (in milliseconds)

metadata/reader_test.go

Lines changed: 254 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,47 +33,273 @@
3333
package metadata
3434

3535
import (
36-
"io"
36+
"compress/gzip"
37+
"fmt"
38+
_ "net/http/pprof"
3739
"os"
3840
"testing"
41+
"time"
3942

43+
"github.com/awslabs/soci-snapshotter/util/testutil"
4044
"github.com/awslabs/soci-snapshotter/ztoc"
41-
bolt "go.etcd.io/bbolt"
45+
"golang.org/x/sync/errgroup"
4246
)
4347

44-
func TestMetadataReader(t *testing.T) {
45-
testReader(t, newTestableReader)
48+
var allowedPrefix = [4]string{"", "./", "/", "../"}
49+
50+
var srcCompressions = map[string]int{
51+
"gzip-nocompression": gzip.NoCompression,
52+
"gzip-bestspeed": gzip.BestSpeed,
53+
"gzip-bestcompression": gzip.BestCompression,
54+
"gzip-defaultcompression": gzip.DefaultCompression,
55+
"gzip-huffmanonly": gzip.HuffmanOnly,
4656
}
4757

48-
func newTestableReader(sr *io.SectionReader, toc ztoc.TOC, opts ...Option) (testableReader, error) {
49-
f, err := os.CreateTemp("", "readertestdb")
50-
if err != nil {
51-
return nil, err
58+
func TestMetadataReader(t *testing.T) {
59+
sampleTime := time.Now().Truncate(time.Second)
60+
tests := []struct {
61+
name string
62+
in []testutil.TarEntry
63+
want []check
64+
}{
65+
{
66+
name: "files",
67+
in: []testutil.TarEntry{
68+
testutil.File("file1", "foofoo", testutil.WithFileMode(0644|os.ModeSetuid)),
69+
testutil.Dir("dir1/"),
70+
testutil.File("dir1/file2.txt", "bazbazbaz", testutil.WithFileOwner(1000, 1000)),
71+
testutil.File("file3.txt", "xxxxx", testutil.WithFileModTime(sampleTime)),
72+
testutil.File("file4.txt", "", testutil.WithFileXattrs(map[string]string{"testkey": "testval"})),
73+
},
74+
want: []check{
75+
numOfNodes(6), // root dir + 1 dir + 4 files
76+
hasFile("file1", 6),
77+
hasMode("file1", 0644|os.ModeSetuid),
78+
hasFile("dir1/file2.txt", 9),
79+
hasOwner("dir1/file2.txt", 1000, 1000),
80+
hasFile("file3.txt", 5),
81+
hasModTime("file3.txt", sampleTime),
82+
hasFile("file4.txt", 0),
83+
// For details on the keys of Xattrs, see https://pkg.go.dev/archive/tar#Header
84+
hasXattrs("file4.txt", map[string]string{"testkey": "testval"}),
85+
},
86+
},
87+
{
88+
name: "dirs",
89+
in: []testutil.TarEntry{
90+
testutil.Dir("dir1/", testutil.WithDirMode(os.ModeDir|0600|os.ModeSticky)),
91+
testutil.Dir("dir1/dir2/", testutil.WithDirOwner(1000, 1000)),
92+
testutil.File("dir1/dir2/file1.txt", "testtest"),
93+
testutil.File("dir1/dir2/file2", "x"),
94+
testutil.File("dir1/dir2/file3", "yyy"),
95+
testutil.Dir("dir1/dir3/", testutil.WithDirModTime(sampleTime)),
96+
testutil.Dir("dir1/dir3/dir4/", testutil.WithDirXattrs(map[string]string{"testkey": "testval"})),
97+
testutil.File("dir1/dir3/dir4/file4", "1111111111"),
98+
},
99+
want: []check{
100+
numOfNodes(9), // root dir + 4 dirs + 4 files
101+
hasDirChildren("dir1", "dir2", "dir3"),
102+
hasDirChildren("dir1/dir2", "file1.txt", "file2", "file3"),
103+
hasDirChildren("dir1/dir3", "dir4"),
104+
hasDirChildren("dir1/dir3/dir4", "file4"),
105+
hasMode("dir1", os.ModeDir|0600|os.ModeSticky),
106+
hasOwner("dir1/dir2", 1000, 1000),
107+
hasModTime("dir1/dir3", sampleTime),
108+
hasXattrs("dir1/dir3/dir4", map[string]string{"testkey": "testval"}),
109+
hasFile("dir1/dir2/file1.txt", 8),
110+
hasFile("dir1/dir2/file2", 1),
111+
hasFile("dir1/dir2/file3", 3),
112+
hasFile("dir1/dir3/dir4/file4", 10),
113+
},
114+
},
115+
{
116+
name: "hardlinks",
117+
in: []testutil.TarEntry{
118+
testutil.File("file1", "foofoo", testutil.WithFileOwner(1000, 1000)),
119+
testutil.Dir("dir1/"),
120+
testutil.Link("dir1/link1", "file1"),
121+
testutil.Link("dir1/link2", "dir1/link1"),
122+
testutil.Dir("dir1/dir2/"),
123+
testutil.File("dir1/dir2/file2.txt", "testtest"),
124+
testutil.Link("link3", "dir1/dir2/file2.txt"),
125+
testutil.Symlink("link4", "dir1/link2"),
126+
},
127+
want: []check{
128+
numOfNodes(6), // root dir + 2 dirs + 1 file(linked) + 1 file(linked) + 1 symlink
129+
hasFile("file1", 6),
130+
hasOwner("file1", 1000, 1000),
131+
hasFile("dir1/link1", 6),
132+
hasOwner("dir1/link1", 1000, 1000),
133+
hasFile("dir1/link2", 6),
134+
hasOwner("dir1/link2", 1000, 1000),
135+
hasFile("dir1/dir2/file2.txt", 8),
136+
hasFile("link3", 8),
137+
hasDirChildren("dir1", "link1", "link2", "dir2"),
138+
hasDirChildren("dir1/dir2", "file2.txt"),
139+
sameNodes("file1", "dir1/link1", "dir1/link2"),
140+
sameNodes("dir1/dir2/file2.txt", "link3"),
141+
linkName("link4", "dir1/link2"),
142+
hasNumLink("file1", 3), // parent dir + 2 links
143+
hasNumLink("link3", 2), // parent dir + 1 link
144+
hasNumLink("dir1", 3), // parent + "." + child's ".."
145+
},
146+
},
147+
{
148+
name: "various files",
149+
in: []testutil.TarEntry{
150+
testutil.Dir("dir1/"),
151+
testutil.File("dir1/../dir1///////////////////file1", ""),
152+
testutil.Chardev("dir1/cdev", 10, 11),
153+
testutil.Blockdev("dir1/bdev", 100, 101),
154+
testutil.Fifo("dir1/fifo"),
155+
},
156+
want: []check{
157+
numOfNodes(6), // root dir + 1 file + 1 dir + 1 cdev + 1 bdev + 1 fifo
158+
hasFile("dir1/file1", 0),
159+
hasChardev("dir1/cdev", 10, 11),
160+
hasBlockdev("dir1/bdev", 100, 101),
161+
hasFifo("dir1/fifo"),
162+
},
163+
},
52164
}
53-
defer os.Remove(f.Name())
54-
db, err := bolt.Open(f.Name(), 0600, nil)
55-
if err != nil {
56-
return nil, err
165+
for _, tt := range tests {
166+
tt := tt
167+
for _, prefix := range allowedPrefix {
168+
prefix := prefix
169+
for srcCompresionName, srcCompression := range srcCompressions {
170+
srcCompresionName, srcCompression := srcCompresionName, srcCompression
171+
t.Run(tt.name+"-"+srcCompresionName, func(t *testing.T) {
172+
opts := []testutil.BuildTarOption{
173+
testutil.WithPrefix(prefix),
174+
}
175+
176+
ztoc, sr, err := ztoc.BuildZtocReader(t, tt.in, srcCompression, 64, opts...)
177+
if err != nil {
178+
t.Fatalf("failed to build ztoc: %v", err)
179+
}
180+
telemetry, checkCalled := newCalledTelemetry()
181+
182+
// create a metadata reader
183+
r, err := newTestableReader(sr, ztoc.TOC, WithTelemetry(telemetry))
184+
if err != nil {
185+
t.Fatalf("failed to create new reader: %v", err)
186+
}
187+
defer r.Close()
188+
t.Logf("vvvvv Node tree vvvvv")
189+
t.Logf("[%d] ROOT", r.RootID())
190+
dumpNodes(t, r, r.RootID(), 1)
191+
t.Logf("^^^^^^^^^^^^^^^^^^^^^")
192+
for _, want := range tt.want {
193+
want(t, r)
194+
}
195+
if err := checkCalled(); err != nil {
196+
t.Errorf("telemetry failure: %v", err)
197+
}
198+
})
199+
}
200+
}
57201
}
58-
r, err := NewReader(db, sr, toc, opts...)
202+
}
203+
204+
func BenchmarkMetadataReader(b *testing.B) {
205+
testCases := []struct {
206+
name string
207+
entries int
208+
}{
209+
{
210+
name: "Create metadata.Reader with 1,000 TOC entries",
211+
entries: 1000,
212+
},
213+
{
214+
name: "Create metadata.Reader with 10,000 TOC entries",
215+
entries: 10_000,
216+
},
217+
{
218+
name: "Create metadata.Reader with 50,000 TOC entries",
219+
entries: 50_000,
220+
},
221+
{
222+
name: "Create metadata.Reader with 100,000 TOC entries",
223+
entries: 100_000,
224+
},
225+
}
226+
cwdPath, err := os.Getwd()
59227
if err != nil {
60-
return nil, err
228+
b.Fatal(err)
61229
}
62-
return &testableReadCloser{
63-
testableReader: r.(*reader),
64-
closeFn: func() error {
65-
db.Close()
66-
return os.Remove(f.Name())
67-
},
68-
}, nil
69-
}
230+
for _, tc := range testCases {
231+
toc, err := generateTOC(tc.entries)
232+
if err != nil {
233+
b.Fatalf("failed to generate TOC: %v", err)
234+
}
235+
b.ResetTimer()
236+
b.Run(tc.name, func(b *testing.B) {
237+
for i := 0; i < b.N; i++ {
238+
b.StopTimer()
239+
tempDB, clean, err := newTempDB(cwdPath)
240+
defer func() {
241+
b.StopTimer()
242+
clean()
243+
b.StartTimer()
244+
}()
245+
if err != nil {
246+
b.Fatalf("failed to initialize temp db: %v", err)
247+
}
248+
b.StartTimer()
249+
if _, err := NewReader(tempDB, nil, toc); err != nil {
250+
b.Fatalf("failed to create new reader: %v", err)
251+
}
252+
}
70253

71-
type testableReadCloser struct {
72-
testableReader
73-
closeFn func() error
254+
})
255+
}
74256
}
75257

76-
func (r *testableReadCloser) Close() error {
77-
r.closeFn()
78-
return r.testableReader.Close()
258+
func BenchmarkConcurrentMetadataReader(b *testing.B) {
259+
smallTOC, err := generateTOC(1000)
260+
if err != nil {
261+
b.Fatalf("failed to generate TOC: %v", err)
262+
}
263+
mediumTOC, err := generateTOC(10_000)
264+
if err != nil {
265+
b.Fatalf("failed to generate TOC: %v", err)
266+
}
267+
largeTOC, err := generateTOC(50_000)
268+
if err != nil {
269+
b.Fatalf("failed to generate TOC: %v", err)
270+
}
271+
cwdPath, err := os.Getwd()
272+
if err != nil {
273+
b.Fatal(err)
274+
}
275+
tocs := []ztoc.TOC{smallTOC, mediumTOC, largeTOC}
276+
var eg errgroup.Group
277+
b.ResetTimer()
278+
b.Run("Write small, medium and large TOC concurrently", func(b *testing.B) {
279+
for i := 0; i < b.N; i++ {
280+
b.StopTimer()
281+
tempDB, clean, err := newTempDB(cwdPath)
282+
defer func() {
283+
b.StopTimer()
284+
clean()
285+
b.StartTimer()
286+
}()
287+
if err != nil {
288+
b.Fatalf("failed to initialize temp db: %v", err)
289+
}
290+
b.StartTimer()
291+
for _, toc := range tocs {
292+
toc := toc
293+
eg.Go(func() error {
294+
if _, err := NewReader(tempDB, nil, toc); err != nil {
295+
return fmt.Errorf("failed to create new reader: %v", err)
296+
}
297+
return nil
298+
})
299+
}
300+
if err := eg.Wait(); err != nil {
301+
b.Fatal(err)
302+
}
303+
}
304+
})
79305
}

0 commit comments

Comments
 (0)