Node.js instrumentation

Graphdat-SDK-Node is a Node.js package forked from "nodetime". It works on linux (and the like), OSX and Windows.

Installation

Add our dependency to your package.json:

"graphdat": "x.x.x"

Install the package:

npm install

Before any other require statements in your app, require graphdat:

require('graphdat');

This code automatically hooks into the http object, so if you run this on the same machine which is running the graphdat agent then you will see which endpoints are being called by your web server in the Request Count and Response Time graphs. 


an example of what you will see when you do the most basic instrumentation of your node application. 

*NOTE* by default we only display endpoints that take over 100ms to process. Any endpoints that run quicker than that will not show up. If you are only interested in routes faster than this limit add or modify the "agent_request_resp_time_threshold' property of your /etc/graphdat.conf agent configuration. Also note that there is a server side limit so setting the client side threshold to lower than the server threshold will have no effect. The server side threshold is currently 100ms.

Multiple data points

Having the http endpoints graphs is very helpful but the grapdhat sdk also lets you provide more information about the functions running inside each endpoint. 

Take a look at this example expressjs route. 

/*
    This example expressjs endpoint will authorize
    user from session, then fetch the projects
    associated with them.
 */
app.get('/graphdat/example', function(req, res) {
    req.graphdat.begin('auth');
    var userId = req.session.userId;
    
    db.getUserById(userId, function(user) {
        req.graphdat.end('auth');

        req.graphdat.begin('projects');
        db.getProjectsByUserId(user.id, function(projects){
            req.graphdat.end('projects');

            // we dont need to provide the associated 
            // req.graphdat.end('res.send'); for the 
            // last item.
            req.graphdat.begin('res.send');
            res.send(projects);
        });
    });
});

When you require the graphdat module, we include the graphdat property to any http request and response objects that get generated. You can then use that to instrument your code with begin() and end() statements. 

The example above will display a stacked bar graph on your graphdat dill down dashboard, showing you the breakdown between each of the different trace items


Want to go deeper?

Need to get some instrumentation into that getProjectsByUserId call? We've got your covered:

We've added a helper to the req and res objects: gdcall. Use it to start the call into your function and we will begin an instrumentation block, end it for you when your callback gets called, and inside the function called, you will have all of the regular graphdat SDK stuff available to you, passed in attached to the callback.

/*
    This example expressjs endpoint will authorize
    user from session, then fetch the projects
    associated with them.
 */
app.get('/graphdat/example', function(req, res) {
    req.graphdat.begin('auth');
    var userId = req.session.userId;
    
    db.getUserById(userId, function(user) {
        req.graphdat.end('auth');
        req.gdcall(db.getProjectsByUserId, user.id, function(projects){
            // we dont need to provide the associated 
            // req.graphdat.end('res.send'); for the 
            // last item.
            req.graphdat.begin('res.send');
            res.send(projects);
        });
    });
});

And then inside the getProjectsByUserId function you can instrument that block in the same way as you normally would, using the graphdat object that is now attached to the callback:

function getProjectsByUserId(userId, cb) {
// ...
cb.graphdat.begin("work");
// ...
cb.gdcall(otherWork, otherCallback);
// ...
cb.graphdat.end("work");
// ...
}

Just the context

Just want to pass in the context without starting a new block? Use trace:

/*
    This example expressjs endpoint will authorize
    user from session, then fetch the projects
    associated with them.
 */
app.get('/graphdat/example', function(req, res) {
    req.graphdat.begin('auth');
    var userId = req.session.userId;
    
    db.getUserById(userId, function(user) {
        req.graphdat.end('auth');
        req.graphdat.begin('projects');
        db.getProjectsByUserId(user.id, req.graphdat.trace(function(projects){
            req.graphdat.end('projects');
            // we dont need to provide the associated 
            // req.graphdat.end('res.send'); for the 
            // last item.
            req.graphdat.begin('res.send');
            res.send(projects);
        }));
    });
});

Inside the getProjectsByUserId you use the graphdat object on the callback just like in the gdcall case.

Asynchronous

Both gdcall and trace begin a call context to start the new block on and to pass into the target function. This allows that function to be called asynchronously, in parallel, and the call stack trace that is generated will be neat and tidy and only as deep as needed.

require('graphdat');
var http = require('http');
http.createServer(function (req, res)
{
req.gdcall(fun1, function()
{
res.writeHead(200, {'Content-Type':'text/plain'});
res.end('Hello World\n');
});
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
function fun1(cb)
{
var c = 0;
for(var i=0; i < 10; i++)
{
cb.gdcall(fun2, function()
{
if (++c == 10)
cb();
});

}
}
function fun2(cb)
{
setTimeout(function()
{
cb();
}, 500);
}

This will generate a call stack trace that includes "/", "/fun1", and "/fun1/fun2" timers. The value for / and /fun1 will be about 500ms as the whole request takes that long, and the fun2 will be the sum of the 10 parellel calls to it, so about 5000ms.

Recursive

We also manage call stack depth for recursive functions or function chains. Functions that a re-called in a recursive call graph are timed individually and the data reported is the sum of all calls into the function.

require('graphdat');
var http = require('http');
http.createServer(function (req, res)
                  {
                 req.gdcall(fun1, function()
                 {
                 res.writeHead(200, {'Content-Type':'text/plain'});
                 res.end('Hello World\n');
                 });
                  }).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
function fun1(cb)
{
var c = 0;

recurse();

function recurse()
{
cb.graphdat.begin('recurse');
if (++c >= 10)
{
cb();
return;
}
recurse();
}
}

This generates a call stack trace that includes "/", "/fun1", and "/fun1/recurse" where the timer for the recurse path is the sum of all 10 calls of that function.

Express.js

We have some extra love for express users. You wont find your raw urls in the dashboard, we will send you route timers for each of the paths that you map. So a route defined as in this test app:

require('graphdat');
var express = require('express'),
app = express();

var registerRoutes = function(app) {
app.get("/fun/:id", function (req, res) {
req.graphdat.begin("fun");
res.send({
        success: true
        });
});
}
registerRoutes(app);
app.listen(1337, function() {
console.log('Server running at http://127.0.0.1:1337/');
});

And hit with urls like "127.0.0.1:1337/fun/123", "127.0.0.1:1337/fun/456" and "127.0.0.1:1337/fun/789" will report timings for route "/fun/:id" only.

Configuration

Configuration information can be passed to the library via the config(options) method on the object returned by require('graphdat').

Options supported:

NameDefaultMeaning
enabled
true

when true, the sdk will collect and send data to the agent

socketFile
/tmp/gd.agent.sock

The file descriptor to use for communications between the library and the agent on Linux and OSX

socketPort
26873

The port on localhost to use for communications between the library and the agent on Windows

logger
console.log

a custom logger that will be called for all logging actions (see below for details)

debug
false

when truthy, extra debug information will be logged

debug.sync_write
false

log the details of each write to the agent

debug.dump_payload
false

dump the data sent to the agent

debug.http_begin
false

log when an http connection begun

debug.http_end
false

log when an http connection is ended

debug.http_trace
false

log when the context is passed into another method from a http handler

suppress.context_pop_automatic
false

suppress the warning message generated when a request ends with open instrumentation blocks (see advanced instrumentation documentation for more details)

E.g.:

require('graphdat')
    .config({
        enabled: true,
        debug: {
            dump_payload: true,
            http_begin: true
        },
        suppress: {
            context_pop_automatic: true
        },
        socketPort: '22222',
        logger: myLogger
    });

Logging

The library will log errors to the console by default, or to your custom logger function if provided via the config(options) method.

E.g.:

var myLogger = function(logType, message/*[,object]*/) {
    message = "graphdat reports:: " + message;
    var args = Array.prototype.slice.apply(arguments, [ 1 ]);
    console.log.apply(console, args);
}

Uninstallation

Remove the require('graphdat') line from your .js file

Remove any other references to graphdat SDK functions from your codebase

Remove the graphdat dependency from your package.json

Delete the graphdat directory in your node_modules directory

Feedback and Knowledge Base