From 20e9d9ec01d7d90a42c96d053f1a222fe1a069ff Mon Sep 17 00:00:00 2001 From: Bryan Cantrill Date: Sun, 15 Aug 2010 15:41:17 -0700 Subject: [PATCH] primordial support for libdtrace --- LICENSE | 18 +++ README | 9 ++ example.js | 11 ++ libdtrace.cc | 324 ++++++++++++++++++++++++++++++++++++++++++++ tests/test-basic.js | 94 +++++++++++++ wscript | 16 +++ 6 files changed, 472 insertions(+) create mode 100644 LICENSE create mode 100644 README create mode 100644 example.js create mode 100644 libdtrace.cc create mode 100644 tests/test-basic.js create mode 100644 wscript diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5a8730 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright 2010 Bryan Cantrill. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..8d882c0 --- /dev/null +++ b/README @@ -0,0 +1,9 @@ + +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. Currently, only the most basic DTrace +enablings are supported -- it doesn't support rather major functionality +like aggregations and error handling. + diff --git a/example.js b/example.js new file mode 100644 index 0000000..cfbd256 --- /dev/null +++ b/example.js @@ -0,0 +1,11 @@ +var sys = require('sys'); +var libdtrace = require('libdtrace'); +var dtp = new libdtrace.Consumer(); + +dtp.strcompile('BEGIN { trace("hello world"); }'); +dtp.go(); + +dtp.consume(function (probe, rec) { + sys.puts(sys.inspect(probe)); + sys.puts(sys.inspect(rec)); +}); diff --git a/libdtrace.cc b/libdtrace.cc new file mode 100644 index 0000000..8dba490 --- /dev/null +++ b/libdtrace.cc @@ -0,0 +1,324 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace v8; +using std::string; +using std::vector; + +class DTraceConsumer : node::ObjectWrap { +public: + static void Initialize(Handle target); + +protected: + DTraceConsumer(); + ~DTraceConsumer(); + + Handle error(const char *fmt, ...); + Handle badarg(const char *msg); + + static int consume(const dtrace_probedata_t *data, + const dtrace_recdesc_t *rec, void *arg); + static int bufhandler(const dtrace_bufdata_t *bufdata, void *arg); + + static Handle New(const Arguments& args); + static Handle Consume(const Arguments& args); + static Handle Strcompile(const Arguments& args); + static Handle Setopt(const Arguments& args); + static Handle Go(const Arguments& args); + static Handle Stop(const Arguments& args); + +private: + dtrace_hdl_t *dtc_handle; + static Persistent dtc_templ; + const Arguments *dtc_args; + Local dtc_consume; + Handle dtc_error; +}; + +Persistent DTraceConsumer::dtc_templ; + +DTraceConsumer::DTraceConsumer() : node::ObjectWrap() +{ + int err; + dtrace_hdl_t *dtp; + + if ((dtc_handle = dtp = dtrace_open(DTRACE_VERSION, 0, &err)) == NULL) + throw (dtrace_errmsg(NULL, err)); + + /* + * Set our buffer size and aggregation buffer size to the de facto + * standard of 4M. + */ + (void) dtrace_setopt(dtp, "bufsize", "4m"); + (void) dtrace_setopt(dtp, "aggsize", "4m"); + + if (dtrace_handle_buffered(dtp, DTraceConsumer::bufhandler, NULL) == -1) + throw (dtrace_errmsg(dtp, dtrace_errno(dtp))); +}; + +DTraceConsumer::~DTraceConsumer() +{ + dtrace_close(dtc_handle); +} + +void +DTraceConsumer::Initialize(Handle target) +{ + HandleScope scope; + Local dtc = + FunctionTemplate::New(DTraceConsumer::New); + + dtc_templ = Persistent::New(dtc); + dtc_templ->InstanceTemplate()->SetInternalFieldCount(1); + dtc_templ->SetClassName(String::NewSymbol("Consumer")); + + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "strcompile", + DTraceConsumer::Strcompile); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "setopt", DTraceConsumer::Setopt); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "go", DTraceConsumer::Go); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "consume", + DTraceConsumer::Consume); + NODE_SET_PROTOTYPE_METHOD(dtc_templ, "stop", DTraceConsumer::Stop); + + target->Set(String::NewSymbol("Consumer"), dtc_templ->GetFunction()); +} + +Handle +DTraceConsumer::New(const Arguments& args) +{ + HandleScope scope; + DTraceConsumer *dtc; + + try { + dtc = new DTraceConsumer(); + } catch (char const *msg) { + return (ThrowException(Exception::Error(String::New(msg)))); + } + + dtc->Wrap(args.Holder()); + + return (args.This()); +} + +Handle +DTraceConsumer::error(const char *fmt, ...) +{ + char buf[1024], buf2[1024]; + char *err = buf; + va_list ap; + + va_start(ap, fmt); + (void) vsnprintf(buf, sizeof (buf), fmt, ap); + + if (buf[strlen(buf) - 1] != '\n') { + /* + * If our error doesn't end in a new-line, we'll append the + * strerror of errno. + */ + (void) snprintf(err = buf2, sizeof (buf2), + "%s: %s", buf, strerror(errno)); + } else { + buf[strlen(buf) - 1] = '\0'; + } + + return (ThrowException(Exception::Error(String::New(err)))); +} + +Handle +DTraceConsumer::badarg(const char *msg) +{ + return (ThrowException(Exception::TypeError(String::New(msg)))); +} + +Handle +DTraceConsumer::Strcompile(const Arguments& args) +{ + DTraceConsumer *dtc = ObjectWrap::Unwrap(args.Holder()); + dtrace_hdl_t *dtp = dtc->dtc_handle; + dtrace_prog_t *dp; + dtrace_proginfo_t info; + + if (args.Length() < 1 || !args[0]->IsString()) + return (dtc->badarg("expected program")); + + String::Utf8Value program(args[0]->ToString()); + + if ((dp = dtrace_program_strcompile(dtp, *program, + DTRACE_PROBESPEC_NAME, 0, 0, NULL)) == NULL) { + return (dtc->error("couldn't compile '%s': %s\n", *program, + dtrace_errmsg(dtp, dtrace_errno(dtp)))); + } + + if (dtrace_program_exec(dtp, dp, &info) == -1) { + return (dtc->error("couldn't execute '%s': %s\n", *program, + dtrace_errmsg(dtp, dtrace_errno(dtp)))); + } + + return (Undefined()); +} + +Handle +DTraceConsumer::Setopt(const Arguments& args) +{ + DTraceConsumer *dtc = ObjectWrap::Unwrap(args.Holder()); + dtrace_hdl_t *dtp = dtc->dtc_handle; + dtrace_prog_t *dp; + dtrace_proginfo_t info; + int rval; + + if (args.Length() < 1 || !args[0]->IsString()) + return (dtc->badarg("expected an option to set")); + + String::Utf8Value option(args[0]->ToString()); + + if (args.Length() >= 2) { + if (!args[1]->IsString()) + return (dtc->badarg("expected value for option")); + + String::Utf8Value optval(args[1]->ToString()); + rval = dtrace_setopt(dtp, *option, *optval); + } else { + rval = dtrace_setopt(dtp, *option, NULL); + } + + if (rval != 0) { + return (dtc->error("couldn't set option '%s': %s\n", *option, + dtrace_errmsg(dtp, dtrace_errno(dtp)))); + } + + return (Undefined()); +} + +Handle +DTraceConsumer::Go(const Arguments& args) +{ + DTraceConsumer *dtc = ObjectWrap::Unwrap(args.Holder()); + dtrace_hdl_t *dtp = dtc->dtc_handle; + + if (dtrace_go(dtp) == -1) { + return (dtc->error("couldn't enable tracing: %s\n", + dtrace_errmsg(dtp, dtrace_errno(dtp)))); + } + + return (Undefined()); +} + +Handle +DTraceConsumer::Stop(const Arguments& args) +{ + DTraceConsumer *dtc = ObjectWrap::Unwrap(args.Holder()); + dtrace_hdl_t *dtp = dtc->dtc_handle; + + if (dtrace_stop(dtp) == -1) { + return (dtc->error("couldn't disable tracing: %s\n", + dtrace_errmsg(dtp, dtrace_errno(dtp)))); + } + + return (Undefined()); +} + +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. + */ + return (DTRACE_HANDLE_OK); +} + +int +DTraceConsumer::consume(const dtrace_probedata_t *data, + const dtrace_recdesc_t *rec, void *arg) +{ + DTraceConsumer *dtc = (DTraceConsumer *)arg; + 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)); + + if (rec == NULL) { + Local argv[1] = { probe }; + dtc->dtc_consume->Call(dtc->dtc_args->This(), 1, argv); + return (DTRACE_CONSUME_NEXT); + } + + if (rec->dtrd_action != DTRACEACT_DIFEXPR) { + dtc->dtc_error = dtc->error("unsupported action type %d " + "in record for %s:%s:%s:%s\n", rec->dtrd_action, + pd->dtpd_provider, pd->dtpd_mod, + pd->dtpd_func, pd->dtpd_name); + return (DTRACE_CONSUME_ABORT); + } + + Local record = Object::New(); + void *addr = data->dtpda_data; + + switch (rec->dtrd_size) { + case sizeof (uint64_t): + datum = Number::New(*((int64_t *)addr)); + break; + + case sizeof (uint32_t): + datum = Integer::New(*((int32_t *)addr)); + break; + + case sizeof (uint16_t): + datum = Integer::New(*((uint16_t *)addr)); + break; + + case sizeof (uint8_t): + datum = Integer::New(*((uint8_t *)addr)); + break; + + default: + datum = String::New((const char *)addr); + } + + record->Set(String::New("data"), datum); + + Local argv[2] = { probe, record }; + + dtc->dtc_consume->Call(dtc->dtc_args->This(), 2, argv); + + return (rec == NULL ? DTRACE_CONSUME_NEXT : DTRACE_CONSUME_THIS); +} + +Handle +DTraceConsumer::Consume(const Arguments& args) +{ + DTraceConsumer *dtc = ObjectWrap::Unwrap(args.Holder()); + dtrace_hdl_t *dtp = dtc->dtc_handle; + dtrace_workstatus_t status; + + if (!args[0]->IsFunction()) + return (dtc->badarg("expected function as argument")); + + dtc->dtc_consume = Local::Cast(args[0]); + dtc->dtc_args = &args; + dtc->dtc_error = Undefined(); + + status = dtrace_work(dtp, NULL, NULL, DTraceConsumer::consume, dtc); + + if (status == -1 && !dtc->dtc_error->IsUndefined()) + return (dtc->dtc_error); + + return (Undefined()); +} + +extern "C" void +init (Handle target) +{ + DTraceConsumer::Initialize(target); +} diff --git a/tests/test-basic.js b/tests/test-basic.js new file mode 100644 index 0000000..c5a18c9 --- /dev/null +++ b/tests/test-basic.js @@ -0,0 +1,94 @@ +var sys = require('sys'); +var libdtrace = require('libdtrace'); +var assert = require('assert'); + +dtp = new libdtrace.Consumer(); +assert.throws(function () { dtp.strcompile(); }); +assert.throws(function () { dtp.strcompile(61707); }); +assert.throws(function () { dtp.strcompile('this is not D'); }); +assert.throws(function () { dtp.strcompile('bogus-probe { trace(0); }') }); +dtp.strcompile('BEGIN { trace(9904); }'); + +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('bufpolicy', 'ring'); +dtp.setopt('bufpolicy', 'switch'); + +seen = false; +lastrec = false; + +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'); + + if (!rec) { + assert.ok(seen); + lastrec = true; + } else { + seen = true; + assert.equal(rec.data, 9904); + } +}); + +assert.ok(seen, 'did not consume expected record'); +assert.ok(lastrec, 'did not see delineator between EPIDs'); +assert.throws(function () { dtp.go(); }); +assert.throws(function () { dtp.strcompile('BEGIN { trace(0); }') }); + +dtp.stop(); + +/* + * Now test that END clauses work properly. + */ +dtp = new libdtrace.Consumer(); +dtp.strcompile('END { trace(61707); }'); + +dtp.go(); + +seen = false; + +dtp.consume(function testend (probe, rec) { + assert.ok(false); +}); + +dtp.stop(); + +dtp.consume(function testend_consume (probe, rec) { + assert.equal(probe.provider, 'dtrace'); + assert.equal(probe.module, ''); + assert.equal(probe.function, ''); + assert.equal(probe.name, 'END'); + + if (!rec) + return; + + assert.equal(rec.data, 61707); +}); + +dtp = new libdtrace.Consumer(); +dtp.strcompile('tick-1sec { trace(i++); }'); +dtp.go(); +secs = 0; +val = 0; + +id = setInterval(function testtick () { + assert.ok(secs++ < 10, 'failed to terminate (val is ' + val + ')'); + + dtp.consume(function testtick_consume (probe, rec) { + if (!rec) + return; + + if ((val = rec.data) > 5) + clearInterval(id); + + sys.puts(sys.inspect(rec)); + }); +}, 1000); + diff --git a/wscript b/wscript new file mode 100644 index 0000000..e1c62b0 --- /dev/null +++ b/wscript @@ -0,0 +1,16 @@ +srcdir = '.' +blddir = 'build' +VERSION = '0.0.1' + +def set_options(opt): + opt.tool_options('compiler_cxx') + +def configure(conf): + conf.check_tool('compiler_cxx') + conf.check_tool('node_addon') + +def build(bld): + obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') + obj.target = 'libdtrace' + obj.ldflags = '-ldtrace' + obj.source = 'libdtrace.cc'