1 /* See license.txt for terms of usage */ 2 3 4 FBL.ns(function() { with (FBL) { 5 6 var toggleProfiling = $("fbToggleProfiling"); 7 8 // ************************************************************************************************ 9 10 Firebug.Profiler = extend(Firebug.Module, 11 { 12 dispatchName: "profiler", 13 showContext: function(browser, context) 14 { 15 this.setEnabled(context); 16 }, 17 18 onPanelEnable: function(panelName) 19 { 20 if (FBTrace.DBG_DISPATCH) 21 FBTrace.sysout("Profiler.onPanelEnable panelName: "+panelName+"\n"); 22 23 if (panelName == "net" || panelName == "script") 24 this.setEnabled(); 25 }, 26 27 onPanelDisable: function(panelName) 28 { 29 if (FBTrace.DBG_DISPATCH) 30 FBTrace.sysout("Profiler.onPanelDisable panelName: "+panelName+"\n"); 31 32 if (panelName == "net" || panelName == "script") 33 this.setEnabled(); 34 }, 35 36 setEnabled: function() 37 { 38 // The profiler is available only if the debugger (script panel) and console are enabled. 39 var debuggerEnabled = Firebug.Debugger.isAlwaysEnabled(); 40 var consoleEnabled = Firebug.Console.isAlwaysEnabled(); 41 toggleProfiling.disabled = !debuggerEnabled || !consoleEnabled; 42 43 // Update button's tooltip. 44 var tooltipText = toggleProfiling.disabled ? $STR("ProfileButton.Disabled.Tooltip") 45 : $STR("ProfileButton.Enabled.Tooltip"); 46 toggleProfiling.setAttribute("tooltiptext", tooltipText); 47 }, 48 49 toggleProfiling: function(context) 50 { 51 if (fbs.profiling) 52 this.stopProfiling(context); 53 else 54 this.startProfiling(context); 55 }, 56 57 startProfiling: function(context, title) 58 { 59 fbs.startProfiling(); 60 61 Firebug.chrome.setGlobalAttribute("cmd_toggleProfiling", "checked", "true"); 62 63 var isCustomMessage = !!title; 64 if (!isCustomMessage) 65 title = $STR("ProfilerStarted"); 66 67 context.profileRow = this.logProfileRow(context, title); 68 context.profileRow.customMessage = isCustomMessage ; 69 }, 70 71 isProfiling: function() 72 { 73 return (Firebug.chrome.getGlobalAttribute("cmd_toggleProfiling", "checked") === "true") 74 }, 75 76 stopProfiling: function(context, cancelReport) 77 { 78 var totalTime = fbs.stopProfiling(); 79 if (totalTime == -1) 80 return; 81 82 Firebug.chrome.setGlobalAttribute("cmd_toggleProfiling", "checked", "false"); 83 84 if (cancelReport) 85 delete context.profileRow; 86 else 87 this.logProfileReport(context) 88 }, 89 90 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 91 92 logProfileRow: function(context, title) 93 { 94 var row = Firebug.Console.openGroup(title, context, "profile", 95 Firebug.Profiler.ProfileCaption, true, null, true); 96 setClass(row, "profilerRunning"); 97 98 Firebug.Console.closeGroup(context, true); 99 100 return row; 101 }, 102 103 logProfileReport: function(context) 104 { 105 var calls = []; 106 var totalCalls = 0; 107 var totalTime = 0; 108 109 var sourceFileMap = context.sourceFileMap; 110 if (FBTrace.DBG_SOURCEFILES) 111 { 112 for (url in sourceFileMap) 113 FBTrace.sysout("logProfileReport: "+sourceFileMap[url]+"\n"); 114 } 115 116 jsd.enumerateScripts({enumerateScript: function(script) 117 { 118 if (script.callCount) 119 { 120 if (!Firebug.filterSystemURLs || !isSystemURL(script.fileName)) 121 { 122 var sourceLink = FBL.getSourceLinkForScript(script, context); 123 if (sourceLink && sourceLink.href in sourceFileMap) 124 { 125 var call = new ProfileCall(script, context, script.callCount, script.totalExecutionTime, 126 script.totalOwnExecutionTime, script.minExecutionTime, script.maxExecutionTime, sourceLink); 127 calls.push(call); 128 129 totalCalls += script.callCount; 130 totalTime += script.totalOwnExecutionTime; 131 } 132 } 133 script.clearProfileData(); 134 } 135 }}); 136 137 for (var i = 0; i < calls.length; ++i) 138 calls[i].percent = Math.round((calls[i].totalOwnTime/totalTime) * 100 * 100) / 100; 139 140 calls.sort(function(a, b) 141 { 142 return a.totalOwnTime < b.totalOwnTime ? 1 : -1; 143 }); 144 145 totalTime = Math.round(totalTime * 1000) / 1000; 146 147 var groupRow = context.profileRow && context.profileRow.ownerDocument 148 ? context.profileRow 149 : this.logProfileRow(context, ""); 150 delete context.profileRow; 151 152 removeClass(groupRow, "profilerRunning"); 153 154 if (totalCalls > 0) 155 { 156 var captionBox = groupRow.getElementsByClassName("profileCaption").item(0); 157 if (!groupRow.customMessage) 158 captionBox.textContent = $STR("Profile"); 159 var timeBox = groupRow.getElementsByClassName("profileTime").item(0); 160 timeBox.textContent = $STRP("plural.Profile_Time", [totalTime, totalCalls], 1); 161 162 var groupBody = groupRow.lastChild; 163 var sizer = Firebug.Profiler.ProfileTable.tag.replace({}, groupBody); 164 var table = sizer.firstChild; 165 var tHeader = table.lastChild; // no rows inserted. 166 167 var tag = Firebug.Profiler.ProfileCall.tag; 168 var insert = tag.insertRows; 169 170 for (var i = 0; i < calls.length; ++i) { 171 calls[i].index = i; 172 context.throttle(insert, tag, [{object: calls[i]}, tHeader]); 173 } 174 175 context.throttle(groupRow.scrollIntoView, groupRow); 176 } 177 else 178 { 179 var captionBox = groupRow.getElementsByClassName("profileCaption").item(0); 180 captionBox.textContent = $STR("NothingToProfile"); 181 } 182 } 183 }); 184 185 // ************************************************************************************************ 186 187 Firebug.Profiler.ProfileTable = domplate( 188 { 189 tag: 190 DIV({class: "profileSizer", "tabindex": "-1" }, 191 TABLE({class: "profileTable", cellspacing: 0, cellpadding: 0, width: "100%", "role": "grid"}, 192 TBODY({class: "profileTbody", "role": "presentation"}, 193 TR({class: "headerRow focusRow profileRow subFocusRow", onclick: "$onClick", "role": "row"}, 194 TH({class: "headerCell alphaValue a11yFocus", "role": "columnheader"}, 195 DIV({class: "headerCellBox"}, 196 $STR("Function") 197 ) 198 ), 199 TH({class: "headerCell a11yFocus" , "role": "columnheader"}, 200 DIV({class: "headerCellBox", title: $STR("CallsHeaderTooltip")}, 201 $STR("Calls") 202 ) 203 ), 204 TH({class: "headerCell headerSorted a11yFocus", "role": "columnheader", "aria-sort": "descending"}, 205 DIV({class: "headerCellBox", title: $STR("PercentTooltip")}, 206 $STR("Percent") 207 ) 208 ), 209 TH({class: "headerCell a11yFocus", "role": "columnheader"}, 210 DIV({class: "headerCellBox", title: $STR("OwnTimeHeaderTooltip")}, 211 $STR("OwnTime") 212 ) 213 ), 214 TH({class: "headerCell a11yFocus", "role": "columnheader"}, 215 DIV({class: "headerCellBox", title: $STR("TimeHeaderTooltip")}, 216 $STR("Time") 217 ) 218 ), 219 TH({class: "headerCell a11yFocus", "role": "columnheader"}, 220 DIV({class: "headerCellBox", title: $STR("AvgHeaderTooltip")}, 221 $STR("Avg") 222 ) 223 ), 224 TH({class: "headerCell a11yFocus", "role": "columnheader"}, 225 DIV({class: "headerCellBox", title: $STR("MinHeaderTooltip")}, 226 $STR("Min") 227 ) 228 ), 229 TH({class: "headerCell a11yFocus", "role": "columnheader"}, 230 DIV({class: "headerCellBox", title: $STR("MaxHeaderTooltip")}, 231 $STR("Max") 232 ) 233 ), 234 TH({class: "headerCell alphaValue a11yFocus", "role": "columnheader"}, 235 DIV({class: "headerCellBox"}, 236 $STR("File") 237 ) 238 ) 239 ) 240 ) 241 ) 242 ), 243 244 onClick: function(event) 245 { 246 var table = getAncestorByClass(event.target, "profileTable"); 247 var header = getAncestorByClass(event.target, "headerCell"); 248 if (!header) 249 return; 250 251 var numerical = !hasClass(header, "alphaValue"); 252 253 var colIndex = 0; 254 for (header = header.previousSibling; header; header = header.previousSibling) 255 ++colIndex; 256 257 this.sort(table, colIndex, numerical); 258 }, 259 260 sort: function(table, colIndex, numerical) 261 { 262 var tbody = getChildByClass(table, "profileTbody"); 263 264 var values = []; 265 for (var row = tbody.childNodes[1]; row; row = row.nextSibling) 266 { 267 var cell = row.childNodes[colIndex]; 268 var value = numerical ? parseFloat(cell.textContent) : cell.textContent; 269 values.push({row: row, value: value}); 270 } 271 272 values.sort(function(a, b) { return a.value < b.value ? -1 : 1; }); 273 274 var headerRow = tbody.firstChild; 275 var headerSorted = getChildByClass(headerRow, "headerSorted"); 276 removeClass(headerSorted, "headerSorted"); 277 if (headerSorted) 278 headerSorted.removeAttribute('aria-sort'); 279 280 var header = headerRow.childNodes[colIndex]; 281 setClass(header, "headerSorted"); 282 283 if (!header.sorted || header.sorted == 1) 284 { 285 removeClass(header, "sortedDescending"); 286 setClass(header, "sortedAscending"); 287 header.setAttribute("aria-sort", "ascending"); 288 289 header.sorted = -1; 290 291 for (var i = 0; i < values.length; ++i) { 292 values[i].row.setAttribute("odd", (i % 2)); 293 tbody.appendChild(values[i].row); 294 } 295 } 296 else 297 { 298 removeClass(header, "sortedAscending"); 299 setClass(header, "sortedDescending"); 300 header.setAttribute("aria-sort", "descending") 301 302 header.sorted = 1; 303 304 for (var i = values.length-1; i >= 0; --i) { 305 values[i].row.setAttribute("odd", (Math.abs(i-values.length-1) % 2)); 306 tbody.appendChild(values[i].row); 307 } 308 } 309 } 310 }); 311 312 // ************************************************************************************************ 313 314 Firebug.Profiler.ProfileCaption = domplate(Firebug.Rep, 315 { 316 tag: 317 SPAN({class: "profileTitle", "role": "status"}, 318 SPAN({class: "profileCaption"}, "$objects"), 319 " ", 320 SPAN({class: "profileTime"}, "") 321 ) 322 }); 323 324 // ************************************************************************************************ 325 326 Firebug.Profiler.ProfileCall = domplate(Firebug.Rep, 327 { 328 tag: 329 TR({"class": "focusRow profileRow subFocusRow", odd: "$object|isOddRow", "role": "row"}, 330 TD({"class": "profileCell", "role": "presentation"}, 331 FirebugReps.OBJECTLINK("$object|getCallName") 332 ), 333 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object.callCount"), 334 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object.percent%"), 335 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object.totalOwnTime|roundTime\\ms"), 336 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object.totalTime|roundTime\\ms"), 337 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object|avgTime|roundTime\\ms"), 338 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object.minTime|roundTime\\ms"), 339 TD({"class": "a11yFocus profileCell", "role": "gridcell"}, "$object.maxTime|roundTime\\ms"), 340 TD({class: "linkCell profileCell", "role": "presentation"}, 341 TAG(FirebugReps.SourceLink.tag, {object: "$object|getSourceLink"}) 342 ) 343 ), 344 345 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 346 347 isOddRow: function(call) 348 { 349 return (call.index % 2) ? 1 : 0; 350 }, 351 352 getCallName: function(call) 353 { 354 return cropString(getFunctionName(call.script, call.context), 60); 355 }, 356 357 avgTime: function(call) 358 { 359 return call.totalTime / call.callCount; 360 }, 361 362 getSourceLink: function(call) 363 { 364 return call.sourceLink; 365 }, 366 367 roundTime: function(ms) 368 { 369 return Math.round(ms * 1000) / 1000; 370 }, 371 372 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 373 374 className: "profile", 375 376 supportsObject: function(object) 377 { 378 return object instanceof ProfileCall; 379 }, 380 381 inspectObject: function(call, context) 382 { 383 var sourceLink = this.getSourceLink(call); 384 Firebug.chrome.select(sourceLink); 385 }, 386 387 getTooltip: function(call) 388 { 389 try 390 { 391 var fn = unwrapIValue(call.script.functionObject); 392 return FirebugReps.Func.getTooltip(fn, call.context); 393 } 394 catch (exc) 395 { 396 if (FBTrace.DBG_ERRORS) 397 FBTrace.sysout("profiler.getTooltip FAILS ", exc); 398 } 399 }, 400 401 getContextMenuItems: function(call, target, context) 402 { 403 var fn = unwrapIValue(call.script.functionObject); 404 return FirebugReps.Func.getContextMenuItems(fn, call.script, context); 405 } 406 }); 407 408 // ************************************************************************************************ 409 410 function ProfileCall(script, context, callCount, totalTime, totalOwnTime, minTime, maxTime, sourceLink) 411 { 412 this.script = script; 413 this.context = context; 414 this.callCount = callCount; 415 this.totalTime = totalTime; 416 this.totalOwnTime = totalOwnTime; 417 this.minTime = minTime; 418 this.maxTime = maxTime; 419 this.sourceLink = sourceLink; 420 } 421 422 // ************************************************************************************************ 423 424 Firebug.registerModule(Firebug.Profiler); 425 Firebug.registerRep(Firebug.Profiler.ProfileCall); 426 427 // ************************************************************************************************ 428 429 }}); 430