| # dtrace-provider - Native DTrace providers for Node.js apps. | |
| This extension allows you to create native DTrace providers for your | |
| Node.js applications. That is, to create providers and probes which | |
| expose information specific to your application, rather than | |
| information about the node runtime. | |
| You could use this to expose high-level information about the inner | |
| workings of your application, or to create a specific context in which | |
| to look at information from other runtime or system-level providers. | |
| The provider is not created in the usual way, by declaring it and then | |
| changing the build process to include it, but instead dynamically at | |
| runtime. This is done entirely in-process, and there is no background | |
| compiler or [dtrace(1M)](https://illumos.org/man/1M/dtrace) invocation. | |
| The process creating the provider need not run as root. | |
| ## INSTALL | |
| $ npm install dtrace-provider | |
| ## EXAMPLE | |
| Here's a simple example of creating a provider: | |
| ```javascript | |
| var d = require('dtrace-provider'); | |
| var dtp = d.createDTraceProvider("nodeapp"); | |
| var p1 = dtp.addProbe("probe1", "int", "int"); | |
| var p2 = dtp.addProbe("probe2", "char *"); | |
| dtp.enable(); | |
| ``` | |
| Probes may be fired via the provider object: | |
| ```javascript | |
| dtp.fire("probe1", function() { | |
| return [1, 2]; | |
| }); | |
| dtp.fire("probe2", function() { | |
| return ["hello, dtrace via provider", "foo"]; | |
| }); | |
| ``` | |
| or via the probe objects themselves: | |
| ```javascript | |
| p1.fire(function() { | |
| return [1, 2, 3, 4, 5, 6]; | |
| }); | |
| p2.fire(function() { | |
| return ["hello, dtrace via probe", "foo"]; | |
| }); | |
| ``` | |
| Note that `.fire()` takes a callback that returns the arguments to be | |
| provided when the DTrace probe actually fires. This allows you to call | |
| `.fire()` unconditionally when you want to fire the probe, but the | |
| callback will be invoked only when the DTrace probe is actually | |
| enabled. This allows you to create probes whose arguments might be | |
| expensive to construct, and only do any work when the probe is | |
| actually enabled. (Examples might include converting a large object to | |
| a string representation or gathering large amounts of information.) | |
| In some cases, creating a new closure to pass to `.fire()` each time | |
| it's called may introduce unwanted overhead. For extremely | |
| CPU-intensive or memory-conscious workloads, you can avoid this by | |
| lifting the closures for your hot probes into an outer scope. You can | |
| then supply arguments to that function as additional arguments to | |
| `.fire()`. As an example, you can convert the following program: | |
| ```javascript | |
| function manipulateObj(largeObj) { | |
| var count = 0; | |
| var name = null; | |
| ... | |
| p1.fire(function () { | |
| return [count, keyToValue(name), JSON.stringify(largeObj)]; | |
| }); | |
| } | |
| ``` | |
| Into this one: | |
| ```javascript | |
| function f(a, b, c) { | |
| return [a, keyToValue(b), JSON.stringify(c)]; | |
| } | |
| function manipulateObj(largeObj) { | |
| var count = 0; | |
| var name = null; | |
| ... | |
| p1.fire(f, count, name, largeObj); | |
| } | |
| ``` | |
| Be careful to avoid passing `.fire()` additional arguments that are | |
| themselves expensive to construct, as that undermines the design goal | |
| here: minimizing the effect of disabled probes. | |
| This example creates a provider called "nodeapp", and adds two | |
| probes. It then enables the provider, at which point the provider | |
| becomes visible to DTrace. | |
| The probes are then fired, which produces this output: | |
| $ sudo dtrace -Z -n 'nodeapp*:::probe1{ trace(arg0); trace(arg1) }' \ | |
| -n 'nodeapp*:::probe2{ trace(copyinstr(arg0)); }' | |
| dtrace: description 'nodeapp*:::probe1' matched 0 probes | |
| dtrace: description 'nodeapp*:::probe2' matched 0 probes | |
| CPU ID FUNCTION:NAME | |
| 1 123562 func:probe1 1 2 | |
| 1 123563 func:probe2 hello, dtrace | |
| Arguments are captured by a callback only executed when the probe is | |
| enabled. This means you can do more expensive work to gather arguments. | |
| The maximum number of arguments supported is 32. | |
| Available argument types are "int", for integer numeric values, | |
| "char *" for strings, and "json" for objects rendered into JSON strings. | |
| Arguments typed as "json" will be created as "char *" probes in | |
| DTrace, but objects passed to these probe arguments will be | |
| automatically serialized to JSON before being passed to DTrace. This | |
| feature is best used in conjunction with the json() D subroutine, but | |
| is available whether or not the platform supports it. | |
| # create a json probe: | |
| var dtp = d.createDTraceProvider("nodeapp"); | |
| var p1 = dtp.addProbe("j1", "json"); | |
| dtp.enable(); | |
| p1.fire(function() { return { "foo": "bar" }; }); | |
| # on a platform supporting json(): | |
| $ sudo dtrace -Z -n 'nodeapp*:::j1{ this->j = copyinstr(arg0); \ | |
| trace(json(this->j, "foo")) }' | |
| dtrace: description 'nodeapp$target:::j1' matched 0 probes | |
| CPU ID FUNCTION:NAME | |
| 0 68712 j1:j1 bar | |
| ## PLATFORM SUPPORT | |
| This libusdt-based Node.JS module supports 64 and 32 bit processes on | |
| Mac OS X and Solaris-like systems such as illumos or SmartOS. As more | |
| platform support is added to libusdt, those platforms will be | |
| supported by this module. See libusdt's status at: | |
| https://github.com/chrisa/libusdt#readme | |
| When using Mac OS X, be aware that as of 10.11 (El Capitan), DTrace use | |
| is restricted, and you'll probably want to | |
| [disable SIP](http://internals.exposed/blog/dtrace-vs-sip.html) to | |
| effectively use DTrace. | |
| FreeBSD 10 and 11 are also supported, but you'll need to make sure that | |
| you have the DTrace headers installed in `/usr/src` otherwise libusdt | |
| won't be able to compile. You can | |
| [clone them using SVN](https://www.freebsd.org/doc/handbook/svn.html), | |
| or find the correct `src.txz` | |
| [here](http://ftp.freebsd.org/pub/FreeBSD/releases/) and extract that. | |
| Also note that FreeBSD 10 is restricted to only 4 working arguments per | |
| probe. | |
| Platforms not supporting DTrace (notably, Linux and Windows) may | |
| install this module without building libusdt, with a stub no-op | |
| implementation provided for compatibility. This allows cross-platform | |
| npm modules to embed probes and include a dependency on this module. | |
| GNU Make is required to build libusdt; the build scripts will look for | |
| gmake in `PATH` first, and then for make. | |
| ### TROUBLESHOOTING BUILD ISSUES | |
| If compilation fails during installation on platforms with DTrace, then | |
| the library will fall back to the stub implementation that does nothing. | |
| To force an installation failure when compiling fails, set the environment | |
| variable `NODE_DTRACE_PROVIDER_REQUIRE` to `hard`: | |
| ```shell | |
| $ NODE_DTRACE_PROVIDER_REQUIRE=hard npm install | |
| ``` | |
| This will then show you the output of the build process so you can see at | |
| which point it's having an issue. Common issues are: | |
| - Missing a C/C++ compiler toolchain for your platform. | |
| - `python` is Python 3 instead of Python 2; run `npm config set python python2.7` | |
| (or similar) to set the Python binary npm uses. | |
| - On OS X you may need to agree to the XCode license if that's the compiler | |
| toolchain you're using. This will usually manifest with an error like | |
| `Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.` | |
| To accept the license, you can run `sudo xcodebuild -license`. | |
| Once you've found and fixed the issue, you can run `npm rebuild` to rerun | |
| the lifecycle scripts. | |
| ## CAVEATS | |
| There is some overhead to probes, even when disabled. Probes are | |
| already using the "is-enabled" feature of DTrace to control execution | |
| of the arguments-gathering callback, but some work still needs to be | |
| done before that's checked. This overhead should not be a problem | |
| unless probes are placed in particularly hot code paths. | |
| ## CONTRIBUTING | |
| To clone the project's source code: | |
| $ git clone --recursive https://github.com/chrisa/node-dtrace-provider.git | |
| For issues, please use the [GitHub issue tracker](https://github.com/chrisa/node-dtrace-provider/issues) | |
| linked to the repository. GitHub pull requests are very welcome. | |
| ## RUNNING THE TESTS | |
| ```shell | |
| $ npm install | |
| $ sudo ./node_modules/.bin/tap --tap test/*.test.js | |
| ``` | |
| ## OTHER IMPLEMENTATIONS | |
| This node extension is derived from the ruby-dtrace gem, via the Perl | |
| module Devel::DTrace::Provider, both of which provide the same | |
| functionality to those languages. | |