whats-the-damage
Version:
What's the damage? ...of running that script in real time, CPU time, and memory usage
315 lines (263 loc) • 9.46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getEnvironment = undefined;
var _path = require("path");
var _path2 = _interopRequireDefault(_path);
var _child_process = require("child_process");
var _statsLite = require("stats-lite");
var _statsLite2 = _interopRequireDefault(_statsLite);
var _os = require("os");
var _os2 = _interopRequireDefault(_os);
var _pidusage = require("pidusage");
var _pidusage2 = _interopRequireDefault(_pidusage);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
/*
* HELLO READER OF SOURCE CODE
*
* The two interesting functions in here are the default export
* at the very bottom of this file called DamageOf
*
* And forker which is just below this.
*/
var harnessPath = _path2.default.join(__dirname, "./harness.js");
var forker = function forker(script, opts) {
if (global.gc) {
global.gc(); // attempt to free any memory before forking
}
var args = Array.isArray(script) ? script : [script];
var timeStart = process.hrtime();
var forkee = (0, _child_process.fork)(harnessPath, args, { silent: true });
var closed = false;
return new Promise(function (resolve, reject) {
var messages = {};
var snapshots = [];
forkee.on("message", function (message) {
var keys = Object.keys(message);
keys.forEach(function (key) {
switch (key) {
case "memoryStart":
case "memoryEnd":
case "cpuUsageEnd":
messages[key] = message[key];
break;
default:
console.log("Unknown message from", script, `message[${key}]=`, message[key]);
}
});
});
var watchPid = function watchPid() {
if (closed) return;
_pidusage2.default.stat(forkee.pid, function (err, stat) {
var hrtime = process.hrtime(timeStart);
var snapshot = Object.assign({}, stat, {
time: parseFloat(hrtime.join("."))
});
snapshots.push(snapshot);
});
setTimeout(watchPid, opts.snapshotEveryMilliseconds);
};
watchPid();
var messageToString = function messageToString(data) {
return data instanceof Buffer ? data.toString("utf8") : data;
};
forkee.stdout.on("data", function (data) {
console.log("stdout from", script, "(ignoring): ", messageToString(data));
});
forkee.stderr.on("data", function (data) {
console.log("stderr from", script, "(failing)", messageToString(data));
reject({ error: data });
});
forkee.on("close", function (exitCode) {
var duration = process.hrtime(timeStart);
closed = true;
setTimeout(function () {
resolve({
time: parseFloat(duration.join(".")),
memory: memoryDiff(messages.memoryStart, messages.memoryEnd),
cpu: messages.cpuUsageEnd,
snapshot: {
everyMilliseconds: opts.snapshotEveryMilliseconds,
snapshots
},
exitCode
});
}, opts.snapshotEveryMilliseconds); // wait until any snapshot might finish
});
});
};
var memoryDiff = function memoryDiff(start, end) {
return Object.assign.apply(Object, [{}].concat(_toConsumableArray(Object.keys(start).map(function (key) {
return {
[key]: end[key] - start[key]
};
}))));
};
var runScriptsOnce = function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(scripts, opts) {
var damages, i;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
if (!opts.async) {
_context.next = 6;
break;
}
_context.next = 3;
return Promise.all(scripts.map(function (script) {
return forker(script, opts);
}));
case 3:
return _context.abrupt("return", _context.sent);
case 6:
// synchronous ... more accurate (less competition for resources)
damages = [];
i = 0;
case 8:
if (!(i < scripts.length)) {
_context.next = 17;
break;
}
_context.t0 = damages;
_context.next = 12;
return forker(scripts[i], opts);
case 12:
_context.t1 = _context.sent;
_context.t0.push.call(_context.t0, _context.t1);
case 14:
i++;
_context.next = 8;
break;
case 17:
return _context.abrupt("return", damages);
case 18:
case "end":
return _context.stop();
}
}
}, _callee, undefined);
}));
return function runScriptsOnce(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
var averageDamagesRows = function averageDamagesRows(damagesRows, options) {
return damagesRows[0].map(function (damageResponse, i) {
return {
time: getAverages(damagesRows.map(function (d) {
return d[i].time;
})),
cpu: {
user: getAverages(damagesRows.map(function (d) {
return d[i].cpu.user;
})),
system: getAverages(damagesRows.map(function (d) {
return d[i].cpu.system;
}))
},
memory: {
rss: getAverages(damagesRows.map(function (d) {
return d[i].memory.rss;
})),
heapTotal: getAverages(damagesRows.map(function (d) {
return d[i].memory.heapTotal;
})),
heapUsed: getAverages(damagesRows.map(function (d) {
return d[i].memory.heapUsed;
})),
external: getAverages(damagesRows.map(function (d) {
return d[i].memory.external;
}))
},
snapshot: {
everyMilliseconds: damageResponse.snapshot.everyMilliseconds, // constant, no need to average
snapshots: damageResponse.snapshot.snapshots.map(function (d, snapshotIndex) {
return {
cpu: getAverages(damagesRows.map(function (d) {
return d[i].snapshot.snapshots[snapshotIndex].cpu;
})),
memory: getAverages(damagesRows.map(function (d) {
return d[i].snapshot.snapshots[snapshotIndex].memory;
})),
time: getAverages(damagesRows.map(function (d) {
return d[i].snapshot.snapshots[snapshotIndex].time;
}))
};
})
},
exitCode: damageResponse.exitCode,
repeat: options.repeat
};
});
};
var getAverages = function getAverages(values) {
return {
mean: _statsLite2.default.mean(values),
median: _statsLite2.default.median(values),
standardDeviation: _statsLite2.default.stdev(values),
max: Math.max.apply(Math, _toConsumableArray(values)),
min: Math.min.apply(Math, _toConsumableArray(values))
};
};
var defaultOptions = {
async: false, // running tests in parallel will cause eratic stats. not recommended.
snapshotEveryMilliseconds: 1000,
repeat: 10,
progress: function progress() {
var _console;
(_console = console).log.apply(_console, arguments);
}
};
var getEnvironment = exports.getEnvironment = function getEnvironment() {
return {
versions: process.versions,
arch: _os2.default.arch(),
cpus: _os2.default.cpus(),
totalmem: _os2.default.totalmem(),
type: _os2.default.type()
};
};
var DamageOf = function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee2(scripts, options) {
var opts, damagesRows, i;
return regeneratorRuntime.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
opts = Object.assign({}, defaultOptions, options);
damagesRows = [];
i = 0;
case 3:
if (!(i < opts.repeat)) {
_context2.next = 13;
break;
}
_context2.t0 = damagesRows;
_context2.next = 7;
return runScriptsOnce(scripts, opts);
case 7:
_context2.t1 = _context2.sent;
_context2.t0.push.call(_context2.t0, _context2.t1);
if (opts.progress) opts.progress("Test loop", i);
case 10:
i++;
_context2.next = 3;
break;
case 13:
return _context2.abrupt("return", averageDamagesRows(damagesRows, opts));
case 14:
case "end":
return _context2.stop();
}
}
}, _callee2, undefined);
}));
return function DamageOf(_x3, _x4) {
return _ref2.apply(this, arguments);
};
}();
exports.default = DamageOf;