diff --git a/lib/dtrace.js b/lib/dtrace.js new file mode 100644 index 000000000..17ae4e5d0 --- /dev/null +++ b/lib/dtrace.js @@ -0,0 +1,81 @@ +// Load modules + +var Utils = require('./utils'); +// dtrace-provider loaded inline when installed + + +// Declare internals + +var internals = {}; + + +module.exports = internals.DTrace = function (name) { + + Utils.assert(this.constructor === internals.DTrace, 'DTrace must be instantiated using new'); + + if (!internals.DTrace.isInstalled()) { + this.report = function () {}; + return; + } + + this._probes = {}; + this._dtrace = new internals.DTrace.Provider(name); +}; + + +internals.DTrace.prototype.report = function (key/* arg1, arg2 */) { + + var args = Array.prototype.slice.call(arguments, 1); + var probe = this._probes[key] || this._addProbe(key, args); // If probe not found create and add it + + probe.fire(function () { + + return args; + }); +}; + + +internals.DTrace.prototype._addProbe = function (key, values) { + + var paramTypes = []; + for (var i = 0, il = values.length; i < il; ++i) { + var value = values[i]; + if (typeof value === 'number') { + paramTypes.push('int'); + } + else if (value !== null && typeof value === 'object') { + paramTypes.push('json'); + } + else { + paramTypes.push('char *'); + } + } + + var probe = this._dtrace.addProbe.apply(this._dtrace, [key].concat(paramTypes)); + this._probes[key] = probe; + + this._dtrace.disable(); // Provider must be disabled/enabled for probe to be visible + this._dtrace.enable(); + + return probe; +}; + + +internals.DTrace.isInstalled = function () { + + var result = false; + try { + result = !!require.resolve('dtrace-provider'); + } + catch (err) {} + + internals.DTrace.isInstalled = function () { + + return result; + }; + + return result; +}; + + +internals.DTrace.Provider = internals.DTrace.isInstalled() && require('dtrace-provider').DTraceProvider; \ No newline at end of file diff --git a/lib/request.js b/lib/request.js index 944096b35..47e649301 100755 --- a/lib/request.js +++ b/lib/request.js @@ -436,11 +436,13 @@ internals.Request.bindPre = function (pre) { request.pre[pre.assign] = result; } + request.server._dtrace.report('pre.end', pre.assign, result); return exit(); }; enter(function () { + request.server._dtrace.report('pre.start', pre.assign); pre.method(request, finalize); }); }); diff --git a/lib/server.js b/lib/server.js index 44a2eea11..dd80b66e2 100755 --- a/lib/server.js +++ b/lib/server.js @@ -9,6 +9,7 @@ var Boom = require('boom'); var Catbox = require('catbox'); var Auth = require('./auth'); var Defaults = require('./defaults'); +var DTrace = require('./dtrace'); var Request = require('./request'); var Router = require('./router'); var Schema = require('./schema'); @@ -83,6 +84,7 @@ module.exports = internals.Server = function (/* host, port, options */) { this._router = new Router(this); this._ext = new Ext(); this._stateDefinitions = {}; + this._dtrace = new DTrace('' + this._port); if (args.pack) { this.pack = args.pack; @@ -246,7 +248,7 @@ internals.Server.prototype._stop = function (options, callback) { }, options.timeout); self.listener.close(function () { - + self.listener.removeAllListeners(); clearTimeout(timeoutId); callback(); diff --git a/package.json b/package.json index dc3a4c947..71a9432da 100755 --- a/package.json +++ b/package.json @@ -50,7 +50,8 @@ "request": "2.22.x", "handlebars": "1.0.x", "jade": "0.33.x", - "hapi-plugin-test": "1.x.x" + "hapi-plugin-test": "1.x.x", + "dtrace-provider": "0.2.x" }, "bin": { "hapi": "./bin/hapi" diff --git a/test/integration/dtrace.js b/test/integration/dtrace.js new file mode 100644 index 000000000..e895c0151 --- /dev/null +++ b/test/integration/dtrace.js @@ -0,0 +1,212 @@ +// Load modules + +var Lab = require('lab'); +var Hapi = require('../..'); +var DTrace = require('../../lib/dtrace'); + + +// Declare internals + +var internals = {}; + + +// Test shortcuts + +var expect = Lab.expect; +var before = Lab.before; +var after = Lab.after; +var describe = Lab.experiment; +var it = Lab.test; + + +describe('DTrace', function () { + + it('doesn\'t fire probes when dtrace provider isn\'t installed', function (done) { + + var provider = DTrace.Provider; + var isInstalled = DTrace.isInstalled; + DTrace.Provider = function () { + + return { + enable: function () {}, + disable: function () {}, + addProbe: function () { + + return { + fire: function (fn) { + + expect(fn).to.not.exist; + } + }; + } + }; + }; + DTrace.isInstalled = function () { + + return false; + }; + + var server = new Hapi.Server(); + + + var pre1 = function (request, next) { + + next('Hello'); + }; + + server.route({ method: '*', path: '/', config: { + handler: function () { + + this.reply('OK'); + }, + pre: [ + { method: pre1, assign: 'm1' } + ] + }}); + + server.inject({ url: '/' }, function () { + + DTrace.Provider = provider; + DTrace.isInstalled = isInstalled; + done(); + }); + }); + + it('fires correct probe on prerequisites when dtrace-provider is installed', function (done) { + + var provider = DTrace.Provider; + var isInstalled = DTrace.isInstalled; + DTrace.Provider = function () { + + return { + enable: function () {}, + disable: function () {}, + addProbe: function () { + + return { + fire: function (fn) { + + expect(fn()).to.contain('m1'); + DTrace.Provider = provider; + DTrace.isInstalled = isInstalled; + done(); + } + }; + } + }; + }; + DTrace.isInstalled = function () { + + return true; + }; + + var server = new Hapi.Server(); + var pre1 = function (request, next) { + + next('Hello'); + }; + + server.route({ method: '*', path: '/', config: { + handler: function () { + + this.reply('OK'); + }, + pre: [ + { method: pre1, assign: 'm1' } + ] + }}); + + server.inject({ url: '/' }, function () {}); + }); + + it('allows probes to be added dynamically', function (done) { + + var runNum = 0; + var provider = DTrace.Provider; + DTrace.Provider = function () { + + return { + enable: function () {}, + disable: function () {}, + addProbe: function () { + + return { + fire: function (fn) { + + if (runNum++ === 0) { + expect(fn()).to.contain(20); + expect(fn()).to.contain('some value'); + } + else { + expect(fn()).to.contain(1); + expect(fn()).to.contain('3'); + } + } + }; + } + }; + }; + var server = new Hapi.Server(); + + server.route({ method: '*', path: '/', config: { + handler: function () { + + this.server._dtrace.report('my.handler.start', 20, 'some value'); + this.reply('OK'); + this.server._dtrace.report('my.handler.end', 1, '3'); + } + }}); + + server.inject({ url: '/' }, function () { + DTrace.Provider = provider; + done(); + }); + }); + + it('probes add the correct data types', function (done) { + + var provider = DTrace.Provider; + DTrace.Provider = function () { + + return { + enable: function () {}, + disable: function () {}, + addProbe: function (key, val1, val2, val3) { + + expect(key).to.equal('my.probe'); + expect(val1).to.equal('int'); + expect(val2).to.equal('char *'); + expect(val3).to.equal('json'); + DTrace.Provider = provider; + done(); + + return { + fire: function () {} + }; + } + }; + }; + var server = new Hapi.Server(); + server._dtrace.report('my.probe', 20, 'some value', { some: 'obj' }); + }); + + it('allows probes to be added dynamically with the dtrace-provider installed', function (done) { + + var server = new Hapi.Server(); + + server.route({ method: '*', path: '/', config: { + handler: function () { + + this.server._dtrace.report('my.handler.start', 20, ['some value', 1]); + this.reply('OK'); + this.server._dtrace.report('my.handler.end', 1, '3'); + } + }}); + + server.inject({ url: '/' }, function (res) { + + expect(res.statusCode).to.equal(200); + done(); + }); + }); +}); diff --git a/test/integration/server.js b/test/integration/server.js index f1f834dcb..2ee0981f5 100755 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -38,6 +38,7 @@ describe('Server', function () { it('won\'t stop until all connections are closed', function (done) { + var isDone = false; var server = Hapi.createServer(0); server.start(function () { @@ -57,7 +58,8 @@ describe('Server', function () { server.listener.getConnections(function (err, count) { expect(count).to.equal(0); - done(); + !isDone && done(); + isDone = true; }); }); @@ -154,18 +156,18 @@ describe('Server', function () { }); }); }); - + it('removes connection event listeners after it stops', function (done) { var server = Hapi.createServer(0); server.start(function () { - + server.stop(function () { - + server.start(function () { - + server.stop(function () { - + expect(server.listeners('connection').length).to.be.eql(0); done(); });