Skip to content

Instantly share code, notes, and snippets.

@6174
Created August 7, 2013 04:02
Show Gist options
  • Save 6174/6171096 to your computer and use it in GitHub Desktop.
Save 6174/6171096 to your computer and use it in GitHub Desktop.
profile a javascript functions
for (var method in jQuery.fn)(function (method) {
if (method == "init") return;
var old = jQuery.fn[method];
jQuery.fn[method] = function () {
if (!internal && curEvent) {
internal = true;
var m = curEvent.methods[curEvent.methods.length] = {
name: method,
inLength: this.length,
args: formatArgs(arguments)
};
var start = (new Date).getTime();
var ret = old.apply(this, arguments);
m.duration = (new Date).getTime() - start;
if (curEvent.event == "inline") curEvent.duration += m.duration;
if (ret && ret.jquery) m.outLength = ret.length;
internal = false;
return ret;
} else {
return old.apply(this, arguments);
}
};
})(method);
/*!
* jProfile - JavaScript Performance Profile Tool for jQuery-enabled Site.
* http://code.google.com/p/jprofile/
* Inspired by John Resig article: http://ejohn.org/blog/deep-profiling-jquery-apps/
*
* Copyright 2011, sanshi.me
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jmarker.googlecode.com/svn/trunk/license/license.htm
*
* Version: 1.2
* Date: Aug 2, 2011
*/
(function($){
// Get or set cookie.
function cookie(name, value) {
if(value === undefined) {
var returned;
$.each(document.cookie.split(';'), function(index, item) {
var tmp = $.trim(item).split('=');
if(name === tmp[0]) {
returned = decodeURIComponent(tmp[1]);
return false;
}
});
return returned;
} else {
var expires = '', secure = '';
if(value === null) {
expires = ';expires=' + new Date(2000, 1, 1).toUTCString();
}
if(window.location.protocol === 'https:') {
secure = ';secure=1';
}
document.cookie = [name, '=', encodeURIComponent(value), ';path=/', expires, secure].join('');
}
}
// Get function name.
function getFunctionName(fn)
{
var name = /\W*function\s+([\w\$]+)\(/.exec(fn);
if(name) {
return name[1];
}
return '';
}
// Inspired by http://ejohn.org/blog/deep-profiling-jquery-apps/.
// Form elem to string.
function formatElem(elem) {
var str = "";
if (elem.tagName) {
str = "<" + elem.tagName.toLowerCase();
if (elem.id) str += "#" + elem.id;
if (elem.className) str += "." + elem.className.replace(/ /g, ".");
str += ">";
} else {
str = elem.nodeName;
}
return str;
}
// Inspired by http://ejohn.org/blog/deep-profiling-jquery-apps/.
// Format function arguments.
function formatArgs(args) {
var str = [];
for (var i = 0; i < args.length; i++) {
var item = args[i];
if (item && item.constructor == Array) {
str.push("Array(" + item.length + ")");
} else if (item && item.jquery) {
str.push("jQuery(" + item.length + ")");
} else if (item && item.nodeName) {
str.push(formatElem(item));
} else if (item && typeof item == "function") {
str.push("function()");
} else if (item && typeof item == "object") {
str.push("{...}");
} else if (typeof item == "string") {
str.push('"' + item.replace(/&/g, "&amp;").replace(/</g, "&lt;") + '"');
} else {
str.push(item + "");
}
}
return str.join(", ");
}
// Borrow from jQuery source code.
var class2type = [], types = "Boolean Number String Function Array Date RegExp Object".split(" "),
i, count = types.length, name;
// Populate the class2type map
for(i=0; i<count; i++) {
name = types[i];
class2type[ "[object " + name + "]" ] = name.toLowerCase();
}
function getType(obj) {
return obj == null ?
String(obj) :
class2type[ Object.prototype.toString.call(obj) ] || "object";
}
// Emit messages to console.
function log() {
/*
if (typeof(console) !== "undefined") {
for(var i = 0; i < arguments.length; i++) {
console.log('[jProfile] ' + arguments[i]);
}
}
*/
}
function initProfile() {
var cookiejprofile = cookie('jprofile'), namespaces, fullnames = [], i, count, namespace;
if(initialized || !cookiejprofile) {
return;
}
function backupFN(fn, fullname, name, parent) {
var old = fn;
fullnames.push(fullname);
parent[name] = function(){
if(!stopped) {
var args = formatArgs(arguments);
var start = (new Date).getTime();
var ret;
// If this function call is through "new" operator.
// Sometimes arguments.callee is undefined, for example the jQuery.isArray
if(arguments.callee.prototype && this instanceof arguments.callee) {
// http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
function F(args2) {
return old.apply(this, args2);
}
F.prototype = old.prototype;
ret = new F(arguments);
} else {
ret = old.apply(this, arguments);
}
var time = (new Date).getTime() - start;
var detail = {
time: time,
args: args
};
if(logs[fullname]) {
logs[fullname].details.push(detail);
} else {
logs[fullname] = {
calls: 0,
total: 0,
avg: 0,
min: 0,
max: 0,
details: [detail]
};
}
} else {
ret = old.apply(this, arguments);
}
return ret;
}
// Function can also has methods, don't forget these methods.
for(var itemname in old) {
parent[name][itemname] = old[itemname];
}
// And prototype property.
parent[name].prototype = old.prototype;
}
function checkObj(obj, fullname, name, parent) {
var type = getType(obj), itemname, item;
if(type === 'function') {
backupFN(obj, fullname, name, parent);
}
if(obj && type !== 'array' && type !== 'string' && type !== 'number' && getType(obj.hasOwnProperty) === 'function') {
for (itemname in obj) {
if(obj.hasOwnProperty(itemname)) {
item = obj[itemname];
// window.window === window
// jQuery.fn.constructor === jQuery will introduce endless loop
if(item !== obj && itemname !== 'constructor') {
checkObj(item, fullname + '.' + itemname, itemname, obj);
}
}
}
}
}
initialized = true;
namespaces = cookiejprofile.replace(/,/ig, ';').split(';');
count = namespaces.length;
for(i=0; i<count; i++) {
namespace = $.trim(namespaces[i]);
var obj = eval(namespace),
fullname = namespace,
name = namespace,
parent = window,
dotIndex = fullname.indexOf('.');
if(dotIndex >= 0) {
name = fullname.substr(dotIndex + 1);
parent = eval(fullname.substr(0, dotIndex));
}
checkObj(obj, fullname, name, parent);
}
log('Backup Functions: ' + fullnames.join('; '));
}
// Init profile automatically.
var initialized = false; logs = {}, stopped = false;
initProfile();
// Construct UI display.
$(function() {
$('head').append('<style type="text/css">' +
'#jprofile{ position: absolute; top: 5px; right: 5px; z-index: 10000; font-size: 13px; line-height: 1.3em; }' +
'#jprofile a{ color: #52727F; text-decoration: none; }' +
'#jprofile table td{ text-align: left; border-bottom: solid 1px #eee; padding-top: 5px; }' +
'#jprofile table th{ text-align: left; border-bottom: solid 2px #ddd; }' +
'#jprofile_disabled{ background-color: #fff; padding: 10px; border-radius: 5px; box-shadow: 0 0 5px red; border: 2px solid red; display: block; }' +
'#jprofile_enabled{ background-color: #fff; padding: 10px; border-radius: 5px; box-shadow: 0 0 5px green; border: 2px solid green; display: none; text-align: right; }' +
'#jprofile_display{ width: 550px; display: none; min-height: 300px; margin-top: 5px; padding-top: 5px; }' +
'#jprofile_display table{ width: 100%; text-align: left; }' +
'#jprofile_display table td.name a{ }' +
'#jprofile_display .nologs{ color: #333333; font-size: 1.2em; margin-top: 100px; text-align: center; }' +
'#jprofile_detail { background-color: #fff; border: 2px solid #999; border-radius: 5px; box-shadow: 0 0 10px #ccc; padding: 10px; position: absolute; min-width: 500px; display: none; max-width: 650px; overflow: auto; }' +
'#jprofile_detail table{ width: 100%; text-align: left; }' +
'</style>');
var html = [];
html.push('<div id="jprofile">');
html.push('<div id="jprofile_disabled">');
html.push('<input class="enable" type="button" value="Enable jProfile" title="Enable jProfile"/>');
html.push('</div>');
html.push('<div id="jprofile_enabled">');
html.push('<input class="start" type="button" disabled="disabled" value="Start" title="Start jProfile"/>');
html.push('<input class="stop" type="button" value="Stop" title="Stop profile"/>');
html.push('<input class="clearlogs" type="button" value="Clear" title="Clear current logs"/>');
html.push('<input class="display" type="button" value="Display" title="Display the logs"/>');
html.push('<input class="disable" type="button" value="Disable jProfile" title="Disable jProfile"/>');
html.push('<div id="jprofile_display">');
html.push('</div>');
html.push('</div>');
html.push('<div id="jprofile_detail">');
html.push('</div>');
html.push('</div>');
$(document.body).append(html.join(''));
var disabledContainer = $('#jprofile_disabled'),
enabledContainer = $('#jprofile_enabled'),
displayContainer = $('#jprofile_display'),
detailContainer = $('#jprofile_detail'),
enableNode = disabledContainer.find('.enable'),
startNode = enabledContainer.find('.start'),
stopNode = enabledContainer.find('.stop');
if(cookie('jprofile')) {
disabledContainer.hide();
enabledContainer.show();
}
enableNode.click(function() {
var profileNS = $.trim(window.prompt('Please input namespaces you want to profile, separate multi-namespaces by semicolon or comma:'));
if(profileNS) {
cookie('jprofile', profileNS);
window.location.reload(true);
/*
disabledContainer.hide();
enabledContainer.show();
initProfile();
*/
}
});
enabledContainer.find('.disable').click(function() {
cookie('jprofile', null);
window.location.reload(true);
/*
stopped = true;
logs = {};
disabledContainer.show();
enabledContainer.hide();
*/
});
startNode.click(function() {
stopped = false;
startNode.attr('disabled', true);
stopNode.attr('disabled', false);
});
stopNode.click(function() {
stopped = true;
startNode.attr('disabled', false);
stopNode.attr('disabled', true);
});
enabledContainer.find('.clearlogs').click(function() {
logs = {};
displayContainer
.data('sort-data', jProfile())
.data('sort-by', 'total')
.data('sort-order', 'desc');
if(displayContainer.is(':visible')) {
showDisplayTable();
}
});
enabledContainer.find('.display').click(function() {
if(displayContainer.is(':visible')) {
detailContainer.hide();
displayContainer.hide();
} else {
displayContainer
.data('sort-data', jProfile())
.data('sort-by', 'total')
.data('sort-order', 'desc');
showDisplayTable();
}
});
displayContainer.delegate('th[data-sort-by] a', 'click', function() {
var th = $(this).parent('th'),
sortBy = th.attr('data-sort-by'),
data = displayContainer.data('sort-data'),
sortOrder = 'desc';
if(!th.hasClass('desc') && !th.hasClass('asc')) {
th.siblings().removeClass('desc').removeClass('asc').end().addClass('desc');
sortOrder = 'desc';
} else if(th.hasClass('desc')) {
th.removeClass('desc').addClass('asc');
sortOrder = 'asc';
} else {
th.removeClass('asc').addClass('desc');
sortOrder = 'desc';
}
data.sort(function(a, b) {
if(sortOrder === 'desc') {
return b[sortBy] - a[sortBy];
} else {
return a[sortBy] - b[sortBy];
}
});
displayContainer
.data('sort-data', data)
.data('sort-by', sortBy)
.data('sort-order', sortOrder);
showDisplayTable();
});
displayContainer.delegate('td.name a', 'click', function() {
var html = [], tr = $(this).parents('tr'), trposition,
sortData = displayContainer.data('sort-data'),
details = sortData[tr.index() - 1].details;
details.sort(function(a, b) {
return b.time - a.time;
});
html.push('<table class="details">');
html.push('<tr>');
html.push('<th style="width: 50px;">Time</th>');
html.push('<th>Args</th>');
html.push('</tr>');
$.each(details, function(index, detail) {
html.push('<tr>');
html.push('<td>'+ detail.time +'</td>');
html.push('<td>'+ detail.args +'</td>');
html.push('</tr>');
});
html.push('</table>');
detailContainer.html(html.join(''));
trposition = tr.position();
detailContainer.css({
top: trposition.top,
right: enabledContainer.outerWidth() + 5
}).show();
});
function showDisplayTable() {
var html = [],
sortData = displayContainer.data('sort-data'),
sortBy = displayContainer.data('sort-by'),
sortOrder = displayContainer.data('sort-order');
if(sortData.length) {
html.push('<table class="main">');
html.push('<tr>');
$.each([['name', 'Name'], ['calls', 'Calls'], ['total', 'Total'], ['avg', 'Avg'], ['min', 'Min'], ['max', 'Max']], function(index, item) {
html.push('<th data-sort-by="');
html.push(item[0]);
html.push('"');
if(item[0] === sortBy) {
html.push(' class="'+ sortOrder +'"');
}
html.push('><a href="javascript:;">');
html.push(item[1]);
html.push('</a></th>');
});
html.push('</tr>');
$.each(sortData, function(index, item) {
html.push('<tr>');
html.push('<td class="name"><a href="javascript:;">'+ item.name +'</a></td>');
html.push('<td>'+ item.calls +'</td>');
html.push('<td>'+ item.total +'</td>');
html.push('<td>'+ item.avg +'</td>');
html.push('<td>'+ item.min +'</td>');
html.push('<td>'+ item.max +'</td>');
html.push('</tr>');
});
html.push('</table>');
} else {
html.push('<div class="nologs">There is no logs.</div>');
}
displayContainer.html(html.join('')).show();
detailContainer.hide();
}
// Enable jProfile use the Ctrl+0
$(document).keydown(function(event) {
if(event.ctrlKey && event.keyCode === 48) {
enableNode.click();
}
});
});
// Public interfaces.
window.jProfile = function(sortfield) {
var logArray = [];
$.each(logs, function(logname, log) {
var calls = log.details.length, alltime = 0, avg = 0, min = 1000, max = 0;
$.each(log.details, function(index, detail){
var time = detail.time
alltime += time;
if(time <= min) {
min = time;
}
if(time >= max) {
max = time;
}
});
logArray.push({
name: logname,
calls: calls,
total: alltime,
avg: parseInt(alltime / calls, 10),
min: min,
max: max,
details: log.details
});
});
logArray.sort(function(a, b) {
return b.total - a.total;
});
return logArray;
};
})(jQuery);
Hide details
Change log
r4 by sanshi.ustc on Aug 2, 2011 Diff
Support jQuery namespace.
Go to:
Older revisions
r3 by sanshi.ustc on Jul 22, 2011 Diff
r2 by sanshi.ustc on Jul 21, 2011 Diff
All revisions of this file
File info
Size: 14151 bytes, 466 lines
View raw file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment