Skip to content

Commit 2d9e2e4

Browse files
authored
Merge pull request #1 from moxious/v0.3.0
V0.3.0
2 parents 5f5e49c + 39d4e58 commit 2d9e2e4

29 files changed

+312
-82
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ See the `workloads` directory for the format of the probability table.
3737
You can use the script `npm run graph-workload` as a synonym for running the index.js file, but keep in mind npm requires an extra `--` argument prior to passing
3838
program arguments, as in, `npm run graph-workload -- --n 20`
3939

40+
# Examples
41+
42+
Create a lot of nodes as fast as possible:
43+
44+
```
45+
npm run graph-workload -- -a localhost -u neo4j -p admin --query 'Unwind range(1,1000000) as id create (n);' -n 50 --concurrency 4
46+
```
47+
4048
# Tests
4149

4250
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "src/run-workload.js",
66
"scripts": {
77
"graph-workload": "node src/run-workload.js",
8-
"test": "nyc --reporter=lcov --reporter=text-lcov mocha --recursive"
8+
"test": "nyc --reporter=lcov mocha --recursive"
99
},
1010
"repository": {
1111
"type": "git",

src/SimpleQueryStrategy.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const Strategy = require('./Strategy');
2+
3+
/**
4+
* Represents a container class for a strategy that is just running some
5+
* simple query with no setup.
6+
*/
7+
module.exports = class SimpleQueryStrategy extends Strategy {
8+
constructor(props) {
9+
super(props);
10+
this.name = props.name || 'SimpleQuery';
11+
this.query = props.query;
12+
this.params = props.params || {};
13+
14+
if (!(props.mode === 'READ') && !(props.mode === 'WRITE')) {
15+
throw new Error('Mode must be READ or WRITE');
16+
}
17+
18+
this.mode = props.mode;
19+
}
20+
21+
run() {
22+
const f = (s) => {
23+
const txRunner = tx => tx.run(this.query, this.params);
24+
25+
if (this.props.mode === 'READ') {
26+
return s.readTransaction(txRunner);
27+
}
28+
29+
return s.writeTransaction(txRunner);
30+
};
31+
32+
return this.time(f);
33+
}
34+
}

src/Strategy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Strategy {
1515
}
1616

1717
getName() { return this.name; }
18-
run(driver) {
18+
run() {
1919
return Promise.reject('Override me in subclass');
2020
}
2121

src/read-strategy/AggregateReadStrategy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ class AggregateReadStrategy extends Strategy {
88
this.name = 'AggregateRead';
99
}
1010

11-
run(driver) {
12-
const f = (s = driver.session()) => s.readTransaction(tx => tx.run(`
11+
run() {
12+
const f = (s) => s.readTransaction(tx => tx.run(`
1313
MATCH (v:NAryTree)
1414
WHERE id(v) % $r = 0
1515
RETURN min(v.val), max(v.val), stdev(v.val), count(v.val)`,

src/read-strategy/LongPathReadStrategy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ class LongPathReadStrategy extends Strategy {
88
this.name = 'LongPathRead';
99
}
1010

11-
run(driver) {
11+
run() {
1212
const start = 1 + this.randInt(1000);
1313

14-
const f = (s = driver.session()) => s.readTransaction(tx => tx.run(`
14+
const f = (s) => s.readTransaction(tx => tx.run(`
1515
MATCH p=(s:NAryTree { val: $start })-[r:child*]->(e:NAryTree { val: $end })
1616
RETURN count(r)`,
1717
{ start, end: start + this.randInt(500) }));

src/read-strategy/MetadataReadStrategy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class MetadataReadStrategy extends Strategy {
88
this.name = 'MetadataRead';
99
}
1010

11-
run(driver) {
11+
run() {
1212
const i = this.randInt(50);
1313

1414
const f = (s) => {

src/read-strategy/RandomAccessReadStrategy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ class RandomAccessReadStrategy extends Strategy {
88
this.limit = props.limit || 50;
99
}
1010

11-
run(driver) {
11+
run() {
1212
const nth = Math.floor(Math.random() * this.prime) + 1;
1313
const skip = Math.floor(Math.random() * 1000) + 1;
1414

15-
const f = (s = driver.session()) => s.readTransaction(tx => tx.run(`
15+
const f = (s) => s.readTransaction(tx => tx.run(`
1616
MATCH (node)
1717
WHERE id(node) % $prime = $nth
1818
RETURN keys(node)

src/run-configuration.js

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const terminateAfter = require('./termination-condition');
22
const ProbabilityTable = require('./stats/ProbabilityTable');
3+
const _ = require('lodash');
34

45
const usageStr = `
56
Usage: run-workload.js -p password
@@ -8,11 +9,13 @@ Usage: run-workload.js -p password
89
[-n hits] how many total queries to run
910
[--ms milliseconds] how many milliseconds to test for
1011
[--workload /path/to/workload.json] probability table spec
12+
[--query CYPHER_QUERY] single cypher query to run
1113
[--concurrency c] how many concurrent queries to run (default: 10)
1214
[--checkpoint cn] how often to print results in milliseconds (default: 5000)
1315
[--fail-fast] if specified, the work will stop after encountering one failure.
1416
1517
You may only specify one of the options --n or --ms.
18+
You may only specify one of the options --workload or --query
1619
`;
1720

1821
const defaultProbabilityTable = [
@@ -28,21 +31,11 @@ const defaultProbabilityTable = [
2831
[1, 'rawWrite'],
2932
];
3033

31-
const generateFromArgs = (args) => {
32-
const badlyConfigured = (
33-
// User is being inconsistent about when to stop.
34-
(args.n && args.ms) ||
35-
// We don't know where to connect...
36-
(!process.env.NEO4J_URI && !args.a) ||
37-
// Don't know what password to use...
38-
(!process.env.NEO4J_PASSWORD && !args.p)
39-
);
40-
41-
if (badlyConfigured) {
42-
usage();
43-
}
44-
45-
const probabilityTable = args.workload ? require(args.workload) : defaultProbabilityTable;
34+
/**
35+
* @param {*} args a yargs object
36+
* @returns { iterateUntil, runType } of when to stop.
37+
*/
38+
const chooseTerminationType = (args) => {
4639
let iterateUntil;
4740
let runType;
4841

@@ -58,25 +51,81 @@ const generateFromArgs = (args) => {
5851
runType = 'counted';
5952
}
6053

61-
const p = Number(args.concurrency) || Number(process.env.CONCURRENCY);
62-
63-
const failFast = ('fail-fast' in args) ? args['fail-fast'] : false;
54+
return { iterateUntil, runType };
55+
};
6456

57+
/**
58+
* @param {*} args a yargs object
59+
* @returns { username, password, address } of where to connect
60+
*/
61+
const chooseConnectionDetails = (args) => {
6562
const addressify = str =>
6663
str.indexOf('://') === -1 ? `bolt://${str}` : str;
6764

68-
const obj = {
65+
return {
6966
username: args.u || process.env.NEO4J_USER || 'neo4j',
7067
password: args.p || process.env.NEO4J_PASSWORD,
7168
address: addressify(args.a || process.env.NEO4J_URI),
72-
probabilityTable: new ProbabilityTable(probabilityTable),
73-
runType,
74-
checkpointFreq: args.checkpoint || process.env.CHECKPOINT_FREQUENCY || 5000,
69+
};
70+
};
71+
72+
const chooseConcurrency = (args) => {
73+
const p = Number(args.concurrency) || Number(process.env.CONCURRENCY);
74+
return {
7575
concurrency: (!Number.isNaN(p) && p > 0) ? p : 10,
76-
iterateUntil,
76+
};
77+
};
78+
79+
const chooseProbabilityTable = (args) => {
80+
let ptData = args.workload ? require(args.workload) : defaultProbabilityTable;
81+
82+
if (args.query) {
83+
// Always run the custom query.
84+
ptData = [
85+
[ 1.0, 'custom' ],
86+
];
87+
}
88+
89+
const result = {
90+
probabilityTable: new ProbabilityTable(ptData)
91+
};
92+
93+
if (args.query) {
94+
result.query = args.query;
95+
}
96+
97+
return result;
98+
};
99+
100+
const generateFromArgs = (args) => {
101+
const badlyConfigured = (
102+
// User is being inconsistent about when to stop.
103+
(args.n && args.ms) ||
104+
// Trying to specify to both run a single query and a different workload...
105+
(args.query && args.workload) ||
106+
// We don't know where to connect...
107+
(!process.env.NEO4J_URI && !args.a) ||
108+
// Don't know what password to use...
109+
(!process.env.NEO4J_PASSWORD && !args.p)
110+
);
111+
112+
if (badlyConfigured) {
113+
usage();
114+
}
115+
116+
const terminationType = chooseTerminationType(args);
117+
const connectionDetails = chooseConnectionDetails(args);
118+
const concurrency = chooseConcurrency(args);
119+
const probabilityTable = chooseProbabilityTable(args);
120+
121+
const failFast = ('fail-fast' in args) ? args['fail-fast'] : false;
122+
123+
// Merge sub-objects.
124+
const obj = _.merge({
125+
checkpointFreq: args.checkpoint || process.env.CHECKPOINT_FREQUENCY || 5000,
77126
failFast,
78127
phase: 'NOT_STARTED',
79-
};
128+
}, terminationType, probabilityTable, connectionDetails, concurrency);
80129

81130
if (obj.runType === 'counted') {
82131
obj.n = args.n || 10000;
@@ -101,6 +150,7 @@ module.exports = {
101150
.describe('n', 'number of hits on the database')
102151
.describe('ms', 'number of milliseconds to execute')
103152
.describe('workload', 'absolute path to JSON probability table/workload')
153+
.describe('query', 'Cypher query to run')
104154
.default('concurrency', 10)
105155
.default('checkpoint', 5000)
106156
.demandOption(['p'])

src/stats/ProbabilityTable.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const _ = require('lodash');
2+
13
/**
24
* Represents a table of probabilities, and different chosen outcomes.
35
* A table is an array of arrays, where the first cell is a float from 0-1,
@@ -35,6 +37,10 @@ module.exports = class ProbabilityTable {
3537
}
3638
}
3739

40+
getLabels() {
41+
return _.uniq(this.data.map(row => row[1]));
42+
}
43+
3844
choose() {
3945
const roll = Math.random();
4046

0 commit comments

Comments
 (0)