Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
81ff782
go.mod
chilagrow May 23, 2025
8f28c00
add as replacement file directive
chilagrow May 23, 2025
a9f26bb
mongobench package
chilagrow May 23, 2025
395c882
parsing
chilagrow May 26, 2025
dfde50b
file is not valid csv
chilagrow May 26, 2025
3234b1e
comment and example
chilagrow May 26, 2025
9e07c18
gitignore
chilagrow May 26, 2025
3d9f81e
lint
chilagrow May 26, 2025
779d885
make it run
chilagrow May 26, 2025
3e72a3f
allow pushing array of results
chilagrow May 27, 2025
580b57b
allow flexibility to the way measurements are pushed
chilagrow May 27, 2025
da52444
config
chilagrow May 27, 2025
ec091fb
lint
chilagrow May 27, 2025
ded39d4
resolve merge conflict
chilagrow May 27, 2025
777922e
check nil
chilagrow May 27, 2025
20986fd
test
chilagrow May 27, 2025
bd7d2ea
Merge branch 'pusher-measurement-interface' into mongodb-benchmarking
chilagrow May 27, 2025
8639d2d
workaround
chilagrow May 27, 2025
4a81d99
comment
chilagrow May 27, 2025
001b791
use fork
chilagrow May 27, 2025
57b8e4e
lint
chilagrow May 27, 2025
24e1596
tooling
chilagrow May 27, 2025
c5c81cc
Merge branch 'mongodb-benchmarking-tooling' into mongodb-benchmarking
chilagrow May 27, 2025
afe5c7e
interface check
chilagrow May 27, 2025
b738d6c
run in CI
chilagrow May 27, 2025
221fbe1
no ferretdb v1
chilagrow May 27, 2025
029d36c
file path
chilagrow May 27, 2025
1c26d6f
fix
chilagrow May 27, 2025
1b5a43d
Update internal/runner/mongobench/mongobench.go
chilagrow May 28, 2025
5715b6e
Do not use git submodule
AlekSi May 29, 2025
32ebf5d
undo workflow changes
chilagrow May 29, 2025
308d32b
Merge branch 'mongodb-benchmarking-tooling' into mongodb-benchmarking
chilagrow May 29, 2025
f489f0e
update
chilagrow May 29, 2025
70c9c9c
Merge branch 'mongodb-benchmarking' of github.com:chilagrow/dance int…
chilagrow May 29, 2025
bf2931f
Merge branch 'main' into mongodb-benchmarking
chilagrow May 29, 2025
0e52d40
report last resutl
chilagrow May 29, 2025
561d8bd
comment
chilagrow May 29, 2025
ad06952
Merge branch 'main' into mongodb-benchmarking
chilagrow May 30, 2025
303fd37
copilot
chilagrow May 30, 2025
dba2454
Merge branch 'main' into mongodb-benchmarking
AlekSi Jun 4, 2025
8f154cc
Merge branch 'main' into mongodb-benchmarking
AlekSi Jun 9, 2025
aed63f8
Merge branch 'main' into mongodb-benchmarking
AlekSi Jun 16, 2025
9b386bf
Merge branch 'main' into mongodb-benchmarking
AlekSi Jun 17, 2025
53b5533
Merge branch 'main' into mongodb-benchmarking
chilagrow Jun 18, 2025
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
2 changes: 2 additions & 0 deletions .github/workflows/dance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:
- { config: mongo-tools }
- { config: mongo-core-test, verbose: true } # verbose to view test output on CI

- { config: mongobench-runall }

- { config: ycsb-workloada }
- { config: ycsb-workloada2 }
- { config: ycsb-workloadb }
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
/dumps/mongodump_tests/
/vendor/
cover.txt

# mongodb-benchmarking
projects/benchmark_results_*.csv
3 changes: 3 additions & 0 deletions cmd/dance/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/FerretDB/dance/internal/runner"
"github.com/FerretDB/dance/internal/runner/command"
"github.com/FerretDB/dance/internal/runner/gotest"
"github.com/FerretDB/dance/internal/runner/mongobench"
"github.com/FerretDB/dance/internal/runner/ycsb"
)

