UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

347 lines (293 loc) 9.49 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2007-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * qooxdoo profiler. * * All functions of qooxdoo classes (constructors, members, statics) can be profiled * using this class. * * To enable profiling this class must be loaded <b>before</b> <code>qx.Class</code> is * loaded. This can be achieved by making <code>qx.core.Aspect</code> and * <code>qx.dev.Profile</code> a load time dependency of <code>qx.Class</code>. * Further more the variant <code>qx.aspects</code> must be set to <code>on</code>. */ qx.Bootstrap.define("qx.dev.Profile", { statics : { /** * Storage for profiling data * * @internal */ __profileData : {}, /** * Array for call stack-like data types. * * @internal */ __callStack : [], /** * Flag marking profiler run. * * @internal */ __doProfile : true, /** * Profiler execution time. Subtracted for more accurate calculations. * * @internal */ __callOverhead : undefined, /** * Amount of times to run calculation of profiler overhead. * * @internal */ __calibrateCount : 4000, /** * Clear profiling data and start profiling. */ start : function() { this.__doProfile = true; this.__profileData = {}; this.__callStack.splice(0, this.__callStack.length-2); }, /** * Stop profiling. */ stop : function() { this.__doProfile = false; }, /** * Return the profiling data as JSON data structure. * * Example: * <pre class="javascript"> * { * "qx.core.ObjectRegistry.toHashCode (static)":{ * * "totalTime":3, * "ownTime":3, * "callCount":218, * "subRoutineCalls":0, * "name":"qx.core.ObjectRegistry.toHashCode", * "type":"static" * }, * "qx.core.Object.addListener (member)":{ * "totalTime":19, * "ownTime":12, * "callCount":59, * "subRoutineCalls":251, * "name":"qx.core.Object.addListener", * "type":"member" * }, * "qx.ui.table.cellrenderer.Default (constructor)":{ * "totalTime":2, * "ownTime":1, * "callCount":1, * "subRoutineCalls":4, * "name":"qx.ui.table.cellrenderer.Default", * "type":"constructor" * } * } * </pre> * * @return {Map} The current profiling data. */ getProfileData : function() { return this.__profileData; }, /** * Show profiling results in a popup window. The results are sorted by the * function's own time. * * @param maxLength {Integer?100} maximum number of entries to display. */ showResults : function(maxLength) { this.stop(); this.normalizeProfileData(); var data = Object.values(this.__profileData); data = data.sort(function(a,b) { return a.calibratedOwnTime<b.calibratedOwnTime ? 1: -1; }); data = data.slice(0, maxLength || 100); var str = ["<table><tr><th>Name</th><th>Type</th><th>Own time</th><th>Avg time</th><th>calls</th></tr>"]; for (var i=0; i<data.length; i++) { var profData = data[i]; if (profData.name == "qx.core.Aspect.__calibrateHelper") { continue; } str.push("<tr><td>"); str.push(profData.name, "()"); str.push("</td><td>"); str.push(profData.type); str.push("</td><td>"); str.push(profData.calibratedOwnTime.toPrecision(3)); str.push("ms</td><td>"); str.push((profData.calibratedOwnTime/profData.callCount).toPrecision(3)); str.push("ms</td><td>"); str.push(profData.callCount); str.push("</td></tr>"); } str.push("</table>"); var win = window.open("about:blank", "profileLog"); var doc = win.document; doc.open(); doc.write("<html><head><style type='text/css'>body{font-family:monospace;font-size:11px;background:white;color:black;}</style></head><body>"); doc.write(str.join("")); doc.write("</body></html>"); doc.close(); }, /** * Measure the overhead of calling a wrapped function vs. calling an * unwrapped function. * * @lint ignoreDeprecated(eval) * * @param count {Integer} Number of iterations to measure. * @return {Number} Overhead of a wrapped function call in milliseconds. */ __calibrate : function(count) { // we use eval to unroll the loop because we don't want to measure the loop overhead. // Measure wrapped function var fcn; var code = ["var fcn = function(){ var fcn=qx.dev.Profile.__calibrateHelper;"]; for (var i=0; i<count; i++) { code.push("fcn();"); } code.push("};"); eval(code.join("")); var start = new Date(); fcn(); var end = new Date(); var profTime = end - start; // Measure unwrapped function var code = [ "var plainFunc = function() {};", "var fcn = function(){ var fcn=plainFunc;" ]; for (var i=0; i<count; i++) { code.push("fcn();"); } code.push("};"); eval(code.join("")); var start = new Date(); fcn(); var end = new Date(); var plainTime = end - start; // Compute per call overhead return ((profTime - plainTime) / count); }, /** * Helper to measure overhead. */ __calibrateHelper : function() {}, /** * Normalize profiling data by subtracting the overhead of wrapping from the * function's own time. */ normalizeProfileData : function() { if (this.__callOverhead == undefined) { this.__callOverhead = this.__calibrate(this.__calibrateCount); } for (var key in this.__profileData) { var profileData = this.__profileData[key]; profileData.calibratedOwnTime = Math.max(profileData.ownTime - (profileData.subRoutineCalls * this.__callOverhead), 0); profileData.calibratedAvgTime = profileData.calibratedOwnTime / profileData.callCount; } }, /** * This function will be called before each function call. (Start timing) * * @param fullName {String} Full name of the function including the class name. * @param fcn {Function} Function to time. * @param type {String} Function type as in parameter with same name to * {@link qx.core.Aspect#addAdvice} * @param args {arguments} The arguments passed to the wrapped function */ profileBefore : function(fullName, fcn, type, args) { var me = qx.dev.Profile; if (!me.__doProfile) { return; } var callData = { subRoutineTime : 0, subRoutineCalls : 0 }; me.__callStack.push(callData); callData.startTime = new Date(); }, /** * This function will be called after each function call. (Stop timing) * * @param fullName {String} Full name of the function including the class name. * @param fcn {Function} Function to time. * @param type {String} Function type as in parameter with same name to * {@link qx.core.Aspect#addAdvice} * @param args {arguments} The arguments passed to the wrapped function * @param returnValue {var} return value of the wrapped function. */ profileAfter : function(fullName, fcn, type, args, returnValue) { var me = qx.dev.Profile; if (!me.__doProfile) { return; } var endTime = new Date(); var callData = me.__callStack.pop(); var totalTime = endTime - callData.startTime; var ownTime = totalTime - callData.subRoutineTime; if (me.__callStack.length > 0) { var lastCall = me.__callStack[me.__callStack.length-1]; lastCall.subRoutineTime += totalTime; lastCall.subRoutineCalls += 1; } var fcnKey = fullName + " (" + type + ")"; if (me.__profileData[fcnKey] === undefined) { me.__profileData[fcnKey] = { totalTime: 0, ownTime: 0, callCount: 0, subRoutineCalls: 0, name: fullName, type : type }; } var functionData = me.__profileData[fcnKey]; functionData.totalTime += totalTime; functionData.ownTime += ownTime; functionData.callCount += 1; functionData.subRoutineCalls += callData.subRoutineCalls; } }, defer : function(statics) { if (qx.core.Environment.get("qx.aspects")) { // Inform user qx.Bootstrap.debug("Enable global profiling..."); // Add advices for profiling qx.core.Aspect.addAdvice(statics.profileBefore, "before"); qx.core.Aspect.addAdvice(statics.profileAfter, "after"); statics.__calibrateHelper = qx.core.Aspect.wrap("qx.dev.Profile.__calibrateHelper", statics.__calibrateHelper, "static"); } } });