Skip to content

Commit

Permalink
Break some stuff
Browse files Browse the repository at this point in the history
- Moves `-w builtin:blah` and `-w myfile` to
  `-b blah` and `-f myfile`, respectively
- Adds `-S 'RETURN 1'`
- Changes backslash-prefixed metacommands to colon-prefixwq

oh this isn't vim
  • Loading branch information
jakewins committed Apr 15, 2021
1 parent 01c18d6 commit 403c671
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 93 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,33 @@ The variables are available to subsequent meta-commands and as parameters in you

Here is a small example with two meta-commands and one query:

\set numPeople $scale * 1000000
\set personId random() * $numPeople
:set numPeople $scale * 1000000
:set personId random() * $numPeople
MATCH (p:Person {id: $personId}) RETURN p;

Scripts are currently ran as a single transaction, though that may change before 1.0.

The following meta-commands are currently supported:

\set <variable> <expression>
ex: \set myParam random() * 1000
:set <variable> <expression>
ex: :set myParam random() * 1000

\sleep <expression> <unit>
ex: \sleep random() * 60 ms
:sleep <expression> <unit>
ex: :sleep random() * 60 ms

All expressions supported by pgbench 10 are supported, please see the pgbench docs linked above.

Beyond the pgbench expressions, neobench also supports lists:

\set myLiteralList [1,2,[3]]
\set myRange range(1,10) // inclusive on both sides to match cypher range()
:set myLiteralList [1,2,[3]]
:set myRange range(1,10) // inclusive on both sides to match cypher range()

For simulating simple bulk-insert operations, there is `random_matrix`.
This function generates a matrix with each column populated with a random integer in a specified range:

// Generates a 100-row matrix with 3 columns.
// The first column will have random values between [1,5], the second [1,100] and the third [-10,10].
\set myMatrix random_matrix(100, [1, 5], [1, 100], [-10, 10])
:set myMatrix random_matrix(100, [1, 5], [1, 100], [-10, 10])

# Contributions

Expand Down
113 changes: 75 additions & 38 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ var fEncryptionMode string
var fDuration time.Duration
var fProgress time.Duration
var fVariables map[string]string
var fWorkloads []string
var fBuiltinWorkloads []string
var fWorkloadFiles []string
var fWorkloadScripts []string
var fOutputFormat string
var fNoCheckCertificates bool
var fMaxConnLifetime time.Duration
Expand All @@ -46,10 +48,14 @@ func init() {
pflag.StringVarP(&fEncryptionMode, "encryption", "e", "auto", "whether to use encryption, `auto`, `true` or `false`")
pflag.DurationVarP(&fDuration, "duration", "d", 60*time.Second, "duration to run, ex: 15s, 1m, 10h")
pflag.StringToStringVarP(&fVariables, "define", "D", nil, "defines variables for workload scripts and query parameters")
pflag.StringSliceVarP(&fWorkloads, "workload", "w", []string{"builtin:tpcb-like"}, "path to workload script or builtin:[tpcb-like,ldbc-like]")
pflag.BoolVarP(&fLatencyMode, "latency", "l", false, "run in latency testing more rather than throughput mode")
pflag.StringVarP(&fOutputFormat, "output", "o", "auto", "output format, `auto`, `interactive` or `csv`")

// Flags defining the workload to run
pflag.StringSliceVarP(&fBuiltinWorkloads, "builtin", "b", []string{}, "built-in workload to run 'tpcb-like' or 'ldbc-like', default is tpcb-like")
pflag.StringSliceVarP(&fWorkloadFiles, "file", "f", []string{}, "path to workload script file(s)")
pflag.StringArrayVarP(&fWorkloadScripts, "script", "S", []string{}, "script(s) to run, directly specified on the command line")

// Less common command line vars
pflag.DurationVar(&fProgress, "progress", 10*time.Second, "interval to report progress, ex: 15s, 1m, 1h")
pflag.BoolVar(&fNoCheckCertificates, "no-check-certificates", false, "disable TLS certificate validation, exposes your credentials to anyone on the network")
Expand All @@ -73,6 +79,11 @@ Options:
os.Exit(1)
}

