From b9f000620733b35bc26b451449b15eb02886a175 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 07:09:33 -0500 Subject: [PATCH 01/10] Strategy#run no longer requires a driver object --- src/Strategy.js | 2 +- src/read-strategy/AggregateReadStrategy.js | 4 ++-- src/read-strategy/LongPathReadStrategy.js | 4 ++-- src/read-strategy/MetadataReadStrategy.js | 2 +- src/read-strategy/RandomAccessReadStrategy.js | 4 ++-- src/write-strategy/FatNodeAppendStrategy.js | 8 ++------ src/write-strategy/IndexHeavyStrategy.js | 2 +- src/write-strategy/LockTortureStrategy.js | 2 +- src/write-strategy/LuceneWriteStrategy.js | 6 +----- src/write-strategy/MergeWriteStrategy.js | 2 +- src/write-strategy/NAryTreeStrategy.js | 2 +- src/write-strategy/RandomLinkageStrategy.js | 4 ++-- src/write-strategy/RawWriteStrategy.js | 4 ++-- src/write-strategy/StarWriteStrategy.js | 2 +- src/write-strategy/WritePropertyDataStrategy.js | 4 ++-- test/read-strategy/read-strategy_test.js | 2 +- test/write-strategy/write-strategy_test.js | 2 +- 17 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/Strategy.js b/src/Strategy.js index 5af7bbc..eb269e0 100644 --- a/src/Strategy.js +++ b/src/Strategy.js @@ -15,7 +15,7 @@ class Strategy { } getName() { return this.name; } - run(driver) { + run() { return Promise.reject('Override me in subclass'); } diff --git a/src/read-strategy/AggregateReadStrategy.js b/src/read-strategy/AggregateReadStrategy.js index 6ffa39e..bc12b2e 100644 --- a/src/read-strategy/AggregateReadStrategy.js +++ b/src/read-strategy/AggregateReadStrategy.js @@ -8,8 +8,8 @@ class AggregateReadStrategy extends Strategy { this.name = 'AggregateRead'; } - run(driver) { - const f = (s = driver.session()) => s.readTransaction(tx => tx.run(` + run() { + const f = (s) => s.readTransaction(tx => tx.run(` MATCH (v:NAryTree) WHERE id(v) % $r = 0 RETURN min(v.val), max(v.val), stdev(v.val), count(v.val)`, diff --git a/src/read-strategy/LongPathReadStrategy.js b/src/read-strategy/LongPathReadStrategy.js index 3789893..379f0f0 100644 --- a/src/read-strategy/LongPathReadStrategy.js +++ b/src/read-strategy/LongPathReadStrategy.js @@ -8,10 +8,10 @@ class LongPathReadStrategy extends Strategy { this.name = 'LongPathRead'; } - run(driver) { + run() { const start = 1 + this.randInt(1000); - const f = (s = driver.session()) => s.readTransaction(tx => tx.run(` + const f = (s) => s.readTransaction(tx => tx.run(` MATCH p=(s:NAryTree { val: $start })-[r:child*]->(e:NAryTree { val: $end }) RETURN count(r)`, { start, end: start + this.randInt(500) })); diff --git a/src/read-strategy/MetadataReadStrategy.js b/src/read-strategy/MetadataReadStrategy.js index 5c61c2f..9970352 100644 --- a/src/read-strategy/MetadataReadStrategy.js +++ b/src/read-strategy/MetadataReadStrategy.js @@ -8,7 +8,7 @@ class MetadataReadStrategy extends Strategy { this.name = 'MetadataRead'; } - run(driver) { + run() { const i = this.randInt(50); const f = (s) => { diff --git a/src/read-strategy/RandomAccessReadStrategy.js b/src/read-strategy/RandomAccessReadStrategy.js index 37d1c4d..13ea53f 100644 --- a/src/read-strategy/RandomAccessReadStrategy.js +++ b/src/read-strategy/RandomAccessReadStrategy.js @@ -8,11 +8,11 @@ class RandomAccessReadStrategy extends Strategy { this.limit = props.limit || 50; } - run(driver) { + run() { const nth = Math.floor(Math.random() * this.prime) + 1; const skip = Math.floor(Math.random() * 1000) + 1; - const f = (s = driver.session()) => s.readTransaction(tx => tx.run(` + const f = (s) => s.readTransaction(tx => tx.run(` MATCH (node) WHERE id(node) % $prime = $nth RETURN keys(node) diff --git a/src/write-strategy/FatNodeAppendStrategy.js b/src/write-strategy/FatNodeAppendStrategy.js index 988e1f7..926fed0 100644 --- a/src/write-strategy/FatNodeAppendStrategy.js +++ b/src/write-strategy/FatNodeAppendStrategy.js @@ -9,11 +9,7 @@ class FatNodeAppendStrategy extends Strategy { this.label = props.label; } - run(driver) { - if (!this.session) { - this.session = driver.session(); - } - + run() { const p = this.randInt(10000000); const r = p - 10000; @@ -29,7 +25,7 @@ class FatNodeAppendStrategy extends Strategy { }))`; this.lastParams = { uuid: uuid.v4(), data }; - const f = (s = driver.session()) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); + const f = (s) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); return this.time(f); } } diff --git a/src/write-strategy/IndexHeavyStrategy.js b/src/write-strategy/IndexHeavyStrategy.js index 2567525..6325b1f 100644 --- a/src/write-strategy/IndexHeavyStrategy.js +++ b/src/write-strategy/IndexHeavyStrategy.js @@ -44,7 +44,7 @@ class IndexHeavyStrategy extends Strategy { .then(() => session.close()); } - run(driver) { + run() { const id = uuid.v4(); const q = ` MERGE (c:Customer { username: $username }) diff --git a/src/write-strategy/LockTortureStrategy.js b/src/write-strategy/LockTortureStrategy.js index 0b84948..e09ff93 100644 --- a/src/write-strategy/LockTortureStrategy.js +++ b/src/write-strategy/LockTortureStrategy.js @@ -31,7 +31,7 @@ class LockTortureStrategy extends Strategy { .then(() => session.close()); } - run(driver) { + run() { const id1 = this.randInt(this.n); const id2 = this.randInt(this.n); diff --git a/src/write-strategy/LuceneWriteStrategy.js b/src/write-strategy/LuceneWriteStrategy.js index 60bc343..535750f 100644 --- a/src/write-strategy/LuceneWriteStrategy.js +++ b/src/write-strategy/LuceneWriteStrategy.js @@ -26,11 +26,7 @@ class LuceneWriteStrategy extends Strategy { .then(() => session.close()); } - run(driver) { - if (!this.session) { - this.session = driver.session(); - } - + run() { const p = this.randInt(10000000); const r = p - 10000; diff --git a/src/write-strategy/MergeWriteStrategy.js b/src/write-strategy/MergeWriteStrategy.js index 1ac70cc..3fe203c 100644 --- a/src/write-strategy/MergeWriteStrategy.js +++ b/src/write-strategy/MergeWriteStrategy.js @@ -22,7 +22,7 @@ class MergeWriteStrategy extends Strategy { .then(() => session.close()); } - run(driver) { + run() { this.lastQuery = ` MERGE (n:MergeNode { id: $id1 }) ON CREATE SET n.uuid = $u1 SET n:SimpleWrite MERGE (p:MergeNode { id: $id2 }) ON CREATE SET p.uuid = $u2 SET p:SimpleWrite diff --git a/src/write-strategy/NAryTreeStrategy.js b/src/write-strategy/NAryTreeStrategy.js index ed45e6b..32394df 100644 --- a/src/write-strategy/NAryTreeStrategy.js +++ b/src/write-strategy/NAryTreeStrategy.js @@ -22,7 +22,7 @@ class NAryTreeStrategy extends Strategy { .then(() => session.close()); } - run(driver) { + run() { this.tracker = (this.tracker || 1) + 1; this.lastParams = { tracker: this.tracker }; diff --git a/src/write-strategy/RandomLinkageStrategy.js b/src/write-strategy/RandomLinkageStrategy.js index 7915b15..0a5e0bd 100644 --- a/src/write-strategy/RandomLinkageStrategy.js +++ b/src/write-strategy/RandomLinkageStrategy.js @@ -9,7 +9,7 @@ class RandomLinkageStrategy extends Strategy { this.n = props.n || 1000000; } - run(driver) { + run() { this.lastQuery = ` MATCH (a) @@ -28,7 +28,7 @@ class RandomLinkageStrategy extends Strategy { idx2: this.randInt(this.n), id: uuid.v4(), }; - const f = (s = driver.session()) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); + const f = (s) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); return this.time(f); } } diff --git a/src/write-strategy/RawWriteStrategy.js b/src/write-strategy/RawWriteStrategy.js index 6753d41..a5af553 100644 --- a/src/write-strategy/RawWriteStrategy.js +++ b/src/write-strategy/RawWriteStrategy.js @@ -21,7 +21,7 @@ class RawWriteStrategy extends Strategy { // .then(() => session.close()); // } - run(driver) { + run() { this.lastQuery = ` FOREACH (id IN range(0,${this.n}) | CREATE (:RawWriteNode { @@ -38,7 +38,7 @@ class RawWriteStrategy extends Strategy { );`; this.lastParams = { uuid: uuid.v4() }; - const f = (s = driver.session()) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); + const f = (s) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); return this.time(f); } } diff --git a/src/write-strategy/StarWriteStrategy.js b/src/write-strategy/StarWriteStrategy.js index c75a120..aaa3a5e 100644 --- a/src/write-strategy/StarWriteStrategy.js +++ b/src/write-strategy/StarWriteStrategy.js @@ -12,7 +12,7 @@ class StarWriteStrategy extends Strategy { this.n = props.n || MAX_STAR_SIZE; } - run(driver) { + run() { const id = uuid.v4(); const q = ` CREATE (h:Hub { id: $id }) diff --git a/src/write-strategy/WritePropertyDataStrategy.js b/src/write-strategy/WritePropertyDataStrategy.js index 4f5f93a..377d6bc 100644 --- a/src/write-strategy/WritePropertyDataStrategy.js +++ b/src/write-strategy/WritePropertyDataStrategy.js @@ -20,7 +20,7 @@ class WritePropertyDataStrategy extends Strategy { .then(() => session.close()); } - run(driver) { + run() { const p = this.randInt(10000); const r = p - 1000; @@ -29,7 +29,7 @@ class WritePropertyDataStrategy extends Strategy { data.push(uuid.v4()); } - const f = (s = driver.session()) => s.writeTransaction(tx => tx.run(` + const f = (s) => s.writeTransaction(tx => tx.run(` MATCH (a:Node) WHERE a.id >= $r and a.id <= $p WITH a LIMIT 100 SET a.list${randInt(100)} = $data SET a:WriteArray diff --git a/test/read-strategy/read-strategy_test.js b/test/read-strategy/read-strategy_test.js index e3c10d4..9ed226a 100644 --- a/test/read-strategy/read-strategy_test.js +++ b/test/read-strategy/read-strategy_test.js @@ -39,7 +39,7 @@ describe('Read Strategies', function() { it('has a setup method', () => expect(s.setup(driver)).to.be.fulfilled); it('runs exactly 1 read query, and that run is timed.', () => - s.run(driver) + s.run() .then(results => { expect(sp.inUse).to.equal(0); expect(sp.session.reads).to.equal(1); diff --git a/test/write-strategy/write-strategy_test.js b/test/write-strategy/write-strategy_test.js index 350d680..2e82197 100644 --- a/test/write-strategy/write-strategy_test.js +++ b/test/write-strategy/write-strategy_test.js @@ -49,7 +49,7 @@ describe('Write Strategies', function() { it('has a setup method', () => expect(s.setup(driver)).to.be.fulfilled); it('runs exactly 1 write query, and for it to be timed.', () => - s.run(driver) + s.run() .then(results => { expect(sp.inUse).to.equal(0); expect(sp.session.reads).to.equal(0); From fed17968b0c020b1825e47ad15275b699d17c305 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 07:24:43 -0500 Subject: [PATCH 02/10] remove text coverage report --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c3689f3..62ef02b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/run-workload.js", "scripts": { "graph-workload": "node src/run-workload.js", - "test": "nyc --reporter=lcov --reporter=text-lcov mocha --recursive" + "test": "nyc --reporter=lcov mocha --recursive" }, "repository": { "type": "git", From f94606fc6a3d5233caa5b2547682dbf170e5e2e3 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 07:24:50 -0500 Subject: [PATCH 03/10] simple query strategy first commit --- src/SimpleQueryStrategy.js | 34 +++++++++++++++++ test/SimpleQueryStrategy_test.js | 65 ++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/SimpleQueryStrategy.js create mode 100644 test/SimpleQueryStrategy_test.js diff --git a/src/SimpleQueryStrategy.js b/src/SimpleQueryStrategy.js new file mode 100644 index 0000000..a2354af --- /dev/null +++ b/src/SimpleQueryStrategy.js @@ -0,0 +1,34 @@ +const Strategy = require('./Strategy'); + +/** + * Represents a container class for a strategy that is just running some + * simple query with no setup. + */ +module.exports = class SimpleQueryStrategy extends Strategy { + constructor(props) { + super(props); + this.name = props.name || 'SimpleQuery'; + this.query = props.query; + this.params = props.params || {}; + + if (!(props.mode === 'READ') && !(props.mode === 'WRITE')) { + throw new Error('Mode must be READ or WRITE'); + } + + this.mode = props.mode; + } + + run() { + const f = (s) => { + const txRunner = tx => tx.run(this.query, this.params); + + if (this.props.mode === 'READ') { + return s.readTransaction(txRunner); + } + + return s.writeTransaction(txRunner); + }; + + return this.time(f); + } +} \ No newline at end of file diff --git a/test/SimpleQueryStrategy_test.js b/test/SimpleQueryStrategy_test.js new file mode 100644 index 0000000..be7db5d --- /dev/null +++ b/test/SimpleQueryStrategy_test.js @@ -0,0 +1,65 @@ +const expect = require('chai').expect; +const SimpleQueryStrategy = require('../src/SimpleQueryStrategy'); +const Promise = require('bluebird'); +const mocks = require('./mocks'); +const chai = require('chai'); +const chaiAsPromised = require("chai-as-promised"); +chai.use(chaiAsPromised); + +describe('Simple Query Strategy', () => { + let s; + const props = { + mode: 'READ', + query: 'RETURN 1', + name: 'PING', + sessionPool: new mocks.MockSessionPool(), + }; + const name = 'FooStrategy'; + + beforeEach(() => { + s = new SimpleQueryStrategy(props); + }); + + it('requires a mode of READ or WRITE', () => { + const badProps = { + query: 'RETURN 1', + mode: 'eh' + }; + + expect(() => new SimpleQueryStrategy(badProps)).to.throw(Error); + badProps.mode = null; + expect(() => new SimpleQueryStrategy(badProps)).to.throw(Error); + const goodProps = { + query: 'RETURN 1', + mode: 'WRITE' + }; + + expect(() => new SimpleQueryStrategy(goodProps)).to.not.throw(Error); + }); + + it('calls readTransaction on mode READ', () => { + props.mode = 'READ'; + props.sessionPool = new mocks.MockSessionPool(); + const sq = new SimpleQueryStrategy(props); + + return sq.run() + .then(() => { + console.log(props.sessionPool.session); + expect(props.sessionPool.session.reads).to.equal(1); + expect(props.sessionPool.session.writes).to.equal(0); + }); + }); + + it('calls writeTransaction on mode WRITE', () => { + props.mode = 'WRITE'; + props.sessionPool = new mocks.MockSessionPool(); + const sq = new SimpleQueryStrategy(props); + + return sq.run() + .then(() => { + console.log(props.sessionPool.session); + expect(props.sessionPool.session.reads).to.equal(0); + expect(props.sessionPool.session.writes).to.equal(1); + }); + }); +}); \ No newline at end of file From 66c7e73dfe005540973e1a3bf8d4765130359fd1 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 07:32:10 -0500 Subject: [PATCH 04/10] break run-config detection out into sections --- src/run-configuration.js | 84 ++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/src/run-configuration.js b/src/run-configuration.js index 9cad8d4..2febd9c 100644 --- a/src/run-configuration.js +++ b/src/run-configuration.js @@ -1,5 +1,6 @@ const terminateAfter = require('./termination-condition'); const ProbabilityTable = require('./stats/ProbabilityTable'); +const _ = require('lodash'); const usageStr = ` Usage: run-workload.js -p password @@ -28,21 +29,11 @@ const defaultProbabilityTable = [ [1, 'rawWrite'], ]; -const generateFromArgs = (args) => { - const badlyConfigured = ( - // User is being inconsistent about when to stop. - (args.n && args.ms) || - // We don't know where to connect... - (!process.env.NEO4J_URI && !args.a) || - // Don't know what password to use... - (!process.env.NEO4J_PASSWORD && !args.p) - ); - - if (badlyConfigured) { - usage(); - } - - const probabilityTable = args.workload ? require(args.workload) : defaultProbabilityTable; +/** + * @param {*} args a yargs object + * @returns { iterateUntil, runType } of when to stop. + */ +const chooseTerminationType = (args) => { let iterateUntil; let runType; @@ -58,25 +49,70 @@ const generateFromArgs = (args) => { runType = 'counted'; } - const p = Number(args.concurrency) || Number(process.env.CONCURRENCY); - - const failFast = ('fail-fast' in args) ? args['fail-fast'] : false; + return { iterateUntil, runType }; +}; +/** + * @param {*} args a yargs object + * @returns { username, password, address } of where to connect + */ +const chooseConnectionDetails = (args) => { const addressify = str => str.indexOf('://') === -1 ? `bolt://${str}` : str; - const obj = { + return { username: args.u || process.env.NEO4J_USER || 'neo4j', password: args.p || process.env.NEO4J_PASSWORD, address: addressify(args.a || process.env.NEO4J_URI), - probabilityTable: new ProbabilityTable(probabilityTable), - runType, - checkpointFreq: args.checkpoint || process.env.CHECKPOINT_FREQUENCY || 5000, + }; +}; + +const chooseWorkload = (args) => { + +} + +const chooseConcurrency = (args) => { + const p = Number(args.concurrency) || Number(process.env.CONCURRENCY); + return { concurrency: (!Number.isNaN(p) && p > 0) ? p : 10, - iterateUntil, + }; +}; + +const chooseProbabilityTable = (args) => { + const ptData = args.workload ? require(args.workload) : defaultProbabilityTable; + + return { + probabilityTable: new ProbabilityTable(ptData) + }; +}; + +const generateFromArgs = (args) => { + const badlyConfigured = ( + // User is being inconsistent about when to stop. + (args.n && args.ms) || + // We don't know where to connect... + (!process.env.NEO4J_URI && !args.a) || + // Don't know what password to use... + (!process.env.NEO4J_PASSWORD && !args.p) + ); + + if (badlyConfigured) { + usage(); + } + + const terminationType = chooseTerminationType(args); + const connectionDetails = chooseConnectionDetails(args); + const concurrency = chooseConcurrency(args); + const probabilityTable = chooseProbabilityTable(args); + + const failFast = ('fail-fast' in args) ? args['fail-fast'] : false; + + // Merge sub-objects. + const obj = _.merge({ + checkpointFreq: args.checkpoint || process.env.CHECKPOINT_FREQUENCY || 5000, failFast, phase: 'NOT_STARTED', - }; + }, terminationType, probabilityTable, connectionDetails, concurrency); if (obj.runType === 'counted') { obj.n = args.n || 10000; From 607dffb1cfe2b1bd4fd0ece426924af181e588f7 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 09:04:16 -0500 Subject: [PATCH 05/10] return labels --- src/stats/ProbabilityTable.js | 6 ++++++ test/stats/ProbabilityTable_test.js | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/stats/ProbabilityTable.js b/src/stats/ProbabilityTable.js index 80d9aa9..c854262 100644 --- a/src/stats/ProbabilityTable.js +++ b/src/stats/ProbabilityTable.js @@ -1,3 +1,5 @@ +const _ = require('lodash'); + /** * Represents a table of probabilities, and different chosen outcomes. * 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 { } } + getLabels() { + return _.uniq(this.data.map(row => row[1])); + } + choose() { const roll = Math.random(); diff --git a/test/stats/ProbabilityTable_test.js b/test/stats/ProbabilityTable_test.js index bc9b017..b63c1de 100644 --- a/test/stats/ProbabilityTable_test.js +++ b/test/stats/ProbabilityTable_test.js @@ -54,4 +54,19 @@ describe('Stats', function() { expect(choice).to.be.oneOf([HEADS, TAILS]); } }); + + it('can return unique labels', () => { + const pt = new ProbabilityTable([ + [ 0.1, 'cat' ], + [ 0.2, 'cat' ], + [ 0.5, 'dog' ], + [ 1, 'fox' ], + ]); + + const labels = pt.getLabels(); + expect(labels).to.contain('cat'); + expect(labels).to.contain('dog'); + expect(labels).to.contain('fox'); + expect(labels.length).to.equal(3); + }); }); \ No newline at end of file From 9183f1ff2133c6f16474e213531d592db9473cc3 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 09:04:30 -0500 Subject: [PATCH 06/10] fix writes that fail under high concurrency because tracker gets too high in value --- src/write-strategy/NAryTreeStrategy.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/write-strategy/NAryTreeStrategy.js b/src/write-strategy/NAryTreeStrategy.js index 32394df..5ddd767 100644 --- a/src/write-strategy/NAryTreeStrategy.js +++ b/src/write-strategy/NAryTreeStrategy.js @@ -4,8 +4,9 @@ const Promise = require('bluebird'); class NAryTreeStrategy extends Strategy { constructor(props) { super(props); - this.n = props.n; + this.n = props.n || 2; this.name = 'NAryTree'; + this.tracker = -10; } setup(driver) { @@ -23,14 +24,14 @@ class NAryTreeStrategy extends Strategy { } run() { - this.tracker = (this.tracker || 1) + 1; + // this.tracker++; this.lastParams = { tracker: this.tracker }; this.lastQuery = ` MATCH (p:Leaf) WHERE p.val >= $tracker - WITH p ORDER BY p.val DESC - LIMIT 10 + WITH p ORDER BY p.val DESC, rand() + LIMIT ${this.n} WHERE NOT (p)-[:child]->(:NAryTree) WITH p REMOVE p:Leaf @@ -41,7 +42,7 @@ class NAryTreeStrategy extends Strategy { ` }).join('\n') } - RETURN p.val; + RETURN count(p) as value; `; const f = (s) => s.writeTransaction(tx => tx.run(this.lastQuery, this.lastParams)); From 108f3c52727a5de8039f7290fcfcf678b12540cc Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 09:04:40 -0500 Subject: [PATCH 07/10] build strategy table from runConfig --- src/strategies.js | 71 ++++++++++++++++++++++++++++++----------- test/strategies_test.js | 13 ++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/strategies.js b/src/strategies.js index aceeab7..4d5bc7e 100644 --- a/src/strategies.js +++ b/src/strategies.js @@ -1,6 +1,7 @@ /** * Library of strategies for easy inclusion. */ +const SimpleQueryStrategy = require('./SimpleQueryStrategy'); const NAryTreeStrategy = require('./write-strategy/NAryTreeStrategy'); const FatNodeAppendStrategy = require('./write-strategy/FatNodeAppendStrategy'); const MergeWriteStrategy = require('./write-strategy/MergeWriteStrategy'); @@ -16,28 +17,60 @@ const LongPathReadStrategy = require('./read-strategy/LongPathReadStrategy'); const RandomAccessReadStrategy = require('./read-strategy/RandomAccessReadStrategy'); /** - * Produces a strategy table + * Produces a strategy table by looking through the probability table and assigning + * only those strategies that are needed. * @param {SessionPool} sessionPool + * @param {runConfig} a runconfig object * @returns {Object} key is strategy name, value is a Strategy instance object. */ -const builder = sessionPool => ({ - // WRITE STRATEGIES - naryWrite: new NAryTreeStrategy({ n: 2, sessionPool }), - fatnodeWrite: new FatNodeAppendStrategy({ sessionPool }), - mergeWrite: new MergeWriteStrategy({ n: 1000000, sessionPool }), - rawWrite: new RawWriteStrategy({ n: 10, sessionPool }), - randomLinkage: new RandomLinkageStrategy({ n: 1000000, sessionPool }), - starWrite: new StarWriteStrategy({ sessionPool }), - indexHeavy: new IndexHeavyStrategy({ sessionPool }), - lockTorture: new LockTortureStrategy({ sessionPool }), - luceneWrite: new LuceneWriteStrategy({ sessionPool }), +const builder = (sessionPool, runConfig) => { + const defaultStratTable = { + // WRITE STRATEGIES + naryWrite: new NAryTreeStrategy({ n: 2, sessionPool }), + fatnodeWrite: new FatNodeAppendStrategy({ sessionPool }), + mergeWrite: new MergeWriteStrategy({ n: 1000000, sessionPool }), + rawWrite: new RawWriteStrategy({ n: 10, sessionPool }), + randomLinkage: new RandomLinkageStrategy({ n: 1000000, sessionPool }), + starWrite: new StarWriteStrategy({ sessionPool }), + indexHeavy: new IndexHeavyStrategy({ sessionPool }), + lockTorture: new LockTortureStrategy({ sessionPool }), + luceneWrite: new LuceneWriteStrategy({ sessionPool }), - // READ STRATEGIES - aggregateRead: new AggregateReadStrategy({ sessionPool }), - metadataRead: new MetadataReadStrategy({ sessionPool }), - longPathRead: new LongPathReadStrategy({ sessionPool }), - randomAccess: new RandomAccessReadStrategy({ sessionPool }), -}); + // READ STRATEGIES + aggregateRead: new AggregateReadStrategy({ sessionPool }), + metadataRead: new MetadataReadStrategy({ sessionPool }), + longPathRead: new LongPathReadStrategy({ sessionPool }), + randomAccess: new RandomAccessReadStrategy({ sessionPool }), + }; + + if (runConfig) { + // By choosing which strats are in the table (by probability table) + // We're limiting which setup methods get run too. + const labels = runConfig.probabilityTable.getLabels(); + const chosenTable = {}; + + labels.forEach(label => { + const strat = defaultStratTable[label]; + + if (label === 'custom') { + chosenTable[label] = new SimpleQueryStrategy({ + sessionPool, + query: runConfig.query, + mode: runConfig.mode || 'WRITE', + }); + return; + } else if(!strat) { + throw new Error(`Probability table references strat ${label} which is unknown`); + } + + chosenTable[label] = strat; + }); + + return chosenTable; + } + + return defaultStratTable; +}; /** * Reports key stats about strategies @@ -66,5 +99,5 @@ const showLastQuery = strategyTable => { }; module.exports = { - builder, report, showLastQuery, + builder, report, showLastQuery, }; \ No newline at end of file diff --git a/test/strategies_test.js b/test/strategies_test.js index 1c0d8ef..41c5cac 100644 --- a/test/strategies_test.js +++ b/test/strategies_test.js @@ -1,12 +1,19 @@ const expect = require('chai').expect; const assert = require('chai').assert; const strategies = require('../src/strategies'); +const ProbabilityTable = require('../src/stats/ProbabilityTable'); const Strategy = require('../src/Strategy'); const mocks = require('./mocks'); describe('Strategies', function() { const pool = new mocks.MockSessionPool(); let table; + let mockRunConfig = { + probabilityTable: new ProbabilityTable([ + [ 1.0, 'custom'], + ]), + query: "RETURN 1", + }; beforeEach(() => { table = strategies.builder(pool); @@ -18,6 +25,12 @@ describe('Strategies', function() { Object.values(table).forEach(strat => expect(strat).to.be.instanceOf(Strategy)); }); + it('should build a strategy table with only things in the probability table', () => { + const tbl = strategies.builder(pool, mockRunConfig); + expect(Object.keys(tbl).length).to.equal(1); + expect(tbl['custom']).to.be.instanceOf(Strategy); + }); + it('can report', () => { strategies.report(table); }); From 1610e3f5a3b1ed105cf76f687d6fa7ea081d9ff9 Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 09:04:55 -0500 Subject: [PATCH 08/10] allow passing a single query as a workload --- src/run-configuration.js | 26 ++++++++++++++++++++------ src/workload.js | 2 +- test/run-configuration_test.js | 10 ++++++++++ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/run-configuration.js b/src/run-configuration.js index 2febd9c..4a31702 100644 --- a/src/run-configuration.js +++ b/src/run-configuration.js @@ -9,11 +9,13 @@ Usage: run-workload.js -p password [-n hits] how many total queries to run [--ms milliseconds] how many milliseconds to test for [--workload /path/to/workload.json] probability table spec + [--query CYPHER_QUERY] single cypher query to run [--concurrency c] how many concurrent queries to run (default: 10) [--checkpoint cn] how often to print results in milliseconds (default: 5000) [--fail-fast] if specified, the work will stop after encountering one failure. You may only specify one of the options --n or --ms. +You may only specify one of the options --workload or --query `; const defaultProbabilityTable = [ @@ -67,10 +69,6 @@ const chooseConnectionDetails = (args) => { }; }; -const chooseWorkload = (args) => { - -} - const chooseConcurrency = (args) => { const p = Number(args.concurrency) || Number(process.env.CONCURRENCY); return { @@ -79,17 +77,32 @@ const chooseConcurrency = (args) => { }; const chooseProbabilityTable = (args) => { - const ptData = args.workload ? require(args.workload) : defaultProbabilityTable; + let ptData = args.workload ? require(args.workload) : defaultProbabilityTable; - return { + if (args.query) { + // Always run the custom query. + ptData = [ + [ 1.0, 'custom' ], + ]; + } + + const result = { probabilityTable: new ProbabilityTable(ptData) }; + + if (args.query) { + result.query = args.query; + } + + return result; }; const generateFromArgs = (args) => { const badlyConfigured = ( // User is being inconsistent about when to stop. (args.n && args.ms) || + // Trying to specify to both run a single query and a different workload... + (args.query && args.workload) || // We don't know where to connect... (!process.env.NEO4J_URI && !args.a) || // Don't know what password to use... @@ -137,6 +150,7 @@ module.exports = { .describe('n', 'number of hits on the database') .describe('ms', 'number of milliseconds to execute') .describe('workload', 'absolute path to JSON probability table/workload') + .describe('query', 'Cypher query to run') .default('concurrency', 10) .default('checkpoint', 5000) .demandOption(['p']) diff --git a/src/workload.js b/src/workload.js index 63878e1..b665feb 100644 --- a/src/workload.js +++ b/src/workload.js @@ -66,7 +66,7 @@ class Workload { neo4j.auth.basic(this.runConfig.username, this.runConfig.password)); this.sessionPool = pool.getPool(this.driver, this.runConfig.concurrency); - this.strategyTable = strategies.builder(this.sessionPool); + this.strategyTable = strategies.builder(this.sessionPool, this.runConfig); this.stats = new WorkloadStats(this.runConfig); this.promisePool = new PromisePool(promiseProducer(this), this.runConfig.concurrency); diff --git a/test/run-configuration_test.js b/test/run-configuration_test.js index f90071a..5de1cab 100644 --- a/test/run-configuration_test.js +++ b/test/run-configuration_test.js @@ -43,4 +43,14 @@ describe('Run Configuration', function() { expect(c.runType).to.equal('timed'); expect(c.ms).to.equal(newArgs.ms); }); + + it('returns a query and fixed probability table when asked', () => { + const newArgs = _.merge({ + query: 'RETURN 1;' + }, args); + + const c = runConfig.generateFromArgs(newArgs); + expect(c.query).to.equal(newArgs.query); + expect(c.probabilityTable.choose()).to.equal('custom'); + }); }); \ No newline at end of file From 6002622d9a1a31e9c7211b49fbf232aba1e7edba Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 09:05:01 -0500 Subject: [PATCH 09/10] examples --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 33c1ac7..d01a9bb 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,14 @@ See the `workloads` directory for the format of the probability table. 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 program arguments, as in, `npm run graph-workload -- --n 20` +# Examples + +Create a lot of nodes as fast as possible: + +``` +npm run graph-workload -- -a localhost -u neo4j -p admin --query 'Unwind range(1,1000000) as id create (n);' -n 50 --concurrency 4 +``` + # Tests ``` From 39d4e58ca9c82b51dd6f7319860cd9311ffcd58a Mon Sep 17 00:00:00 2001 From: Moxious Date: Tue, 26 Feb 2019 09:05:06 -0500 Subject: [PATCH 10/10] nary only workload --- src/workloads/nary-only.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/workloads/nary-only.json diff --git a/src/workloads/nary-only.json b/src/workloads/nary-only.json new file mode 100644 index 0000000..ffc3100 --- /dev/null +++ b/src/workloads/nary-only.json @@ -0,0 +1,3 @@ +[ + [ 1.0, "naryWrite" ] +] \ No newline at end of file