diff --git a/lib/http.js b/lib/http.js index 3e500b34c60..6dccd97841b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -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. diff --git a/lib/net.js b/lib/net.js index f37bc3931ca..95967258726 100644 --- a/lib/net.js +++ b/lib/net.js @@ -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 diff --git a/src/node.cc b/src/node.cc index 512c1d97f12..582b0d23c41 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -2028,6 +2029,8 @@ static void Load(int argc, char *argv[]) { Local global = v8::Context::GetCurrent()->Global(); Local args[1] = { Local::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 index 00000000000..80010e9b4f7 --- /dev/null +++ b/src/node.d @@ -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 { + 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 { + 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 index 00000000000..0f7726a6209 --- /dev/null +++ b/src/node_dtrace.cc @@ -0,0 +1,113 @@ +#include + +#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 = ""; + +#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 _##conn = Local::Cast(arg); \ + SLURP_INT(_##conn, fd, &conn.fd); \ + SLURP_STRING(_##conn, remoteAddress, &conn.remote); \ + SLURP_INT(_##conn, remotePort, &conn.port); + +Handle 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 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 DTRACE_HTTP_SERVER_REQUEST(const Arguments& args) { + node_dtrace_http_request_t req; + + if (!NODE_HTTP_SERVER_REQUEST_ENABLED()) + return Undefined(); + + HandleScope scope; + + Local arg0 = Local::Cast(args[0]); + Local arg1 = Local::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 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 target) { + static struct { + const char *name; + Handle (*func)(const Arguments&); + Persistent 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::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 index 00000000000..267c7292169 --- /dev/null +++ b/src/node_dtrace.h @@ -0,0 +1,28 @@ +#ifndef NODE_DTRACE_H_ +#define NODE_DTRACE_H_ + +#include +#include + +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 target); + +} + +#endif diff --git a/src/node_provider.d b/src/node_provider.d new file mode 100644 index 00000000000..3d11f39416b --- /dev/null +++ b/src/node_provider.d @@ -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 diff --git a/test/common.js b/test/common.js index 1bc7d4fd35f..51d5e25ae19 100644 --- a/test/common.js +++ b/test/common.js @@ -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 8fb475ac98a..bc07ba36e9b 100644 --- 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"):