Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 68 additions & 48 deletions bson/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
package bson

import (
"archive/tar"
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"sync"
"testing"
)
Expand Down Expand Up @@ -144,54 +144,74 @@ var nestedInstance = nestedtest1{
},
}

const extendedBSONDir = "../testdata/extended_bson"
const extendedBSONTGZ = "../testdata/specifications/source/benchmarking/data/extended_bson.tgz"

var (
extJSONFiles map[string]map[string]any
extJSONFilesMu sync.Mutex
)

// readExtJSONFile reads the GZIP-compressed extended JSON document from the given filename in the
// "extended BSON" test data directory (../testdata/extended_bson) and returns it as a
// map[string]any. It panics on any errors.
func readExtJSONFile(filename string) map[string]any {
// readExtJSONFile reads the named JSON file from the extended_bson.tgz archive and returns it as a
// map[string]any. The first call decompresses the archive and caches all entries; subsequent calls
// only look up the cache. It calls b.Fatal on any errors.
func readExtJSONFile(b *testing.B, filename string) map[string]any {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] Use testing.TB instead of *testing.B so that the loader is agnostic. In addition, I think we should make a test for the loader to ensure the structure is what we expect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the hard-coded file names underneath to cross-verify the structure in the tarball is what we expect.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[question] The original drivers ticket aimed to use strong types for these tests. Should we do the same? See this comment: https://github.com/mongodb/mongo-go-driver/pull/2400/changes#r3312898922

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updates of strong-typed benchmarks are in "deep_bson.json.gz". They are verified in #2403.

b.Helper()
extJSONFilesMu.Lock()
defer extJSONFilesMu.Unlock()
if v, ok := extJSONFiles[filename]; ok {
return v
}
filePath := path.Join(extendedBSONDir, filename)
file, err := os.Open(filePath)
if err != nil {
panic(fmt.Sprintf("error opening file %q: %s", filePath, err))
}
defer func() {
_ = file.Close()
}()

gz, err := gzip.NewReader(file)
if err != nil {
panic(fmt.Sprintf("error creating GZIP reader: %s", err))
}
defer func() {
_ = gz.Close()
}()
if extJSONFiles == nil {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Suggest using sync.Once to help with readability:

func readExtJSONFile(b *testing.B, filename string) map[string]any {
    b.Helper()
    extJSONFilesOnce.Do(func() {
        // load into extJSONFiles
    })

    v, ok := extJSONFiles["extended_bson/"+filename]
    if !ok {
        b.Fatalf("file %q not found in %q", filename, extendedBSONTGZ)
        return nil
    }
    return v
}

file, err := os.Open(extendedBSONTGZ)
if err != nil {
b.Fatalf("error opening %q: %s", extendedBSONTGZ, err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
b.Fatalf("error opening %q: %s", extendedBSONTGZ, err)
b.Fatalf("error opening %q: %v", extendedBSONTGZ, err)

return nil
}
defer func() {
_ = file.Close()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] We should check this error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the closing error affects loading, and we don't need to log it either.

}()

gz, err := gzip.NewReader(file)
if err != nil {
b.Fatalf("error creating gzip reader: %s", err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
b.Fatalf("error creating gzip reader: %s", err)
b.Fatalf("error creating gzip reader: %v", err)

return nil
}
defer func() {
_ = gz.Close()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] We should check this error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the closing error affects loading, and we don't need to log it either.

}()

data, err := ioutil.ReadAll(gz)
if err != nil {
panic(fmt.Sprintf("error reading GZIP contents of file: %s", err))
}
extJSONFiles = make(map[string]map[string]any)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] It doesn't matter in this case but for hygiene suggest populating a local map and assigning this at the end of sync.Once, to avoid partially filling and then erroring.


var v map[string]any
err = UnmarshalExtJSON(data, false, &v)
if err != nil {
panic(fmt.Sprintf("error unmarshalling extended JSON: %s", err))
tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
b.Fatalf("error reading tar: %s", err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
b.Fatalf("error reading tar: %s", err)
b.Fatalf("error reading tar: %v", err)

return nil
}
if hdr.Typeflag != tar.TypeReg {
continue
}
data, err := io.ReadAll(tr)
if err != nil {
b.Fatalf("error reading tar entry %q: %s", hdr.Name, err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
b.Fatalf("error reading tar entry %q: %s", hdr.Name, err)
b.Fatalf("error reading tar entry %q: %v", hdr.Name, err)

return nil
}
var v map[string]any
if err = UnmarshalExtJSON(data, false, &v); err != nil {
b.Fatalf("error unmarshalling %q: %s", hdr.Name, err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
b.Fatalf("error unmarshalling %q: %s", hdr.Name, err)
b.Fatalf("error unmarshalling %q: %v", hdr.Name, err)

return nil
}
extJSONFiles[hdr.Name] = v
}
}

if extJSONFiles == nil {
extJSONFiles = make(map[string]map[string]any)
v, ok := extJSONFiles["extended_bson/"+filename]
if !ok {
b.Fatalf("file %q not found in %q", filename, extendedBSONTGZ)
return nil
}
extJSONFiles[filename] = v
return v
}

Expand All @@ -213,16 +233,16 @@ func BenchmarkMarshal(b *testing.B) {
value: encodetestBsonD,
},
{
desc: "deep_bson.json.gz",
value: readExtJSONFile("deep_bson.json.gz"),
desc: "deep_bson.json",
value: readExtJSONFile(b, "deep_bson.json"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] We don't need to hardcode the files, just iterate over the results of decompressing the tarball. We should rename readExtJSONFile to loadExtendedBSON. Then we should iterate over the results:

func TestLoadExtendedBSON(t *testing.T) {
	loadExtendedBSON(t)

	for filename, _ := range extJSONFiles {
		t.Run(filename, func(t *testing.T) {
			// Test / Benchmark
		})
	}
}

},
{
desc: "flat_bson.json.gz",
value: readExtJSONFile("flat_bson.json.gz"),
desc: "flat_bson.json",
value: readExtJSONFile(b, "flat_bson.json"),
},
{
desc: "full_bson.json.gz",
value: readExtJSONFile("full_bson.json.gz"),
desc: "full_bson.json",
value: readExtJSONFile(b, "full_bson.json"),
},
}

Expand Down Expand Up @@ -314,16 +334,16 @@ func BenchmarkUnmarshal(b *testing.B) {
value: nestedInstance,
},
{
name: "deep_bson.json.gz",
value: readExtJSONFile("deep_bson.json.gz"),
name: "deep_bson.json",
value: readExtJSONFile(b, "deep_bson.json"),
},
{
name: "flat_bson.json.gz",
value: readExtJSONFile("flat_bson.json.gz"),
name: "flat_bson.json",
value: readExtJSONFile(b, "flat_bson.json"),
},
{
name: "full_bson.json.gz",
value: readExtJSONFile("full_bson.json.gz"),
name: "full_bson.json",
value: readExtJSONFile(b, "full_bson.json"),
},
}

