Created
August 7, 2013 04:02
-
-
Save 6174/6171096 to your computer and use it in GitHub Desktop.
profile a javascript functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, "&").replace(/</g, "<") + '"'); | |
} 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