dashboard.png

Don’t let their small size fool you, Dashboard widgets are surprisingly CPU hungry. Take each widget’s intensely graphical nature and penchant for polling and parsing files from the internet. Add to this Dashboard’s slew of special effect and the fact that your average user keeps 5 or more widgets running at a time, and it’s not hard to arrive at a resource situation that can make the fans on your G5 kick up in anticipation.

But that’s ok, because these processor cycles are only used when Dashboard is activated. As soon as your widgets fade away into their OpenGL-accelerated purgatory, their resource usage drops to zero and your full-sized applications have the undivided attention of your CPU again. Right?

If we’re talking well behaved widgets, then yes, this is true. Fill your Dashboard with as many of the Apple-shipped calculators, flight trackers, and calendars as you please. When you hide it, you will see no impact on your system CPU (RAM is still consumed, but that’s an article for another day).

What most users (and some widget developers) don’t realize, though, is that Dashboard does not guarantee resources consumed by widgets will be freed once it is hidden. It is the responsibility of each individual widget to keep track of its processing and return it to the system when put away. If designers fail to do this, users will notice their computers run more slowly after Dashboard is run, conclude it’s a resource hog, and maybe even take the drastic step of disabling it all together.

If there is one commandment, then, that widget developers must follow, it is Thou shalt not consume CPU when the Dashboard is hidden! So how can you tell if your widget is leaching processor cycles? And if it is, what can you do to fix it? Read on!

Widget Activity

Activity Monitor is a great application. One part ps, one part top, and generous helping of the HIG makes for good eats where process stats are concerned. You can find your copy in /Applications/Utilities. Launch it, set the “Show” drop-down menu to “My Processes” or “All Processes”, and type “dashboard” into the “Filter” field. Then click on the “% CPU” table header until it displays a down arrow.

nocpu.png

Now only widgets are displayed, with those that are taking up processor at the top of the list. If you don’t see any items in your list, make sure you’ve opened Dashboard at least once. OS X doesn’t actually execute widgets until the first time they are viewed.

Once Activity Monitor is set up, it’s good practice to leave it running in the background while developing your widget. As you’re coding, steal an occasional glance at the CPU column. If Dashboard has been hidden for more than a few seconds and this column contains any values greater than 0.00, you have a problem. One of your widgets is eating computing power even when not in use!

The good news is the vast majority of CPU consumption issues are easy to fix. Of all the cycle-hungry widgets I’ve diagnosed since 10.4 was released, the great majority have suffered from one of only two fatal flaws: the improper handling of animated .gifs and/or uncleared JavaScript intervals. But to deal with either of these problems, we’re first going to need some hooks into the widget’s life cycle.

The Hook Brings You Back

Apple — ever prescient in matters pertaining to the needs of Dashboard developers — gifted the widget object with two event handlers that fit nicely into our current discussion: onshow and onhide.

As their names suggest, functions assigned to onshow and onhide are triggered when Dashboard is shown or hidden (respectively. I hope you didn’t need me to tell you that!) Normally, these hooks are assigned at the top level of any included JavaScript file:

if (window.widget){
    widget.onhide = onhide;
    widget.onshow = onshow;
}

function onhide() {
    //stop processing...
}

function onshow() {
    //restart processing...
}

Now when Dashboard is hidden, the onhide() function is called, putting an end to all unnecessary processing. When the Dashboard is shown again, onshow() gets called to restart everything that needs to be restarted. The if() block around the assignments above is only there to ensure these don’t get called when opened in something that doesn’t understand widgets (like if you were to try to open your widget in a web browser, for example). It’s not strictly needed if your only going to run your widget in Dashboard, but it’s a good habit to get in to. Who knows what Apple will change in 10.5, eh?

Now that we have a place to put our CPU squelching code, let’s take a closer look at some of the causes of processor overruns.

Looking a .gif Horse in the Mouth

Given Dashboard’s relation to Safari and WebCore, it’s no surprise that the most frequent method used to add animation to a web page is also the easiest way to add animation to your widget. I speak, of course, of animated .gifs! And while the .gif format has some technical — and, dare I say, ethical — limitations, animated .gifs, when used properly, can add a lot to a widget.