// If no workloads at all are specified, we run tpc-b
if len(fBuiltinWorkloads) == 0 && len(fWorkloadScripts) == 0 && len(fWorkloadFiles) == 0 {
fBuiltinWorkloads = []string{"tpcb-like"}
}

seed := time.Now().Unix()
scenario := describeScenario()

Expand Down Expand Up @@ -128,7 +139,7 @@ Options:
}

if fInitMode {
err = initWorkload(fWorkloads, dbName, fScale, seed, driver, out)
err = initWorkload(fBuiltinWorkloads, dbName, fScale, seed, driver, out)
if err != nil {
log.Fatalf("%+v", err)
}
Expand Down Expand Up @@ -170,30 +181,30 @@ func createWorkload(driver neo4j.Driver, dbName string, variables map[string]int
var err error
scripts := make([]neobench.Script, 0)
csvLoader := neobench.NewCsvLoader()
for _, path := range fWorkloads {
parts := strings.Split(path, "@")
weight := 1.0
if len(parts) > 1 {
weight, err = strconv.ParseFloat(parts[1], 64)
if err != nil {
log.Fatalf("Failed to parse weight; value after @ symbol for workload weight must be a number: %s", path)
}
path = parts[0]
for _, rawPath := range fBuiltinWorkloads {
path, weight := splitScriptAndWeight(rawPath)
builtinScripts, err := loadBuiltinWorkload(path, weight)
if err != nil {
return neobench.Workload{}, errors.Wrapf(err, "failed to load script '%s'", path)
}
scripts = append(scripts, builtinScripts...)
}

if strings.HasPrefix(path, "builtin:") {
builtinScripts, err := loadBuiltinWorkload(path, weight)
if err != nil {
return neobench.Workload{}, errors.Wrapf(err, "failed to load script '%s'", path)
}
scripts = append(scripts, builtinScripts...)
} else {
script, err := loadScript(driver, dbName, variables, path, weight, csvLoader)
if err != nil {
return neobench.Workload{}, errors.Wrapf(err, "failed to load script '%s'", path)
}
scripts = append(scripts, script)
for _, rawPath := range fWorkloadFiles {
path, weight := splitScriptAndWeight(rawPath)
script, err := loadScriptFile(driver, dbName, variables, path, weight, csvLoader)
if err != nil {
return neobench.Workload{}, errors.Wrapf(err, "failed to load script '%s'", path)
}
scripts = append(scripts, script)
}

for i, scriptContent := range fWorkloadScripts {
script, err := loadScript(driver, dbName, variables, fmt.Sprintf("-S #%d", i), scriptContent, 1.0, csvLoader)
if err != nil {
return neobench.Workload{}, errors.Wrapf(err, "failed to parse script '%s'", scriptContent)
}
scripts = append(scripts, script)
}

return neobench.Workload{
Expand All @@ -204,14 +215,34 @@ func createWorkload(driver neo4j.Driver, dbName string, variables map[string]int
}, err
}

func loadScript(driver neo4j.Driver, dbName string, vars map[string]interface{}, path string, weight float64,
// Splits command-line specified scripts-with-weight into script and weight
// -f my.script@100 becomes "myscript", 100.0
// -b tpcb-like@10 becomes "tpcb-like", 10.0
func splitScriptAndWeight(raw string) (string, float64) {
parts := strings.Split(raw, "@")
if len(parts) < 2 {
return raw, 1.0
}
weight, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
log.Fatalf("Failed to parse weight; value after @ symbol for workload weight must be a number: %s", raw)
}
return parts[0], weight
}

func loadScriptFile(driver neo4j.Driver, dbName string, vars map[string]interface{}, path string, weight float64,
csvLoader *neobench.CsvLoader) (neobench.Script, error) {
scriptContent, err := ioutil.ReadFile(path)
if err != nil {
return neobench.Script{}, fmt.Errorf("failed to read workload file at %s: %s", path, err)
}

script, err := neobench.Parse(path, string(scriptContent), weight)
return loadScript(driver, dbName, vars, path, string(scriptContent), weight, csvLoader)
}

func loadScript(driver neo4j.Driver, dbName string, vars map[string]interface{}, path, scriptContent string, weight float64,
csvLoader *neobench.CsvLoader) (neobench.Script, error) {
script, err := neobench.Parse(path, scriptContent, weight)
if err != nil {
return neobench.Script{}, err
}
Expand All @@ -222,17 +253,17 @@ func loadScript(driver neo4j.Driver, dbName string, vars map[string]interface{},
}

func loadBuiltinWorkload(path string, weight float64) ([]neobench.Script, error) {
if path == "builtin:tpcb-like" {
if path == "tpcb-like" {
script, err := neobench.Parse("builtin:tpcp-like", builtin.TPCBLike, weight)
return []neobench.Script{script}, err
}

if path == "builtin:match-only" {
if path == "match-only" {
script, err := neobench.Parse("builtin:match-only", builtin.MatchOnly, weight)
return []neobench.Script{script}, err
}

if path == "builtin:ldbc-like" {
if path == "ldbc-like" {
ic2Rate, ic6Rate, ic10Rate, ic14Rate := 37.0, 129.0, 30.0, 49.0
totalRate := ic2Rate + ic6Rate + ic10Rate + ic14Rate
ic2, err := neobench.Parse("builtin:ldbc-like/ic2", builtin.LDBCIC2, ic2Rate/totalRate*weight)
Expand All @@ -259,22 +290,22 @@ func loadBuiltinWorkload(path string, weight float64) ([]neobench.Script, error)
}, err
}

if path == "builtin:ldbc-like/ic2" {
if path == "ldbc-like/ic2" {
script, err := neobench.Parse("builtin:ldbc-like/ic2", builtin.LDBCIC2, weight)
return []neobench.Script{script}, err
}

if path == "builtin:ldbc-like/ic6" {
if path == "ldbc-like/ic6" {
script, err := neobench.Parse("builtin:ldbc-like/ic6", builtin.LDBCIC6, weight)
return []neobench.Script{script}, err
}

if path == "builtin:ldbc-like/ic10" {
if path == "ldbc-like/ic10" {
script, err := neobench.Parse("builtin:ldbc-like/ic10", builtin.LDBCIC10, weight)
return []neobench.Script{script}, err
}

if path == "builtin:ldbc-like/ic14" {
if path == "ldbc-like/ic14" {
script, err := neobench.Parse("builtin:ldbc-like/ic14", builtin.LDBCIC14, weight)
return []neobench.Script{script}, err
}
Expand All @@ -284,8 +315,14 @@ func loadBuiltinWorkload(path string, weight float64) ([]neobench.Script, error)

func describeScenario() string {
out := strings.Builder{}
for _, path := range fWorkloads {
out.WriteString(fmt.Sprintf(" -w %s", path))
for _, path := range fBuiltinWorkloads {
out.WriteString(fmt.Sprintf(" -b %s", path))
}
for _, path := range fWorkloadFiles {
out.WriteString(fmt.Sprintf(" -f %s", path))
}
for _, script := range fWorkloadScripts {
out.WriteString(fmt.Sprintf(" -S \"%s\"", script))
}
out.WriteString(fmt.Sprintf(" -c %d", fClients))
out.WriteString(fmt.Sprintf(" -s %d", fScale))
Expand Down Expand Up @@ -363,13 +400,13 @@ func collectResults(databaseName, scenario string, out neobench.Output, concurre

func initWorkload(paths []string, dbName string, scale, seed int64, driver neo4j.Driver, out neobench.Output) error {
for _, path := range paths {
if path == "builtin:tpcb-like" {
if path == "tpcb-like" {
return builtin.InitTPCBLike(scale, dbName, driver, out)
}
if path == "builtin:match-only" {
if path == "match-only" {
return builtin.InitTPCBLike(scale, dbName, driver, out)
}
if path == "builtin:ldbc-like" {
if path == "ldbc-like" {
return builtin.InitLDBCLike(scale, seed, dbName, driver, out)
}
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/neobench/builtin/ldbc_like.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

const LDBCIC2 = `
\set personId random(1, 9892 * $scale)
:set personId random(1, 9892 * $scale)
MATCH (:Person {id:{personId}})-[:KNOWS]-(friend),
(friend)<-[:HAS_CREATOR]-(message)
Expand All @@ -26,8 +26,8 @@ LIMIT 20
`

const LDBCIC6 = `
\set personId random(1, 9892 * $scale)
\set tagId random(1, 16080)
:set personId random(1, 9892 * $scale)
:set tagId random(1, 16080)
MATCH (knownTag:Tag {name: "Tag-" + $tagId})
MATCH (person:Person {id:$personId})-[:KNOWS*1..2]-(friend)
Expand All @@ -45,8 +45,8 @@ LIMIT 10;
`

const LDBCIC10 = `
\set personId random(1, 9892 * $scale)
\set birthdayMonth random(1, 13)
:set personId random(1, 9892 * $scale)
:set birthdayMonth random(1, 13)
MATCH (person:Person {id:$personId})-[:KNOWS*2..2]-(friend),
(friend)-[:IS_LOCATED_IN]->(city)
Expand All @@ -72,8 +72,8 @@ LIMIT 10;
`

const LDBCIC14 = `
\set personOne random(1, 9892 * $scale)
\set personTwo random(1, 9892 * $scale)
:set personOne random(1, 9892 * $scale)
:set personTwo random(1, 9892 * $scale)
MATCH path = allShortestPaths((person1:Person {id:$personOne})-[:KNOWS*0..]-(person2:Person {id:$personTwo}))
RETURN
Expand Down
10 changes: 5 additions & 5 deletions pkg/neobench/builtin/tpcb_like.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
)

const TPCBLike = `
\set aid random(1, 100000 * $scale)
\set bid random(1, 1 * $scale)
\set tid random(1, 10 * $scale)
\set delta random(-5000, 5000)
:set aid random(1, 100000 * $scale)
:set bid random(1, 1 * $scale)
:set tid random(1, 10 * $scale)
:set delta random(-5000, 5000)
MATCH (account:Account {aid:$aid})
SET account.balance = account.balance + $delta;
Expand All @@ -21,7 +21,7 @@ CREATE (:History { tid: $tid, bid: $bid, aid: $aid, delta: $delta, mtime: timest
`

const MatchOnly = `
\set aid random(1, 100000 * $scale)
:set aid random(1, 100000 * $scale)
MATCH (account:Account {aid:$aid}) RETURN account.balance;
`

Expand Down
8 changes: 6 additions & 2 deletions pkg/neobench/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func Parse(filename, script string, weight float64) (Script, error) {
if tok == scanner.EOF {
break
} else if tok == '\\' {
c.fail(fmt.Errorf("breaking change: meta-commands now use ':' rather than '\\' as prefix " +
"to align with the rest of the Neo4j ecosystem"))
break
} else if tok == ':' {
parseMetaCommand(&output, c)
} else if tok == '\n' {
c.Next()
Expand All @@ -43,7 +47,7 @@ func Parse(filename, script string, weight float64) (Script, error) {
}

func parseMetaCommand(s *Script, c *parseContext) {
expect(c, '\\')
expect(c, ':')
cmd := ident(c)

switch cmd {
Expand Down Expand Up @@ -79,7 +83,7 @@ func parseMetaCommand(s *Script, c *parseContext) {
case "us":
unit = time.Microsecond
default:
c.fail(fmt.Errorf("\\sleep command must use 'us', 'ms', or 's' unit argument - or none. got: %s", unitStr))
c.fail(fmt.Errorf(":sleep command must use 'us', 'ms', or 's' unit argument - or none. got: %s", unitStr))
}
}
s.Commands = append(s.Commands, SleepCommand{
Expand Down
Loading

0 comments on commit 403c671

Please sign in to comment.