Skip to content

Instantly share code, notes, and snippets.

@trevnorris
Created November 16, 2022 19:33
Show Gist options
  • Save trevnorris/20a5241bd9b232fd7726170e1f43f400 to your computer and use it in GitHub Desktop.
Save trevnorris/20a5241bd9b232fd7726170e1f43f400 to your computer and use it in GitHub Desktop.
Use async_hooks to automatically create stack traces of resources
8 TickObject
at AsyncStackTrace.initFn (index.js:89:18)
6 TCPWRAP
at AsyncStackTrace.initFn (index.js:89:18)
3 TCPSERVERWRAP
at AsyncStackTrace.initFn (index.js:89:18)
at Object.<anonymous> (test/http-noop.js:11:4)
'use strict';
const { createHook } = require('async_hooks');
const { createHash } = require('crypto');
const { openSync, writeSync } = require('fs');
const { EOL } = require('os');
const { now } = performance;
// The constructed AsyncHook
const _hook = Symbol('hook');
// Track which stack have already been printed.
const _sha_map = Symbol('sha_map');
/**
* sha of each stack so the stacks can be reconstructed. Printed, but does not
* need to be stored:
* {
* type: 'sha'
* sha: <sha of stack>
* stack: <full stack>
* }
* Object that contains the list of sha's that have been generated to quickly
* check if the if the previous object needs to be printed again. This is stored
* in memory and not printed.
* sha_map {
* [sha]: true
* }
* Information about each init, with a reference to the sha of the stack. This
* is also printed and not kept in memory
* {
* eid, tid, start, stop, sha, type: 'info'
* }
*/
let output = process.stdout.fd;
// Calculate where to print log.
for (let i = 0; i < process.argv.length; i++) {
const o = process.argv[i];
let file = null;
if (o.indexOf('--async-stack-output') != 0)
continue;
if (o.indexOf('--async-stack-output=') === 0) {
if (o.length <= 21)
throw new Error('Provide location for output');
file = o.substr(21);
} else {
if (process.argv.length <= i + 1)
throw new Error('Provide location for output');
file = process.argv[i + 1];
}
if (!file)
throw new Error(`Invalid file: ${file}`);
output = openSync(file, 'w+');
}
class AsyncStackTrace {
constructor(opts) {
this[_hook] = createAsyncHook(this);
this[_sha_map] = {};
if (opts.enable === true) {
this[_hook].enable();
}
}
enable() {
this[_hook].enable();
}
disable() {
this[_hook].disable();
}
}
function createAsyncHook(self) {
return createHook({
init: initFn.bind(self),
destroy(eid) {
writeSync(output, JSON.stringify({
ot: 'destroy', eid, timestamp: now() }) + EOL);
},
});
}
function initFn(eid, type, tid, resource) {
Error.stackTraceLimit = Infinity;
// TODO
//const stack = {};
//Error.captureStackTrace(stack, null);
const stack = (new Error).stack.substr(5 + EOL.length);
const sha = createHash('sha1').update(stack).digest('hex');
writeSync(output, JSON.stringify({
ot: 'init', eid, tid, sha, type, timestamp: now() }) + EOL);
if (this[_sha_map][sha])
return;
this[_sha_map][sha] = true;
writeSync(output, JSON.stringify({ ot: 'sha', sha, stack }) + EOL);
}
// new AsyncStackTrace({ enable: true });
module.exports = AsyncStackTrace;
'use strict';
module.exports = { parse, filter };
const builtins = require('module').builtinModules;
const { EOL } = require('os');
const { isBuffer } = Buffer;
const mv = +process.version.split('.')[0].substr(1);
const coreModuleRegExp = mv < 16 ?
/^ at (?:[^/\\(]+ \(|)((?<![/\\]).+)\.js:\d+:\d+\)?$/ :
/^ at (?:[^/\\(]+ \(|)node:(.+):\d+:\d+\)?$/;
function parse(file) {
if (isBuffer(file))
file = file.toString();
const objs = file.trim().split(/\r?\n/).map(e => JSON.parse(e));
const eids = {};
const shas = {};
while (objs.length > 0) {
const e = objs.pop();
if (e.ot === 'sha') {
shas[e.sha] = e;
continue;
}
if (!eids[e.eid]) {
eids[e.eid] = { };
}
if (e.ot === 'init') {
const o = eids[e.eid] = { };
o.eid = e.eid;
o.tid = e.tid;
o.sha = e.sha;
o.type = e.type;
o.init = e.timestamp;
o.destroy = -1;
} else if (e.ot === 'destroy') {
eids[e.eid].destroy = e.timestamp;
} else {
throw new Error(`invalid data ${e}`);
}
}
for (let o in eids) {
const tid = eids[o].tid;
if (!eids[tid])
continue;
if (!Array.isArray(eids[tid].children)) {
eids[tid].children = [];
}
//eids[tid].children.push(+o);
eids[tid].children.push(eids[o]);
}
return [ eids, shas ];
}
function filter(stack) {
const lines = stack.split(/\r?\n/);
const newl = [];
for (let e of lines) {
const core = e.match(coreModuleRegExp);
if (core === null) {
newl.push(e);
continue;
}
if (builtins.includes(core[1]) || core[1].indexOf('internal') === 0) {
continue;
}
newl.push(e);
}
return newl.join(EOL);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment