@acromedia/sloth
Version:
Resource profiler for node, utilizing child processes
156 lines (155 loc) • 5.93 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const colors_1 = require("colors");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const asciichart_1 = require("asciichart");
const createChart_1 = __importDefault(require("../helpers/createChart"));
function bytes(val) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (val === 0) {
return '0 Bytes';
}
const i = Math.floor(Math.log(val) / Math.log(1024));
if (i === 0) {
return `${val} ${sizes[i]}`;
}
return `${val / parseFloat((1024 ** i).toFixed(2))} ${sizes[i]}`;
}
class ProfileResults {
/**
* Wraps the results of the Profiler into a class.
*
* Makes it easy to implement functions that
* interact with the data.
*
* It is also helpful for providing useful jsdoc
* definitions for the properties of the data.
*/
constructor(data) {
if (!data)
throw Error('Results data was null');
this.data = data;
}
/**
* Get average memory usage.
*/
averageMemoryUsage() {
let total = 0;
this.data.mem_list.forEach((m) => {
total += m;
});
return total / this.data.mem_list.length;
}
/**
* Get median memory usage.
*/
medianMemoryUsage() {
return this.data.mem_list.sort((a, b) => a - b)[Math.round(this.data.mem_list.length / 2)];
}
/**
* Get most frequently occuring value in memory usage.
*/
modeMemoryUsage() {
const freqMap = {};
let maxCount = 0;
let largest = 0;
this.data.mem_list.forEach((m) => {
if (!freqMap[m])
freqMap[m] = 1;
else
freqMap[m] += 1;
if (freqMap[m] > maxCount) {
maxCount = freqMap[m];
largest = m;
}
});
return largest;
}
/**
* Get the amount of memory taken up at a certain point within the test.
*
* @param {Number} ms
*/
memoryAtElapsed(ms) {
if (ms > this.data.time_elapsed)
throw new Error('Time provided was greater than total profile time.');
return this.data.mem_list[Math.round(ms / this.data.timestep_ms)];
}
/**
* Save data as a snapshot for comparison in future tests.
*/
saveSnapshot(filename, path = path_1.default.join(path_1.default.resolve('.'), '__snapshots__')) {
if (!filename)
throw Error('You must provide a file name.');
if (!fs_1.default.existsSync(path)) {
fs_1.default.mkdirSync(path);
}
// Mostly just for user reference, not checked in code at all
this.data.last_updated = new Date().toLocaleString();
return fs_1.default.writeFileSync(`${path + filename}.json`, JSON.stringify(this.data), 'utf8');
}
compareToSnapshot(path, options = {}) {
let obj;
try {
obj = JSON.parse(fs_1.default.readFileSync(path).toString());
}
catch (e) {
// Create a new snapshot an compare to that ()
const filename = path.split('/')[path.split('/').length - 1].split('.')[0];
const folderPath = path.split(`${filename}.json`)[0];
this.saveSnapshot(filename, folderPath);
return {
time_elapsed: 0,
start_usage_bytes: 0,
peak_usage_bytes: 0,
end_usage_bytes: 0,
};
}
const comparison = {
time_elapsed: this.data.time_elapsed - obj.time_elapsed,
start_usage_bytes: this.data.start_usage_bytes - obj.start_usage_bytes,
peak_usage_bytes: this.data.peak_usage_bytes - obj.peak_usage_bytes,
end_usage_bytes: this.data.end_usage_bytes - obj.end_usage_bytes,
};
if (options.logResultsDiff) {
Object.keys(comparison).forEach((k) => {
const isLess = comparison[k] > 0;
const color = isLess ? colors_1.red : colors_1.green;
const symbol = isLess ? '+' : '-';
if (k === 'time_elapsed') {
console.log(`Time elapsed: ${color(`${String(comparison[k])}ms`)}`);
return;
}
console.log(`${(k[0].toUpperCase() + k.substr(1)).replace(/_/g, ' ')}: ${color(`${symbol}${bytes(Number(isLess ? comparison[k] : -comparison[k]))}`)}`);
});
}
if (options.graph) {
const chartData = [
this.data.mem_list.map((n) => n / 1024),
obj.mem_list.map((n) => n / 1024),
];
if (options.graph === 'text') {
// Graph memory chart
console.log((0, asciichart_1.plot)(chartData.sort((a, b) => a.length - b.length), {
height: 10,
colors: [
this.data.mem_list.length > obj.mem_list.length ? asciichart_1.red : asciichart_1.blue,
this.data.mem_list.length > obj.mem_list.length ? asciichart_1.blue : asciichart_1.red,
],
}), '\nBlue - Current Run\nRed - Snapshot Run');
}
else if (options.graph === 'image') {
const imageName = `${Date.now()}.svg`;
const imagePath = options.graph_path ? `${options.graph_path}/${imageName}` : `${path_1.default.resolve('.')}/${imageName}`;
(0, createChart_1.default)(chartData, imagePath);
console.log(`Memory usage graph saved to ${imagePath}!`);
}
}
return comparison;
}
}
exports.default = ProfileResults;