frontend SPOF survey

October 13, 2011 9:10 am | 3 Comments

Pat Meenan had a great blog post yesterday, Testing for Frontend SPOF. “SPOF” means single point of failure. I coined the term frontend SPOF to describe the all-too-likely situation where the HTML document returns successfully, but some other resource (a stylesheet, script, or font file) blocks the entire website from loading. This typically manifests itself as a blank white screen that the user stares out for 20 seconds or longer.

Frontend SPOF happens most frequently with third party content. If the HTML document returns successfully, then all the resources from the main website are likely to return successfully, as well. Third party content, however, isn’t controlled by the main website and thus could be suffering an outage or overload while the main website is working fine. As a result, the uptime of a website is no greater than the availability of the third party resources it uses that are in a position to cause frontend SPOF.

In my blog post of the same name I describe how Frontend SPOF happens and ways to avoid it, but I don’t provide a way for website owners to determine which third party resources may cause frontend SPOF. This is where Pat comes in. He’s created a public blackhole server: blackhole.webpagetest.org with the static IP address 72.66.115.13. Pointing your third party resources to this blackhole and reloading the page tells you if those resources cause frontend SPOF. Since Pat is the creator of WebPagetest.org, he has integrated this into the scripting capabilities of that tool so website owners can load their website and determine if any third party resources cause frontend SPOF.

/etc/hosts

I took a different approach outlined by Pat: I added the following lines to my /etc/hosts file (your location may vary) mapping these third party hostnames to point to the blackhole server:

72.66.115.13 apis.google.com
72.66.115.13 www.google-analytics.com
72.66.115.13 connect.facebook.net
72.66.115.13 platform.twitter.com
72.66.115.13 s7.addthis.com
72.66.115.13 l.addthiscdn.com
72.66.115.13 cf.addthis.com
72.66.115.13 api-public.addthis.com
72.66.115.13 widget.quantcast.com
72.66.115.13 ak.quantcast.com
72.66.115.13 assets.omniture.com
72.66.115.13 www.omniture.com
72.66.115.13 scripts.omniture.com
72.66.115.13 b.voicefive.com
72.66.115.13 ar.voicefive.com
72.66.115.13 c.statcounter.com
72.66.115.13 www.statcounter.com
72.66.115.13 www-beta.statcounter.com
72.66.115.13 js.revsci.net

After restarting my browser all requests to these hostnames will timeout. Pat’s blog post mentions 20 seconds for a timeout. He was running on Windows. I’m running on my Macbook where the timeout is 75 seconds! Any website that references third party content on these hostnames in a way that produces frontend SPOF will be blank for 75 seconds – an easy failure to spot.

survey says

THE GOOD: At this point I started loading the top 100 US websites. I was pleasantly surprised. None of the top 20 websites suffered from frontend SPOF. There were several that loaded third party content from these hostnames, but they had safeguarded themselves:

  • MSN makes a request to ar.voicefive.com, but does it asynchronously using a document.write technique.
  • AOL references platform.twitter.com, but puts the SCRIPT tag at the very bottom of the BODY so page rendering isn’t blocked.
  • IMDB uses the async version of Google Analytics, and puts the platform.twitter.com widget in an iframe.
  • LiveJournal goes above and beyond by wrapping the Google +1 and Facebook widgets in a homegrown async script loader.

THE BAD: Going through the top 100 I found five websites that had frontend SPOF:

  1. CNET loads http://platform.twitter.com/widgets.js in the HEAD as a blocking script.
  2. StumbleUpon loads http://connect.facebook.net/en_US/all.js at the top of BODY as a blocking script.
  3. NFL loads http://connect.facebook.net/en_US/all.js in the HEAD as a blocking script.
  4. Hulu, incredibly, loads Google Analytics in the HEAD as a blocking script. Please use the async snippet!
  5. Expedia loads http://connect.facebook.net/en_US/all.js as a blocking script in the middle of the page, so the right half of the page is blocked from rendering.

These results, although better than I expected, are still alarming. Although I only found five websites with frontend SPOF, that’s 5% of the overall sample. The percentage will likely grow as the sample size grows because best practices are more widely adopted by the top sites. Also, my list of third party hostnames is a small subset of all widgets and analytics available on the Web. And remember, I didn’t even look at ads.

Is it really worth blocking your site’s entire page for a widget button or analytics beacon – especially when workarounds exist? If you’re one of the five sites that faltered above, do yourself and your users a favor and find a way to avoid frontend SPOF. And if you’re outside the top 100, test your site using Pat’s blackhole server by editing /etc/hosts or following Pat’s instructions for testing frontend SPOF on WebPagetest.org.

3 Comments

App cache & localStorage survey

September 26, 2011 9:51 pm | 26 Comments

In preparation for my talk at HTML5 Dev Conf I surveyed the Alexa US Top 10 websites to see which ones use app cache and localStorage. I mostly focus on mobile these days so it’s natural to think I ran these tests from a mobile browser, which I did. But I also tested with a desktop browser.

Some people might ask, Why a desktop browser?

To which I would reply, Why not a desktop browser?

I blogged previously about how Google and Bing use localStorage on mobile devices. It’s a powerful performance technique. They break up their JavaScript and CSS into smaller blocks and save them in localStorage. Simultaneously they set a cookie so that the server knows not to send that payload on subsequent searches, a savings of 150-170 kB before gzipping.

In the comments on that post Simon asked:

I’m curious why the techniques with LocalStorage are used for the mobile versions of the search sites but not for the standard desktop versions – I would think that this technique would work well [on] all clients, no?

I agree – this and other HTML5 web storage techniques make sense for the desktop, too. There are some reasons why we’re seeing these used first on mobile:

  • Mobile latencies are higher and connection speeds are lower, so clientside caching is more important on mobile.
  • Mobile disk cache sizes are smaller than desktop sizes, so a better alternative is needed for mobile.
  • There are still desktop browsers with significant market share that are missing many HTML5 capabilities, whereas mobile browsers have more support for HTML5.

