async_hooks in node.js, illustrated
async_hooks, the new and still experimental API that came out with node 8 got my attention a few weeks back, so I decided to do a bit of exploring as to what it can help me do.
async_hooks API essentially makes it easier for you to track your resources (bye dtrace
?). You start off by initializing it with an object of callbacks: init
, before
, after
and destroy
.
.
.
var asyncHooks = require('async_hooks')var hooks = {
init: init,
before: before,
after: after,
destroy: destroy
}var asyncHook = asyncHooks.createHook(hooks)
async_hooks is based around a concept of resources. A resource
triggers async_hooks callbacks from above to be called, and could be anything from TTYWRAP
to SSLCONNECTION
to the one you defined with the Embedder API (more on this later). If you were to write a server with http.createServer()
, start the async_hooks API, and look at init
callback’s resource, you will end up with these ones, for example:
var http = require('http')// asyncHook being defined in code snippet above
asyncHook.enable()http.createServer(function (req, res) {
res.end('hello qts')
}).listen(8079)function init (asyncId, type, triggerId) {
fs.writeSync(1, `${type} \n`)
}
function init (asyncId, type, triggerId) {}
This init
callback is probably the most interesting one — it allows you to access the current resource, and look into what caused it to trigger. This means you will eventually be able to create a nice structure to figure out what really goes on within your application.
I thought of this as a nice segue into tracing — accessing init
's asyncId
and triggerId
, starting a timer to track your operation and eventually destroying it during a destroy
. With that, I wrote on-async-hook, a simple async_hook trace emitter.
on-async-hook
creates a trace when async_hooks calls their init
callback, and groups resources based on their triggerId
. I wanted to time operations, so on-async-hook
trace structure also includes start and end time, which it then adds during init
and removes ondestroy
. A simple hello world server will end up with a trace like this, for example:
Embedder API
If you are working with native C++ bindings, you may want to define your own resources. You could do that easily with the Embedder API. Here is an example setting this up with mafintosh’s utp-native library:
var AsyncResource = require('async_hooks').AsyncResource
var utp = require('utp-native')var resource = new AsyncResource('UTPNative')var server = utp.createServer(function (socket) {
socket.pipe(socket)
})server.listen(1337, function () {
var socket = utp.connect(1337) resource.emitBefore()
socket.write('hello qts')
resource.emitAfter() socket.on('data', function (data) {
console.log('resourceId', resource.asyncId())
console.log('triggerAsyncId', resource.triggerAsyncId())
console.log('this is my data ', data)
})
})
server.on('close', function () {
resource.emitDestroy()
})
What’s next?
async_hooks is still pretty experimental — things broke™, and API is possibly still changing (i.e. async_hooks.executionAsyncId()
used to be async_hooks.currentId()
, o/), but it’s pretty awesome to see node putting together new ways for us to monitor our applications.
Further info:
- Performance Timing API jasnell has been working on landed in node last week. This is pretty exciting news for your node application performance monitoring and I’d definitely suggest looking into it.
- If you’re using async_hooks in production, there is an issue open for gathering some information.
- Since async_hooks is only available starting node 8, I would also suggest to look into davidmarkclem’s async-tracer — it supports node’s previous versions.