AJAX In Action [141]
stopwatch.watches=new Array();
Array of registered timers
stopwatch.getWatch=function(id,startNow){
Entry point for client code
var watch=stopwatch.watches[id];
if (!watch){
watch=new stopwatch.StopWatch(id);
}
if (startNow){
watch.start();
}
return watch;
}
stopwatch.StopWatch=function(id){
Stopwatch object constructor
this.id=id;
stopwatch.watches[id]=this;
this.events=new Array();
this.objViewSpec=[
{name: "count", type: "simple"},
{name: "total", type: "simple"},
{name: "events", type: "array", inline:true}
];
}
stopwatch.StopWatch.prototype.start=function(){
this.current=new TimedEvent();
}
stopwatch.StopWatch.prototype.stop=function(){
if (this.current){
this.current.stop();
this.events.append(this.current);
this.count++;
this.total+=this.current.duration;
this.current=null;
}
}
stopwatch.TimedEvent=function(){
Timed event object constructor
this.start=new Date();
Licensed to jonathan zheng 284 CHAPTER 8 Performance this.objViewSpec=[ {name: "start", type: "simple"}, {name: "duration", type: "simple"} ]; } stopwatch.TimedEvent.prototype.stop=function(){ var stop=new Date(); this.duration=stop-this.start; } stopwatch.report=function(div){ Profile report generator var realDiv=xGetElementById(div); var report=new objviewer.ObjectViewer(stopwatch.watches,realDiv); } Our stopwatch system is composed of one or more categories, each of which can time one active event at a time and maintain a list of previous timed events. When client code calls stopwatch.start() with a given ID as argument, the system will create a new StopWatch object for that category or else reuse the existing one. The client code can then start() and stop() the watch several times. On each call to stop(), a TimedEvent object is generated, noting the start time and duration of that timed event. If a stopwatch is started multiple times without being stopped in between, all but the latest call to start() will be discarded. This results in an object graph of StopWatch categories, each containing a history of timed events, as illustrated in figure 8.1. Stopwatch (singleton) Category + id : String Watches 0...n Event + start : Date + duration : Number Events 0...n Figure 8.1 Object graph of stopwatch library classes. Each category is represented by an object that contains a history of events for that category. All categories are accessible from the stopwatch.watches singleton. Licensed to jonathan zheng JavaScript execution speed 285 When data has been gathered, the entire object graph can be queried and visualized. The render() function here makes use of the ObjectViewer library that we encountered in chapter 5 to automatically render a report. We leave it as an exercise to the reader to output the data in CSV format for cutting and pasting into a file. Listing 8.3 shows how to apply the stopwatch code to our example “time consuming” function. Listing 8.3 Timing code with the stopwatch library function myTimeConsumingFunction(){ var watch=stopwatch.getWatch("my time consuming function",true); ... //do something interesting and time-consuming! ... watch.stop(); } The stopwatch code can now be added relatively unobtrusively into our code. We can define as few or as many categories as we like, for different purposes. In this case, we named the category after the function name. Before we move on, let’s apply this to a working example. A suitable candidate is the mousemat example that we used in chapter 4 when discussing the Observer pattern and JavaScript events. The example has two processes watching mouse movements over the main mousemat DOM element. One writes the current coordinates to the browser status bar, and the other plots the mouse cursor position in a small thumbnail element. Both are providing us with useful information, but they involve some processing overhead, too. We might wonder which is taking up the most processor time. Using our stopwatch library, we can easily add profiling capabilities to the example. Listing 8.4 shows