diff --git a/tracing/AsyncWrap/README.md b/tracing/AsyncWrap/README.md deleted file mode 100644 index 911fb47..0000000 --- a/tracing/AsyncWrap/README.md +++ /dev/null @@ -1,343 +0,0 @@ -Node.js tracing - AsyncWrap -================================================================================ - -AsyncWrap is two things. One is a -[class abstraction](https://github.com/nodejs/node/blob/master/src/async-wrap.h) -that provides an internal mechanism for handling asynchronous tasks, such as -calling a callback. -The other part is an API for setting up hooks and allows one to get -structural tracing information about the life of handle objects. In the context -of tracing the latter is usually what is meant. - -_The reasoning for the current naming confusion is that the API part implements -the hooks through the AsyncWrap class, but this is not inherently necessary. For -example if v8 provided those facilities the AsyncWrap class would not need be -involved in the AsyncWrap API._ - -For the remaining description the API part is what is meant by AsyncWrap. - -## Table of Content - -* [Handle Objects](#handle-objects) -* [The API](#the-api) -* [Example](#example) -* [Things You Might Not Expect](#things-you-might-not-expect) -* [Resources](#resources) - -## Handle Objects - -AsyncWrap emits events (hooks) that inform the consumer about the life of all -handle objects in node. Thus in order to understand AsyncWrap one must first -understand handle objects. - -Node's core API is mostly defined in JavaScript. However ECMAScript does not -define any API for creating a TCP socket, reading a file etc.. That logic is -implemented in C++ using libuv and the v8 API. The JavaScript in node core -interacts with this C++ layer using the handle objects. - -For example in `net.connect(port, address)` that creates a TCP connection, -two handle objects (`TCPConnectWrap` and `TCP`) are created: - -```javascript -const TCP = process.binding('tcp_wrap').TCP; -const TCPConnectWrap = process.binding('tcp_wrap').TCPConnectWrap; - -const req = new TCPConnectWrap(); -req.oncomplete = oncomplete; -req.address = address; -req.port = port; - -const socket = new TCP(); -socket.onread = onread; -socket.connect(req, address, port); - -// later -socket.destroy(); -``` - -The first handle object (`TCPConnectWrap`) is for connecting the socket, the -second one (`TCP`) is for maintaining the connection. - -`TCPConnectWrap` gets its information by setting properties on the handle -object, like `address` and `port`. Those properties are read by the C++ layer, -but can also be inspected from the AsyncWrap hooks. When the handle is created -using `new TCPConnectWrap()` the `init` hook is called. - -An `oncomplete` property is also set, this is the callback for when the -connection is made or failed. Just before calling `oncomplete` the `pre` hook -is called, just after the `post` hook is called. - -The `TCP` handle works exactly the same way, except that the information -is passed as arguments to a method `.connect` and the `onread` function -is called multiple times, thus it behaves like an event. This also means that -the `pre` and `post` hooks are called multiple times. - -At some later time the `socket.destroy()` is called, this will call the -`destroy` hook for the `socket` handle. Other handle objects aren't explicitly -destroyed, in that case the `destroy` hook is called when the handle object is -garbage collected by v8. - -Thus one should expect the hooks be called in the following order: - -```javascript -init // TCPConnectWrap -init // TCP -=== tick === -pre // TCPConnectWrap -post // TCPConnectWrap -=== tick === -pre // TCP -post // TCP -=== tick === -pre // TCP -post // TCP -=== tick === -... -=== tick === -destroy // TCP -=== tick === -destroy // TCPConnectWrap -``` - -_tick_ indicates there is at least one tick (as in `process.nextTick()`) between -the text above and the text below. - -## The API - -At the moment there is not a high level API for AsyncWrap. It is simply exposed -through `process.binding`: - -```javascript -const asyncWrap = process.binding('async_wrap'); -``` - -_Be warned that this API is not an official API and can change at any time, even -if it's just patch update._ - -To assign the hooks call: - -```javascript -asyncWrap.setupHooks({init, pre, post, destroy}); -function init(uid, provider, parentUid, parentHandle) { /* consumer code */ } -function pre(uid) { /* consumer code */ } -function post(uid, didThrow) { /* consumer code */ } -function destroy(uid) { /* consumer code */ } -``` - -Note that only the `init` function is required and that calling -`asyncWrap.setupHooks` again will overwrite the previous hooks. - -#### Enable And Disable - -Because there is a small performance penalty in just calling a noop function, -AsyncWrap is not enabled by default. To enable AsyncWrap call: - -```javascript -asyncWrap.enable(); -``` - -Note that handle objects created before AsyncWrap is enabled will never -be tracked, even after `.enable()` is called. Similarly when AsyncWrap is -disabled, the handle objects there are already being tracked will continue -to be tracked. Finally there are some cases where handle objects have a parent. -For example a TCP server creates TCP sockets, thus the socket handles have the -server handle as a parent. In this case AsyncWrap should be enabled before -the server handle is created, for the TCP sockets to be tracked. - -Disabling AsyncWrap can be done with: - -```javascript -asyncWrap.disable(); -``` - -#### The Hooks - -Currently there are 4 hooks: `init`, `pre`, `post` `destroy`. The `this` -variable refers to the handle object, they all have a `uid` argument. `post` -provides information about the execution of the callback. Finally -`init` provides extra information about the creation of the handle object. - -```javascript -function init(uid, provider, parentUid, parentHandle) { } -function pre(uid) { } -function post(uid, didThrow) { } -function destroy(uid) { } -``` - -##### this - -In the `init`, `pre` and `post` cases the `this` variable is the handle object. -Users may read properties from this object such as `port` and `address` in the -`TCPConnectWrap` case. - -In the `init` hook the handle object is not yet fully constructed, thus some -properties are not safe to read. This causes problems when doing -`util.inspect(this)` or similar. - -In the `destroy` hook `this` is `null`, this is because the handle objects has -been deleted by the garbage collector and thus doesn't exists. - -##### provider - -This is an integer that refer to names defined in the `asyncWrap.Providers` -object map. - -At the time of writing this is the current list: - -```javascript -{ NONE: 0, - CRYPTO: 1, - FSEVENTWRAP: 2, - FSREQWRAP: 3, - GETADDRINFOREQWRAP: 4, - GETNAMEINFOREQWRAP: 5, - HTTPPARSER: 6, - JSSTREAM: 7, - PIPEWRAP: 8, - PIPECONNECTWRAP: 9, - PROCESSWRAP: 10, - QUERYWRAP: 11, - SHUTDOWNWRAP: 12, - SIGNALWRAP: 13, - STATWATCHER: 14, - TCPWRAP: 15, - TCPCONNECTWRAP: 16, - TIMERWRAP: 17, - TLSWRAP: 18, - TTYWRAP: 19, - UDPWRAP: 20, - UDPSENDWRAP: 21, - WRITEWRAP: 22, - ZLIB: 23 } -``` - -##### uid - -The `uid` is a unique integer that identifies each handle object. You can use -the `uid` to store information related to the handle object, by using it as -a key for a shared `Map` object. - -As such the user could also store information on the `this` object, but this -may be unsafe since the user could accidentally overwrite an undocumented -property. The `this` object is also not available in the `destroy` hook, thus -the `uid` is generally the recommended choice. - -##### parentUid - -In some cases the handle was created from another handle object. In those -cases the `parentUid` argument is set to the uid of the creating handle object. -If there is no parent then it is just `null`. - -The most common case is the TCP server. The TCP server itself is a `TCP` handle, -but when receiving new connection it creates another `TCP` handle that is -responsible for the new socket. It does this before emitting the `onconnection` -handle event, thus the asyncWrap hooks are called in the following order: - -```javascript -init // TCP (socket) -pre // TCP (server) -post // TCP (server) -``` - -This means it is not possible to know in what handle context the new socket -handle was created using the `pre` and `post` hooks. However the -`parentUid` argument provides this information. - -##### parentHandle - -This is similar to parentUid but is the actual parent handle object. - -##### didThrow - -If the callback threw, but the process was allowed to continue due to either -`uncaughtException` or the `domain` module, then `didThrow` will be true. - -## Example - -A classic use case for AsyncWrap is to create a long-stack-trace tool. - -```javascript -const asyncWrap = process.binding('async_wrap'); - -asyncWrap.setupHooks({init, pre, post, destroy}); -asyncWrap.enable(); - -// global state variable, that contains the stack traces and the current uid -const stack = new Map(); -stack.set(-1, ''); - -let currentUid = -1; - -function init(uid, provider, parentUid, parentHandle) { - // When a handle is created, collect the stack trace such that we later - // can see what involved the handle constructor. - const localStack = (new Error()).stack.split('\n').slice(1).join('\n'); - - // Compute the full stack and store on the `Map` using the `uid` as key. - const extraStack = stack.get(parentUid || currentUid); - stack.set(uid, localStack + '\n' + extraStack); -} -function pre(uid) { - // A callback is about to be called, update the `currentUid` such that - // it is correct for when another handle is initialized or `getStack` is - // called. - currentUid = uid; -} -function post(uid, didThrow) { - // At the time of writing there are some odd cases where there is no handle - // context, this line prevents that from resulting in wrong stack trace. But - // the stack trace will be shorter compared to what ideally should happen. - currentUid = -1; -} - -function destroy(uid) { - // Once the handle is destroyed no other handle objects can be created with - // this handle as its immediate context. Thus its associated stack can be - // deleted. - stack.delete(uid); -} - -function getStack(message) { - const localStack = new Error(message); - return localStack.stack + '\n' + stack.get(currentUid); -} -module.exports = getStack; -``` - -Please note that this example is way simpler than what is required from a -complete long-stack-trace implementation. - -## Things You Might Not Expect - -* It is not obvious when a handle object is created. For example the TCP server -creates the `TCP` handle when `.listen` is called and it may perform an DNS -lookup before that. - -* `console.log` is async and thus invokes AsyncWrap, thus using `console.log` -inside one of the hooks, creates an infinite recursion. Use `fs.writeSync(1, msg)` -or `process._rawDebug(msg)` instead. The latter is a little nicer because it -uses `util.inspect`. On the other hand `fs.writeSync` is a documented function. - -* `process.nextTick` never creates a handle object. You will have to monkey patch -this. - -* Timer functions (like `setTimeout`) shares a single `Timer` handle, thus you -will usually have to monkey patch those functions. - -* Promises are also not tracked by AsyncWrap. The `Promise` constructor can also -be monkey patched, but unfortunately it is quite difficult. See -[async-listener](https://github.com/othiym23/async-listener/blob/14d01b2b82817fff9993f065587f9009f3d2126b/index.js#L257L407) -for how to do it. - -## Resources - -* Status overview of AsyncWrap: https://github.com/nodejs/tracing-wg/issues/29 -* An intro to AsyncWrap by Trevor Norris: http://blog.trevnorris.com/2015/02/asyncwrap-tutorial-introduction.html (outdated) -* Slides from a local talk Andreas Madsen did on AsyncWrap: -https://github.com/AndreasMadsen/talk-async-wrap (outdated) -* Complete (hopefully) long-stack-trace module that uses AsyncWrap: https://github.com/AndreasMadsen/trace -* Visualization tool for AsyncWrap: https://github.com/AndreasMadsen/dprof - ----- - -If you have more info to provide, please send us a pull request! diff --git a/tracing/AsyncWrap/example-trace/run.js b/tracing/AsyncWrap/example-trace/run.js deleted file mode 100644 index a5307b7..0000000 --- a/tracing/AsyncWrap/example-trace/run.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const fs = require('fs'); -const net = require('net'); -const getStack = require('./trace.js'); - -Error.stackTraceLimit = Infinity; - -const server = net.createServer(function (socket) { - - fs.open(__filename, 'r', function (err, fd) { - if (err) throw err; - - fs.close(fd, function (err) { - if (err) throw err; - - console.log(getStack('trace')); - socket.end('hallo world'); - }); - }) -}); - -server.listen(0, 'localhost', function () { - const addr = server.address(); - const socket = net.connect(addr.port, addr.address, function () { - socket.once('readable', function () { - socket.read(); - socket.once('readable', server.close.bind(server)); - }); - }); -}); diff --git a/tracing/AsyncWrap/example-trace/trace.js b/tracing/AsyncWrap/example-trace/trace.js deleted file mode 100644 index d2ef71e..0000000 --- a/tracing/AsyncWrap/example-trace/trace.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -const asyncWrap = process.binding('async_wrap'); - -asyncWrap.setupHooks({init, pre, post, destroy}); -asyncWrap.enable(); - -// global state variable, that contains the stack traces and the current uid -const stack = new Map(); -stack.set(-1, ''); - -let currentUid = -1; - -function init(uid, provider, parentUid, parentHandle) { - // When a handle is created, collect the stack trace such that we later - // can see what involved the handle constructor. - const localStack = (new Error()).stack.split('\n').slice(1).join('\n'); - - // Compute the full stack and store on the `Map` using the `uid` as key. - const extraStack = stack.get(parentUid || currentUid); - stack.set(uid, localStack + '\n' + extraStack); -} -function pre(uid) { - // A callback is about to be called, update the `currentUid` such that - // it is correct for when another handle is initialized or `getStack` is - // called. - currentUid = uid; -} -function post(uid, didThrow) { - // At the time of writing there are some odd cases where there is no handle - // context, this line prevents that from resulting in wrong stack trace. But - // the stack trace will be shorter compared to what ideally should happen. - currentUid = -1; -} - -function destroy(uid) { - // Once the handle is destroyed no other handle objects can be created with - // this handle as its immediate context. Thus its associated stack can be - // deleted. - stack.delete(uid); -} - -function getStack(message) { - const localStack = new Error(message); - return localStack.stack + '\n' + stack.get(currentUid); -} -module.exports = getStack; diff --git a/tracing/README.md b/tracing/README.md index 1959f9c..6fbfb48 100644 --- a/tracing/README.md +++ b/tracing/README.md @@ -1,5 +1,5 @@ ## Tracing -- [AsyncWrap](./AsyncWrap/README.md) - Most native objects are represented indirectly by the [AsyncWrap class](https://github.com/nodejs/node/blob/master/src/async-wrap.h), so hooks have been added to that class to trace lifecycle (init/destroy) and callback activity (pre/post) related to these objects. +- [AsyncHooks](https://nodejs.org/api/async_hooks.html) - Tracks the lifetime of asynconuse requests (init/destroy) and their callback activity (before/after) - [OS Tracing](./os-tracing/README.md) - LTTng (Linux), SystemTap (Linux), DTrace (OSX), ETW (Windows)