|
1 /* |
|
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved. |
|
3 Code licensed under the BSD License: |
|
4 http://developer.yahoo.net/yui/license.txt |
|
5 version: 3.0.0 |
|
6 build: 1549 |
|
7 */ |
|
8 YUI.add('profiler', function(Y) { |
|
9 |
|
10 /** |
|
11 * The YUI JavaScript profiler. |
|
12 * @module profiler |
|
13 * @requires yui |
|
14 */ |
|
15 |
|
16 //------------------------------------------------------------------------- |
|
17 // Private Variables and Functions |
|
18 //------------------------------------------------------------------------- |
|
19 |
|
20 var container = {}, //Container object on which to put the original unprofiled methods. |
|
21 report = {}, //Profiling information for functions |
|
22 stopwatches = {}, //Additional stopwatch information |
|
23 |
|
24 WATCH_STARTED = 0, |
|
25 WATCH_STOPPED = 1, |
|
26 WATCH_PAUSED = 2, |
|
27 |
|
28 //shortcuts |
|
29 L = Y.Lang; |
|
30 |
|
31 /* (intentionally not documented) |
|
32 * Creates a report object with the given name. |
|
33 * @param {String} name The name to store for the report object. |
|
34 * @return {Void} |
|
35 * @method createReport |
|
36 * @private |
|
37 */ |
|
38 function createReport(name){ |
|
39 report[name] = { |
|
40 calls: 0, |
|
41 max: 0, |
|
42 min: 0, |
|
43 avg: 0, |
|
44 points: [] |
|
45 }; |
|
46 return report[name]; |
|
47 } |
|
48 |
|
49 /* (intentionally not documented) |
|
50 * Called when a method ends execution. Marks the start and end time of the |
|
51 * method so it can calculate how long the function took to execute. Also |
|
52 * updates min/max/avg calculations for the function. |
|
53 * @param {String} name The name of the function to mark as stopped. |
|
54 * @param {int} duration The number of milliseconds it took the function to |
|
55 * execute. |
|
56 * @return {Void} |
|
57 * @method saveDataPoint |
|
58 * @private |
|
59 * @static |
|
60 */ |
|
61 function saveDataPoint(name, duration){ |
|
62 |
|
63 //get the function data |
|
64 var functionData /*:Object*/ = report[name]; |
|
65 |
|
66 //just in case clear() was called |
|
67 if (!functionData){ |
|
68 functionData = createReport(name); |
|
69 } |
|
70 |
|
71 //increment the calls |
|
72 functionData.calls++; |
|
73 functionData.points.push(duration); |
|
74 |
|
75 //if it's already been called at least once, do more complex calculations |
|
76 if (functionData.calls > 1) { |
|
77 functionData.avg = ((functionData.avg*(functionData.calls-1))+duration)/functionData.calls; |
|
78 functionData.min = Math.min(functionData.min, duration); |
|
79 functionData.max = Math.max(functionData.max, duration); |
|
80 } else { |
|
81 functionData.avg = duration; |
|
82 functionData.min = duration; |
|
83 functionData.max = duration; |
|
84 } |
|
85 |
|
86 } |
|
87 |
|
88 //------------------------------------------------------------------------- |
|
89 // Public Interface |
|
90 //------------------------------------------------------------------------- |
|
91 |
|
92 /** |
|
93 * Profiles functions in JavaScript. |
|
94 * @class Profiler |
|
95 * @static |
|
96 */ |
|
97 Y.Profiler = { |
|
98 |
|
99 //------------------------------------------------------------------------- |
|
100 // Utility Methods |
|
101 //------------------------------------------------------------------------- |
|
102 |
|
103 /** |
|
104 * Removes all report data from the profiler. |
|
105 * @param {String} name (Optional) The name of the report to clear. If |
|
106 * omitted, then all report data is cleared. |
|
107 * @return {Void} |
|
108 * @method clear |
|
109 * @static |
|
110 */ |
|
111 clear: function(name){ |
|
112 if (L.isString(name)){ |
|
113 delete report[name]; |
|
114 delete stopwatches[name]; |
|
115 } else { |
|
116 report = {}; |
|
117 stopwatches = {}; |
|
118 } |
|
119 }, |
|
120 |
|
121 /** |
|
122 * Returns the uninstrumented version of a function/object. |
|
123 * @param {String} name The name of the function/object to retrieve. |
|
124 * @return {Function|Object} The uninstrumented version of a function/object. |
|
125 * @method getOriginal |
|
126 * @static |
|
127 */ |
|
128 getOriginal: function(name){ |
|
129 return container[name]; |
|
130 }, |
|
131 |
|
132 /** |
|
133 * Instruments a method to have profiling calls. |
|
134 * @param {String} name The name of the report for the function. |
|
135 * @param {Function} method The function to instrument. |
|
136 * @return {Function} An instrumented version of the function. |
|
137 * @method instrument |
|
138 * @static |
|
139 */ |
|
140 instrument: function(name, method){ |
|
141 |
|
142 //create instrumented version of function |
|
143 var newMethod = function () { |
|
144 |
|
145 var start = new Date(), |
|
146 retval = method.apply(this, arguments), |
|
147 stop = new Date(); |
|
148 |
|
149 saveDataPoint(name, stop-start); |
|
150 |
|
151 return retval; |
|
152 |
|
153 }; |
|
154 |
|
155 //copy the function properties over |
|
156 Y.mix(newMethod, method); |
|
157 |
|
158 //assign prototype and flag as being profiled |
|
159 newMethod.__yuiProfiled = true; |
|
160 newMethod.prototype = method.prototype; |
|
161 |
|
162 //store original method |
|
163 container[name] = method; |
|
164 container[name].__yuiFuncName = name; |
|
165 |
|
166 //create the report |
|
167 createReport(name); |
|
168 |
|
169 //return the new method |
|
170 return newMethod; |
|
171 }, |
|
172 |
|
173 //------------------------------------------------------------------------- |
|
174 // Stopwatch Methods |
|
175 //------------------------------------------------------------------------- |
|
176 |
|
177 /** |
|
178 * Pauses profiling information for a given name. |
|
179 * @param {String} name The name of the data point. |
|
180 * @return {Void} |
|
181 * @method pause |
|
182 * @static |
|
183 */ |
|
184 pause: function(name){ |
|
185 var now = new Date(), |
|
186 stopwatch = stopwatches[name]; |
|
187 |
|
188 if (stopwatch && stopwatch.state == WATCH_STARTED){ |
|
189 stopwatch.total += (now - stopwatch.start); |
|
190 stopwatch.start = 0; |
|
191 stopwatch.state = WATCH_PAUSED; |
|
192 } |
|
193 |
|
194 }, |
|
195 |
|
196 /** |
|
197 * Start profiling information for a given name. The name cannot be the name |
|
198 * of a registered function or object. This is used to start timing for a |
|
199 * particular block of code rather than instrumenting the entire function. |
|
200 * @param {String} name The name of the data point. |
|
201 * @return {Void} |
|
202 * @method start |
|
203 * @static |
|
204 */ |
|
205 start: function(name){ |
|
206 if(container[name]){ |
|
207 throw new Error("Cannot use '" + name + "' for profiling through start(), name is already in use."); |
|
208 } else { |
|
209 |
|
210 //create report if necessary |
|
211 if (!report[name]){ |
|
212 createReport(name); |
|
213 } |
|
214 |
|
215 //create stopwatch object if necessary |
|
216 if (!stopwatches[name]){ |
|
217 stopwatches[name] = { |
|
218 state: WATCH_STOPPED, |
|
219 start: 0, |
|
220 total: 0 |
|
221 }; |
|
222 } |
|
223 |
|
224 if (stopwatches[name].state == WATCH_STOPPED){ |
|
225 stopwatches[name].state = WATCH_STARTED; |
|
226 stopwatches[name].start = new Date(); |
|
227 } |
|
228 |
|
229 } |
|
230 }, |
|
231 |
|
232 /** |
|
233 * Stops profiling information for a given name. |
|
234 * @param {String} name The name of the data point. |
|
235 * @return {Void} |
|
236 * @method stop |
|
237 * @static |
|
238 */ |
|
239 stop: function(name){ |
|
240 var now = new Date(), |
|
241 stopwatch = stopwatches[name]; |
|
242 |
|
243 if (stopwatch){ |
|
244 if (stopwatch.state == WATCH_STARTED){ |
|
245 saveDataPoint(name, stopwatch.total + (now - stopwatch.start)); |
|
246 } else if (stopwatch.state == WATCH_PAUSED){ |
|
247 saveDataPoint(name, stopwatch.total); |
|
248 } |
|
249 |
|
250 //reset stopwatch information |
|
251 stopwatch.start = 0; |
|
252 stopwatch.total = 0; |
|
253 stopwatch.state = WATCH_STOPPED; |
|
254 } |
|
255 }, |
|
256 |
|
257 //------------------------------------------------------------------------- |
|
258 // Reporting Methods |
|
259 //------------------------------------------------------------------------- |
|
260 |
|
261 /** |
|
262 * Returns the average amount of time (in milliseconds) that the function |
|
263 * with the given name takes to execute. |
|
264 * @param {String} name The name of the function whose data should be returned. |
|
265 * If an object type method, it should be 'constructor.prototype.methodName'; |
|
266 * a normal object method would just be 'object.methodName'. |
|
267 * @return {float} The average time it takes the function to execute. |
|
268 * @method getAverage |
|
269 * @static |
|
270 */ |
|
271 getAverage : function (name /*:String*/) /*:float*/ { |
|
272 return report[name].avg; |
|
273 }, |
|
274 |
|
275 /** |
|
276 * Returns the number of times that the given function has been called. |
|
277 * @param {String} name The name of the function whose data should be returned. |
|
278 * @return {int} The number of times the function was called. |
|
279 * @method getCallCount |
|
280 * @static |
|
281 */ |
|
282 getCallCount : function (name /*:String*/) /*:int*/ { |
|
283 return report[name].calls; |
|
284 }, |
|
285 |
|
286 /** |
|
287 * Returns the maximum amount of time (in milliseconds) that the function |
|
288 * with the given name takes to execute. |
|
289 * @param {String} name The name of the function whose data should be returned. |
|
290 * If an object type method, it should be 'constructor.prototype.methodName'; |
|
291 * a normal object method would just be 'object.methodName'. |
|
292 * @return {float} The maximum time it takes the function to execute. |
|
293 * @method getMax |
|
294 * @static |
|
295 */ |
|
296 getMax : function (name /*:String*/) /*:int*/ { |
|
297 return report[name].max; |
|
298 }, |
|
299 |
|
300 /** |
|
301 * Returns the minimum amount of time (in milliseconds) that the function |
|
302 * with the given name takes to execute. |
|
303 * @param {String} name The name of the function whose data should be returned. |
|
304 * If an object type method, it should be 'constructor.prototype.methodName'; |
|
305 * a normal object method would just be 'object.methodName'. |
|
306 * @return {float} The minimum time it takes the function to execute. |
|
307 * @method getMin |
|
308 * @static |
|
309 */ |
|
310 getMin : function (name /*:String*/) /*:int*/ { |
|
311 return report[name].min; |
|
312 }, |
|
313 |
|
314 /** |
|
315 * Returns an object containing profiling data for a single function. |
|
316 * The object has an entry for min, max, avg, calls, and points). |
|
317 * @return {Object} An object containing profile data for a given function. |
|
318 * @method getFunctionReport |
|
319 * @static |
|
320 * @deprecated Use getReport() instead. |
|
321 */ |
|
322 getFunctionReport : function (name /*:String*/) /*:Object*/ { |
|
323 return report[name]; |
|
324 }, |
|
325 |
|
326 /** |
|
327 * Returns an object containing profiling data for a single function. |
|
328 * The object has an entry for min, max, avg, calls, and points). |
|
329 * @return {Object} An object containing profile data for a given function. |
|
330 * @method getReport |
|
331 * @static |
|
332 */ |
|
333 getReport : function (name /*:String*/) /*:Object*/ { |
|
334 return report[name]; |
|
335 }, |
|
336 |
|
337 /** |
|
338 * Returns an object containing profiling data for all of the functions |
|
339 * that were profiled. The object has an entry for each function and |
|
340 * returns all information (min, max, average, calls, etc.) for each |
|
341 * function. |
|
342 * @return {Object} An object containing all profile data. |
|
343 * @static |
|
344 */ |
|
345 getFullReport : function (filter /*:Function*/) /*:Object*/ { |
|
346 filter = filter || function(){return true;}; |
|
347 |
|
348 if (L.isFunction(filter)) { |
|
349 var fullReport = {}; |
|
350 |
|
351 for (var name in report){ |
|
352 if (filter(report[name])){ |
|
353 fullReport[name] = report[name]; |
|
354 } |
|
355 } |
|
356 |
|
357 return fullReport; |
|
358 } |
|
359 }, |
|
360 |
|
361 //------------------------------------------------------------------------- |
|
362 // Profiling Methods |
|
363 //------------------------------------------------------------------------- |
|
364 |
|
365 /** |
|
366 * Sets up a constructor for profiling, including all properties and methods on the prototype. |
|
367 * @param {string} name The fully-qualified name of the function including namespace information. |
|
368 * @param {Object} owner (Optional) The object that owns the function (namespace or containing object). |
|
369 * @return {Void} |
|
370 * @method registerConstructor |
|
371 * @static |
|
372 */ |
|
373 registerConstructor : function (name /*:String*/, owner /*:Object*/) /*:Void*/ { |
|
374 this.registerFunction(name, owner, true); |
|
375 }, |
|
376 |
|
377 /** |
|
378 * Sets up a function for profiling. It essentially overwrites the function with one |
|
379 * that has instrumentation data. This method also creates an entry for the function |
|
380 * in the profile report. The original function is stored on the container object. |
|
381 * @param {String} name The full name of the function including namespacing. This |
|
382 * is the name of the function that is stored in the report. |
|
383 * @param {Object} owner (Optional) The object that owns the function. If the function |
|
384 * isn't global then this argument is required. This could be the namespace that |
|
385 * the function belongs to or the object on which it's |
|
386 * a method. |
|
387 * @param {Boolean} registerPrototype (Optional) Indicates that the prototype should |
|
388 * also be instrumented. Setting to true has the same effect as calling |
|
389 * registerConstructor(). |
|
390 * @return {Void} |
|
391 * @method registerFunction |
|
392 * @static |
|
393 */ |
|
394 registerFunction : function(name /*:String*/, owner /*:Object*/, registerPrototype /*:Boolean*/) /*:Void*/{ |
|
395 |
|
396 //figure out the function name without namespacing |
|
397 var funcName = (name.indexOf(".") > -1 ? |
|
398 name.substring(name.lastIndexOf(".")+1) : name), |
|
399 method, |
|
400 prototype; |
|
401 |
|
402 //if owner isn't an object, try to find it from the name |
|
403 if (!L.isObject(owner)){ |
|
404 owner = eval(name.substring(0, name.lastIndexOf("."))); |
|
405 } |
|
406 |
|
407 //get the method and prototype |
|
408 method = owner[funcName]; |
|
409 prototype = method.prototype; |
|
410 |
|
411 //see if the method has already been registered |
|
412 if (L.isFunction(method) && !method.__yuiProfiled){ |
|
413 |
|
414 //replace the function with the profiling one |
|
415 owner[funcName] = this.instrument(name, method); |
|
416 |
|
417 /* |
|
418 * Store original function information. We store the actual |
|
419 * function as well as the owner and the name used to identify |
|
420 * the function so it can be restored later. |
|
421 */ |
|
422 container[name].__yuiOwner = owner; |
|
423 container[name].__yuiFuncName = funcName; //overwrite with less-specific name |
|
424 |
|
425 //register prototype if necessary |
|
426 if (registerPrototype) { |
|
427 this.registerObject(name + ".prototype", prototype); |
|
428 } |
|
429 |
|
430 } |
|
431 |
|
432 }, |
|
433 |
|
434 |
|
435 /** |
|
436 * Sets up an object for profiling. It takes the object and looks for functions. |
|
437 * When a function is found, registerMethod() is called on it. If set to recrusive |
|
438 * mode, it will also setup objects found inside of this object for profiling, |
|
439 * using the same methodology. |
|
440 * @param {String} name The name of the object to profile (shows up in report). |
|
441 * @param {Object} owner (Optional) The object represented by the name. |
|
442 * @param {Boolean} recurse (Optional) Determines if subobject methods are also profiled. |
|
443 * @return {Void} |
|
444 * @method registerObject |
|
445 * @static |
|
446 */ |
|
447 registerObject : function (name /*:String*/, object /*:Object*/, recurse /*:Boolean*/) /*:Void*/{ |
|
448 |
|
449 //get the object |
|
450 object = (L.isObject(object) ? object : eval(name)); |
|
451 |
|
452 //save the object |
|
453 container[name] = object; |
|
454 |
|
455 for (var prop in object) { |
|
456 if (typeof object[prop] == "function"){ |
|
457 if (prop != "constructor" && prop != "superclass"){ //don't do constructor or superclass, it's recursive |
|
458 this.registerFunction(name + "." + prop, object); |
|
459 } |
|
460 } else if (typeof object[prop] == "object" && recurse){ |
|
461 this.registerObject(name + "." + prop, object[prop], recurse); |
|
462 } |
|
463 } |
|
464 |
|
465 }, |
|
466 |
|
467 /** |
|
468 * Removes a constructor function from profiling. Reverses the registerConstructor() method. |
|
469 * @param {String} name The full name of the function including namespacing. This |
|
470 * is the name of the function that is stored in the report. |
|
471 * @return {Void} |
|
472 * @method unregisterFunction |
|
473 * @static |
|
474 */ |
|
475 unregisterConstructor : function(name /*:String*/) /*:Void*/{ |
|
476 |
|
477 //see if the method has been registered |
|
478 if (L.isFunction(container[name])){ |
|
479 this.unregisterFunction(name, true); |
|
480 } |
|
481 }, |
|
482 |
|
483 /** |
|
484 * Removes function from profiling. Reverses the registerFunction() method. |
|
485 * @param {String} name The full name of the function including namespacing. This |
|
486 * is the name of the function that is stored in the report. |
|
487 * @return {Void} |
|
488 * @method unregisterFunction |
|
489 * @static |
|
490 */ |
|
491 unregisterFunction : function(name /*:String*/, unregisterPrototype /*:Boolean*/) /*:Void*/{ |
|
492 |
|
493 //see if the method has been registered |
|
494 if (L.isFunction(container[name])){ |
|
495 |
|
496 //check to see if you should unregister the prototype |
|
497 if (unregisterPrototype){ |
|
498 this.unregisterObject(name + ".prototype", container[name].prototype); |
|
499 } |
|
500 |
|
501 //get original data |
|
502 var owner /*:Object*/ = container[name].__yuiOwner, |
|
503 funcName /*:String*/ = container[name].__yuiFuncName; |
|
504 |
|
505 //delete extra information |
|
506 delete container[name].__yuiOwner; |
|
507 delete container[name].__yuiFuncName; |
|
508 |
|
509 //replace instrumented function |
|
510 owner[funcName] = container[name]; |
|
511 |
|
512 //delete supporting information |
|
513 delete container[name]; |
|
514 } |
|
515 |
|
516 |
|
517 }, |
|
518 |
|
519 /** |
|
520 * Unregisters an object for profiling. It takes the object and looks for functions. |
|
521 * When a function is found, unregisterMethod() is called on it. If set to recrusive |
|
522 * mode, it will also unregister objects found inside of this object, |
|
523 * using the same methodology. |
|
524 * @param {String} name The name of the object to unregister. |
|
525 * @param {Boolean} recurse (Optional) Determines if subobject methods should also be |
|
526 * unregistered. |
|
527 * @return {Void} |
|
528 * @method unregisterObject |
|
529 * @static |
|
530 */ |
|
531 unregisterObject : function (name /*:String*/, recurse /*:Boolean*/) /*:Void*/{ |
|
532 |
|
533 //get the object |
|
534 if (L.isObject(container[name])){ |
|
535 var object = container[name]; |
|
536 |
|
537 for (var prop in object) { |
|
538 if (typeof object[prop] == "function"){ |
|
539 this.unregisterFunction(name + "." + prop); |
|
540 } else if (typeof object[prop] == "object" && recurse){ |
|
541 this.unregisterObject(name + "." + prop, recurse); |
|
542 } |
|
543 } |
|
544 |
|
545 delete container[name]; |
|
546 } |
|
547 |
|
548 } |
|
549 |
|
550 |
|
551 }; |
|
552 |
|
553 |
|
554 |
|
555 }, '3.0.0' ,{requires:['oop']}); |