artillery
Version:
Cloud-scale load testing. https://www.artillery.io
113 lines (93 loc) • 3.42 kB
text/typescript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import createDebug from 'debug';
const _debug = createDebug('plugin:apdex');
const METRICS = {
satisfied: 'apdex.satisfied',
tolerated: 'apdex.tolerated',
frustrated: 'apdex.frustrated'
};
class ApdexPlugin {
// Untyped JS class - properties assigned dynamically
[key: string]: any;
constructor(script, _events) {
this.script = script;
if (
global.artillery &&
Number(global.artillery.version.slice(0, 1)) > 1 &&
typeof process.env.LOCAL_WORKER_ID !== 'undefined'
) {
const t =
script.config.apdex?.threshold ??
script.config.plugins?.apdex?.threshold ??
500;
if (!script.config.processor) {
script.config.processor = {};
}
// In the main thread config.processor may still be an unresolved
// path (a string). Attaching functions to it was a silent no-op
// under sloppy mode; ES modules are strict, so guard explicitly.
// Workers load the processor into an object before plugins run.
const canAttach = typeof script.config.processor === 'object';
script.scenarios.forEach((scenario) => {
scenario.afterResponse = [].concat(scenario.afterResponse || []);
scenario.afterResponse.push('apdexAfterResponse');
});
function apdexAfterResponse(_req, res, _userContext, events, done) {
const total = res.timings.phases.total;
events.emit('counter', METRICS.satisfied, 0);
events.emit('counter', METRICS.tolerated, 0);
events.emit('counter', METRICS.frustrated, 0);
if (total <= t) {
events.emit('counter', METRICS.satisfied, 1);
} else if (total <= 4 * t) {
events.emit('counter', METRICS.tolerated, 1);
} else {
events.emit('counter', METRICS.frustrated, 1);
}
return done();
}
if (canAttach) {
script.config.processor.apdexAfterResponse = apdexAfterResponse;
}
return;
}
global.artillery.ext({
ext: 'beforeExit',
method: async (testInfo) => {
const hasApdexConfig =
this.script?.config?.apdex !== undefined ||
this.script?.config?.plugins?.apdex !== undefined;
if (!hasApdexConfig) {
return;
}
const s = testInfo.report.counters[METRICS.satisfied] || 0;
const t = testInfo.report.counters[METRICS.tolerated] || 0;
const f = testInfo.report.counters[METRICS.frustrated] || 0;
const total = s + t + f;
if (total > 0) {
const apdexScore = (s + t / 2) / total;
let ranking = '';
if (apdexScore >= 0.94) {
ranking = 'excellent';
} else if (apdexScore >= 0.85) {
ranking = 'good';
} else if (apdexScore >= 0.7) {
ranking = 'fair';
} else if (apdexScore >= 0.49) {
ranking = 'poor';
} else {
ranking = 'unacceptable';
}
global.artillery.apdexPlugin = {
apdex: apdexScore,
ranking
};
console.log(`\nApdex score: ${apdexScore} (${ranking})`);
}
}
});
}
}
export { ApdexPlugin as Plugin };