Last active
December 6, 2022 00:19
-
-
Save psenger/6887387a7c280abaed9fc42bacbd1c9b to your computer and use it in GitHub Desktop.
[NodeJS Performance Hooks with Async Hooks] #NodeJS #AsyncHook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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 | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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); | |
})(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @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