UNPKG

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
"use strict"; 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;