Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

primordial support for libdtrace

  • Loading branch information...
commit 20e9d9ec01d7d90a42c96d053f1a222fe1a069ff 0 parents
@bcantrill bcantrill authored
18 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.
9 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.
+
11 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));
+});
324 libdtrace.cc
@@ -0,0 +1,324 @@
+#include <v8.h>
+#include <node.h>
+#include <string.h>
+#include <unistd.h>
+#include <node_object_wrap.h>
+#include <errno.h>
+#include <string>
+#include <vector>
+#include <dtrace.h>
+
+using namespace v8;
+using std::string;
+using std::vector;
+
+class DTraceConsumer : node::ObjectWrap {
+public:
+ static void Initialize(Handle<Object> target);
+
+protected:
+ DTraceConsumer();
+ ~DTraceConsumer();
+
+ Handle<Value> error(const char *fmt, ...);
+ Handle<Value> 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<Value> New(const Arguments& args);
+ static Handle<Value> Consume(const Arguments& args);
+ static Handle<Value> Strcompile(const Arguments& args);
+ static Handle<Value> Setopt(const Arguments& args);
+ static Handle<Value> Go(const Arguments& args);
+ static Handle<Value> Stop(const Arguments& args);
+
+private:
+ dtrace_hdl_t *dtc_handle;
+ static Persistent<FunctionTemplate> dtc_templ;
+ const Arguments *dtc_args;
+ Local<Function> dtc_consume;
+ Handle<Value> dtc_error;
+};
+
+Persistent<FunctionTemplate> 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<Object> target)
+{
+ HandleScope scope;
+ Local<FunctionTemplate> dtc =
+ FunctionTemplate::New(DTraceConsumer::New);
+
+ dtc_templ = Persistent<FunctionTemplate>::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<Value>
+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<Value>
+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<Value>
+DTraceConsumer::badarg(const char *msg)
+{
+ return (ThrowException(Exception::TypeError(String::New(msg))));
+}
+
+Handle<Value>
+DTraceConsumer::Strcompile(const Arguments& args)
+{
+ DTraceConsumer *dtc = ObjectWrap::Unwrap<DTraceConsumer>(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<Value>
+DTraceConsumer::Setopt(const Arguments& args)
+{
+ DTraceConsumer *dtc = ObjectWrap::Unwrap<DTraceConsumer>(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<Value>
+DTraceConsumer::Go(const Arguments& args)
+{
+ DTraceConsumer *dtc = ObjectWrap::Unwrap<DTraceConsumer>(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<Value>
+DTraceConsumer::Stop(const Arguments& args)
+{
+ DTraceConsumer *dtc = ObjectWrap::Unwrap<DTraceConsumer>(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<Value> datum;
+
+ Local<Object> 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<Value> 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<Object> 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<Value> argv[2] = { probe, record };
+
+ dtc->dtc_consume->Call(dtc->dtc_args->This(), 2, argv);
+
+ return (rec == NULL ? DTRACE_CONSUME_NEXT : DTRACE_CONSUME_THIS);
+}
+
+Handle<Value>
+DTraceConsumer::Consume(const Arguments& args)
+{
+ DTraceConsumer *dtc = ObjectWrap::Unwrap<DTraceConsumer>(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<Function>::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<Object> target)
+{
+ DTraceConsumer::Initialize(target);
+}
94 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);
+
16 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'
Please sign in to comment.
Something went wrong with that request. Please try again.