@hirosystems/api-toolkit
Version:
API development toolkit
268 lines • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.initCpuProfiling = initCpuProfiling;
exports.initHeapSnapshot = initHeapSnapshot;
const inspector = require("inspector");
const helpers_1 = require("../helpers");
const logger_1 = require("../logger");
function isInspectorNotConnectedError(error) {
const ERR_INSPECTOR_NOT_CONNECTED = 'ERR_INSPECTOR_NOT_CONNECTED';
const isNodeError = (r) => r instanceof Error && 'code' in r;
return isNodeError(error) && error.code === ERR_INSPECTOR_NOT_CONNECTED;
}
/**
* Connects and enables a new `inspector` session, then starts an internal v8 CPU profiling process.
* @returns A function to stop the profiling, and return the CPU profile result object.
* The result object can be used to create a `.cpuprofile` file using JSON.stringify.
* Use VSCode or Chrome's 'DevTools for Node' (under chrome://inspect) to visualize the `.cpuprofile` file.
* @param samplingInterval - Optionally set sampling interval in microseconds, default is 1000 microseconds.
*/
function initCpuProfiling(samplingInterval) {
const sessionStopwatch = (0, helpers_1.stopwatch)();
const session = new inspector.Session();
session.connect();
logger_1.logger.info(`[CpuProfiler] Connect session took ${sessionStopwatch.getElapsedAndRestart()}ms`);
const start = async () => {
const sw = (0, helpers_1.stopwatch)();
logger_1.logger.info(`[CpuProfiler] Enabling profiling...`);
await new Promise((resolve, reject) => {
try {
session.post('Profiler.enable', error => {
if (error) {
logger_1.logger.error(error, '[CpuProfiler] Error enabling profiling');
reject(error);
}
else {
logger_1.logger.info(`[CpuProfiler] Profiling enabled`);
resolve();
}
});
}
catch (error) {
logger_1.logger.error(error, '[CpuProfiler] Error enabling profiling');
reject(error);
}
});
logger_1.logger.info(`[CpuProfiler] Enable session took ${sw.getElapsedAndRestart()}ms`);
if (samplingInterval !== undefined) {
logger_1.logger.info(`[CpuProfiler] Setting sampling interval to ${samplingInterval} microseconds`);
await new Promise((resolve, reject) => {
try {
session.post('Profiler.setSamplingInterval', { interval: samplingInterval }, error => {
if (error) {
logger_1.logger.error(error, '[CpuProfiler] Error setting sampling interval');
reject(error);
}
else {
logger_1.logger.info(`[CpuProfiler] Set sampling interval`);
resolve();
}
});
}
catch (error) {
logger_1.logger.error(error, '[CpuProfiler] Error setting sampling interval');
reject(error);
}
});
logger_1.logger.info(`[CpuProfiler] Set sampling interval took ${sw.getElapsedAndRestart()}ms`);
}
logger_1.logger.info(`[CpuProfiler] Profiling starting...`);
await new Promise((resolve, reject) => {
try {
session.post('Profiler.start', error => {
if (error) {
logger_1.logger.error(error, '[CpuProfiler] Error starting profiling');
reject(error);
}
else {
sessionStopwatch.restart();
logger_1.logger.info(`[CpuProfiler] Profiling started`);
resolve();
}
});
}
catch (error) {
logger_1.logger.error(error, '[CpuProfiler] Error starting profiling');
reject(error);
}
});
logger_1.logger.info(`[CpuProfiler] Start profiler took ${sw.getElapsedAndRestart()}ms`);
};
const stop = async () => {
const sw = (0, helpers_1.stopwatch)();
logger_1.logger.info(`[CpuProfiler] Profiling stopping...`);
try {
return await new Promise((resolve, reject) => {
try {
session.post('Profiler.stop', (error, profileResult) => {
if (error) {
logger_1.logger.error(error, '[CpuProfiler] Error stopping profiling');
reject(error);
}
else {
logger_1.logger.info(`[CpuProfiler] Profiling stopped`);
resolve(profileResult.profile);
}
});
}
catch (error) {
reject(error);
}
});
}
finally {
logger_1.logger.info(`[CpuProfiler] Stop profiler took ${sw.getElapsedAndRestart()}ms`);
}
};
const dispose = async () => {
const sw = (0, helpers_1.stopwatch)();
try {
logger_1.logger.info(`[CpuProfiler] Disabling profiling...`);
await new Promise((resolve, reject) => {
try {
session.post('Profiler.disable', error => {
if (error && isInspectorNotConnectedError(error)) {
logger_1.logger.info(`[CpuProfiler] Profiler already disconnected`);
resolve();
}
else if (error) {
logger_1.logger.error(error, '[CpuProfiler] Error disabling profiling');
reject(error);
}
else {
logger_1.logger.info(`[CpuProfiler] Profiling disabled`);
resolve();
}
});
}
catch (error) {
if (isInspectorNotConnectedError(error)) {
logger_1.logger.info(`[CpuProfiler] Profiler already disconnected`);
resolve();
}
else {
reject();
}
}
});
}
finally {
session.disconnect();
logger_1.logger.info(`[CpuProfiler] Disable and disconnect profiler took ${sw.getElapsedAndRestart()}ms`);
}
};
return { start, stop, dispose, session, sessionType: 'cpu', stopwatch: sessionStopwatch };
}
/**
* Connects and enables a new `inspector` session, then creates an internal v8 Heap profiler snapshot.
* @param outputStream - An output stream that heap snapshot chunks are written to.
* The result stream can be used to create a `.heapsnapshot` file.
* Use Chrome's 'DevTools for Node' (under chrome://inspect) to visualize the `.heapsnapshot` file.
*/
function initHeapSnapshot(outputStream) {
const sw = (0, helpers_1.stopwatch)();
const session = new inspector.Session();
session.connect();
let totalSnapshotByteSize = 0;
const start = async () => {
logger_1.logger.info(`[HeapProfiler] Enabling profiling...`);
await new Promise((resolve, reject) => {
try {
session.post('HeapProfiler.enable', error => {
if (error) {
logger_1.logger.error(error, '[HeapProfiler] Error enabling profiling');
reject(error);
}
else {
sw.restart();
logger_1.logger.info(`[HeapProfiler] Profiling enabled`);
resolve();
}
});
}
catch (error) {
logger_1.logger.error(error, '[HeapProfiler] Error enabling profiling');
reject(error);
}
});
session.on('HeapProfiler.addHeapSnapshotChunk', message => {
// Note: this doesn't handle stream back-pressure, but we don't have control over the
// `HeapProfiler.addHeapSnapshotChunk` callback in order to use something like piping.
// So in theory on a slow `outputStream` (usually an http connection response) this can cause OOM.
logger_1.logger.info(`[HeapProfiler] Writing heap snapshot chunk of size ${message.params.chunk.length}`);
totalSnapshotByteSize += message.params.chunk.length;
outputStream.write(message.params.chunk, error => {
if (error) {
logger_1.logger.error(error, `[HeapProfiler] Error writing heap profile chunk to output stream: ${error.message}`);
}
});
});
};
const stop = async () => {
logger_1.logger.info(`[HeapProfiler] Taking snapshot...`);
await new Promise((resolve, reject) => {
try {
session.post('HeapProfiler.takeHeapSnapshot', undefined, (error) => {
if (error) {
logger_1.logger.error(error, '[HeapProfiler] Error taking snapshot');
reject(error);
}
else {
logger_1.logger.info(`[HeapProfiler] Taking snapshot completed, ${totalSnapshotByteSize} bytes...`);
resolve();
}
});
}
catch (error) {
logger_1.logger.error(error, '[HeapProfiler] Error taking snapshot');
reject(error);
}
});
logger_1.logger.info(`[HeapProfiler] Draining snapshot buffer to stream...`);
const writeFinishedPromise = new Promise((resolve, reject) => {
outputStream.on('finish', () => resolve());
outputStream.on('error', error => reject(error));
});
outputStream.end();
await writeFinishedPromise;
logger_1.logger.info(`[HeapProfiler] Finished draining snapshot buffer to stream`);
return { totalSnapshotByteSize };
};
const dispose = async () => {
try {
logger_1.logger.info(`[HeapProfiler] Disabling profiling...`);
await new Promise((resolve, reject) => {
try {
session.post('HeapProfiler.disable', error => {
if (error && isInspectorNotConnectedError(error)) {
logger_1.logger.info(`[HeapProfiler] Profiler already disconnected`);
resolve();
}
else if (error) {
logger_1.logger.error(error, '[HeapProfiler] Error disabling profiling');
reject(error);
}
else {
logger_1.logger.info(`[HeapProfiler] Profiling disabled`);
resolve();
}
});
}
catch (error) {
if (isInspectorNotConnectedError(error)) {
logger_1.logger.info(`[HeapProfiler] Profiler already disconnected`);
resolve();
}
else {
reject();
}
}
});
}
finally {
session.disconnect();
}
};
return { start, stop, dispose, session, sessionType: 'memory', stopwatch: sw };
}
//# sourceMappingURL=inspector-util.js.map