Node.js Performance Monitoring
a DIY approach

daniel@khan.io | @dkhan

https://github.com/danielkhan/InstrumentationExample.git

Our C++ folks ...

daniel@khan.io | @dkhan

Famous sources of performance problemsĀ in Node.js applications

The Memory Leak

Heap

daniel@khan.io | @dkhan

The CPU Hog

Latency

daniel@khan.io | @dkhan

The moment when you find out about the problem ...

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

My take on performance monitoring

"Let's find out about problems before my customers do"

daniel@khan.io | @dkhan

Tool Time

nodemon

daniel@khan.io | @dkhan

nodemon

v8-profiler

v8::HeapSnapshot

v8::CpuProfile

daniel@khan.io | @dkhan

Memory leak detection

var fs = require('fs');
var profiler = require('v8-profiler');
var _datadir = null;
var nextMBThreshold = 0;

// .......
// Schedule heapdump attempt every x seconds
// .......

function heapDump() {
    var memMB = process.memoryUsage().rss / 1048576;
    if (memMB > nextMBThreshold) {
        console.log('Current memory usage: %j', process.memoryUsage());
        nextMBThreshold += 100;
        var snap = profiler.takeSnapshot('profile');
        saveHeapSnapshot(snap, _datadir);
    }
}

function saveHeapSnapshot(snapshot, datadir) {
    var buffer = '';
    var stamp = Date.now();
    snapshot.serialize(
        function iterator(data, length) {
            buffer += data;
        }, function complete() {

            var name = stamp + '.heapsnapshot';
            fs.writeFile(datadir + '/' + name , buffer, function () {
                console.log('Heap snapshot written to ' + name);
            });
        }
    );
}

daniel@khan.io | @dkhan

Current memory usage: {"rss":4357156864,"heapTotal":921054976,"heapUsed":870913648}

var express = require('express');
var router = express.Router();

var theThing = null;

var replaceThing = function () {
    var originalThing = theThing;
    var unused = function () {
        if (originalThing) console.log("hi");
    };
    theThing = {
        longStr: new Array(1000000).join('*'),
        someMethod: function () {
            console.log('someMessage');
        }
    };
};

router.get('/leak', function (req, res, next) {
    replaceThing();
    return res.json({message: 'Everything is fine!'})
});

Meet our Leak

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

Chrome Developer Tools

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

CPU hog detection

// ........

/**
 * Starts profiling and schedules its end
 */
function startProfiling() {
    var stamp = Date.now();
    var id = 'profile-' + stamp;

    // Use stdout directly to bypass eventloop
    fs.writeSync(1, 'Start profiler with Id [' + id + ']\n');

    // Start profiling
    profiler.startProfiling(id);

    // Schedule stop of profiling in x seconds
    setTimeout(function () {
        stopProfiling(id)
    }, 1000);
}

/**
 * Stops the profiler and writes the data to a file
 * @param id the id of the profiler process to stop
 */
function stopProfiling(id) {
    var profile = profiler.stopProfiling(id);
    fs.writeFile(_datadir + '/' + id + '.cpuprofile.json', JSON.stringify(profile), function () {
        console.log('Profiler data written');
    });
}

daniel@khan.io | @dkhan

var primCalculator = function(start, end){
    var primes = [];
    for(var number = start; number < end; number++){
        var primeNumberDividers = []; //there should only be 2: 1 & number
        for(var divider = 1; divider <= number; divider++){
            if(number % divider === 0){
                primeNumberDividers.push(divider);
            }
        }
        if(primeNumberDividers.length === 2){
            primes.push(number);
        }
    }
    return primes;
};

router.get('/blocking', function(req, res, next) {
    return res.json(primCalculator(10000, 100000));
});

Let's create a CPU hog

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

daniel@khan.io | @dkhan

Thank you!

daniel@khan.io


@dkhan

Slides
http://bit.ly/1E3B4YB