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