diff --git a/README.md b/README.md index 86ed31b..e4ee33c 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,10 @@ 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. +### `consumer.version()` + +Returns the version string, as returned from `dtrace -V`. + Examples -------- diff --git a/libdtrace.cc b/libdtrace.cc index ce5b522..1b63e79 100644 --- a/libdtrace.cc +++ b/libdtrace.cc @@ -28,6 +28,43 @@ #include +/* + * This is a tad unsightly: if we didn't find the definition of the + * llquantize() aggregating action, we're going to redefine it here (along + * with its support cast of macros). This allows node-libdtrace to operate + * on a machine that has llquantize(), even if it was compiled on a machine + * without the support. + */ +#ifndef DTRACEAGG_LLQUANTIZE + +#define DTRACEAGG_LLQUANTIZE (DTRACEACT_AGGREGATION + 9) + +#define DTRACE_LLQUANTIZE_FACTORSHIFT 48 +#define DTRACE_LLQUANTIZE_FACTORMASK ((uint64_t)UINT16_MAX << 48) +#define DTRACE_LLQUANTIZE_LOWSHIFT 32 +#define DTRACE_LLQUANTIZE_LOWMASK ((uint64_t)UINT16_MAX << 32) +#define DTRACE_LLQUANTIZE_HIGHSHIFT 16 +#define DTRACE_LLQUANTIZE_HIGHMASK ((uint64_t)UINT16_MAX << 16) +#define DTRACE_LLQUANTIZE_NSTEPSHIFT 0 +#define DTRACE_LLQUANTIZE_NSTEPMASK UINT16_MAX + +#define DTRACE_LLQUANTIZE_FACTOR(x) \ + (uint16_t)(((x) & DTRACE_LLQUANTIZE_FACTORMASK) >> \ + DTRACE_LLQUANTIZE_FACTORSHIFT) + +#define DTRACE_LLQUANTIZE_LOW(x) \ + (uint16_t)(((x) & DTRACE_LLQUANTIZE_LOWMASK) >> \ + DTRACE_LLQUANTIZE_LOWSHIFT) + +#define DTRACE_LLQUANTIZE_HIGH(x) \ + (uint16_t)(((x) & DTRACE_LLQUANTIZE_HIGHMASK) >> \ + DTRACE_LLQUANTIZE_HIGHSHIFT) + +#define DTRACE_LLQUANTIZE_NSTEP(x) \ + (uint16_t)(((x) & DTRACE_LLQUANTIZE_NSTEPMASK) >> \ + DTRACE_LLQUANTIZE_NSTEPSHIFT) +#endif + using namespace v8; using std::string; using std::vector; @@ -45,11 +82,13 @@ class DTraceConsumer : node::ObjectWrap { boolean_t valid(const dtrace_recdesc_t *); const char *action(const dtrace_recdesc_t *, char *, int); Local record(const dtrace_recdesc_t *, caddr_t); + Local probedesc(const dtrace_probedesc_t *); Local *ranges_cached(dtrace_aggvarid_t); Local *ranges_cache(dtrace_aggvarid_t, Local *); Local *ranges_quantize(dtrace_aggvarid_t); Local *ranges_lquantize(dtrace_aggvarid_t, uint64_t); + Local *ranges_llquantize(dtrace_aggvarid_t, uint64_t, int); static int consume(const dtrace_probedata_t *data, const dtrace_recdesc_t *rec, void *arg); @@ -65,6 +104,7 @@ class DTraceConsumer : node::ObjectWrap { static Handle Setopt(const Arguments& args); static Handle Go(const Arguments& args); static Handle Stop(const Arguments& args); + static Handle Version(const Arguments& args); private: dtrace_hdl_t *dtc_handle; @@ -93,7 +133,7 @@ DTraceConsumer::DTraceConsumer() : node::ObjectWrap() (void) dtrace_setopt(dtp, "bufsize", "4m"); (void) dtrace_setopt(dtp, "aggsize", "4m"); - if (dtrace_handle_buffered(dtp, DTraceConsumer::bufhandler, NULL) == -1) + if (dtrace_handle_buffered(dtp, DTraceConsumer::bufhandler, this) == -1) throw (dtrace_errmsg(dtp, dtrace_errno(dtp))); dtc_ranges = NULL; @@ -126,11 +166,11 @@ DTraceConsumer::Initialize(Handle target) DTraceConsumer::Consume); NODE_SET_PROTOTYPE_METHOD(dtc_templ, "aggwalk", DTraceConsumer::Aggwalk); - NODE_SET_PROTOTYPE_METHOD(dtc_templ, "aggmin", - DTraceConsumer::Aggmin); - NODE_SET_PROTOTYPE_METHOD(dtc_templ, "aggmax", - DTraceConsumer::Aggmax); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "aggmin", DTraceConsumer::Aggmin); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "aggmax", DTraceConsumer::Aggmax); NODE_SET_PROTOTYPE_METHOD(dtc_templ, "stop", DTraceConsumer::Stop); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "version", + DTraceConsumer::Version); target->Set(String::NewSymbol("Consumer"), dtc_templ->GetFunction()); } @@ -185,6 +225,7 @@ DTraceConsumer::action(const dtrace_recdesc_t *rec, char *buf, int size) { DTRACEAGG_STDDEV, "stddev()" }, { DTRACEAGG_QUANTIZE, "quantize()" }, { DTRACEAGG_LQUANTIZE, "lquantize()" }, + { DTRACEAGG_LLQUANTIZE, "llquantize()" }, { DTRACEACT_NONE, NULL }, }; @@ -410,13 +451,35 @@ DTraceConsumer::Stop(const Arguments& args) return (Undefined()); } +Local +DTraceConsumer::probedesc(const dtrace_probedesc_t *pd) +{ + Local probe = Object::New(); + probe->Set(String::New("provider"), String::New(pd->dtpd_provider)); + probe->Set(String::New("module"), String::New(pd->dtpd_mod)); + probe->Set(String::New("function"), String::New(pd->dtpd_func)); + probe->Set(String::New("name"), String::New(pd->dtpd_name)); + + return (probe); +} + int DTraceConsumer::bufhandler(const dtrace_bufdata_t *bufdata, void *arg) { - /* - * We do nothing here -- but should we wish to ever support complete - * dtrace(1) compatibility via node.js, we will need to do work here. - */ + dtrace_probedata_t *data = bufdata->dtbda_probe; + const dtrace_recdesc_t *rec = bufdata->dtbda_recdesc; + DTraceConsumer *dtc = (DTraceConsumer *)arg; + + if (rec == NULL || rec->dtrd_action != DTRACEACT_PRINTF) + return (DTRACE_HANDLE_OK); + + Local probe = dtc->probedesc(data->dtpda_pdesc); + Local record = Object::New(); + record->Set(String::New("data"), String::New(bufdata->dtbda_buffered)); + Local argv[2] = { probe, record }; + + dtc->dtc_callback->Call(dtc->dtc_args->This(), 2, argv); + return (DTRACE_HANDLE_OK); } @@ -428,11 +491,7 @@ DTraceConsumer::consume(const dtrace_probedata_t *data, dtrace_probedesc_t *pd = data->dtpda_pdesc; Local datum; - Local probe = Object::New(); - probe->Set(String::New("provider"), String::New(pd->dtpd_provider)); - probe->Set(String::New("module"), String::New(pd->dtpd_mod)); - probe->Set(String::New("function"), String::New(pd->dtpd_func)); - probe->Set(String::New("name"), String::New(pd->dtpd_name)); + Local probe = dtc->probedesc(data->dtpda_pdesc); if (rec == NULL) { Local argv[1] = { probe }; @@ -443,6 +502,12 @@ DTraceConsumer::consume(const dtrace_probedata_t *data, if (!dtc->valid(rec)) { char errbuf[256]; + /* + * If this is a printf(), we'll defer to the bufhandler. + */ + if (rec->dtrd_action == DTRACEACT_PRINTF) + return (DTRACE_CONSUME_THIS); + dtc->dtc_error = dtc->error("unsupported action %s " "in record for %s:%s:%s:%s\n", dtc->action(rec, errbuf, sizeof (errbuf)), @@ -452,14 +517,12 @@ DTraceConsumer::consume(const dtrace_probedata_t *data, } Local record = Object::New(); - record->Set(String::New("data"), dtc->record(rec, data->dtpda_data)); - Local argv[2] = { probe, record }; dtc->dtc_callback->Call(dtc->dtc_args->This(), 2, argv); - return (rec == NULL ? DTRACE_CONSUME_NEXT : DTRACE_CONSUME_THIS); + return (DTRACE_CONSUME_THIS); } Handle @@ -584,6 +647,59 @@ DTraceConsumer::ranges_lquantize(dtrace_aggvarid_t varid, return (ranges_cache(varid, ranges)); } +Local * +DTraceConsumer::ranges_llquantize(dtrace_aggvarid_t varid, + const uint64_t arg, int nbuckets) +{ + int64_t value = 1, next, step; + Local *ranges; + int bucket = 0, order; + uint16_t factor, low, high, nsteps; + + if ((ranges = ranges_cached(varid)) != NULL) + return (ranges); + + factor = DTRACE_LLQUANTIZE_FACTOR(arg); + low = DTRACE_LLQUANTIZE_LOW(arg); + high = DTRACE_LLQUANTIZE_HIGH(arg); + nsteps = DTRACE_LLQUANTIZE_NSTEP(arg); + + ranges = new Local[nbuckets]; + + for (order = 0; order < low; order++) + value *= factor; + + ranges[bucket] = Array::New(2); + ranges[bucket]->Set(0, Number::New(0)); + ranges[bucket]->Set(1, Number::New(value - 1)); + bucket++; + + next = value * factor; + step = next > nsteps ? next / nsteps : 1; + + while (order <= high) { + ranges[bucket] = Array::New(2); + ranges[bucket]->Set(0, Number::New(value)); + ranges[bucket]->Set(1, Number::New(value + step - 1)); + bucket++; + + if ((value += step) != next) + continue; + + next = value * factor; + step = next > nsteps ? next / nsteps : 1; + order++; + } + + ranges[bucket] = Array::New(2); + ranges[bucket]->Set(0, Number::New(value)); + ranges[bucket]->Set(1, Number::New(INT64_MAX)); + + assert(bucket + 1 == nbuckets); + + return (ranges_cache(varid, ranges)); +} + int DTraceConsumer::aggwalk(const dtrace_aggdata_t *agg, void *arg) { @@ -665,7 +781,8 @@ DTraceConsumer::aggwalk(const dtrace_aggdata_t *agg, void *arg) break; } - case DTRACEAGG_LQUANTIZE: { + case DTRACEAGG_LQUANTIZE: + case DTRACEAGG_LLQUANTIZE: { Local lquantize = Array::New(); const int64_t *data = (int64_t *)(agg->dtada_data + aggrec->dtrd_offset); @@ -673,11 +790,13 @@ DTraceConsumer::aggwalk(const dtrace_aggdata_t *agg, void *arg) int i, j = 0; uint64_t arg = *data++; - uint16_t levels = DTRACE_LQUANTIZE_LEVELS(arg); + int levels = (aggrec->dtrd_size / sizeof (uint64_t)) - 1; - ranges = dtc->ranges_lquantize(aggdesc->dtagd_varid, arg); + ranges = (aggrec->dtrd_action == DTRACEAGG_LQUANTIZE ? + dtc->ranges_lquantize(aggdesc->dtagd_varid, arg) : + dtc->ranges_llquantize(aggdesc->dtagd_varid, arg, levels)); - for (i = 0; i <= levels + 1; i++) { + for (i = 0; i < levels; i++) { if (!data[i]) continue; @@ -762,6 +881,12 @@ DTraceConsumer::Aggmax(const Arguments& args) return (Number::New(INT64_MAX)); } +Handle +DTraceConsumer::Version(const Arguments& args) +{ + return (String::New(_dtrace_version)); +} + extern "C" void init (Handle target) { diff --git a/tests/test-llquantize.js b/tests/test-llquantize.js new file mode 100644 index 0000000..d4ef511 --- /dev/null +++ b/tests/test-llquantize.js @@ -0,0 +1,101 @@ +var sys = require('sys'); +var libdtrace = require('libdtrace'); +var assert = require('assert'); + +dtp = new libdtrace.Consumer(); + +ver = dtp.version().split(' ')[2].split('.'); + +if (parseInt(ver[0]) == 1 && parseInt(ver[1]) <= 8) { + sys.puts('llquantize() not present in version ' + dtp.version() + + '; not testing.'); + process.exit(0); +} + +prog = 'BEGIN\n{\n'; + +for (i = 0; i < 101; i++) + prog += '\t@ = llquantize(' + i + ', 10, 0, 1, 20);\n'; + +prog += '}\n'; + +dtp.strcompile(prog); + +dtp.go(); + +dtp.aggwalk(function (varid,key, val) { + var expected = [ + [ [ 0, 0 ], 1 ], + [ [ 1, 1 ], 1 ], + [ [ 2, 2 ], 1 ], + [ [ 3, 3 ], 1 ], + [ [ 4, 4 ], 1 ], + [ [ 5, 5 ], 1 ], + [ [ 6, 6 ], 1 ], + [ [ 7, 7 ], 1 ], + [ [ 8, 8 ], 1 ], + [ [ 9, 9 ], 1 ], + [ [ 10, 14 ], 5 ], + [ [ 15, 19 ], 5 ], + [ [ 20, 24 ], 5 ], + [ [ 25, 29 ], 5 ], + [ [ 30, 34 ], 5 ], + [ [ 35, 39 ], 5 ], + [ [ 40, 44 ], 5 ], + [ [ 45, 49 ], 5 ], + [ [ 50, 54 ], 5 ], + [ [ 55, 59 ], 5 ], + [ [ 60, 64 ], 5 ], + [ [ 65, 69 ], 5 ], + [ [ 70, 74 ], 5 ], + [ [ 75, 79 ], 5 ], + [ [ 80, 84 ], 5 ], + [ [ 85, 89 ], 5 ], + [ [ 90, 94 ], 5 ], + [ [ 95, 99 ], 5 ], + [ [ 100, 9223372036854776000 ], 1 ], + ]; + + assert.deepEqual(val, expected); +}); + +dtp = new libdtrace.Consumer(); + +prog = 'BEGIN\n{\n'; + +for (i = 0; i < 10100; i += 50) + prog += '\t@ = llquantize(' + i + ', 10, 2, 3, 10);\n'; + +prog += '}\n'; + +dtp.strcompile(prog); + +dtp.go(); + +dtp.aggwalk(function (varid, key, val) { + var expected = + [ [ [ 0, 99 ], 2 ], + [ [ 100, 199 ], 2 ], + [ [ 200, 299 ], 2 ], + [ [ 300, 399 ], 2 ], + [ [ 400, 499 ], 2 ], + [ [ 500, 599 ], 2 ], + [ [ 600, 699 ], 2 ], + [ [ 700, 799 ], 2 ], + [ [ 800, 899 ], 2 ], + [ [ 900, 999 ], 2 ], + [ [ 1000, 1999 ], 20 ], + [ [ 2000, 2999 ], 20 ], + [ [ 3000, 3999 ], 20 ], + [ [ 4000, 4999 ], 20 ], + [ [ 5000, 5999 ], 20 ], + [ [ 6000, 6999 ], 20 ], + [ [ 7000, 7999 ], 20 ], + [ [ 8000, 8999 ], 20 ], + [ [ 9000, 9999 ], 20 ], + [ [ 10000, 9223372036854776000 ], 2 ] + ]; + + assert.deepEqual(val, expected); +}); + diff --git a/tests/test-printf.js b/tests/test-printf.js new file mode 100644 index 0000000..9c57975 --- /dev/null +++ b/tests/test-printf.js @@ -0,0 +1,21 @@ +var sys = require('sys'); +var libdtrace = require('libdtrace'); +var assert = require('assert'); + +dtp = new libdtrace.Consumer(); +dtp.strcompile('BEGIN { printf("{ foo: %d", 123); printf(", bar: %d", 456); }'); + +dtp.go(); + +dtp.consume(function testbasic (probe, rec) { + assert.equal(probe.provider, 'dtrace'); + assert.equal(probe.module, ''); + assert.equal(probe.function, ''); + assert.equal(probe.name, 'BEGIN'); + + sys.puts(sys.inspect(probe)); + sys.puts(sys.inspect(rec)); +}); + +dtp.stop(); + diff --git a/tests/test-version.js b/tests/test-version.js new file mode 100644 index 0000000..40340ed --- /dev/null +++ b/tests/test-version.js @@ -0,0 +1,33 @@ +var sys = require('sys'); +var libdtrace = require('libdtrace'); +var assert = require('assert'); +var fs = require('fs'); + +var dtp = new libdtrace.Consumer(); + +var ver = dtp.version(); + +elems = ver.split(' '); +assert.equal(elems.length, 3); +assert.equal(elems[0], 'Sun'); +assert.equal(elems[1], 'D'); + +vernum = elems[2].split('.'); + +assert.ok(vernum.length >= 2, + 'expected dot-delimited version; found "' + elems[2] + '"'); + +/* + * Given the constraints around changing the major number for DTrace, it's + * reasonable to assume that if does indeed happen, other elements of + * node-libdtrace have broken as well. + */ +assert.equal(vernum[0], '1', 'According to dt_open.c: "The major number ' + + 'should be incremented when a fundamental change has been made that ' + + 'would affect all consumers, and would reflect sweeping changes to ' + + 'DTrace or the D language." Given that the major number seems to have ' + + 'been bumped to ' + vernum[0] + ', it appears that this rapture is upon ' + + 'us! Committing ritual suicide accordingly...'); + +assert.ok(parseInt(vernum[1], 10) >= 5); +