Now let me explain the italics above. When popping an animated gif onto a web page, nobody stops to think about how much processing power is involved in rendering it. Why should they? After all, it is seldom the case that more than one web page is displayed at a time. And it’s even more rare that the entirety of a page is shown by a browser (unless it’s a very short page or a very large screen). Finally, a browser is not expected to be “always on”. Most users look at a page and then close their browser until they need to look at another one.

Widgets, however, are different. They are always expected be switched on and at the ready. They are always displayed in their entirety. And there are, on average, five-or-more of them open at any given time.

So a web page that includes a .gif that takes 3% of the CPU to display only burdens your processor a little bit — and even then only while the browser is open and the animated .gif is actually on the screen. But if five widgets include the very same .gif, 15% the processor will be tied up from the first time you open Dashboard to the moment you shutdown your computer. Widget makers can not afford to leave animated .gif running in their widgets. An example widget called PrimeDirective underscores this moral.

animated.png

Download it, run it, and then click on the “Show” button. An array of all-singing all-dancing animated .gifs appears. Leave these .gifs running and exit Dashboard. Now look at your Activity Monitor.

animatedusage.png

Scary, right? So what’s to be done?

It turns out animated .gifs only use CPU when they are actually displayed on the widget. Thus, if you hide them by setting their display style to “none”, their usage drops to zero. So all you have to do is give each of your animated gifs has an id property, and then for each one add something like:

document.getElementById("myGifId").style.display = "none";

to the onhide() function we set up above. To display the .gifs again when Dashboard is opened, put:

document.getElementById("myGifId").style.display = "inline";

into onshow(). This is more or less what the PrimeDirective widget does when “Auto-disable” is set to “Yes”. Go ahead and show the animated .gifs and click on the “Auto-disable” button until “Yes” is displayed. Now exit Dashboard. See how the CPU percentage drops to 0.00? Taking care of .gifs is easy!

Illicit Intervals

Dealing with JavaScript intervals is less so. In fact, my recommendation to you is not to use them at all! setInterval() and clearInterval() should be as verboten as goto. The potential for abuse is too great.

And alternatives exist! If you need a function to execute after a given delay, setTimeout() and clearTimeout() are better choices. They execute once and only once, so there’s no chance they can continue abusing the CPU on into the night. And if you really need to loop a given function over and over, the AppleClasses now include AppleAnimator and AppleAnimation which let you loop a function and specify a total duration — again eliminating the chance that a function might spin until the cows come home.

But if you have to use an interval, best to learn how to use it safely. Any time you call setInterval() make sure you are assigning it to a global variable so that you can clearInterval() it at a later time. And then make sure you do clearInterval() every interval whenever Dashboard is hidden. The PrimeDirective widget shows what can happen if you don’t. Start it up, make sure that “Auto-disable” is set to “No”, and click the “Start” button. This sets up an interval that displays a random number once every .1 seconds.

random.png

If you exit Dashboard now and look at Activity Monitor, you’ll see that once again your widget is eating someone else’s lunch. And that’s only with a single interval left running!

randomusage.png

The solution is simple enough. Clear all intervals! For every setInterval() you have in your code, you should have a matching clearInterval() in your onhide() handler. You can then use onshow() to start it back up again. Something like the following:

var timer = null;
var active = false;
function someEvent(event){
    timer = setInterval("doSomethingOverAndOver()", 100);
    active = true;
}


function onhide(){
    if(null != timer){
        clearInterval(timer);
        timer = null;
    }
}

function onshow(){
    if(true == active){
        timer = setInterval("doSomethingOverAndOver()", 100);
    }
}

Again, PrimeDirective implements something very similar to this when “Auto-disable” is set to “Yes”. Check it out!

Am I Missing Something?

As I said, most of the erroneous processor pounding I’ve seen from widgets has been the direct result of one of these two issues. But that doesn’t mean .gifs and intervals are the only things that spin their wheels behind closed doors. I’d love to hear some of your stories about bugs that caused (hopefully beta!) versions of your Dashboard creations to put your CPU in a sleeper hold. Leave your comments below.