Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed avg(); fixed setopt() values; added more tests

  • Loading branch information...
commit 0cc75005f7c81126f2093d75981aee0ea1d4657c 1 parent d5bd50b
@bcantrill bcantrill authored
View
9 README
@@ -1,9 +0,0 @@
-
-dtrace, a node.js addon for controlling DTrace enablings
---------------------------------------------------------
-
-This is a simple (and, for the moment, crude) addon to provide native
-libdtrace to node.js programs, supporting basic enablings (including
-aggregations). Currently, esoteric actions are not supported -- this is
-not designed to allow a drop-in replacement for dtrace(1).
-
View
201 README.md
@@ -0,0 +1,201 @@
+
+node-libdtrace
+==============
+
+Overview
+--------
+
+node-libdtrace is a Node.js addon that interfaces to libdtrace, allowing
+node programs to control DTrace enablings.
+
+Status
+------
+
+The primary objective is not to create a `dtrace(1M)` alternative in node, but
+rather to allow node programs to create and control programmatically useful
+DTrace enablings. That is, the goal is software-software interaction, and as
+such, DTrace actions related to controlling output (e.g., `printf()`,
+`printa()`) are not supported. Error handling is, for the moment, weak.
+
+Platforms
+---------
+
+This should work on any platform that supports DTrace, and is known to work on
+Mac OS X (tested on 10.6.4) and Solaris, OpenSolaris and derivatives (tested on
+Nevada 121 and later).
+
+Installation
+------------
+
+As an addon, nod-libdtrace is installed in the usual way:
+
+ % node-waf configure
+ % node-waf build
+ % node-waf
+
+API
+---
+
+### `new libdtrace.Consumer()`
+
+Create a new libdtrace consumer, which will correspond to a new `libdtrace`
+state. If DTrace cannot be initalized for any reason, this will throw an
+exception with the `message` member set to the more detailed reason from
+libdtrace. Note that one particularly common failure mode is attempting to
+initialize DTrace without the necessary level of privilege; in this case, for
+example, the `message` member will be:
+
+ DTrace requires additional privileges
+
+(The specifics of this particular message should obviously not be
+programmatically depended upon.) If encountering this error, you will
+need to be a user that has DTrace privileges.
+
+### `consumer.strcompile(str)`
+
+Compile the specified `str` as a D program. This is required before
+any call to `consumer.go()`.
+
+### `consumer.go()`
+
+Instruments the system using the specified enabling. Before `consumer.go()`
+is called, the specified D program has been compiled but not executed; once
+`consumer.go()` is called, no further D compilation is possible.
+
+### `consumer.setopt(option, value)`
+
+Sets the specified `option` (a string) to `value` (an integer, boolean,
+string, or string representation of an integer or boolean, as denoted by
+the option being set).
+
+### `consumer.consume(function func (probe, rec) {})`
+
+Consume any DTrace data traced to the principal buffer since the last call to
+`consumer.consume()` (or the call to `consumer.go()` if `consumer.consume()`
+has not been called). For each trace record, `func` will be called and
+passed two arguments:
+
+* `probe` is an object that specifies the probe that corresponds to the
+ trace record in terms of the probe tuple: provider, module, function
+ and name.
+
+* `rec` is an object that has a single member, `data`, that corresponds to
+ the datum within the trace record. If the trace record has been entirely
+ consumed, `rec` will be `undefined`.
+
+In terms of implementation, a call to `consumer.consume()` will result in a
+call to `dtrace_status()` and a principal buffer switch. Note that if the
+rate of consumption exceeds the specified `switchrate` (set via either
+`#pragma D option switchrate` or `consumer.setopt()`), this will result in no
+new data processing.
+
+### `consumer.aggwalk(function func (varid, key, value) {})`
+
+Snapshot and iterate over all aggregation data accumulated since the
+last call to `consumer.aggwalk()` (or the call to `consumer.go()` if
+`consumer.aggwalk()` has not been called). For each aggregate record,
+`func` will be called and passed three arguments:
+
+* `varid` is the identifier of the aggregation variable. These IDs are
+ assigned in program order, starting with 1.
+
+* `key` is an array of keys that, taken with the variable identifier,
+ uniquely specifies the aggregation record.
+
+* `value` is the value of the aggregation record, the meaning of which
+ depends on the aggregating action:
+
+ * For `count()`, `sum()`, `max()` and `min()`, the value is the
+ integer value of the aggregation action
+
+ * For `avg()`, the value is the numeric value of the aggregating action
+
+ * For `quantize()` and `lquantize()`, the value is an array of 2-tuples
+ denoting ranges and value: each element consists of a two element array
+ denoting the range (minimum followed by maximum, inclusive) and the
+ value for that range.
+
+Upon return from `consumer.aggwalk()`, the aggregation data for the specified
+variable and key(s) is removed.
+
+Note that the rate of `consumer.aggwalk()` actually consumes the aggregation
+buffer is clamed by the `aggrate` option; if `consumer.aggwalk()` is called
+more frequently than the specified rate, `consumer.aggwalk()` will not
+induce any additional data processing.
+
+`consumer.aggwalk()` does not iterate over aggregation data in any guaranteed
+order, and may interleave aggregation variables and/or keys.
+
+Examples
+--------
+
+### Hello world
+
+The obligatory "hello world":
+
+ var sys = require('sys');
+ var libdtrace = require('libdtrace');
+ var dtp = new libdtrace.Consumer();
+
+ var prog = 'BEGIN { trace("hello world"); }';
+
+ dtp.strcompile(prog);
+ dtp.go();
+
+ dtp.consume(function (probe, rec) {
+ if (rec)
+ sys.puts(rec.data);
+ });
+
+### Using aggregations
+
+A slightly more sophisticated example showing system calls aggregated and
+sorted by executable name:
+
+ var sys = require('sys');
+ var libdtrace = require('libdtrace');
+ var dtp = new libdtrace.Consumer();
+
+ var prog = 'syscall:::entry { @[execname] = count(); }'
+
+ dtp.strcompile(prog);
+ dtp.go();
+
+ var syscalls = {};
+ var keys = [];
+
+ var pad = function (val, len)
+ {
+ var rval = '', i, str = val + '';
+
+ for (i = 0; i < Math.abs(len) - str.length; i++)
+ rval += ' ';
+
+ rval = len < 0 ? str + rval : rval + str;
+
+ return (rval);
+ };
+
+ setInterval(function () {
+ var i;
+
+ sys.puts(pad('EXECNAME', -40) + pad('COUNT', -10));
+
+ dtp.aggwalk(function (id, key, val) {
+ if (!syscalls.hasOwnProperty(key[0]))
+ keys.push(key[0]);
+
+ syscalls[key[0]] = val;
+ });
+
+ keys.sort();
+
+ for (i = 0; i < keys.length; i++) {
+ sys.puts(pad(keys[i], -40) + pad(syscalls[keys[i]], -10));
+ syscalls[keys[i]] = 0;
+ }
+
+ sys.puts('');
+ }, 1000);
+
+
View
20 example.js
@@ -1,20 +0,0 @@
-var sys = require('sys');
-var libdtrace = require('libdtrace');
-var dtp = new libdtrace.Consumer();
-
-var prog = 'BEGIN { trace("hello world"); }\n'
-prog += 'BEGIN { @["hello"] = sum(1); @["world"] = sum(2); }'
-
-dtp.strcompile(prog);
-
-dtp.go();
-
-dtp.consume(function (probe, rec) {
- sys.puts(sys.inspect(probe));
- sys.puts(sys.inspect(rec));
-});
-
-dtp.aggwalk(function (varid, key, value) {
- sys.puts(sys.inspect(key));
- sys.puts(sys.inspect(value));
-});
View
13 examples/example1.js
@@ -0,0 +1,13 @@
+var sys = require('sys');
+var libdtrace = require('libdtrace');
+var dtp = new libdtrace.Consumer();
+
+var prog = 'BEGIN { trace("hello world"); }\n'
+
+dtp.strcompile(prog);
+dtp.go();
+
+dtp.consume(function (probe, rec) {
+ if (rec)
+ sys.puts(rec.data);
+});
View
45 examples/example2.js
@@ -0,0 +1,45 @@
+var sys = require('sys');
+var libdtrace = require('libdtrace');
+var dtp = new libdtrace.Consumer();
+
+var prog = 'syscall:::entry { @[execname] = count(); }'
+
+dtp.strcompile(prog);
+dtp.go();
+
+var syscalls = {};
+var keys = [];
+
+var pad = function (val, len)
+{
+ var rval = '', i, str = val + '';
+
+ for (i = 0; i < Math.abs(len) - str.length; i++)
+ rval += ' ';
+
+ rval = len < 0 ? str + rval : rval + str;
+
+ return (rval);
+};
+
+setInterval(function () {
+ var i;
+
+ sys.puts(pad('EXECNAME', -40) + pad('COUNT', -10));
+
+ dtp.aggwalk(function (id, key, val) {
+ if (!syscalls.hasOwnProperty(key[0]))
+ keys.push(key[0]);
+
+ syscalls[key[0]] = val;
+ });
+
+ keys.sort();
+
+ for (i = 0; i < keys.length; i++) {
+ sys.puts(pad(keys[i], -40) + pad(syscalls[keys[i]], -10));
+ syscalls[keys[i]] = 0;
+ }
+
+ sys.puts('');
+}, 1000);
View
20 libdtrace.cc
@@ -261,8 +261,11 @@ DTraceConsumer::Setopt(const Arguments& args)
String::Utf8Value option(args[0]->ToString());
if (args.Length() >= 2) {
- if (!args[1]->IsString())
- return (dtc->badarg("expected value for option"));
+ if (args[1]->IsArray())
+ return (dtc->badarg("option value can't be an array"));
+
+ if (args[1]->IsObject())
+ return (dtc->badarg("option value can't be an object"));
String::Utf8Value optval(args[1]->ToString());
rval = dtrace_setopt(dtp, *option, *optval);
@@ -448,7 +451,8 @@ DTraceConsumer::ranges_quantize(dtrace_aggvarid_t varid)
if (i < DTRACE_QUANTIZE_ZEROBUCKET) {
/*
* If we're less than the zero bucket, our range
- * extends from
+ * extends from negative infinity through to the
+ * beginning of our zeroth bucket.
*/
min = i > 0 ? DTRACE_QUANTIZE_BUCKETVAL(i - 1) + 1 :
INT64_MIN;
@@ -562,7 +566,6 @@ DTraceConsumer::aggwalk(const dtrace_aggdata_t *agg, void *arg)
case DTRACEAGG_COUNT:
case DTRACEAGG_MIN:
case DTRACEAGG_MAX:
- case DTRACEAGG_AVG:
case DTRACEAGG_SUM: {
caddr_t addr = agg->dtada_data + aggrec->dtrd_offset;
@@ -571,6 +574,15 @@ DTraceConsumer::aggwalk(const dtrace_aggdata_t *agg, void *arg)
break;
}
+ case DTRACEAGG_AVG: {
+ const int64_t *data = (int64_t *)(agg->dtada_data +
+ aggrec->dtrd_offset);
+
+ assert(aggrec->dtrd_size == sizeof (uint64_t) * 2);
+ val = Number::New(data[1] / (double)data[0]);
+ break;
+ }
+
case DTRACEAGG_QUANTIZE: {
Local<Array> quantize = Array::New();
const int64_t *data = (int64_t *)(agg->dtada_data +
View
62 tests/test-aggregation.js
@@ -24,42 +24,52 @@ dtp.aggwalk(function (varid, key, val) {
dtp = new libdtrace.Consumer();
+var lq = function (val)
+{
+ return (val + ', 3, 7, 3')
+};
+
+aggacts = {
+ max: { args: [ '10', '20' ], expected: 20 },
+ min: { args: [ '10', '20' ], expected: 10 },
+ count: { args: [ '', '' ], expected: 2 },
+ sum: { args: [ '10', '20' ], expected: 30 },
+ avg: { args: [ '30', '1' ], expected: 15.5 },
+ quantize: { args: [ '2', '4', '5', '8' ], expected: [
+ [ [ 2, 3 ], 1 ],
+ [ [ 4, 7 ], 2 ],
+ [ [ 8, 15 ], 1 ]
+ ] },
+ lquantize: { args: [ lq(2), lq(4), lq(5), lq(8) ], expected: [
+ [ [ dtp.aggmin(), 2 ], 1 ],
+ [ [ 3, 5 ], 2 ],
+ [ [ 6, dtp.aggmax() ], 1 ]
+ ] }
+};
+
+varids = [ '' ];
prog = 'BEGIN\n{\n';
-for (i = -32; i < 32; i++)
- prog += '\t@ = quantize(' + i + ');\n';
+for (act in aggacts) {
+ varids.push(act);
-prog += '}\n';
+ for (i = 0; i < aggacts[act].args.length; i++) {
+ prog += '\t@agg' + act + ' = ' + act + '(' +
+ aggacts[act].args[i] + ');\n';
+ }
+}
-sys.puts(prog);
+prog += '}\n';
dtp.strcompile(prog);
dtp.go();
dtp.aggwalk(function (varid, key, val) {
- var expected = [
- [ [ -63, -32 ], 1 ],
- [ [ -31, -16 ], 16 ],
- [ [ -15, -8 ], 8 ],
- [ [ -7, -4 ], 4 ],
- [ [ -3, -2 ], 2 ],
- [ [ -1, -1 ], 1 ],
- [ [ 0, 0 ], 1 ],
- [ [ 1, 1 ], 1 ],
- [ [ 2, 3 ], 2 ],
- [ [ 4, 7 ], 4 ],
- [ [ 8, 15 ], 8 ],
- [ [ 16, 31 ], 16 ],
- ];
+ assert.ok(varids[varid], 'invalid variable ID ' + varid);
+ assert.ok(aggacts[varids[varid]], 'unknown variable ID ' + varid);
- assert.equal(varid, 1);
- assert.ok(key instanceof Array, 'expected key to be an array');
- assert.equal(key.length, 0);
- assert.ok(val instanceof Array, 'expected val to be an array');
- assert.deepEqual(expected, val);
- sys.puts(sys.inspect(val));
+ act = aggacts[varids[varid]];
+ assert.deepEqual(act.expected, val);
});
-delete dtp;
-
View
27 tests/test-options.js
@@ -0,0 +1,27 @@
+var sys = require('sys');
+var libdtrace = require('libdtrace');
+var assert = require('assert');
+
+dtp = new libdtrace.Consumer();
+
+assert.throws(function () { dtp.setopt() });
+assert.throws(function () { dtp.setopt('bogusoption') });
+assert.throws(function () { dtp.setopt('bufpolicy') });
+assert.throws(function () { dtp.setopt('bufpolicy', 100) });
+dtp.setopt('quiet', true);
+assert.throws(function () { dtp.setopt('bufpolicy', true); });
+assert.throws(function () { dtp.setopt('dynvarsize', true); });
+assert.throws(function () { dtp.setopt('dynvarsize', 1.23); });
+dtp.setopt('quiet', 1024);
+assert.throws(function () { dtp.setopt('quiet', { foo: 1024 }); });
+assert.throws(function () { dtp.setopt('quiet', [ false ]); });
+assert.throws(function () { dtp.setopt('quiet', 1.024); });
+assert.throws(function () { dtp.setopt('quiet', 1.024); });
+assert.throws(function () { dtp.setopt('quiet', new Date); });
+dtp.setopt('quiet');
+dtp.setopt('quiet', true);
+dtp.setopt('quiet', false);
+dtp.setopt('quiet', '1');
+dtp.setopt('quiet', '0');
+
+
View
45 tests/test-quantize.js
@@ -0,0 +1,45 @@
+var sys = require('sys');
+var libdtrace = require('libdtrace');
+var assert = require('assert');
+
+dtp = new libdtrace.Consumer();
+
+prog = 'BEGIN\n{\n';
+
+for (i = -32; i < 32; i++)
+ prog += '\t@ = quantize(' + i + ');\n';
+
+prog += '}\n';
+
+sys.puts(prog);
+
+dtp.strcompile(prog);
+
+dtp.go();
+
+dtp.aggwalk(function (varid, key, val) {
+ var expected = [
+ [ [ -63, -32 ], 1 ],
+ [ [ -31, -16 ], 16 ],
+ [ [ -15, -8 ], 8 ],
+ [ [ -7, -4 ], 4 ],
+ [ [ -3, -2 ], 2 ],
+ [ [ -1, -1 ], 1 ],
+ [ [ 0, 0 ], 1 ],
+ [ [ 1, 1 ], 1 ],
+ [ [ 2, 3 ], 2 ],
+ [ [ 4, 7 ], 4 ],
+ [ [ 8, 15 ], 8 ],
+ [ [ 16, 31 ], 16 ],
+ ];
+
+ assert.equal(varid, 1);
+ assert.ok(key instanceof Array, 'expected key to be an array');
+ assert.equal(key.length, 0);
+ assert.ok(val instanceof Array, 'expected val to be an array');
+ assert.deepEqual(expected, val);
+ sys.puts(sys.inspect(val));
+});
+
+delete dtp;
+
View
16 tests/test-stddev.js
@@ -0,0 +1,16 @@
+var sys = require('sys');
+var libdtrace = require('libdtrace');
+var assert = require('assert');
+
+/*
+ * Don't get your hopes up -- this test is asserting that stddev() is
+ * properly not supported.
+ */
+dtp = new libdtrace.Consumer();
+
+dtp.strcompile('BEGIN { @ = stddev(0) }');
+
+dtp.go();
+
+assert.throws(function () { dtp.strcompile('BEGIN { @ = stddev(0) }') });
+
Please sign in to comment.
Something went wrong with that request. Please try again.