Even though the motivation for app cache and localStorage are stronger for mobile, they should also be used when the website is accessed from a desktop browser. I did a quick survey to see which of the top 10 websites were using app cache or localStorage on either mobile or desktop. Here are the results:

Website Mobile Desktop
app cache localStorage app cache localStorage
Google Search no yes no yes [1]
Bing no yes no no
Facebook no yes no no
Yahoo! Front Page no yes [2] no no
YouTube no yes no no
Amazon no no no yes [3]
Twitter yes yes no yes
LinkedIn no no no no
eBay no no no no
MSN.com no no no no
[1] Google Search on the desktop uses sessionStorage, not localStorage.
[2] Yahoo! Front Page only stores two numbers.
[3] Amazon on the desktop only stores a ~30 character string.

Ignoring the small uses of localStorage, 5 of these top 10 websites use localStorage on mobile, but only 2 out of 10 use localStorage (or sessionStorage) on desktop. None of them use app cache, either on mobile or desktop except for Twitter on mobile. I’m surprised no one is using app cache. It’s not appropriate for all applications, such as search, but I would enjoy catching up on Twitter, Facebook, and LinkedIn on the plane – potentially from my laptop in addition to my mobile device. App cache not only brings this offline capability, but provides better caching than the browser’s disk cache.

I’ll repeat this survey in a few months to track the progress. I expect we’ll see the use of localStorage and app cache increase, and for desktop to catch up to mobile.

 

26 Comments

(lack of) Caching for iPhone Home Screen Apps

June 28, 2011 10:14 pm | 10 Comments

Yesterday’s post, Unexpected Reloads in WebKit, revealed an interesting behavior that affects caching in Safari:

When you load the same URL back-to-back in Safari, the second load is treated the same as hitting Reload.

This is bad for performance because the browser issues a Conditional GET request for each resource instead of using the cached resource.

It’s important to be aware of this behavior when testing the primed cache experience in Safari, so web performance engineers should take note. However, in the real world it’s unlikely this behavior has much of an impact on desktop users. Here’s the table from yesterday’s post that shows how this Reload-like behavior is triggered when re-requesting a page:

way of loading URL again like Reload?
hit RETURN in location field yes
delete URL and type it again yes
launch same URL via bookmark yes
click link to same URL yes
go to another URL then type 1st URL again no
modify querystring no
enter URL in a new tab no
Table 1. Triggering reload behavior in Safari

It’s possible that real world users might type the same URL or open the same bookmark two times in a row in the same tab, but it probably doesn’t happen that often. So what’s the big deal?

So what’s the big deal?

Whenever I see strange performance behavior I think about where that behavior might have a significant impact. Is there any place where this back-to-back Safari Reload behavior could have a significant impact? A comment from yesterday’s post hints at the answer:

Why is this article named “Unexpected Reloads in WebKit”?

Chrome is based on Webkit and doesn’t has same issue. Perhaps it would be less confusing to name it “Unexpected Reloads in Safari”.

Other people gave me the same feedback on the backchannel – why did I say “WebKit” instead of “Safari”.

Here’s why: WebKit is used in a lot of browsers. Whenever I see a bug (or a feature) in one popular WebKit-based browser I wonder if it exists in others. The main WebKit-based browsers I focus on are Chrome, Safari, Android, and iPhone. As soon as I noticed this behavior in Safari my next step was to conduct the same tests in Chrome, Android, and iPhone. As the commenter noted, this unexpected Reload behavior does not happen in Chrome. And it does not happen on Android (tested on my Nexus S). But it does happen on iPhone.

Update June 29: In a comment on yesterday’s post, Libo Song correctly pointed out that this back-to-back Reload-like behavior does happen on Android. He tested on Nexus One and I confirmed on Nexus S. Although Android does exhibit the Reload-like behavior when the same URL is entered back-to-back in the same tab, this doesn’t happen very often. The more important issue that is the focus of this post is how this Reload-like behavior slows down the launching of home screen apps. In this regard Android does not exhibit the Reload-like behavior when home screen apps are launched. Here’s a HAR file showing Amazon being loaded twice from the home screen with fast.stevesouders.com in between. The second launch doesn’t generate any HTTP requests.

While it’s true that iPhone users are unlikely to manually launch the same URL twice-in-a-row in the same tab, there is a situation when this happens automatically: when launching home screen apps.

Home screen apps are a powerful feature on iPhone and Android that allow users to save URLs to the home screen and launch them similar to native apps. Unfortunately, launching home screen apps on the iPhone triggers something similar to the Reload behavior we see in Safari – where resources aren’t read from cache and instead generate extra HTTP requests. Let’s take a look at a few examples of home screen apps, starting with simple to more complex.

Amazon: simple URL

Typing http://www.amazon.com/ into the iPhone browser displays a version of Amazon’s front page that is customized for mobile – there’s less content, the images are smaller, etc. However, there is not a prompt to save the URL to the home screen. We can do that anyway using the arrow function key at the bottom of the screen and selecting “Add to Home Screen”.

If you’ve used home screen apps you might have noticed that they always open in the same browser tab. Let’s run a little test to confirm this:

  1. Click the Amazon home screen icon. This opens Amazon in mobile Safari.
  2. Open another tab by clicking the “pages” function key and opening a “New Page”. Enter some non-Amazon URL in this new tab, for example http://fast.stevesouders.com/ (a very lightweight page I use for testing). At this point we have at least two tabs, one with Amazon and one with fast.stevesouders.com, and we’re looking at the fast.stevesouders.com tab.
  3. Go back to the home screen and click the Amazon icon again.
  4. Note that you’re taken back into mobile Safari to the first tab that contains Amazon.

We just opened the exact same URL back-to-back in the same tab. We didn’t do it intentionally – that’s the default behavior for iPhone home screen apps. Here’s a waterfall chart for this test. (You can view an interactive waterfall by loading the HAR file in pcapperf.)

The home screen app URL is http://www.amazon.com/gp/aw/h.html/187-9233150-9797455. The first time the home screen app is launched starts at the top with 187-9233150-9797455. Since the cache was empty all the subsequent resources have 200 responses. There are some 404s for icons followed by the request for fast.stevesouders.com.