Expand Down
52 changes: 50 additions & 2 deletions internal/cmd/benchmark/benchmark_test.go
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change should only be applied for verification in #2403.

Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,56 @@ func loadSourceDocument(b *testing.B, canonicalOnly bool, pathParts ...string) b
return doc
}

func loadBSONExtJSONFile(b *testing.B, canonicalOnly bool, source string) bson.D {
b.Helper()

tgzPath := filepath.Join(testdataDir(b), "specifications", "source", "benchmarking", "data", "extended_bson.tgz")

file, err := os.Open(tgzPath)
require.NoError(b, err, "failed to open %q", tgzPath)
defer file.Close()

gz, err := gzip.NewReader(file)
require.NoError(b, err, "failed to create gzip reader")
defer gz.Close()

tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}

require.NoError(b, err, "failed to read tar")

if hdr.Typeflag != tar.TypeReg {
continue
}

if hdr.Name != bsonDataDir+"/"+source {
continue
}

data, err := io.ReadAll(tr)
require.NoError(b, err, "failed to read tar entry %q", hdr.Name)

var doc bson.D

err = bson.UnmarshalExtJSON(data, canonicalOnly, &doc)
require.NoError(b, err, "failed to unmarshal extended JSON from %q", hdr.Name)

require.NotEmpty(b, doc)

return doc
}

b.Fatalf("file %q not found in %q", bsonDataDir+"/"+source, tgzPath)

return nil
}

func benchmarkBSONEncoding(b *testing.B, canonicalOnly bool, source string) {
doc := loadSourceDocument(b, canonicalOnly, testdataPerfDir(b), bsonDataDir, source)
doc := loadBSONExtJSONFile(b, canonicalOnly, source)

b.ResetTimer()

Expand All @@ -229,7 +277,7 @@ func benchmarkBSONEncoding(b *testing.B, canonicalOnly bool, source string) {
}

func benchmarkBSONDecoding(b *testing.B, canonicalOnly bool, source string) {
doc := loadSourceDocument(b, canonicalOnly, testdataPerfDir(b), bsonDataDir, source)
doc := loadBSONExtJSONFile(b, canonicalOnly, source)

raw, err := bson.Marshal(doc)
require.NoError(b, err, "failed to encode bson data")
Expand Down
Binary file removed testdata/extended_bson/deep_bson.json.gz
Binary file not shown.
Binary file removed testdata/extended_bson/flat_bson.json.gz
Binary file not shown.
Binary file removed testdata/extended_bson/full_bson.json.gz
Binary file not shown.
Loading