Quantcast
Viewing all articles
Browse latest Browse all 37

Spy on any method on an object and profile number of times called

So, Object.observe will become a standard at some point. In the meanwhile, you may want to unobtrusively log any methods called on an object from the outside. So I wrote this little snippet that helps me do just that.

Object.monitor = function(obj, quiet){
	var keys = (function(obj){
			// include from prototype also, any function.
			var keys = [], key;
			for (key in obj) typeof obj[key] === 'function' && keys.push(key);
			return keys;
		}(obj)),
		// alternating foreground colours for odd/even calls.
		colours = [
			'#FFFFFF',
			'#CCCCCC'
		],
		token = '%c',
		monitor = ' monitor:',
		log = function(what, method){
			// more use goes red in console.
			console.log(token + '%d' + token + monitor + '%s',
				'font-family:courier;background-color:rgb(' + Math.min(255, what) + ',0,0);color:' + colours[+(what %2 == 0)],
				what,
				'font-family:courier',
				method
			);
		},
		counters = {};

	keys.forEach(function(key){
		var orig = obj[key];
		Object.defineProperty(obj, key, {
			get: function(){
				key in counters || (counters[key] = 0);
				counters[key]++;
				quiet || log(counters[key], key);
				return orig;
			}
		});
	});

	return function(){
		// serialize the calls and order by counts
		return {
			raw: counters,
			sorted: keys.map(function(key){
				return {
					key: key,
					value: counters[key]
				};
			}).filter(function(obj){
					return !!obj.value;
				}).sort(function(a, b){
					return a.value <= b.value;
				}).map(function(obj){
					var o = {};
					return o[obj.value] = obj.key, o;
				})
		}
	}
};

Basically, saving a reference of the original method, logging and returning the method. Downside: just asking for a reference or toSource does not guarantee the method is being called. If you need to take that into account, simply wrap the method into a function instead.

Object.monitor will return a reference to a function that you can call at any time to inspect the current results.

To use it, simply go:

var foo = {
    count: 0,
    one: function(){
        this.count++;
    },
    two: function(){
        this.count += 2;
    }
};

var fooStats = Object.monitor(foo);
foo.one();
foo.two();
var c = 150;
while(c--) foo.one();

console.log(fooStats()); // returns an object with sorted and raw counters. 

See it in action: http://jsfiddle.net/dimitar/aCcVX/

Tweaks you can do to get different methods:

// only local keys not proto
keys = Object.keys(obj).filter(... fn only ...);
// local and prototype keys on a class
keys = Object.keys(obj).concat(Object.keys(obj.constructor.prototype)).filter(... fn only ...);

The quiet argument will prevent it from spamming your console. You can tweak to use console.count as well if you prefer.


Viewing all articles
Browse latest Browse all 37

Trending Articles