The second launch of the Amazon home screen app (187-9233150-9797455 below fast.stevesouders.com) is where it gets interesting. When the Amazon home screen app is launched the second time, a Conditional GET request is made for all of the resources even though these resources are in the cache with a future expiration date.

All of the resources that are re-requested have an expiration date more than 10 years in the future. For example, the response headers for title_gradient._V233984477_.png are:

content-length: 291
expires: Tue, 06 May 2031 21:44:21 GMT
last-modified: Mon, 10 Aug 2009 11:50:45 GMT
cache-control: max-age=626560472
date: Wed, 29 Jun 2011 01:09:49 GMT
content-type: image/png

We know it was cached because when the Amazon home screen app is launched the second time the Conditional GET request for title_gradient._V233984477_.png has an If-Modified-Since header that contains the last-modified date in the initial response:

if-modified-since: Mon, 10 Aug 2009 11:50:45 GMT

It appears that we’ve stumbled into the Reload-like behavior we saw in Safari on the desktop. Further evidence of this is if you launch the home screen app, then type a new URL over the Amazon URL, and launch the home screen app again the resources are read from cache instead of generating numerous Conditional GET requests. (Load this HAR file in pcapperf to see for yourself.)

Untappd: full screen app

Amazon was a simple home screen app – really just a bookmark on the home screen. Developers can do more with home screen apps to make them launch and look like native apps. As described in Apple’s How-To’s for Safari on iPhone, various parts of the home screen app user experience are customizable including the home screen icon, viewport, and zooming and scaling. Developers can also have their home screen app launch in “full screen mode” by hiding the Safari UI components, including the status bar and location bar. In this situation, every time the home screen app is launched it uses the same “tab” with the exact same URL – thus triggering the Reload behavior.

Let’s have a look at Untappd on the iPhone. The first time you navigate to http://untappd.com/ in iPhone’s browser you get a suggestion to add the web app to the home screen:

After which you’ll have a customized Untappd home screen icon:

Now let’s investigate how caching works for this home screen app. We start by clearing the cache then launching the home screen app. You’ll notice there is no location bar or other Safari controls. Then we go back to the home screen and launch the Untappd home screen app again. The waterfall chart is shown below. (Here’s the HAR file.)

The first time the Untappd home screen app is launched it loads seven HTTP requests. Three of these resources are cacheable: jquery.min.js (1 year), gears_init.js (1 hour), and ga.js (1 day). Loader.gif and ajax-loader.png don’t have a future expiration date, but they do have Last-Modified and ETag response headers that could be used in a Conditional GET request.

But we see that the second time Untappd is launched from the home screen, all of the resources are re-requested. To make matters worse, none of these are Conditional GET requests, so a 200 status code is returned with the full response body.

The punchline

It’s unfortunate that home screen apps suffer from this bad caching behavior on the iPhone. Thankfully, there is a workaround: application cache. I ran similar tests on other home screen apps that use application cache. The resources listed in the CACHE: section of  the manifest file were used on the iPhone without generating Conditional GET requests.

I feel bad about recommending the use of application cache. This is an issue with the browser cache on mobile Safari (and to a lesser degree on desktop Safari) that should be fixed. It’s a significant amount of work for developers to adopt application cache. The plus side is that doing so achieves the ability to work offline.

After this lengthy analysis and numerous waterfalls, here’s the punchline in a nutshell:

Home screen apps on iPhone are slower because resources are re-requested even though they should be read from cache. Use application cache to avoid this performance problem.

Update Oct 12: Home screen apps in iOS 5 do not exhibit this problem. Blaze.io reports that home screen apps use caching as expected. They also have faster JS likely do to the integration of the Nitro engine.

10 Comments

Unexpected Reloads in WebKit

June 27, 2011 4:47 pm | 9 Comments

People who work on web performance often need to load the same URL over and over again. Furthermore, they need to do this while simulating a real user’s empty cache experience and primed cache experience. When I want to analyze the empty cache experience the flow is simple: go to about:blank, clear the browser cache, enter the URL, and hit RETURN.

But what’s the right way to fetch a page repeatedly when analyzing the primed cache experience?

The main goal when testing the primed cache version of a page is to see which resources are read from cache. The goal for better performance is to cache as many responses as possible thus reducing the number of requests made when the cache is primed. If a resource has an expiration date in the future, the browser uses the cached version and doesn’t have to make an HTTP request resulting in a faster page. If a resource is expired (the expiration date is in the past) the browser issues a Conditional GET request using the If-Modified-Since and If-None-Match request headers. If the resource hasn’t changed then the server returns a simple 304 status code with no body. This is faster (because there’s no response body) but still takes time to do the HTTP request. (See my article on ETags for examples of IMS and INM.)

One way to re-request a page is to hit the Reload button, but this doesn’t give an accurate portrayal of the typical primed cache user experience. Hitting Reload causes the browser to always make an IMS/INM request for resources in the page, even for cached resources that have an expiration date in the future. Normally these resources would be used without generating an HTTP request. Although users do occasionally hit the Reload button it’s more likely that they’ll navigate to a page via a link or the location field, both of which avoid the time consuming Conditional GET requests generated when hitting Reload.

The technique I adopted years ago for re-requesting a page when testing the primed cache is to click in the location field and hit RETURN. That’s a fine approach in IE, Firefox, Chrome, and Opera, but not in Safari. Let’s investigate why.

hitting RETURN in the location field

I’m using Untappd as an example. Untappd has 68 requests when loaded on the desktop. Figure 1 shows the waterfall chart for the first 31 requests when loaded in Firefox 4 with an empty cache:

Figure 1. untappd.com – Firefox 4 – empty cache

Most of the resources shown in Figure 1 have an expiration date in the future and therefore won’t generate an HTTP request if the user has a primed cache. To test that I click in the location field and hit RETURN. The resulting waterfall chart is shown in Figure 2. Sure enough the number of HTTP requests drops from 68 to 4!

Figure 2. untappd.com – Firefox 4 – primed cache

