Skip to content

Commit

Permalink
Added profiling functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
jwarkentin committed Jan 12, 2013
1 parent 9927afb commit acc563c
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 34 deletions.
4 changes: 3 additions & 1 deletion .npmignore
@@ -1 +1,3 @@
node_modules
node_modules
.npmignore
.gitignore
87 changes: 75 additions & 12 deletions README.md
@@ -1,19 +1,21 @@
Node Monkey
===========
NodeMonkey
==========

A Node.js module for inspecting and debugging Node applications through a web browser
node-monkey runs a simple server and uses [Socket.IO](https://github.com/LearnBoost/socket.io) to create a websocket connection between the browser and server.
A Node.js module for inspecting and debugging Node applications through a web browser

NodeMonkey runs a simple server and uses [Socket.IO](https://github.com/LearnBoost/socket.io) to create a websocket connection between the browser and server.
It captures anything that would normally be logged to the terminal, converts it to JSON and passes it to the browser
where it can be logged in the console for inspection.

The motivation for this project came from trying to debug a Node server I wrote that used websockets.
Version 0.2.0 also introduces code profiling functionality and the ability to send commands to your Node.js application from your web browser.

The motivation for this project came from trying to debug a Node.js server I wrote that used websockets.
I found it problematic trying to inspect objects with the terminal.
I tried using the built-in debugging that works with the [Chrome Developer Tools plugin](https://github.com/joyent/node/wiki/using-eclipse-as-node-applications-debugger) for Eclipse.
Unfortunately, I ran into a problem where setting breakpoints to inspect objects would cause the server to stop responding to heartbeats and cause the client to disconnect.
Unfortunately, I ran into a problem where setting breakpoints to inspect objects would cause the server to stop responding to heartbeats thus causing the client to disconnect.
This would entirely mess up my debugging efforts. All I really needed to do was have a good way to inspect objects.
I searched Google and found projects like [node-inspector](https://github.com/dannycoates/node-inspector), which doesn't work with the latest versions of Node, and [node-codein](http://thomashunter.name/blog/nodejs-console-object-debug-inspector/) which has many bugs.
And neither works with Firefox. And node-monkey was born!
And neither works with Firefox. And NodeMonkey was born!

Installation
------------
Expand All @@ -25,13 +27,13 @@ npm install node-monkey
Usage
-----

Using node-monkey is extremely easy.
Using NodeMonkey is extremely easy.
All you have to do is include the following line in your Node.js application.
Anything that is logged to the console after this will show up in the browser console once connected.
It captures all output to `console.log()`, `console.warn()` and `console.error()`.

```javascript
require('node-monkey').start([options]);
var nm = require('node-monkey').start([options]);
```

To connect your browser simply go to `http://0.0.0.0:50500` in your web browser.
Expand All @@ -42,6 +44,7 @@ As an alternative to viewing output through this page, you can also view output
```html
<script type="text/javascript" src="http://0.0.0.0:50500/socket.io/socket.io.js"></script>
<script type="text/javascript" src="http://0.0.0.0:50500/client.js"></script>
<script type="text/javascript" src="http://0.0.0.0:50500/underscore.js"></script>
```

**NOTE**: You do NOT have to refresh the page when you restart your Node application to continue to receive output. It will automatically reconnect.
Expand All @@ -54,9 +57,14 @@ Options
* **suppressOutput**: Use this to suppress terminal output when `console.log()` is called, freeing the console from clutter and allowing you to only inspect objects through the browser. Default is `true`.
* **saveOutput**: If data is logged before you are able to connect your browser, you may still want to be able to view this data. Setting this option to `true` causes node-monkey to save the output and dump it out to the browser once you connect. Default is `true`.
* **silent**: If `true` then nothing will be logged to the console when started. Default is `false`.
* **profiler**: This is a nested object of options for the profiler. It's options are listed below.

Examples
--------
### Profiler Options
* **active**: If `true`, the profiler will be enabled when NodeMonkey is included. Defaults to `true`.
Note that it doesn't matter what this is set to if you never call any profiler functions.

Logging Examples
----------------

**Example 1**
```javascript
Expand All @@ -75,6 +83,61 @@ console.warn('You might have done something wrong');
console.error('FATAL ERROR', {message: 'Something broke'});
```

Profiling Examples
------------------

**Example 1 - Setting profiler options**
```javascript
var nm = require('node-monkey').setConfig({
profiler: {
active: false
}
});
```

OR

```javascript
var nm = require('node-monkey');
nm.profiler.setConfig({
active: false
});
```

However, the above example is equivalent to the following:

```javascript
var nm = require('node-monkey');
nm.profiler.pause();
// NOTE: You can reverse this by calling `nm.profiler.resume()`
```

**Example 2 - Using the profiler**
After you have included and configured the profiler you have a couple simple options right now. You can manually start and stop
the timer allowing complete control over what is timed and what data you see in the output.

```javascript
// NOTE: The second parameter for `startTime()` is optional and can simply be any data you want to see
// with the call data that is dumped out for this call
var timer = nm.profiler.startTime('identifier', {some: 'data'});
/* [code that does stuff here] */
nm.profiler.stopTimer(timer);
```

However, if you just want to profile a specific function you can call `profile()` which will return a new profiled version of the function.
If you use this, you have a third parameter which, if set to `true` will cause it to save the arguments the function is called with in the
profile data for each function call.

```javascript
var myfunc = nm.profiler.profile('identifier', function() {
/* [code that does stuff here] */
});
```

Just a final note about the identifiers/keys used when timing functions. They are mainly used as a grouping mechanism. You can use the same
key as many times as you want. If you want to distinguish between different calls using the same key, put something unique in the `params`
argument which follows the identifier.

Contribute
----------

Expand Down
1 change: 0 additions & 1 deletion TODO.md
@@ -1,7 +1,6 @@
- console.trace() functionality
- Provide file and line numbers to client of where console.log() was called
- Pass data through to client, even if there's a fatal exception
- Provide a way to send commands back to the NodeJS app
- Support cyclical objects by overriding JSON.stringify() and JSON.parse(). Have a look at 'cycle.js' here: https://github.com/douglascrockford/JSON-js
- Send full object representation, including function()'s
- Is it possible to rebuild full objects including inheritance?
45 changes: 42 additions & 3 deletions client.js
Expand Up @@ -38,9 +38,9 @@

connection.on('connect', function() {
logMsg(' ');
logMsg('---------------------');
logMsg('Welcome to node-mokey');
logMsg('---------------------');
logMsg(' /--------------------\\');
logMsg(' Welcome to NodeMokey');
logMsg(' \\--------------------/');
});

connection.on('reconnecting', function(delay, attempts) {
Expand Down Expand Up @@ -76,4 +76,43 @@
}, 500);
}



//
// -- NodeMonkey API --
//

window.nm = {
_cmdCall: 0,
_callbacks: {},

cmd: function(cmd, args, callback) {
var cmdId = ++nm._cmdCall;
connection.emit('cmd', {command: cmd, args: args, cmdId: cmdId});

if(callback) {
nm._callbacks[cmdId] = callback;
}
},

_response: function(resp) {
var cb = nm._callbacks[resp.cmdId];
if(cb) {
cb(resp.result);
}
},

profiler: {
pause: function() {
nm.cmd('profiler.pause');
},

resume: function() {
nm.cmd('profiler.resume');
}
}
};

connection.on('cmdResponse', nm._response);

})();
50 changes: 39 additions & 11 deletions index.js
Expand Up @@ -3,13 +3,36 @@ var httpServer = require('http');
var socketIO = require('socket.io');
var fs = require('fs');
//var clientJS = fs.readFileSync('./client.js');
var profiler = require('./profiler.js');

function NodeMonkey() {
this.config = {};
this.msgbuffer = [];

this.profiler = new profiler();
}

_.extend(NodeMonkey.prototype, {
profiler: null,

setConfig: function(config) {
this.config = _.extend({
host: '0.0.0.0',
port: '50500',
suppressOutput: true,
saveOutput: true,
silent: false
}, config || {});

this.config.profiler = _.extend({
activeOnStart: true
}, this.config.profiler || {});

this.profiler.setConfig(this.config.profiler);

return this;
},

consoleMsg: function(type, data) {
// Send to open sockets if there is at least one, otherwise buffer
var consoleData = {type: type, data: Array.prototype.slice.call(data)};
Expand Down Expand Up @@ -57,27 +80,25 @@ _.extend(NodeMonkey.prototype, {
console.error = function() {
that.consoleMsg('error', arguments);
};

return this;
},

start: function(config) {
var that = this;

this.config = _.extend({
host: '0.0.0.0',
port: '50500',
suppressOutput: true,
saveOutput: true,
silent: false
}, config || {});
this.setConfig(config);

var socketIOSrc = 'http://' + this.config.host + ':' + this.config.port + '/socket.io/socket.io.js';
this.srv = httpServer.createServer(function(req, res) {
if(req.url.indexOf('socket.io') === 1) {
} else if(req.url == '/client.js') {
var clientJS = fs.readFileSync(__dirname + '/client.js');
res.end(clientJS);
} else if(req.url == '/underscore.js') {
res.end(fs.readFileSync('./node_modules/underscore/underscore-min.js'));
} else {
res.end('<html><head><title>Node Monkey</title><script type="text/javascript" src="' + socketIOSrc + '"></script><script type="text/javascript" src="/client.js"></script><head><body>Open your console to see output</body></html>');
res.end('<html><head><title>Node Monkey</title><script type="text/javascript" src="' + socketIOSrc + '"></script><script type="text/javascript" src="/underscore.js"></script><script type="text/javascript" src="/client.js"></script><head><body>Open your console to see output</body></html>');
}
}).listen(this.config.port, this.config.host);
this.iosrv = socketIO.listen(this.srv);
Expand All @@ -88,17 +109,24 @@ _.extend(NodeMonkey.prototype, {

this.iosrv.sockets.on('connection', function(socket) {
that.trySendBuffer();

socket.on('cmd', function(cmd) {
var callObj = ['that'].concat(cmd.command.split('.').slice(0, -1)).join('.');
socket.emit('cmdResponse', { cmdId: cmd.cmdId, result: eval('that.' + cmd.command + '.apply(' + callObj + ', cmd.args);') });
});
});

this.replaceConsole();

if(!this.config.silent) {
this.clog('-------------------');
this.clog('node-monkey started');
this.clog('------------------');
this.clog('NodeMonkey started');
this.clog('To inspect output, open a browser to: http://' + this.config.host + ':' + this.config.port);
this.clog('-------------------');
this.clog('------------------');
this.clog(' ');
}

return this;
}
});

Expand Down
9 changes: 5 additions & 4 deletions package.json
@@ -1,9 +1,10 @@
{
"author": "Justin Warkentin <justin.warkentin@gmail.com>",
"name": "node-monkey",
"version": "0.1.2",
"description": "A Node.js module for inspecting and debugging Node applications through a web browser",
"keywords": ["inspect objects", "object inspector", "inspect", "debug", "console.log", "object"],

"version": "0.2.0",
"description": "A Node.js module for inspecting, profiling and debugging Node.js applications through a web browser",
"keywords": ["inspect", "debug", "console.log", "profile"],
"homepage": "https://github.com/jwarkentin/node-monkey",
"bugs": "https://github.com/jwarkentin/node-monkey/issues",
"main": "./index.js",
Expand All @@ -15,4 +16,4 @@
"underscore": "*",
"socket.io": "*"
}
}
}

0 comments on commit acc563c

Please sign in to comment.