Beacon performance

February 9th, 2014. Tagged: performance

Beacons are small requests that our apps make to report some information "home", to the server. Beacons are often used to report visitor stats, JS errors, performance metrics.

Beacons often don't return any data back to the client, but some do.

Example use

<div id="app">Awesome app is awesome</div>
 
<script>

// gather data somehow
var data = {
 time: Date.now(),
 pixelRatio: window.devicePixelRatio
};
 
// construct query string
var qs = Object.keys(data).map(function(key){
  return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
}).join('&');
 
// send beacon
new Image().src = '/stats.png?' + qs; // "/stats.png?time=1391976334544&pixelRatio=1"

</script>

Measuring the beacon's performance

So now you want to know what's the preformance of the beacon - how long did it take to send the beacon out and get a response back.

Simplest thing - add onload and onerror (you'll see why) handlers:

// reporting beacon perf
var reported = false;
var start = Date.now();
function reportBeaconPerf() {
  if (reported) {
    return; // bye
  }
  reported = true;
  new Image().src = '/perf.png?beacon_took=' + (Date.now() - start);
                 // "/perf.png?beacon_took=34802"
}
 
// send beacon
var i = new Image;
i.onerror = i.onload = reportBeaconPerf;
i.src = '/stats.png?' + qs;

You use onload and onerror just in case you decide to return an empty "204 No Content" response from the stats.png beacon. In this case the response is not a valid image, so some browsers may decide, understandably, to fire an error event.

Need more perf data!

Time from declaring you want to make a beacon request (which may be different from the time the browser actually makes it) to the time you get an event back is cool. But you know what's cooler - getting more (and more accu-rat) data. E.g. DNS resolution, connect time, etc. One lump number doesn't tell you where you should focus to improve performance. You need details.

And details you get, thanks to the resource timing API now present in many-a-user-agent. Resource timing is not the same as navigation timing, but the adoption looks promising (http://caniuse.com/#feat=nav-timing). Plus, you're not going to beacon every single time. And probably perf improvements targeted at one browser will help the others (that haven't heard of resource timing yet) too.

Here's a nice intro to resource timing.

To see the magic in action:

  1. In Chrome, go to http://phpied.com
  2. open the console
  3. type
    > var src = "http://phpied.com/favicon.ico?blah=blargh!";
    > new Image().src = src;
  4. type performance.getEntries()
  5. Inspect the last entry in the array

Alternatively, you can also use getEntriesByName(), since you have the URL, e.g.

performance.getEntriesByName(src)[0];

You'll see something like:

rt

Pretty cool, huh? DNS resolution and all.

All the values are relative to performance.timing.navigationStart, in case you need absolute values.

Same-origin restrictions apply, so if you're beaconning to a different domain, make sure the response adds the Timing-Allow-Origin: * HTTP header, like so:

$ curl -I http://connect.facebook.net/en_US/all.js
HTTP/1.1 200 
Cache-Control: public, max-age=1200
Content-Type: application/x-javascript; charset=utf-8
...
Timing-Allow-Origin: *

Improving beacon perf

Now that you know how to measure the beacon perf, you can look at the data and see where you have room for improvements. Here are some pointers to get you started.

Async.

new Image().src is already async, but whatever the file where this JS code lives should be loaded asynchronously as well.

Tiny response.

The smallest response body you can have is 1x1 GIF. PNG is superior format that yields smaller file sizes, but not when it comes to tiny images. The 1x1 image should be 42 bytes. Anything above this is a waste.

No response.

Switch to a 204 response and make the body 0 bytes.

Respond, then log.

From Philip's response here:

It's better to send the header, and flush the output first, and then do your logging. That way the client isn't waiting for you to do your back end work.

Unless, of course, the response is dependent on back end work. In which case, back to the good old profiling of the craziness you're doing server-side. No network involved here.

Remove HTTP headers.

With the big axe. You may send tiny or 0-size response bodies, but what's the benefit if you send tons of headers? Cookie header being the worst.

Thanks!

Thanks for reading! Now go make your beacons faster!

Also keep an eye on Philip's articles here - http://www.lognormal.com/blog/

And please come back and add to that list above with your discoveries.

Comments? Find me on BlueSky, Mastodon, LinkedIn, Threads, Twitter