Skip to content

Commit

Permalink
Refactor phases
Browse files Browse the repository at this point in the history
- New faster implementation of rampTo
- Improved test case for rampTo (accurate expected calculation)
- Add regression test for artilleryio/artillery#215
- Use arrivals@2.1.0 (fixes artilleryio/artillery#215)
  • Loading branch information
hassy committed Dec 8, 2016
1 parent 7573049 commit 1176419
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 43 deletions.
90 changes: 58 additions & 32 deletions lib/phases.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const _ = require('lodash');
const isUndefined = _.isUndefined;
const arrivals = require('arrivals');
const debug = require('debug')('phases');
const Nanotimer = require('nanotimer');
const crypto = require('crypto');
const Rolex = require('rolex');

module.exports = phaser;

Expand Down Expand Up @@ -82,45 +83,70 @@ function createPause(spec, ee) {
}

function createRamp(spec, ee) {
// We will increase the arrival rate by 1 every tick seconds.
// The divisor is the number of distinct arrival rates there will be (6 for
// arrivalRate=5 and rampTo=10)
const tick = 1000 / spec.rampTo; // smallest tick
const r0 = spec.arrivalRate; // initial arrival rate
const periods = spec.rampTo - spec.arrivalRate + 1;
const ticksPerPeriod = (spec.duration / periods) * 1000 / tick;
const periodLenSec = spec.duration / periods;

const tick = spec.duration / (spec.rampTo - spec.arrivalRate + 1); // not precise
debug('tick = %s', tick);
const timer1 = new Nanotimer();
const timer2 = new Nanotimer();
let expected = 0;
for(let i = 1; i <= periods; i++) {
let expectedInPeriod = periodLenSec * i;
expected += expectedInPeriod;
}
expected = Math.floor(expected);

return function task(callback) {
ee.emit('phaseStarted', spec);
let currentRate = spec.arrivalRate;

timer1.setInterval(function createArrivalsAtCurrentRate() {
timer2.clearInterval();
console.log(`expecting ${expected} total arrivals`);

const interArrivalInterval = currentRate > 0 ? (1000 / currentRate) : 0;
debug('currentRate = %s', currentRate);
debug('interArrivalInterval = %s', interArrivalInterval);
let probabilities = crypto.randomBytes(spec.duration * 1000 / tick * 1.25);

if (interArrivalInterval > 0) {
timer2.setInterval(function generateArrival() {
ee.emit('arrival');
debug('arrival');
}, '', interArrivalInterval + 'm');
}
debug(`rampTo: tick = ${tick}ms; r0 = ${r0}; periods = ${periods}; ticksPerPeriod = ${ticksPerPeriod}; period length = ${periodLenSec}s`);

if (currentRate <= spec.rampTo) {
currentRate++;
} else {
timer1.clearInterval();
timer1.setTimeout(function() {
timer1.clearTimeout();
timer2.clearInterval();
return function rampTask(callback) {
ee.emit('phaseStarted', spec);
let currentRate = r0;
let p = (periodLenSec * currentRate) / ticksPerPeriod;
let ticksElapsed = 0;

let i = 0;
const timer = setInterval(function maybeArrival() {
let startedAt = Date.now();
if(++ticksElapsed > ticksPerPeriod) {
debug(`ticksElapsed: ${ticksElapsed}; upping probability or stopping`);
if (currentRate < spec.rampTo) {
currentRate++;
ticksElapsed = 0;

p = (periodLenSec * currentRate) / ticksPerPeriod;

debug(`update: currentRate = ${currentRate} - p = ${p}`);
debug(`\texpecting ~${periodLenSec * currentRate} arrivals before updating again`);
} else {
debug(`done: ticksElapsed = ${ticksElapsed}; currentRate = ${currentRate}; spec.rampTo = ${spec.rampTo} `);

clearInterval(timer);
ee.emit('phaseCompleted', spec);

/*
var profile = profiler.stopProfiling();
profile.export(function(err, result) {
if (err) {
console.log(err);
}
fs.writeFileSync('profile1.json.cpuprofile', result); // extension is important
return callback(null);
});
*/

return callback(null);
}, '', '1000m');
}
}

let prob = probabilities[i++] / 256;
if (prob <= p) {
ee.emit('arrival');
}
}, '', Math.floor(tick * 1e9) + 'n');
}, tick);
};
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"license": "MPL-2.0",
"dependencies": {
"JSONPath": "0.11.2",
"arrivals": "latest",
"arrivals": "2.1.0",
"async": "1.5.2",
"cheerio": "0.20.0",
"debug": "2.2.0",
Expand All @@ -32,6 +32,7 @@
"lodash": "4.13.1",
"nanotimer": "0.3.14",
"request": "2.72.0",
"rolex": "https://codeload.github.com/hassy/rolex/tar.gz/master",
"socket.io-client": "1.4.6",
"stats-lite": "2.0.0",
"tough-cookie": "2.2.2",
Expand Down
40 changes: 30 additions & 10 deletions test/unit/phases.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ const util = require('util');
const _ = require('lodash');
const debug = require('debug')('test:phases');

//
// Ref: https://github.com/shoreditch-ops/artillery/issues/215
//
test('GH #215 regression', function(t) {
const phaseSpec = { duration: 2, arrivalRate: 20 };
let phaser = createPhaser([phaseSpec]);
phaser.on('phaseCompleted', function() {
t.comment('+ phaseCompleted event');
});
// The process will lock up if the Node.js bug is triggered and the test
// will time out.
phaser.on('done', function() {
t.comment('+ done event');
t.end();
});
phaser.run();
});

test('pause', function(t) {
const phaseSpec = {pause: 5};

Expand Down Expand Up @@ -87,18 +105,21 @@ test('arrivalCount', function(t) {

test('ramp', function(t) {
const phaseSpec = {
duration: 20,
arrivalRate: 0,
rampTo: 100
duration: 15,
arrivalRate: 1,
rampTo: 20
};
let phaser = createPhaser([phaseSpec]);

let incBy = (phaseSpec.rampTo - phaseSpec.arrivalRate) / (phaseSpec.duration - 1);
let expected = phaseSpec.arrivalRate;
for(let i = 0; i < phaseSpec.duration; i++) {
let tick = 1000 / (phaseSpec.arrivalRate + i * incBy);
expected += Math.floor(1000 / Math.ceil(tick));

let expected = 0;
let periods = phaseSpec.rampTo - phaseSpec.arrivalRate + 1;
let periodLenSec = phaseSpec.duration / periods;
for(let i = 1; i <= periods; i++) {
let expectedInPeriod = periodLenSec * i;
expected += expectedInPeriod;
}
expected = Math.floor(expected);

t.plan(5);

Expand All @@ -120,7 +141,6 @@ test('ramp', function(t) {
'phaseCompleted event emitted with correct spec');
});
phaser.on('arrival', function() {
//debug('+ arrival');
arrivals++;
});
phaser.on('done', function() {
Expand All @@ -132,7 +152,7 @@ test('ramp', function(t) {
debug('expected: %s, arrived: %s', expected, arrivals);

t.assert(
arrivals * 1.1 > expected,
Math.abs(arrivals - expected) <= expected * 0.2, // large allowance
'seen arrivals within expected bounds');

t.end();
Expand Down

0 comments on commit 1176419

Please sign in to comment.