Land Cantrill's DTrace patch
authorRyan Dahl <ry@tinyclouds.org>
Tue, 25 Jan 2011 01:50:10 +0000 (17:50 -0800)
committerRyan Dahl <ry@tinyclouds.org>
Tue, 25 Jan 2011 02:59:06 +0000 (18:59 -0800)
only works on solaris

lib/http.js
lib/net.js
src/node.cc
src/node.d [new file with mode: 0644]
src/node_dtrace.cc [new file with mode: 0644]
src/node_dtrace.h [new file with mode: 0644]
src/node_provider.d [new file with mode: 0644]
test/common.js
wscript

index 3e500b34c603929986e5df88b169f58f565844d3..6dccd97841b5b54a4822c617bf0176293ec02ffb 100644 (file)
@@ -599,6 +599,7 @@ OutgoingMessage.prototype.end = function(data, encoding) {
   }
 
   this.finished = true;
+  DTRACE_HTTP_SERVER_RESPONSE(this.connection);
 
   // There is the first message on the outgoing queue, and we've sent
   // everything to the socket.
@@ -866,6 +867,7 @@ function connectionListener(socket) {
     var res = new ServerResponse(req);
     debug('server response shouldKeepAlive: ' + shouldKeepAlive);
     res.shouldKeepAlive = shouldKeepAlive;
+    DTRACE_HTTP_SERVER_REQUEST(req, socket);
 
     if (socket._httpMessage) {
       // There are already pending outgoing res, append.
index f37bc3931ca49da4bb2a0f32a44a1b253b92fc4a..9596725872671a69064e6ef02119e8a448b71251 100644 (file)
@@ -789,6 +789,7 @@ Socket.prototype._shutdown = function() {
 Socket.prototype.end = function(data, encoding) {
   if (this.writable) {
     if (this._writeQueueLast() !== END_OF_FILE) {
+      DTRACE_NET_STREAM_END(this);
       if (data) this.write(data, encoding);
       this._writeQueue.push(END_OF_FILE);
       if (!this._connecting) {
@@ -868,6 +869,7 @@ function Server(/* [ options, ] listener */) {
       s.server = self;
       s.resume();
 
+      DTRACE_NET_SERVER_CONNECTION(s);
       self.emit('connection', s);
 
       // The 'connect' event  probably should be removed for server-side
index 512c1d97f12f2d1773665c2fe17834379dcfc62e..582b0d23c41e6999febf44f3568acbc8bdc9fbf9 100644 (file)
@@ -3,6 +3,7 @@
 #include <node.h>
 
 #include <v8-debug.h>
+#include <node_dtrace.h>
 
 #include <locale.h>
 #include <stdio.h>
@@ -2028,6 +2029,8 @@ static void Load(int argc, char *argv[]) {
   Local<Object> global = v8::Context::GetCurrent()->Global();
   Local<Value> args[1] = { Local<Value>::New(process) };
 
+  InitDTrace(global);
+
   f->Call(global, 1, args);
 
   if (try_catch.HasCaught())  {
diff --git a/src/node.d b/src/node.d
new file mode 100644 (file)
index 0000000..80010e9
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * This is the DTrace library file for the node provider, which includes
+ * the necessary translators to get from the args[] to something useful.
+ * Be warned:  the mechanics here are seriously ugly -- and one must always
+ * keep in mind that clean abstractions often require filthy systems.
+ */
+#pragma D depends_on library procfs.d
+
+typedef struct {
+       int32_t fd;
+       int32_t port;
+       uint32_t remote;
+} node_dtrace_connection_t;
+
+typedef struct {
+       int32_t fd;
+       int32_t port;
+       uint64_t remote;
+} node_dtrace_connection64_t;
+
+typedef struct {
+       int fd;
+       string remoteAddress;
+       int remotePort;
+} node_connection_t;
+
+translator node_connection_t <node_dtrace_connection_t *nc> {
+       fd = *(int32_t *)copyin((uintptr_t)&nc->fd, sizeof (int32_t));
+       remotePort =
+           *(int32_t *)copyin((uintptr_t)&nc->port, sizeof (int32_t));
+       remoteAddress = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ?
+           copyinstr((uintptr_t)*(uint32_t *)copyin((uintptr_t)&nc->remote,
+           sizeof (int32_t))) :
+           copyinstr((uintptr_t)*(uint64_t *)copyin((uintptr_t)
+           &((node_dtrace_connection64_t *)nc)->remote, sizeof (int64_t)));
+};
+
+typedef struct {
+       uint32_t url;
+       uint32_t method;
+} node_dtrace_http_request_t;
+
+typedef struct {
+       uint64_t url;
+       uint64_t method;
+} node_dtrace_http_request64_t;
+
+typedef struct {
+       string url;
+       string method;
+} node_http_request_t;
+
+translator node_http_request_t <node_dtrace_http_request_t *nd> {
+       url = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ?
+           copyinstr((uintptr_t)*(uint32_t *)copyin((uintptr_t)&nd->url,
+           sizeof (int32_t))) :
+           copyinstr((uintptr_t)*(uint64_t *)copyin((uintptr_t)
+           &((node_dtrace_http_request64_t *)nd)->url, sizeof (int64_t)));
+       method = curpsinfo->pr_dmodel == PR_MODEL_ILP32 ?
+           copyinstr((uintptr_t)*(uint32_t *)copyin((uintptr_t)&nd->method,
+           sizeof (int32_t))) :
+           copyinstr((uintptr_t)*(uint64_t *)copyin((uintptr_t)
+           &((node_dtrace_http_request64_t *)nd)->method, sizeof (int64_t)));
+};
diff --git a/src/node_dtrace.cc b/src/node_dtrace.cc
new file mode 100644 (file)
index 0000000..0f7726a
--- /dev/null
@@ -0,0 +1,113 @@
+#include <node_dtrace.h>
+
+#ifdef HAVE_DTRACE
+#include "node_provider.h"
+#else
+#define NODE_HTTP_SERVER_REQUEST(arg0, arg1)
+#define NODE_HTTP_SERVER_REQUEST_ENABLED() (0)
+#define NODE_HTTP_SERVER_RESPONSE(arg0)
+#define NODE_HTTP_SERVER_RESPONSE_ENABLED() (0)
+#define NODE_NET_SERVER_CONNECTION(arg0)
+#define NODE_NET_SERVER_CONNECTION_ENABLED() (0)
+#define NODE_NET_STREAM_END(arg0)
+#define NODE_NET_STREAM_END_ENABLED() (0)
+#endif
+
+namespace node {
+
+using namespace v8;
+
+#define SLURP_STRING(obj, member, valp) \
+  String::Utf8Value _##member(obj->Get(String::New(#member))->ToString()); \
+  if ((*(const char **)valp = *_##member) == NULL) \
+    *(const char **)valp = "<unknown>";
+
+#define SLURP_INT(obj, member, valp) \
+  *valp = obj->Get(String::New(#member))->ToInteger()->Value();
+
+#define SLURP_CONNECTION(arg, conn) \
+  node_dtrace_connection_t conn; \
+  Local<Object> _##conn = Local<Object>::Cast(arg); \
+  SLURP_INT(_##conn, fd, &conn.fd); \
+  SLURP_STRING(_##conn, remoteAddress, &conn.remote); \
+  SLURP_INT(_##conn, remotePort, &conn.port);
+
+Handle<Value> DTRACE_NET_SERVER_CONNECTION(const Arguments& args) {
+  if (!NODE_NET_SERVER_CONNECTION_ENABLED())
+    return Undefined();
+
+  HandleScope scope;
+
+  SLURP_CONNECTION(args[0], conn);
+  NODE_NET_SERVER_CONNECTION(&conn);
+
+  return Undefined();
+}
+
+Handle<Value> DTRACE_NET_STREAM_END(const Arguments& args) {
+  if (!NODE_NET_STREAM_END_ENABLED())
+    return Undefined();
+
+  HandleScope scope;
+
+  SLURP_CONNECTION(args[0], conn);
+  NODE_NET_STREAM_END(&conn);
+
+  return Undefined();
+}
+
+Handle<Value> DTRACE_HTTP_SERVER_REQUEST(const Arguments& args) {
+  node_dtrace_http_request_t req;
+
+  if (!NODE_HTTP_SERVER_REQUEST_ENABLED())
+    return Undefined();
+
+  HandleScope scope;
+
+  Local<Object> arg0 = Local<Object>::Cast(args[0]);
+  Local<Object> arg1 = Local<Object>::Cast(args[1]);
+
+  SLURP_STRING(arg0, url, &req.url);
+  SLURP_STRING(arg0, method, &req.method);
+
+  SLURP_CONNECTION(args[1], conn);
+
+  NODE_HTTP_SERVER_REQUEST(&req, &conn);
+  return Undefined();
+}
+
+Handle<Value> DTRACE_HTTP_SERVER_RESPONSE(const Arguments& args) {
+  if (!NODE_HTTP_SERVER_RESPONSE_ENABLED())
+    return Undefined();
+
+  HandleScope scope;
+
+  SLURP_CONNECTION(args[0], conn);
+  NODE_HTTP_SERVER_RESPONSE(&conn);
+
+  return Undefined();
+}
+
+#define NODE_PROBE(name) #name, name
+
+void InitDTrace(Handle<Object> target) {
+  static struct {
+    const char *name;
+    Handle<Value> (*func)(const Arguments&);
+    Persistent<FunctionTemplate> templ;
+  } tab[] = {
+    { NODE_PROBE(DTRACE_NET_SERVER_CONNECTION) },
+    { NODE_PROBE(DTRACE_NET_STREAM_END) },
+    { NODE_PROBE(DTRACE_HTTP_SERVER_REQUEST) },
+    { NODE_PROBE(DTRACE_HTTP_SERVER_RESPONSE) },
+    { NULL }
+  };
+
+  for (int i = 0; tab[i].name != NULL; i++) {
+    tab[i].templ = Persistent<FunctionTemplate>::New(
+        FunctionTemplate::New(tab[i].func));
+    target->Set(String::NewSymbol(tab[i].name), tab[i].templ->GetFunction());
+  }
+}
+
+}
diff --git a/src/node_dtrace.h b/src/node_dtrace.h
new file mode 100644 (file)
index 0000000..267c729
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef NODE_DTRACE_H_
+#define NODE_DTRACE_H_
+
+#include <node.h>
+#include <v8.h>
+
+extern "C" {
+
+typedef struct {
+       int32_t fd;
+       int32_t port;
+       char *remote;
+} node_dtrace_connection_t;
+
+typedef struct {
+       char *url;
+       char *method;
+} node_dtrace_http_request_t;
+
+}
+
+namespace node {
+
+void InitDTrace(v8::Handle<v8::Object> target);
+
+}
+
+#endif
diff --git a/src/node_provider.d b/src/node_provider.d
new file mode 100644 (file)
index 0000000..3d11f39
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * DTrace provider for node.js.
+ */
+
+/*
+ * In order to have the information we need here to create the provider,
+ * we must declare bogus definitions for our depended-upon structures.  And
+ * yes, the fact that we need to do this represents a shortcoming in DTrace,
+ * one that would be resolved by that elusive El Dorado:  dynamic translators.
+ */
+
+typedef struct {
+       int dummy;
+} node_dtrace_connection_t;
+
+typedef struct {
+       int dummy;
+} node_connection_t;
+
+typedef struct {
+       int dummy;
+} node_dtrace_http_request_t;
+
+typedef struct {
+       int dummy;
+} node_http_request_t;
+
+provider node {
+       probe net__server__connection(node_dtrace_connection_t *c) :
+           (node_connection_t *c);
+       probe net__stream__end(node_dtrace_connection_t *c) :
+           (node_connection_t *c);
+       probe http__server__request(node_dtrace_http_request_t *h,
+           node_dtrace_connection_t *c) :
+           (node_http_request_t *h, node_connection_t *c);
+       probe http__server__response(node_dtrace_connection_t *c) :
+           (node_connection_t *c);
+};
+
+#pragma D attributes Evolving/Evolving/ISA provider node provider
+#pragma D attributes Private/Private/Unknown provider node module
+#pragma D attributes Private/Private/Unknown provider node function
+#pragma D attributes Private/Private/ISA provider node name
+#pragma D attributes Evolving/Evolving/ISA provider node args
index 1bc7d4fd35ff0960f4ffb95379e3be37064aac84..51d5e25ae19b58f8a1166ee26c77ebc7084a70ac 100644 (file)
@@ -39,6 +39,13 @@ process.on('exit', function() {
                       process,
                       global];
 
+  if (DTRACE_HTTP_SERVER_RESPONSE) {
+    knownGlobals.push(DTRACE_HTTP_SERVER_RESPONSE);
+    knownGlobals.push(DTRACE_HTTP_SERVER_REQUEST);
+    knownGlobals.push(DTRACE_NET_STREAM_END);
+    knownGlobals.push(DTRACE_NET_SERVER_CONNECTION);
+  }
+
   for (var x in global) {
     var found = false;
 
diff --git a/wscript b/wscript
index 8fb475ac98af31d252ddb1554d47ef48848d1c18..bc07ba36e9b6cf8d3fd80b65801808b9fd05984c 100644 (file)
--- a/wscript
+++ b/wscript
@@ -1,7 +1,8 @@
 #!/usr/bin/env python
 import re
 import Options
-import sys, os, shutil
+import sys, os, shutil, glob
+import Utils
 from Utils import cmd_output
 from os.path import join, dirname, abspath
 from logging import fatal
@@ -158,6 +159,13 @@ def set_options(opt):
                 , dest='shared_libev_libpath'
                 )
 
+  opt.add_option( '--with-dtrace'
+                , action='store_true'
+                , default=False
+                , help='Build with DTrace (experimental)'
+                , dest='dtrace'
+                )
 
   opt.add_option( '--product-type'
                 , action='store'
@@ -214,6 +222,14 @@ def configure(conf):
   #if Options.options.debug:
   #  conf.check(lib='profiler', uselib_store='PROFILER')
 
+  if Options.options.dtrace:
+    if not sys.platform.startswith("sunos"):
+      conf.fatal('DTrace support only currently available on Solaris')
+
+    conf.find_program('dtrace', var='DTRACE', mandatory=True)
+    conf.env["USE_DTRACE"] = True
+    conf.env.append_value("CXXFLAGS", "-DHAVE_DTRACE=1")
+
   if Options.options.efence:
     conf.check(lib='efence', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='EFENCE')
 
@@ -562,7 +578,7 @@ def build(bld):
 
   ### src/native.cc
   def make_macros(loc, content):
-    f = open(loc, 'w')
+    f = open(loc, 'a')
     f.write(content)
     f.close
 
@@ -576,10 +592,27 @@ def build(bld):
     "macros.py"
   )
 
+  ### We need to truncate the macros.py file
+  f = open(macros_loc_debug, 'w')
+  f.close
+  f = open(macros_loc_default, 'w')
+  f.close
+
   make_macros(macros_loc_debug, "")  # leave debug(x) as is in debug build
   # replace debug(x) with nothing in release build
   make_macros(macros_loc_default, "macro debug(x) = ;\n")
 
+  if not bld.env["USE_DTRACE"]:
+    make_macros(macros_loc_default, "macro DTRACE_HTTP_SERVER_RESPONSE(x) = ;\n");
+    make_macros(macros_loc_default, "macro DTRACE_HTTP_SERVER_REQUEST(x) = ;\n");
+    make_macros(macros_loc_default, "macro DTRACE_NET_SERVER_CONNECTION(x) = ;\n");
+    make_macros(macros_loc_default, "macro DTRACE_NET_STREAM_END(x) = ;\n");
+    make_macros(macros_loc_debug, "macro DTRACE_HTTP_SERVER_RESPONSE(x) = ;\n");
+    make_macros(macros_loc_debug, "macro DTRACE_HTTP_SERVER_REQUEST(x) = ;\n");
+    make_macros(macros_loc_debug, "macro DTRACE_NET_SERVER_CONNECTION(x) = ;\n");
+    make_macros(macros_loc_debug, "macro DTRACE_NET_STREAM_END(x) = ;\n");
+
+
   def javascript_in_c(task):
     env = task.env
     source = map(lambda x: x.srcpath(env), task.inputs)
@@ -610,6 +643,65 @@ def build(bld):
     native_cc_debug.rule = javascript_in_c_debug
 
   native_cc.rule = javascript_in_c
+  
+  if bld.env["USE_DTRACE"]:
+    dtrace = bld.new_task_gen(
+      name   = "dtrace",
+      source = "src/node_provider.d",
+      target = "src/node_provider.h",
+      rule   = "%s -x nolibs -h -o ${TGT} -s ${SRC}" % (bld.env.DTRACE),
+      before = "cxx",
+    )
+
+    if bld.env["USE_DEBUG"]:
+      dtrace_g = dtrace.clone("debug")
+
+    bld.install_files('/usr/lib/dtrace', 'src/node.d')
+
+    if sys.platform.startswith("sunos"):
+      #
+      # The USDT DTrace provider works slightly differently on Solaris than on
+      # the Mac; on Solaris, any objects that have USDT DTrace probes must be
+      # post-processed with the DTrace command.  (This is not true on the
+      # Mac, which has first-class linker support for USDT probes.)  On
+      # Solaris, we must therefore post-process our object files.  Waf doesn't
+      # seem to really have a notion for this, so we inject a task after
+      # compiling and before linking, and then find all of the node object
+      # files and shuck them off to dtrace (which will modify them in place
+      # as appropriate).
+      #
+      def dtrace_postprocess(task):
+        abspath = bld.srcnode.abspath(bld.env_of_name(task.env.variant()))
+        objs = glob.glob(abspath + 'src/*.o')
+
+        Utils.exec_command('%s -G -x nolibs -s %s %s' % (task.env.DTRACE,
+          task.inputs[0].srcpath(task.env), ' '.join(objs)))
+
+      dtracepost = bld.new_task_gen(
+        name   = "dtrace-postprocess",
+        source = "src/node_provider.d",
+        always = True,
+        before = "cxx_link",
+        after  = "cxx",
+      )
+
+      bld.env.append_value('LINKFLAGS', 'node_provider.o')
+
+      #
+      # Note that for the same (mysterious) issue outlined above with respect
+      # to assigning the rule to native_cc/native_cc_debug, we must apply the
+      # rule to dtracepost/dtracepost_g only after they have been cloned.  We
+      # also must put node_provider.o on the link line, but because we
+      # (apparently?) lack LINKFLAGS in debug, we (shamelessly) stowaway on
+      # LINKFLAGS_V8_G.
+      #
+      if bld.env["USE_DEBUG"]:
+        dtracepost_g = dtracepost.clone("debug")
+        dtracepost_g.rule = dtrace_postprocess
+        bld.env_of_name("debug").append_value('LINKFLAGS_V8_G',
+          'node_provider.o') 
+
+      dtracepost.rule = dtrace_postprocess
 
   ### node lib
   node = bld.new_task_gen("cxx", product_type)
@@ -639,6 +731,7 @@ def build(bld):
     src/node_timer.cc
     src/node_script.cc
     src/node_os.cc
+    src/node_dtrace.cc
   """
 
   if sys.platform.startswith("win32"):