231 lines
8.2 KiB
Markdown
231 lines
8.2 KiB
Markdown
|
# 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.
|