If you repeat this experiment in Chrome, Firefox, Internet Explorer, and Opera you’ll get similar results – empty cache generates 68 requests, primed cache generates 4 requests. However, the result is very different in Safari 5. It’s important to understand why.

Safari is different

This test shows that Untappd has done a good job of optimizing the primed cache experience – the number of HTTP requests made by the browser drops from 68 to 4. Running the same test in Safari 5 produces different results. Clearing the cache and loading untappd.com in Safari 5 loads 68 HTTP requests – just as before. To test the primed cache experience we click in the location field and hit RETURN. Instead of only 4 requests there are 68 HTTP requests.

Why are there 64 more HTTP requests in Safari 5 for the primed cache test? Looking at the HTTP request headers we see that these are all Conditional GET requests. Let’s use http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js as the example (it’s the 8th request in Figure 1). In the empty cache scenario the HTTP request headers are:

Accept: */*
Cache-Control: max-age=0
Referer: http://untappd.com/
User-Agent: Mozilla/5.0 (Macintosh; [snip...] Safari/533.20.27

The HTTP status code returned for that empty cache request is 200 OK.

In the primed cache test when we hit RETURN in the location field we see that the request for jquery.min.js contains an extra header:

Accept: */*
Cache-Control: max-age=0
If-Modified-Since: Mon, 15 Feb 2010 23:30:12 GMT
Referer: http://untappd.com/
User-Agent: Mozilla/5.0 (Macintosh; [snip...] Safari/533.20.27

The header that’s added in the primed cache test is If-Modified-Since. This is a Conditional GET request. The HTTP status code that’s returned is 304 Not Modified. Even though all I did was hit RETURN in the location field, Safari treated that like hitting the Reload button.

unexpected “reload” in Webkit

Unlike other browsers, Safari 5 treats hitting RETURN in the location field the same as clicking the Reload button. When else does this happen? Assuming you’ve loaded a URL in Safari and are looking at that page, this table lists various ways to load that URL again. For each technique I show whether loading the URL this way generates extra Conditional GET requests similar to clicking Reload.

way of loading URL again like Reload?
hit RETURN in location field yes
delete URL and type it again yes
launch same URL via bookmark yes
click link to same URL yes
go to another URL then type 1st URL again no
modify querystring no
enter URL in a new tab no
Table 1. Triggering reload behavior in Safari

This black box testing indicates that whenever the same URL is loaded back-to-back in the same tab, Safari 5 treats it as a Reload. I was describing this behavior to Jay Freeman (saurik) at Foo Camp. He pointed me to this code from WebCore:

else if (sameURL)
   // Example of this case are sites that reload the same URL with a different cookie
   // driving the generated content, or a master frame with links that drive a target
   // frame, where the user has clicked on the same link repeatedly.
   m_loadType = FrameLoadTypeSame;

Searching in that same file for FrameLoadTypeSame we find this code:

case FrameLoadTypeReload:
case FrameLoadTypeReloadFromOrigin:
case FrameLoadTypeSame:
case FrameLoadTypeReplace:
   history()->updateForReload();
   m_client->transitionToCommittedForNewPage();
   break;

This code doesn’t account for the behavior, but it does show that FrameLoadTypeSame and FrameLoadTypeReload are treated as similar cases in this context, and perhaps that’s why IMS/INM requests are generated.

One important takeaway from this is: don’t hit RETURN in the location field to test primed cache experience in Safari. Instead, go to a different URL and then type the test URL in the location field, or open a new tab and type the URL.

There’s a second more important takeaway from this. I’ll cover that in tomorrow’s post. If you know the answer, please don’t spoil it. Oh what the heck – if you think you know the answer go ahead and add a comment.

9 Comments

HTTP Archive: servers and most 404s

May 9, 2011 5:11 am | 5 Comments

I launched the HTTP Archive about a month ago. The reaction has been positive including supportive tweets from Tim O’Reilly, Werner Vogels, Robert Scoble, and John Resig. I’m also excited about the number of people that have already started contributing to the project. Two new stats charts are available thanks to patches from open source contributors.

James Byers contributed the patch for generating the Most Common Servers pie chart. This chart is similar to BuiltWith’s Web Server chart. BuiltWith shows a higher presence of IIS than shown here. Keep in mind the sample sets are different – the HTTP Archive hits the world’s top ~17K URLs while BuiltWith is covering 1M URLs.

The other new chart comes from Carson McDonald. It shows pages with the most 404s. Definitely a list you don’t want to find your website on.

l’ve added some other features I’ll blog about tomorrow and am planning a bigger announcement later this week, so stay tuned for some more HTTP Archive updates.

5 Comments

HTTP Archive: max-age

April 18, 2011 9:40 pm | 10 Comments

There’s a long list of interesting stats to be added to the HTTP Archive. I’m planning on knocking those off at about one a week. (If someone wants to help that’d be great – contact me. Familiarity with MySQL and Google Charts API is a plus.)

Last week I added an interesting stat looking at the cache lifetime being specified for resources – specifically the value set in the Cache-Control: max-age response header. As a reminder, the HTTP Archive is currently analyzing the top ~17K websites worldwide. Across those websites a total of ~1.4M resources are requested. The chart below shows the distribution of max-age values across all those resources.

56% of the resources don’t have a max-age value and 3% have a zero or negative value. That means only 41% of resources are cacheable. In more concrete terms, the average number of resources downloaded per page is 81. 33 of those are cacheable, but the other 48 will likely generate an HTTP request on every page view. Ouch! That’s going to slow things down. Only 24% of resources are cacheable for more than a day. Adding caching headers is an obvious performance win that needs wider adoption.

10 Comments

Storager case study: Bing, Google

March 28, 2011 9:26 pm | 23 Comments

Storager

Last week I posted my mobile comparison of 11 top sites. One benefit of analyzing top websites is finding new best practices. In that survey I found that the mobile version of Bing used localStorage to reduce the size of their HTML document from ~200 kB to ~30 kB. This is a good best practice in general and makes even more sense on mobile devices where latencies are higher, caches are smaller, and localStorage is widely supported.

I wanted to further explore Bing’s use of localStorage for better performance. One impediment is that there’s no visibility into localStorage on a mobile device. So I created a new bookmarklet, Storager, and added it to the Mobile Perf uber bookmarklet. (In other words, just install Mobile Perf – it bundles Storager and other mobile bookmarklets.)

Storager lets you view, edit, clear, and save localStorage for any web page on any browser – including mobile. Viewing localStorage on a 320×480 screen isn’t ideal, so I did the obvious next step and integrated Storager with Jdrop. With these pieces in place I’m ready to analyze how Bing uses localStorage.

Bing localStorage

My investigation begins by loading Bing on my mobile device – after the usual redirects I end up at the URL http://m.bing.com/?mid=10006. Opening Storager from the Mobile Perf bookmarklet I see that localStorage has ~10 entries. Since I’m not sure when these were written to localStorage I clear localStorage (using Storager) and hit reload. Opening Storager again I see the same ~10 entries and save those to Jdrop. I show the truncated entries below. I made the results public so you can also view the Storager results in Jdrop.

BGINFO: {"PortraitLink":"http://www.bing.com/fd/hpk2/Legzira_EN-US262...
CApp.Home.FD66E1A3: #ContentBody{position:relative;overflow:hidden;height:100%;-w...
CUX.Keyframes.B8625FE...: @-webkit-keyframes scaleout{from{-webkit-transform:scale3d(1,...
CUX.Site.18BDD936: *{margin:0;padding:0}table{border-collapse:separate;border-sp...
CUX.SiteLowRes.C8A1DA...: .blogoN{background-image:url(data:image/png;base64,iVBORw0KGg...
JApp.Home.DE384EBF: (function(){function a(){Type.registerNamespace("SS");SS.Home...
JUX.Compat.0907AAD4: function $(a){return document.getElementById(a)}var FireEvent...
JUX.FrameworkCore.A39...: (function(){function a(){Type.registerNamespace("BM");AjaxSta...
JUX.MsCorlib.172D90C3: window.ss={version:"0.6.1.0",isUndefined:function(a){return a...
JUX.PublicJson.540180...: if(!this.JSON)this.JSON={};(function(){function c(a){return a...
JUX.UXBaseControls.25...: (function(){function a(){Type.registerNamespace("UXControls")...
RMSM.Keys: CUX.Site.18BDD936~CUX.Keyframes.B8625FEE~CApp.Home.FD66E1A3~C...

These entries are written to localStorage as part of downloading the Bing search page. These entries add up to ~170 kB in size (uncompressed). This would explain the large size of the Bing HTML document on mobile. We can verify that these keys are downloaded via the HTML document by searching for a unique string from the data such as “FD66E1A3″. We find this string in the Bing document source (saved in Jdrop) as the id of a STYLE block:

<style data-rms="done" id="CApp.Home.FD66E1A3" rel="stylesheet" type="text/css">
#ContentBody{position:relative;overflow:hidden;height:100%;-webkit-tap-highlight-color:...

Notice how the content of this STYLE block matches the data in localStorage. The other localStorage entries also correspond to SCRIPT and STYLE blocks in the initial HTML document. Bing writes these blocks to localStorage and then on subsequent page views reads them back and inserts them into the document resulting in a much smaller HTML document download size. The Bing server knows which blocks are in the client’s localStorage via a cookie, where the cookie is comprised of the localStorage keys delimited by “~”:

RMSM=JApp.Home.DE384EBF~JUX.UXBaseControls.252CB7BF~JUX.FrameworkCore.A39F6425~
JUX.PublicJson.540180A4~JUX.Compat.0907AAD4~JUX.MsCorlib.172D90C3~CUX.SiteLowRes.C8A1DA4E~
CApp.Home.FD66E1A3~CUX.Keyframes.B8625FEE~CUX.Site.18BDD936~;

Just to be clear, everything above happens during the loading of the blank Bing search page. Once a query is issued the search results page downloads more keys (~95 kB additional data) and expands the cookie with the new key names.

Google localStorage

Another surprise from last week’s survey was that the mobile version of Google Search had 68 images in the results HTML document as data: URIs, compared to only 10 for desktop and iPad. Mobile browsers open fewer TCP connections and these connections are typically slower compared to desktop, so reducing the number of HTTP requests is important.

The additional size from inlining data: URIs doesn’t account for the large size of the Google Search results page, so perhaps localStorage is being seeded here, too. Using Storager we see over 130 entries in localStorage after a search for flowers. Here’s a sample. (As before, the key names and values may be truncated.)

 mres.-8Y5Dw_nSfQztyYx: <style>a{color:#11c}a:visited{color:#551a8b}body{margin:0;pad...
 mres.-Kx7q38gfNkQMtpx: <script> //<![CDATA[ var Zn={},bo=function(a,b){b&&Zn[b]||(ne...
 mres.0kH3gDiUpLA5DKWN: <style>.zl9fhd{padding:5px 0 0}.sc59bg{clear:both}.pyp56b{tex...
 mres.0thHLIQNAKnhcwR4: <style>.fdwkxt{width:49px;height:9px;background:url("data:ima...
 mres.36ZFOahhhEK4t3WE: <script> //<![CDATA[ var kk,U,lk;(function(){var a={};U=funct...
 mres.3lEpts5kTxnI2I5S: <script> //<![CDATA[ var Ec,Fc,Gc=function(a){this.Jl=a},Hc="...
 mres.4fbdvu9mdAaBINjE: <script> //<![CDATA[ u("_clOnSbt",function(){var a=document.g...
 mres.5QIb-AahnDgEGlYP: <script> //<![CDATA[ var cb=function(a){this.Cc=a},db=/\s*;\s...
 mres:time.-8Y5Dw_nSfQ...: 1301368541872
 mres:time.-Kx7q38gfNk...: 1301368542755
 mres:time.0kH3gDiUpLA...: 1301368542257
 mres:time.0thHLIQNAKn...: 1301368542223
 mres:time.36ZFOahhhEK...: 1301368542635
 mres:time.3lEpts5kTxn...: 1301368542579
 mres:time.4fbdvu9mdAa...: 1301368542720
 mres:time.5QIb-AahnDg...: 1301368542856

Searching the search results docsource for a unique key such as “8Y5D” we find:

<style id="r:-8Y5Dw_nSfQztyYx" type="text/css">
a{color:#11c}a:visited{color:#551a8b}body{margin:0;padding:0}...

Again we see that multiple SCRIPT and STYLE blocks are being saved to localStorage totaling 154 kB. On subsequent searches the HTML document size drops from the initial size of 220 kB uncompressed (74 kB compressed) to 67 kB uncompressed (16 kB compressed). In addition to the key names being saved in a cookie, it appears that an epoch time (in milliseconds) is associated with each key.

Conclusion

Bing and Google Search make extensive use of localStorage for stashing SCRIPT and STYLE blocks that are used on subsequent page views. None of the other top sites from my previous post use localStorage in this way. Are Bing and Google Search onto something? Yes, definitely. As I pointed out in my previous post, this is another example of a performance best practice that is used on a top mobile site but is not in the recommendations from Page Speed or YSlow. Many of the performance best practices that I’ve evangelized over the last six years for desktop apply to mobile, but I believe there are specific mobile best practices that we’re just beginning to identify. I’ve started using “High Performance Mobile” as the title of future presentations. Another book? hmmm….

23 Comments

Mobile comparison of Top 11

March 14, 2011 8:01 pm | 7 Comments

A few weeks ago I announced Jdrop while at Webstock. After Webstock I took some vacation time on Waiheke Island. Now that I’m back I wanted to show how Jdrop is useful for doing mobile analysis.

Preamble

As a reminder, Jdrop is a JSON repository in the cloud. Jdrop itself doesn’t do anything specific for mobile analysis. But what it does do is provide a way to gather information from mobile devices and analyze it later from your desktop where you have more screen real estate. A bigger screen is important when you’re looking at things like document source and lists of resources.

The apps that integrate with Jdrop are what actually gather the data and do the analysis. Right now there are three main apps integrated with Jdrop (all of them are bookmarklets): Page Resources, Docsource, and DOM Monster. Rather than add each of these bookmarklets individually to your mobile browser, you can get them all just by adding my uber Mobile Perf bookmarklet.

Actual Analysis

Using the Mobile Perf bookmarklet I analyzed 11 top U.S. sites on the desktop (Firefox 3.6), iPad, and iPhone. On the desktop and iPad I used my Mom’s home wifi; iPhone was over AT&T 3G.

For each site and platform I gathered the following information:

Overall Averages
reqs HTML elems
desktop 31 110 kB 1166
iPad 28 86 kB 908
iPhone 15 75 kB 567

The overall results are interesting. The iPhone versions of these websites are about half the size of their desktop counterparts in terms of number of requests and number of DOM elements, but are 75% when it comes to the size of the HTML document. The increased relative size of HTML documents is an interesting artifact I explore later in this post. The iPad averages fall in between desktop and iPhone – not a surprise given its bigger screen but limited networking and processing power.

A recent feature added to Jdrop is the ability to make data public. I gathered all of this data using Jdrop and made it public which allows me to link directly to the raw data so you can see it as part of the analysis that follows. You can take a look at the Public JSON page on Jdrop and use the filter to search for any other data you’re interested in.

Let the analysis begin.

Google Search

Google Search
reqs HTML elems
desktop 19 199 kB 1456
iPad 15 148 kB 1105
iPhone 6 149 kB 855

Doing a Google search for flowers shows it has versions customized for the iPad as well as the iPhone. The iPad version is about 75% in size compared with the desktop version. The iPhone version, on the other hand, is much smaller than the desktop version: 30% as many requests, 75% of the HTML, and 60% as many DOM elements.

It’s interesting that the ratio of requests between the iPhone and desktop versions is so small but the ratio of HTML size is so high. My first guess was data: URIs. Using data: URIs would reduce the number of requests but increase the size of the HTML document. Sure enough, the iPhone document source has 68 images specified as data: URIs, whereas the iPad and desktop versions only have 10.

This is a great example of how the relevance of performance best practices differs between desktop and mobile.

We’ve known about data: URIs as a performance best practice for years. I first blogged about them back in April of 2007. But neither Page Speed nor YSlow have a rule that recommends using data: URIs. And yet here they’re used extensively for mobile by one of the the most successful websites in the world. Data: URIs might not be a high priority on the desktop where latency is lower and browsers can open more simultaneous connections, but they’re worth another look if you’re trying to speedup your mobile website.

Bing

Bing
reqs HTML elems
desktop 19 93 kB 988
iPad 19 59 kB 889
iPhone 16 198 kB 374

The number of HTTP requests made for a Bing search for flowers is similar across desktop, iPad, and iPhone: 19, 19, and 16. The iPad version is slightly smaller than the desktop version in terms of HTML size (93kB vs. 59kB) and number of DOM elements (988 vs. 889).

The big surprise here is that the size of the iPhone HTML document. At 198kB it’s 2x bigger than the desktop version and 4x bigger than the iPad HTML size, even though its number of DOM elements is less than half of the other versions.

Why is the iPhone HTML document so much bigger than the desktop and iPad versions? This was a bit harder to figure out. My first guess was data: URIs, just like Google Search, but there aren’t any data: URIs in the Bing docsource for iPhone.

I had to unravel this one by looking at the actual response sent over the network. The easiest way to do that is to change my desktop browser user agent to iPhone. (I never do this the first time I’m analyzing a mobile website, but in this case I already have the docsource, page resources, and number of DOM elements, so I can make sure the version I get by changing my user agent matches what I originally received on my mobile device.) I was surprised to see that the size of the HTML document response is ~30K uncompressed (~9K compressed). So why does the Docsource bookmarklet report it as 198kB?

The Docsource bookmarklet uses the document.documentElement.innerHTML property. That property is ~198kB in length. So something is causing the ~30kB response to expand to ~198kB of HTML. Diffing the ~30kB network response with documentElement.innerHTML highlights calls to the RMS._load function:

_load:function(b){
    var a=null;
    if(localStorage)
        try{a=localStorage.getItem(b)}
    catch(c){}
    return a
}

Sure enough, if you clear your localStorage and repeat the search the network transfer size of the HTML document source is ~200kB. Some of the blocks of that HTML document are saved to localStorage so they can be retrieved from there on subsequent searches thus reducing the size of the HTML document. Very nice! You can study the Bing response and see how they structured various STYLE blocks to make it easy to extract that data and save it to localStorage.

Yahoo Search

Yahoo Search
reqs HTML elems
desktop 24 174 kB 2221
iPad 25 83 kB 967
iPhone 25 38 kB 387

In terms of HTML size and number of DOM elements, the iPad and iPhone versions of a Yahoo search for flowers are progressively smaller than the desktop version. The iPad version is about half of the desktop version, and the iPhone is less than a quarter of the desktop version.

Although the iPad and iPhone versions are progressively smaller than the desktop version in terms of HTML size and number of DOM elements, all three versions have about 24-25 HTTP requests. This is counter intuitive – one would think that the smaller screen sizes would have fewer requests. The bulk of these requests are images: the desktop page resources has 17 images, the iPad has 19 images, and iPhone has 15 images. So the iPhone does have fewer images, and those images are generally smaller.

The big difference comes in terms of CSS background images. Both the desktop and iPad versions have 1 CSS background image, compared with 7 for the iPhone version. The iPhone’s CSS background images contain two named “core-spring.png”. They have different version numbers (v=9 and v=12), but are similar. Two of the background images (“os_mag_glass.png” and “arrow_right2.png”) have similar images in “core-sprite.png”. Getting the iPhone down to 1 CSS background image, like the desktop and iPad versions, would eliminate 6 HTTP requests.

Wikipedia

Wikipedia
reqs HTML elems
desktop 47 121 kB 1642
iPad 47 121 kB 1632
iPhone 27 83 kB 1108

Wikipedia’s “flowers” page is identical on the desktop and iPad. The iPhone version is smaller in all three categories. The number of requests on the iPhone is high – 27 compared to the average of 15 requests. Most of these are large images that aren’t visible in the initial page, but they are loaded before the window onload event. This isn’t a huge deal, but it does make page load measurements longer and can block more important resources like scripts at the bottom of the page. It would be better to load these images dynamically after the onload event.

Facebook

Facebook
reqs HTML elems
desktop 16 31 kB 524
iPad 16 29 kB 519
iPhone 4 2 kB 114

The Facebook login page was tested instead of a logged-in page to avoid differences based on user account. The desktop and iPad versions are almost identical. The iPhone version, on the other hand, is less than a quarter of the size of the others. How did they achieve such a reduction of HTTP requests – from 16 down to 4?

Comparing the desktop version’s page resources to the iPhone version’s page resources shows that the iPhone version is much lighter. The desktop version has 4 scripts, 5 stylesheets, 2 images, and 4 CSS background images. The iPhone version has 2 stylesheets and 1 image (plus the HTML document) – that’s it. If you visually compare the desktop and iPhone versions you’ll confirm that the iPhone UI is much simpler, thus explaining the big drop in number of requests.

Twitter

Twitter
reqs HTML elems
desktop 45 57 kB 873
iPad 61 55 kB 886
iPhone 3 12 kB 185

Twitter’s login page is about the same on desktop and iPad. The iPhone version is significantly smaller, especially in terms of number of HTTP requests. A visual comparison shows that the iPhone UI is much simpler than the desktop UI. Not only is the number of HTTP requests dramatically smaller, but the HTML document size is much smaller, as well. There are no data: URIs or calls to localStorage in the iPhone HTML, which keeps the HTML size so small.

Amazon

Amazon
reqs HTML elems
desktop 55 135 kB 1157
iPad 49 134 kB 1157
iPhone 16 11 kB 247

Amazon’s front page is the same on desktop and iPad. The iPhone version is significantly smaller:1/3 as many requests, an HTML document 1/10 the size, and 1/5 the number of DOM elements.

Although the iPhone version is much smaller, the number of HTTP requests could be reduced. The iPhone page resources shows it has 8 CSS background images. Running SpriteMe (yes, you can even run SpriteMe on mobile devices!) shows 3 of these could be eliminated by using sprites. And perhaps the logo, gold chest, and cart could be sprited, as well. A sprite image exists in the desktop page resources that contains the logo, cart, gold chest, and some of the gradients and arrows. Creating a similar sprite for mobile could drop the number of requests from 16 to 11.

eBay

eBay
reqs HTML elems
desktop 39 69 kB 934
iPad 39 64 kB 908
iPhone 22 24 kB 304

eBay’s front page is similar to Amazon in that the desktop and iPad versions are identical. The iPhone version, on the other hand, is 1/2 to 1/3 the size. The size of the HTML document and the number of DOM elements is below the average (which is good), but the number of HTTP requests is higher than average (not so good).

In the 16 HTTP requests in the iPhone page resources there are10 images and 4 CSS background images. Of these, 4-8 look like good candidates for spriting. Alternatively, many of these images are small and could be inlined as data: URIs.

Craigslist

Craigslist
reqs HTML elems
desktop 4 32 kB 1121
iPad 4 32 kB 1115
iPhone 4 32 kB 1115

Craigslist is the same across all three platforms: desktop, iPad, and iPhone. The page is light so performance is not an issue, but the amount of content is visually overwhelming on the iPhone’s small screen.

(Just a funny side note: I tested this from my Mom’s house in North Carolina. Craigslist redirected me to the version for Jacksonville, NC which for some reason has a domain name based on the nearby town of Onslow. I just thought it was funny that my performance testing got sent to a URL starting with “onslow”.)

Yahoo Front Page

Yahoo
reqs HTML elems
desktop 42 216 kB 1013
iPad 22 33 kB 473
iPhone 39 106 kB 1254

There are a few curiosities when comparing the desktop and iPhone versions of the Yahoo front page. The iPhone version has about the same number of HTTP requests and the HTML document is half the size, but the number of DOM elements is greater by ~20%. Although the iPhone version has fewer scripts, stylesheets, and CSS background images, it has 31 images compared to just 16 images in the desktop version. Many of these images are downloaded after the window onload event. It appears that they are being prefetched – they’re not visible on the screen so perhaps are used on subsequent pages.

The iPad version is dramatically smaller than both the desktop and iPhone versions. This is the only website where the iPad version is smaller than the iPhone version. Why would the iPad version be whittled down so much smaller than the iPhone version? The UI is very different. Perhaps there’s less prefetching happening, with less certainty of where the user may go next.

Youtube

Youtube
reqs HTML elems
desktop 29 82 kB 895
iPad 15 193 kB 337
iPhone 5 193 kB 297

There’s something interesting going on with the Youtube page. While the number of HTTP requests and DOM elements gets progressively smaller, the size of the HTML document on the iPad and iPhone is more than 2x larger than that of the desktop version. The iPhone version uses 6 data: URIs compared to the desktop version, but the biggest cause of the larger HTML document on iPhone is inlining JavaScript instead of fetching it from external scripts. Similar to the data: URI technique, this reduces the number of HTTP requests by embedding the resources inside the HTML document.

Conclusion

I hope you’ve found these mobile observations informative, but even more I hope you’ve seen that Jdrop is a useful mechanism for gathering, analyzing, and sharing mobile performance data. Take it for a spin, and if you have a tool, especially a bookmarklet, that does performance analysis contact me about integrating it with Jdrop.

7 Comments

Jdrop – JSON in the cloud

February 16, 2011 3:35 am | 9 Comments

I’m excited to announce the release of Jdrop – a JSON repository in the cloud.

The motivation for Jdrop came from my recent focus on mobile and subsequent launch of bookmarklets for mobile devices (Mobile Perf and Page Resources, followed by Docsource). I like using bookmarklets because they run on all mobile browsers that support JavaScript. Finally – some visibility into mobile performance!

My enthusiasm ebbed once I started using these bookmarklets, however. The information gathered and displayed by these bookmarklets overwhelms the tiny screens on mobile devices. I’m adamant about gathering performance data on actual mobile devices. I don’t want to use emulators or UA switching from my desktop – these techniques introduce bias in the analysis (differences in cache size, connection limits, etc.). Also, they overlook the impact of mobile carrier networks.

I realized what I wanted to do was gather the data on the mobile device, but analyze that data remotely.

Bookmarklets basically perform those two steps: gather data and display data. It was pretty simple to insert a step to save the data to Jdrop. Once the data is in the cloud, it can be accessed from anywhere especially desktops with more screen real estate. The bookmarklet’s display code is easily re-used by wrapping the data in JSON and passing it back to the display code inside Jdrop’s web page. That, in a nutshell, is Jdrop.

I integrated Jdrop with my two bookmarklets: Page Resources and Docsource. And I’m ecstatic to announce that Thomas Fuchs added Jdrop to his DOM Monster bookmarklet. When you run these bookmarklets you see a new “save to Jdrop” link.

All of these bookmarklets, plus others, are in the uber Mobile Perf bookmarklet. The full set of steps are as follows:

On your mobile device:

On your desktop or laptop:

  • sign in to Jdrop
  • click on “My JSON” to view the data you saved

If you have or want to build a bookmarklet focused on mobile performance, I encourage you to integrate it with Jdrop. The Jdrop devdocs explain the necessary changes.

Jdrop is in alpha mode. You’ll likely find bugs or think of new features – if so please add them to the list of issues. Jdrop is open source so you can see all the code. A huge shout out to James Pearce who wrote a ton of code including oauth-php and almost all of the UI.

I gave a sneak peek of Jdrop at my workshop today at Webstock. Along with Jdrop I also demoed the new Blaze Mobile Performance Tool and pcapperf. We’ve got the beginnings of a mobile performance toolkit. I’m starting to gather more data (on my mobile devices) and analyzing that data (on my desktop) thanks to Jdrop and these other tools. I look forward to working with the mobile dev community to create more tools and use those to make a faster mobile web.

9 Comments

Blaze.io launches WPT with mobile devices

February 10, 2011 10:53 am | 2 Comments

About 30 minutes ago Blaze.io launched the Blaze Mobile Performance Tool. This is incredibly exciting. I’ve only played with it a little bit but I wanted to blog about it ASAP to make people aware. Note that the service might get overrun today and tomorrow – so be patient and come back later if you have to.

Everyone reading this hopefully knows about WebPagetest. I consider WebPagetest to be one of the most important performance tools released – ever. Pat Meenan has done an amazing amount of work on it. The reason I think it’s so important is it dramatically lowers the bar for doing performance analysis. You don’t need to install a plugin or exe – all you need is a browser. It’s no coincidence that over a dozen companies including Aptimize, Strangeloop Networks, and Catchpoint have volunteered to host instances of WebPagetest in locations across the globe. Being able to get an HTTP waterfall chart, a Page Speed report, connection info, and screenshots all from one tool is powerful.

Building on the WebPagetest framework, the folks at Blaze.io cracked open some iPhones and Androids and hooked them up. This is a first version so not every feature is available, and my Android tests showed a few quirks that need to be investigated, but this is a great first step.

As shown in the screenshot above, you can see a picture of the site you tested and play a video of that site loading. Clicking on the waterfall chart shows a large version. Right now this doesn’t have a detailed breakdown (DNS, connect, wait, download, etc.). The test I did using their Android device had some resources showing a “1 ms” download time – obviously an issue to investigate. The page size seems larger than expected – I’m assuming this is uncompressed size versus the actual bytes transferred.

I’m sure they have a long todo list. I’d like to see integration with Page Speed. They have a link to view the HAR file. The provided link goes directly to Honza‘s online HAR Viewer. With a little wrangling I was able to download the HAR file to disk and upload it to my HAR to Page Speed tool to get a Page Speed report. More devices would be a huge win.

I’m doing a workshop next week at Webstock on mobile performance tools. I’m so psyched to have another one to show off. Great work Blaze.io!

2 Comments