Skip to content

Instantly share code, notes, and snippets.

@psenger
Last active December 6, 2022 00:19
Show Gist options
  • Save psenger/6887387a7c280abaed9fc42bacbd1c9b to your computer and use it in GitHub Desktop.
Save psenger/6887387a7c280abaed9fc42bacbd1c9b to your computer and use it in GitHub Desktop.
[NodeJS Performance Hooks with Async Hooks] #NodeJS #AsyncHook
// @see https://blog.besson.co/nodejs_async_hooks_to_get_per_request_context/
const asyncHooks = require( 'async_hooks' );
const { v4: uuidv4 } = require( 'uuid' );
const store = new Map();
// The executionAsyncId() - the identifier of the current execution context.
const getExecutionAsyncId = () => asyncHooks.executionAsyncId();
// The triggerAsyncId() - The identifier of the parent resource that triggered the execution of the async resource.
const getTriggerAsyncId = () => asyncHooks.triggerAsyncId();
// https://blog.besson.co/nodejs_async_hooks_to_get_per_request_context/
const asyncHook = asyncHooks.createHook( {
init: ( asyncId, _, triggerAsyncId ) => {
// A new async resource was created
// If our parent asyncId already had a context object
// we assign it to our resource asyncId
if ( store.has( triggerAsyncId ) ) {
store.set( asyncId, store.get( triggerAsyncId ) )
}
},
// before: function (asyncId) { },
// after: function (asyncId) { },
destroy: ( asyncId ) => {
// some cleaning to prevent memory leaks
if ( store.has( asyncId ) ) {
store.delete( asyncId );
}
}
// promiseResolve: function (asyncId) { }
} );
asyncHook.enable();
/**
* set the context so subsequent async calls can use it
* @param {Object} context - the context to set
* @param {number} [context.startTime=( new Date() ).getTime()] - Optional, start time.
* @param {string} [context.correlationId=uuid.v4()] - Optional, correlation id.
*/
const setContext = ( context = {} ) => {
const asyncId = getExecutionAsyncId();
const oldContext = store.get( asyncId ) || {};
const startTime = ( new Date() ).getTime()
const correlationId = uuidv4()
store.set( asyncId, {
// set default, might be overridden by oldContext
startTime,
correlationId,
// former context, shallow copy over
...oldContext,
// override/append with new values from user
...context,
} );
}
/**
* returns what is every in the context, do not assume set has been called.
* @returns {*|undefined}
*/
const getContext = () => {
const asyncId = getExecutionAsyncId();
return store.get( asyncId );
};
module.exports = {
setContext,
getContext
};
// @see https://blog.appsignal.com/2020/09/30/exploring-nodejs-async-hooks.html
const async_hooks = require("async_hooks");
const { performance, PerformanceObserver } = require("perf_hooks");
const hook = async_hooks.createHook({
init(asyncId) {
performance.mark(`init-${asyncId}`);
},
destroy(asyncId) {
performance.mark(`destroy-${asyncId}`);
performance.measure(
`entry-${asyncId}`,
`init-${asyncId}`,
`destroy-${asyncId}`
);
},
});
hook.enable();
const observer = new PerformanceObserver((data) =>
console.log(data.getEntries())
);
observer.observe({
entryTypes: ["measure"],
buffered: true,
});
setTimeout(() => {
console.log("I'm a timeout");
}, 1200);
// @see https://blog.appsignal.com/2020/09/30/exploring-nodejs-async-hooks.html
const fs = require("fs");
const async_hooks = require("async_hooks");
// Sync write to the console
const writeSomething = (phase, more) => {
fs.writeSync(
1,
`Phase: "${phase}", Exec. Id: ${async_hooks.executionAsyncId()} ${
more ? ", " + more : ""
}\n`
);
};
// Create and enable the hook
const timeoutHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
writeSomething(
"Init",
`asyncId: ${asyncId}, type: "${type}", triggerAsyncId: ${triggerAsyncId}`
);
},
before(asyncId) {
writeSomething("Before", `asyncId: ${asyncId}`);
},
destroy(asyncId) {
writeSomething("Destroy", `asyncId: ${asyncId}`);
},
after(asyncId) {
writeSomething("After", `asyncId: ${asyncId}`);
},
});
timeoutHook.enable();
writeSomething("Before call");
// Set the timeout
setTimeout(() => {
writeSomething("Exec. Timeout");
}, 1000);
// Or the following....
const calcPow = async (n, exp) => {
writeSomething("Exec. Promise");
return Math.pow(n, exp);
};
(async () => {
await calcPow(3, 4);
})();
// @see https://blog.appsignal.com/2020/09/30/exploring-nodejs-async-hooks.html
const AsyncResource = require("async_hooks").AsyncResource;
const resource = new AsyncResource('MyOwnResource');
someFunction(function someCallback() {
resource.emitBefore();
// do your stuff...
resource.emitAfter();
});
someOnClose() {
resource.emitDestroy();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment