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 in a gist

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`)
}
resource examples on a sample GET request

function init (asyncId, type, triggerId) {}

async_hooks init callback

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.

create a trace on ‘init’, and group spans by their ‘triggerId’

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:

on-async-hook ‘hello world’ trace

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.