Expand Down Expand Up @@ -196,6 +197,8 @@ func main() {
runner, err = command.New(c.Params.(*config.RunnerParamsCommand), rl, cli.Verbose)
case config.RunnerTypeGoTest:
runner, err = gotest.New(c.Params.(*config.RunnerParamsGoTest), rl, cli.Verbose)
case config.RunnerTypeMongoBench:
runner, err = mongobench.New(c.Params.(*config.RunnerParamsMongoBench), rl)
case config.RunnerTypeYCSB:
runner, err = ycsb.New(c.Params.(*config.RunnerParamsYCSB), rl)
default:
Expand Down
13 changes: 13 additions & 0 deletions internal/config/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const (
// RunnerTypeGoTest indicates a Go test runner.
RunnerTypeGoTest RunnerType = "gotest"

// RunnerTypeMongoBench indicates a MongoDB benchmark test runner.
RunnerTypeMongoBench RunnerType = "mongobench"

// RunnerTypeYCSB indicates a YCSB test runner.
RunnerTypeYCSB RunnerType = "ycsb"
)
Expand Down Expand Up @@ -61,6 +64,15 @@ type RunnerParamsGoTest struct {
// runnerParams implements [RunnerParams] interface.
func (rp *RunnerParamsGoTest) runnerParams() {}

// RunnerParamsMongoBench represents `mongobench` runner parameters.
type RunnerParamsMongoBench struct {
Dir string
Args []string
}

// runnerParams implements [RunnerParams] interface.
func (rp *RunnerParamsMongoBench) runnerParams() {}

// RunnerParamsYCSB represents `ycsb` runner parameters.
type RunnerParamsYCSB struct {
Dir string
Expand All @@ -74,5 +86,6 @@ func (rp *RunnerParamsYCSB) runnerParams() {}
var (
_ RunnerParams = (*RunnerParamsCommand)(nil)
_ RunnerParams = (*RunnerParamsGoTest)(nil)
_ RunnerParams = (*RunnerParamsMongoBench)(nil)
_ RunnerParams = (*RunnerParamsYCSB)(nil)
)
2 changes: 2 additions & 0 deletions internal/configload/configload.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ func loadContent(content, db string) (*config.Config, error) {
p = &runnerParamsCommand{}
case config.RunnerTypeGoTest:
p = &runnerParamsGoTest{}
case config.RunnerTypeMongoBench:
p = &runnerParamsMongoBench{}
case config.RunnerTypeYCSB:
p = &runnerParamsYCSB{}
default:
Expand Down
15 changes: 15 additions & 0 deletions internal/configload/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ func (rp *runnerParamsGoTest) convert() (config.RunnerParams, error) {
}, nil
}

// runnerParamsMongoBench represents `mongobench` runner parameters in the project configuration YAML file.
type runnerParamsMongoBench struct {
Dir string `yaml:"dir"`
Args []string `yaml:"args"`
}

// convert implements [runnerParams] interface.
func (rp *runnerParamsMongoBench) convert() (config.RunnerParams, error) {
return &config.RunnerParamsMongoBench{
Dir: rp.Dir,
Args: rp.Args,
}, nil
}

// runnerParamsYCSB represents `ycsb` runner parameters in the project configuration YAML file.
type runnerParamsYCSB struct {
Dir string `yaml:"dir"`
Expand All @@ -92,5 +106,6 @@ func (rp *runnerParamsYCSB) convert() (config.RunnerParams, error) {
var (
_ runnerParams = (*runnerParamsCommand)(nil)
_ runnerParams = (*runnerParamsGoTest)(nil)
_ runnerParams = (*runnerParamsMongoBench)(nil)
_ runnerParams = (*runnerParamsYCSB)(nil)
)
216 changes: 216 additions & 0 deletions internal/runner/mongobench/mongobench.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright 2021 FerretDB Inc.
//
// 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 mongobench provides `mongobench` runner.
package mongobench

import (
"bufio"
"context"
"errors"
"io"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

"github.com/FerretDB/dance/internal/config"
"github.com/FerretDB/dance/internal/runner"
)

// mongoBench represents `mongoBench` runner.
type mongoBench struct {
p *config.RunnerParamsMongoBench
l *slog.Logger
}

// New creates a new runner with given parameters.
func New(params *config.RunnerParamsMongoBench, l *slog.Logger) (runner.Runner, error) {
return &mongoBench{
p: params,
l: l,
}, nil
}

// parseFileNames parses the file names that store benchmark results.
// Each operation is stored in different files such as `benchmark_results_insert.csv`,
// `benchmark_results_update.csv`, `benchmark_results_delete.csv` and `benchmark_results_upsert.csv`.
func parseFileNames(r io.Reader) ([]string, error) {
var fileNames []string

scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()

if !strings.HasPrefix(line, "Benchmarking completed. Results saved to ") {
continue
}

// parse file name from the line such as `Benchmarking completed. Results saved to benchmark_results_delete.csv`
fileName := strings.TrimSpace(strings.TrimPrefix(line, "Benchmarking completed. Results saved to "))
fileNames = append(fileNames, fileName)
}

if err := scanner.Err(); err != nil {
return nil, err
}

if len(fileNames) == 0 {
return nil, errors.New("no benchmark result files found")
}

return fileNames, nil
}

// readResult reads the file and gets the last measurement and parses it.
// The file contains measurements taken each second while the benchmark was running,
// the last measurement is parsed and returned.
func readResult(filePath string) (result map[string]float64, err error) {
var f *os.File

if f, err = os.Open(filePath); err != nil {
return
}

defer func() {
if e := f.Close(); e != nil && err == nil {
err = e
}
}()

// cannot use [csv.NewReader] because the file does not contain valid CSV,
// it contains 7 header fields while record lines contain 6 fields,
// so we parse it manually and assume the last field `mean_rate` is missing
s := bufio.NewScanner(f)

var lastLine string

for s.Scan() {
line := s.Text()

if strings.TrimSpace(line) != "" {
lastLine = line
}
}

if err = s.Err(); err != nil {
return
}

record := strings.Split(lastLine, ",")
if len(record) < 6 {
return nil, errors.New("insufficient fields")
}

var count, mean, m1Rate, m5Rate, m15Rate float64

if count, err = strconv.ParseFloat(record[1], 64); err != nil {
return
}

if mean, err = strconv.ParseFloat(record[2], 64); err != nil {
return
}

if m1Rate, err = strconv.ParseFloat(record[3], 64); err != nil {
return
}

if m5Rate, err = strconv.ParseFloat(record[4], 64); err != nil {
return
}

if m15Rate, err = strconv.ParseFloat(record[5], 64); err != nil {
return
}

result = map[string]float64{
"count": count,
"mean": mean,
"m1_rate": m1Rate,
"m5_rate": m5Rate,
"m15_rate": m15Rate,
}

return
}

// run runs given command in the given directory and returns parsed results.
func run(ctx context.Context, args []string, dir string) (map[string]config.TestResult, error) {
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr

pipe, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}

defer pipe.Close()

if err = cmd.Start(); err != nil {
return nil, err
}

fileNames, err := parseFileNames(io.TeeReader(pipe, os.Stdout))
if err != nil {
_ = cmd.Process.Kill()
_ = cmd.Wait()

return nil, err
}

if err = cmd.Wait(); err != nil {
return nil, err
}

res := make(map[string]config.TestResult)

for _, fileName := range fileNames {
var m map[string]float64

if m, err = readResult(filepath.Join("..", "projects", fileName)); err != nil {
Copy link

Copilot AI Jul 3, 2025

Choose a reason for hiding this comment

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

Using a hardcoded path '../projects' to locate result files may not align with the runner's working directory. Consider constructing the file path based on the configured 'dir' parameter or the actual command working directory.

Suggested change
if m, err = readResult(filepath.Join("..", "projects", fileName)); err != nil {
if m, err = readResult(filepath.Join(dir, "projects", fileName)); err != nil {

Copilot uses AI. Check for mistakes.
return nil, err
}

op := strings.TrimSuffix(strings.TrimPrefix(fileName, "benchmark_results_"), ".csv")
res[op] = config.TestResult{
Status: config.Pass,
Measurements: m,
}
}

return res, nil
}

// Run implements [runner.Runner] interface.
func (m *mongoBench) Run(ctx context.Context) (map[string]config.TestResult, error) {
bin := filepath.Join("..", "bin", "mongodb-benchmarking")
if _, err := os.Stat(bin); err != nil {
return nil, err
}

bin, err := filepath.Abs(bin)
if err != nil {
return nil, err
}

args := append([]string{bin}, m.p.Args...)

m.l.InfoContext(ctx, "Run", slog.String("cmd", strings.Join(args, " ")))

return run(ctx, args, m.p.Dir)
}
Loading
Loading