<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>NicJ.net</title>
	<atom:link href="https://nicj.net/feed/" rel="self" type="application/rss+xml" />
	<link>https://nicj.net</link>
	<description>Home to Nic Jansma, a software developer at Akamai building high-performance websites, apps and open-source tools.</description>
	<lastBuildDate>Fri, 26 May 2023 10:57:59 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.3</generator>
	<item>
		<title>Beaconing in Practice: An Update on Reliability and the Pending Beacon API</title>
		<link>https://nicj.net/beaconing-in-practice-an-update-on-reliability-and-the-pending-beacon-api/</link>
					<comments>https://nicj.net/beaconing-in-practice-an-update-on-reliability-and-the-pending-beacon-api/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 26 May 2023 10:50:19 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2865</guid>

					<description><![CDATA[<p>Table of Contents Introduction Pending Beacon API Why Pending Beacons? Pending Beacon Experiments Methodology Reliability of XMLHttpRequest vs. sendBeacon() vs. Pending Beacon in Event Handlers onload pagehide or visibilitychange onload or pagehide or visibilitychange Conclusion Reliability of Pending Beacon &#34;now&#34; vs &#34;backgroundTimeout&#34; Reliability of Pending Beacon &#34;backgroundTimeout&#34; once vs. sendBeacon() in Event Handlers Misc Findings [&#8230;]</p>
<p>The post <a href="https://nicj.net/beaconing-in-practice-an-update-on-reliability-and-the-pending-beacon-api/" target="_blank">Beaconing in Practice: An Update on Reliability and the Pending Beacon API</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table of Contents</h2>
<ol>
<li><a href="#beaconing-in-practice-update-introduction">Introduction</a></li>
<li><a href="#beaconing-in-practice-update-pending-beacon-api">Pending Beacon API</a></li>
<li><a href="#beaconing-in-practice-update-why-pending-beacons">Why Pending Beacons?</a></li>
<li><a href="#beaconing-in-practice-update-pending-beacon-experiments">Pending Beacon Experiments</a>
<ol>
<li><a href="#beaconing-in-practice-update-methodology">Methodology</a></li>
<li><a href="#beaconing-in-practice-update-reliability-of-xmlhttprequest-vs-sendbeacon-vs-pending-beacon-in-event-handlers">Reliability of <code>XMLHttpRequest</code> vs. <code>sendBeacon()</code> vs. Pending Beacon in Event Handlers</a>
<ol>
<li><a href="#beaconing-in-practice-update-onload"><code>onload</code></a></li>
<li><a href="#beaconing-in-practice-update-pagehide-or-visibilitychange"><code>pagehide</code> or <code>visibilitychange</code></a></li>
<li><a href="#beaconing-in-practice-update-onload-or-pagehide-or-visibilitychange"><code>onload</code> or <code>pagehide</code> or <code>visibilitychange</code></a></li>
<li><a href="#beaconing-in-practice-update-conclusion">Conclusion</a></li>
</ol>
</li>
<li><a href="#beaconing-in-practice-update-reliability-of-pending-beacon-now-vs-backgroundtimeout">Reliability of Pending Beacon &quot;now&quot; vs &quot;backgroundTimeout&quot;</a></li>
<li><a href="#beaconing-in-practice-update-reliability-of-pending-beacon-backgroundtimeout-once-vs-sendbeacon-in-event-handlers">Reliability of Pending Beacon &quot;backgroundTimeout&quot; once vs. <code>sendBeacon()</code> in Event Handlers</a></li>
</ol>
</li>
<li><a href="#beaconing-in-practice-update-misc-findings">Misc Findings</a></li>
<li><a href="#beaconing-in-practice-update-follow-ups">Follow-Ups</a></li>
<li><a href="#beaconing-in-practice-update-tldr">TL;DR</a></li>
</ol>
<p><a name="beaconing-in-practice-update-introduction"></a></p>
<h2>Introduction</h2>
<p>A few years ago, I wrote an article titled <a href="https://nicj.net/beaconing-in-practice/">Beaconing In Practice</a> that covered all of the aspects of sending telemetry from your web app to a back-end server for analysis (aka &quot;beaconing&quot;).</p>
<p>While the contents of that article are still relatively fresh and accurate, there are two new aspects of beaconing that I would like to cover in this post:</p>
<ul>
<li>The new <a href="https://github.com/WICG/pending-beacon">Pending Beacon API</a></li>
<li>The measured reliability of using <code>XMLHttpRequest</code> (XHR) vs. <code>sendBeacon()</code> vs. Pending Beacon for sending data</li>
</ul>
<p><a name="beaconing-in-practice-update-pending-beacon-api"></a></p>
<h2>Pending Beacon API</h2>
<p>The <a href="https://github.com/WICG/pending-beacon">Pending Beacon API</a> is an exciting new proposal from Google Chrome engineers.</p>
<p>The goal is to provide an API for developers where they can &quot;queue&quot; data to be sent when a page is being unloaded (by the browser, automatically), rather than requiring developers to explicitly send beacons themselves in events like <code>pagehide</code> or <code>visibilitychange</code> (which don&#8217;t always fire reliably).</p>
<p>It is meant to be similar to the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon"><code>navigator.sendBeacon()</code></a> API, with a simple calling style.</p>
<p>Here&#8217;s an example of using the Pending Beacon API to send a beacon when the page is being hidden/unloaded (or a maximum of 60 seconds after &quot;now&quot;):</p>
<pre><code class="language-js">// queue a beacon for the unloading or +60s
var beacon = new window.PendingGetBeacon(
    beaconUrl,
    {
        timeout: 60000, 
        backgroundTimeout: 0 
    });</code></pre>
<p>(note the above API shape is outdated and the Pending Beacon API will utilize <code>fetch()</code> in future versions)</p>
<p>The API is still being discussed, and is actively evolving based on <a href="https://github.com/WICG/pending-beacon/issues">community and browser vendor feedback</a>.</p>
<p>If you want to experiment with the Pending Beacon in Chrome today, you can register for an <a href="https://developer.chrome.com/origintrials/#/view_trial/1581889369113886721">Origin Trial</a> for Chrome 107-115.  Though, again, note that the current API shape (with the <code>window.PendingGetBeacon</code> and <code>window.PendingPostBeacon</code> interfaces) is evolving towards being <a href="https://github.com/WICG/pending-beacon#javascript-api">an option of <code>fetch()</code></a> instead.</p>
<p><a name="beaconing-in-practice-update-why-pending-beacons"></a></p>
<h2>Why Pending Beacons?</h2>
<p>One of the challenges highlighted in the <a href="https://nicj.net/beaconing-in-practice/">Beaconing In Practice</a> article is how to reliably send data once it&#8217;s been gathered in a web app.</p>
<p>Developers frequently use events such as <code>beforeunload</code>/<code>unload</code> or <code>pagehide</code>/<code>visibilitychange</code> as a trigger for beaconing their data, but these events are <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability">not reliably fired</a> on all platforms.  If the events don&#8217;t fire, the beacons don&#8217;t get sent.</p>
<p>For example, if you want to gather all of your data and only send it once as the page is unloading, registering for all 4 of those events will only give you <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-unload">~82.9% reliability</a> in ensuring the data arrives at your server, even when using the <a href="https://nicj.net/beaconing-in-practice/#beaconing-sendbeacon-beacon"><code>sendBeacon()</code> API</a>.</p>
<p>So, wouldn&#8217;t it be lovely if developers had a more reliable way of &quot;queuing&quot; data to be sent, and have the browser automagically send it once the page starts to unload?  That&#8217;s where the Pending Beacon API comes in.</p>
<p>The Pending Beacon API gives developers a way to build a &quot;pending&quot; beacon.  That pending beacon can then be mutated over time, or later discarded.  The browser will then handle sending it (in its latest state) when the page is being hidden or unloading, so developers no longer need to listen to the <code>beforeunload</code>/<code>unload</code>/<code>pagehide</code>/<code>visibilitychange</code> events.</p>
<p>Ideally, Pending Beacon will be a mechanism that can replace usage of <code>sendBeacon()</code> in browsers that support it, giving more reliable delivery of beacon data and better developer ergonomics (by not having to listen for, and send data during, unload-ish events).</p>
<p><a name="beaconing-in-practice-update-pending-beacon-experiments"></a></p>
<h2>Pending Beacon Experiments</h2>
<p>Given those goals, I was curious to see how reliable Pending Beacon would be compared to existing APIs like <code>XMLHttpRequest</code> (XHRs) or the <code>sendBeacon()</code> API.  I performed three experiments comparing how reliably data arrived after using one of those APIs in different scenarios.</p>
<p>Let&#8217;s explore three questions:</p>
<ol>
<li>Can we swap PendingBeacon in for usage of XHR and/or <code>sendBeacon()</code> in unload event handlers?</li>
<li>How reliable is asking PendingBeacon to send data &quot;now&quot; vs with a <code>backgroundTimeout</code>?</li>
<li>How reliable is queuing PendingBeacon data to be sent at page unload vs. listening to event handlers and using <code>sendBeacon()</code> in them?</li>
</ol>
<p><a name="beaconing-in-practice-update-methodology"></a></p>
<h3>Methodology</h3>
<p>Over the course of a month, on a site that I control (with approx 2M page views), I ran an experiment gathering data from browsers using the following three APIs:</p>
<ul>
<li><a href="https://nicj.net/beaconing-in-practice/#beaconing-xhr-beacon"><code>XMLHttpRequest</code></a></li>
<li><a href="https://nicj.net/beaconing-in-practice/#beaconing-sendbeacon-beacon">sendBeacon()</a></li>
<li><a href="https://github.com/WICG/pending-beacon/issues">Pending Beacon API</a></li>
</ul>
<p>All of these APIs sent a small GET request back to the same domain / origin.</p>
<p>For all of the data below, I am only looking at Chrome and Chrome Mobile v107-115 (per the <code>User-Agent</code> string) with support for <code>window.PendingGetBeacon</code>, to ensure a level playing field.  The data in <a href="https://nicj.net/beaconing-in-practice/">Beaconing In Practice</a> looks at reliability across all User-Agents, but the experiments below will focus solely on browsers supporting the Pending Beacon API.</p>
<p>Note that all of these tests were done with the <a href="https://github.com/mingyc/pending-beacon/blob/77291c0d9a98dbe35244df663010ba1f69558451/README.md#javascript-api"><code>PendingGetBeacon</code></a> interface, before the current proposal to have this be a <code>fetch()</code> option.  I&#8217;m unsure how the most recent proposal will affect these results, but I will re-do the test once that <code>fetch()</code> update is available.</p>
<p><a name="beaconing-in-practice-update-reliability-of-xmlhttprequest-vs-sendbeacon-vs-pending-beacon-in-event-handlers"></a></p>
<h3>Reliability of <code>XMLHttpRequest</code> vs. <code>sendBeacon()</code> vs. Pending Beacon in Event Handlers</h3>
<p>The first question I wanted to know was: Can Pending Beacon be easily swapped into existing analytics libraries (like <a href="https://github.com/akamai/boomerang">boomerang.js</a>) to replace <code>sendBeacon()</code> and <code>XMLHttpRequest</code> (XHR) usage, and retain the same (or better) reliability (beacon received rate)?</p>
<p>In boomerang for example, we listen to <code>beforeunload</code> and <code>pagehide</code> to send our final &quot;unload&quot; beacon.  Can we just use Pending Beacon instead?</p>
<p>For this experiment, I segmented visitors into 3 equally-distributed A/B/C groups (given Pending Beacon API support):</p>
<ul>
<li>A: Force <code>PendingGetBeacon</code> (with <code>{ timeout: 0, backgroundTimeout: -1 }</code> so it was sent immediately)</li>
<li>B: Force <code>navigator.sendBeacon()</code></li>
<li>C: Force <code>XMLHttpRequest</code></li>
</ul>
<p>Each group then attempted to send 6 beacons per page load:</p>
<ol>
<li>Immediately in the <code>&lt;head&gt;</code> of the HTML</li>
<li>In the page <code>onload</code> event</li>
<li>In the page <code>beforeunload</code> event</li>
<li>In the page <code>unload</code> event</li>
<li>In the page <code>pagehide</code> event</li>
<li>In the page <code>visibilitychange</code> event (for <code>hidden</code>)</li>
</ol>
<p>By seeing how often each of those beacons arrived, we can consider the reliability of each API, during different page lifecycle events.  I&#8217;m only showing data for page loads where the first step (sending data immediately in the <code>&lt;head&gt;</code>) occurred.</p>
<p>Let&#8217;s break the experimental data down by event first:</p>
<p><a name="beaconing-in-practice-update-onload"></a></p>
<h4><code>onload</code></h4>
<p>The <code>onload</code> event is probably the most common event for an analytics library to fire a beacon.  Marketing and performance analytics tools will often send their main payload at that point in time.</p>
<p>Based on our experimentation, when firing a beacon <strong>just</strong> at the onload event, <code>sendBeacon()</code> seems slightly more reliable than XHR, which is slightly more reliable than <code>PendingGetBeacon</code>.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/onload.svg" alt="reliability at onload" /></p>
<p><code>sendBeacon()</code> being more reliable than XHR is expected &#8212; the whole point of <code>sendBeacon()</code> is to allow the browser to send data asynchronously of the page, in case it unloads after the beacon is queued up.</p>
<p>However, I&#8217;m surprised that <code>PendingGetBeacon</code> appears to be the least reliable (by about 1% less than XHR), at least from my experiments.</p>
<p>Broken down by Desktop and Mobile:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/onload-desktop.svg" alt="reliability at onload - desktop" /></p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/onload-mobile.svg" alt="reliability at onload - mobile" /></p>
<p>Desktop is able to deliver beacons more reliably across all 3 APIs than mobile.  On mobile, <code>PendingGetBeacon</code> is about 2.8% less reliable than <code>sendBeacon()</code>.</p>
<p><strong>Note</strong>: that the above results are for <strong>only</strong> measuring a beacon sent immediately during the page&#8217;s <code>onload</code> event, without accounting for any abandons that happen prior to <code>onload</code>.  That is why these numbers are so low &#8212; if a user abandoned the page prior to the <code>onload</code> event, they would not be counted in the above chart.  See the additional breakdowns below for how these numbers change if you use the <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-avoiding-abandons">suggested abandonment strategy</a> of listening to <code>onload</code>, <code>pagehide</code> and <code>visibilitychange</code>.</p>
<p>I was hoping the Pending Beacon API would be at-least-or-better reliable than <code>sendBeacon()</code>, so I think there&#8217;s something to investigate here.</p>
<p><a name="beaconing-in-practice-update-pagehide-or-visibilitychange"></a></p>
<h4><code>pagehide</code> or <code>visibilitychange</code></h4>
<p>If the intent is to measure events that occur in the page beyond the <code>onload</code> event, i.e. additional performance or reliability metrics (such as Core Web Vitals or JavaScript errors), tools can send a beacon during one of the page&#8217;s <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-unload">unload events</a>, such as <code>beforeunload</code>, <code>unload</code>, <code>pagehide</code> or <code>visibilitychange</code>.</p>
<p>Our <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-avoiding-abandons">recommended strategy</a> is to listen to just <code>pagehide</code> and <code>visibilitychange</code> (for hidden), and not listen to the <code>beforeunload</code> or <code>unload</code> events (which are less reliable and can break BFCache navigations).</p>
<p>So let&#8217;s look at the result of sending a beacon immediately during a <code>pagehide</code> <strong>or</strong> <code>visibilitychange</code> event (if a beacon was received for <strong>either</strong> event):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/pagehide-visibilitychange.svg" alt="reliability at pagehide or visibilitychange" /></p>
<p>This is showing that <code>sendBeacon()</code> is still reigning supreme for reliability (95.8%), with <code>PendingGetBeacon</code> slightly behind (89.1%) and XHR trailing that (84.9%).</p>
<p>However, when we break it down by Desktop:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/pagehide-visibilitychange-desktop.svg" alt="reliability at pagehide or visibilitychange - desktop" /></p>
<p><code>PendingGetBeacon</code> is nearly as reliable as <code>sendBeacon()</code>, with <code>XHR</code> trailing behind, while on Mobile:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/pagehide-visibilitychange-mobile.svg" alt="reliability at pagehide or visibilitychange - mobile" /></p>
<p>There appears to be a huge drop-off in reliability for <code>PendingGetBeacon</code> on Mobile vs. Desktop.</p>
<p>Possibly a bug with Pending Beacon in Chrome&#8217;s initial implementation here?  This data would give me pause in swapping to Pending Beacon right now.</p>
<p><a name="beaconing-in-practice-update-onload-or-pagehide-or-visibilitychange"></a></p>
<h4><code>onload</code> or <code>pagehide</code> or <code>visibilitychange</code></h4>
<p>Finally, let&#8217;s combine the above three events per the suggested <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-avoiding-abandons">abandonment strategy</a>, and see how reliable each API is if we&#8217;re listening for all 3 events (and sending data <strong>once</strong> in any of them).</p>
<p>Of course, this increases the reliability of receiving beacons to the maximum possible, with <code>sendBeacon()</code> able to get a beacon to the server 98% of the time:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/onload-pagehide-visibilitychange.svg" alt="reliability at onload or pagehide or visibilitychange" /></p>
<p>Broken down by Desktop vs. Mobile, we see that Desktop is has an extremely high rate of receiving beacons:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/onload-pagehide-visibilitychange-desktop.svg" alt="reliability at onload or pagehide or visibilitychange - desktop" /></p>
<p>While Mobile continues to show a possible issue with <code>PendingGetBeacon</code> vs. <code>sendBeacon()</code> (a 7.7% drop-off)!</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/onload-pagehide-visibilitychange-mobile.svg" alt="reliability at onload or pagehide or visibilitychange - mobile" /></p>
<p><a name="beaconing-in-practice-update-conclusion"></a></p>
<h4>Conclusion</h4>
<p>From this experiment at least, it appears <code>sendBeacon()</code> continues to be the most reliable way of sending beacon data.</p>
<p>If sending data during <code>onload</code>, <code>sendBeacon()</code> is <em>slightly</em> more reliable than <code>PendingGetBeacon</code>.</p>
<p>However, there appears to be a bug with <code>PendingGetBeacon</code> during a page-unloading scenario like <code>pagehide</code> or <code>visibilitychange</code>, particularly on Mobile.  If the Chrome engineers can figure out a way to increase the reliability there, I would expect the Pending Beacon API to be equivalent to using <code>sendBeacon()</code> (which is our preferred mechanism today).</p>
<p><strong>NOTE</strong>: I measured the reliability of sending beacons during <code>beforeunload</code> and <code>unload</code> as well, but since those events are deprecated / not-recommended / unreliable / break BFCache events, I&#8217;ll skip those results in this post.</p>
<p><a name="beaconing-in-practice-update-reliability-of-pending-beacon-now-vs-backgroundtimeout"></a></p>
<h3>Reliability of Pending Beacon &quot;now&quot; vs &quot;backgroundTimeout&quot;</h3>
<p>The next experiment I ran was to determine if the <code>backgroundTimeout</code> functionality of the Pending Beacon API was reliable to use.</p>
<p>Here&#8217;s the <a href="https://github.com/mingyc/pending-beacon/blob/77291c0d9a98dbe35244df663010ba1f69558451/README.md#javascript-api">description of the parameter</a> (which has <a href="https://github.com/WICG/pending-beacon#javascript-api">changed slightly</a> with the <code>fetch()</code>-based proposal, but I would guess would operate similarly):</p>
<ul>
<li><code>backgroundTimeout</code>: A mutable Number property specifying a timeout in milliseconds whether the timer starts after the page enters the next hidden visibility state. If setting the value &gt;= 0, after the timeout expires, the beacon will be queued for sending by the browser, regardless of whether or not the page has been discarded yet. If the value &lt; 0, it is equivalent to no timeout and the beacon will only be sent by the browser on page discarded or on page evicted from BFCache. The timeout will be reset if the page enters visible state again before the timeout expires. Note that the beacon is not guaranteed to be sent at exactly this many milliseconds after hidden, because the browser has freedom to bundle/batch multiple beacons, and the browser might send out earlier than specified value (see Privacy Considerations). Defaults to -1.</li>
</ul>
<p>In other words, ask the browser to send a beacon after <code>backgroundTimeout</code> milliseconds of being hidden.</p>
<p>This can be very useful as an <strong>alternative</strong> to listening to the <code>pagehide</code> / <code>visibilitychange</code> events for beaconing your &quot;last&quot; bits of data.  If you regularly update your Pending Beacon, you may not need to listen to those event at all.</p>
<p>But can we trust the browser to still send our Pending Beacon, after we&#8217;ve queued it up?</p>
<p>For this experiment, I segmented visitors into 2 equally-distributed A/B groups (given Pending Beacon API support):</p>
<ul>
<li>A: Force <code>PendingGetBeacon</code> to send a beacon now (with <code>{ timeout: 0, backgroundTimeout: -1 }</code></li>
<li>B: Force <code>PendingGetBeacon</code> to send a beacon after 60s or when the page is hidden (with <code>{ timeout: 60000, backgroundTimeout: 0 }</code></li>
</ul>
<p>We are considering doing something similar to group B for <a href="https://github.com/akamai/boomerang">boomerang.js</a>, i.e. send all beacons within 60 seconds of the page load (so the data is still &quot;real-time fresh&quot; in dashboards), and asking the browser to send the data immediately if the user navigates away or closes the browser before then.</p>
<p>Let&#8217;s look at the results of using <code>PendingGetBeacon</code> to send a beacon &quot;now&quot; vs. &quot;when the page is hidden/unloads&quot;:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/pendingbeacon-now-vs-60s-unload.svg" alt="pendingbeacon now vs 60s/unload" /></p>
<p>Given a baseline of 100% meaning we received a &quot;now&quot; <code>PendingGetBeacon</code>, we&#8217;re seeing the 60s timeout + @hidden beacon about 98.5% of the time across Desktop and Mobile.</p>
<p>Desktop is slightly more reliable (99.7%) vs. Mobile (96.4%).</p>
<p>I think this is a great result, confirming the value-add of <code>PendingGetBeacon</code>.  Instead of having to add event listeners for <code>pagehide</code> <code>visibilitychange</code> and a 60s <code>setTimeout()</code>, the browser delivered the beacon very reliably on its own!</p>
<p>Remember, listening to all 4 unload-ish events (<code>beforeunload</code>, <code>unload</code>, <code>pagehide</code>, <code>visibilitychange</code>) and sending a beacon in those events only resulted in ~82.9% reliability!</p>
<p>And in the meantime, the pending beacon could be manipulated to add/remove additional data up until the page unloads.</p>
<p><a name="beaconing-in-practice-update-reliability-of-pending-beacon-backgroundtimeout-once-vs-sendbeacon-in-event-handlers"></a></p>
<h3>Reliability of Pending Beacon &quot;backgroundTimeout&quot; once vs. <code>sendBeacon()</code> in Event Handlers</h3>
<p>Given that the last experiment showed that Pending Beacon with <code>backgroundTimeout</code> was very reliable in sending beacons at page unload, what is the difference between using <code>PendingGetBeacon</code> with <code>backgroundTimeout: 0</code> vs. listening for <code>pagehide</code> and <code>visibilitychange</code> and sending a beacon with <code>sendBeacon()</code>?</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2023/05/pendingbeacon-vs-sendbeacon-for-unload.svg" alt="pendingbeacon vs sendBeacon for unload" /></p>
<p>Great news!  Not only is the <code>PendingGetBeacon</code> more ergonomic (not having to listen for <code>pagehide</code> and <code>visibilitychange</code> events), it&#8217;s more reliably sending data when the page is unloading.</p>
<p>One interesting result I see here, is that the <code>PendingGetBeacon</code> reliability with <code>backgroundTimeout: 0</code> was <strong>more</strong> reliable than listening to <code>pagehide</code> and <code>visibilitychange</code> and using <code>PendingGetBeacon</code> (now) in those events directly.  This is likely due to the fact that <code>pagehide</code> and <code>visibilitychange</code> aren&#8217;t 100% reliable in the first place, but I would hope for it to be as-close-to <code>sendBeacon()</code> reliable as possible.</p>
<p><a name="beaconing-in-practice-update-misc-findings"></a></p>
<h2>Misc Findings</h2>
<ul>
<li>There&#8217;s currently no way to debug Pending Beacon in Chrome Developer Tools &#8212; outgoing beacons are not visible in the Network tab.  This makes it very hard to debug or verify that the feature is working.  When debugging issues with boomerang.js I am constantly reviewing the outgoing beacon, so not having visibility into Dev Tools would be a huge hinderance.  There&#8217;s an <a href="https://github.com/WICG/pending-beacon/issues/67">Github issue</a> tracking this.</li>
<li>While reviewing the data, I found some Samsung Internet browser data in the data-set, indicating that it supported <code>window.PendingGetBeacon</code>.
<ul>
<li>However, I only received data for <code>XMLHttpRequest</code> and <code>sendBeacon()</code> beacons.</li>
<li>Does this mean Samsung Internet browser (which is Chromium-based) is registering <code>window.PendingGetBeacon</code> but not fully implementing the beacon sending?  I will need to investigate more.</li>
</ul>
</li>
</ul>
<p><a name="beaconing-in-practice-update-follow-ups"></a></p>
<h2>Follow-Ups</h2>
<p>First, I want to say that my experiments and conclusions <strong>probably have some flaws</strong>.  I&#8217;ve reviewed and re-reviewed my methodology and queries several times, but I am a human (I think!) and make mistakes.  I&#8217;m hoping others can review this data.</p>
<p>Given that, some follow-ups I plan on doing based on the above findings:</p>
<ul>
<li>Re-do this analysis once the interface is <a href="https://github.com/WICG/pending-beacon#pendingbeacon-based-api">changed to be <code>Fetch</code>-based</a></li>
<li>Review the reliability data with the Chrome engineers to see if we should file bugs for any of the drops in reliability vs. <code>sendBeacon()</code>, in particular:
<ul>
<li>During <code>onload</code> Pending Beacon (with <code>timeout:0</code>) is about 1.2% less reliable than <code>sendBeacon()</code></li>
<li>During <code>pagehide</code> and <code>visibilitychange</code> Pending Beacon (with <code>timeout:0</code>), on Mobile, is about 17.9% less reliable than <code>sendBeacon()</code></li>
</ul>
</li>
<li>Review why the Samsung Internet browser is registering the <code>window.PendingGetBeacon</code> interface but not sending any beacons (was there an error I wasn&#8217;t catching?)</li>
</ul>
<p><a name="beaconing-in-practice-update-tldr"></a></p>
<h2>TL;DR</h2>
<ul>
<li>I&#8217;m really excited for the Pending Beacon API.  I think it&#8217;s going to developers better ergonomics for sending data, and a more reliable way to send beacons at the end of the page lifetime</li>
<li>The new Pending Beacon API is in active development and going through a feedback and Origin Trial cycle</li>
<li>There may be some small reliability issues vs. <code>sendBeacon()</code> that should be investigated before widespread adoption</li>
<li>The <code>navigator.sendBeacon</code> API still seems to be the most reliable mechanism for sending beacons, if you&#8217;re queuing up data to be sent in <code>pagehide</code> or <code>visibilitychange</code> events</li>
</ul><p>The post <a href="https://nicj.net/beaconing-in-practice-an-update-on-reliability-and-the-pending-beacon-api/" target="_blank">Beaconing in Practice: An Update on Reliability and the Pending Beacon API</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/beaconing-in-practice-an-update-on-reliability-and-the-pending-beacon-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Modern Metrics</title>
		<link>https://nicj.net/modern-metrics/</link>
					<comments>https://nicj.net/modern-metrics/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 30 Nov 2022 17:51:55 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2854</guid>

					<description><![CDATA[<p>At performance.now() 2022, I gave a talk titled &#34;Modern Metrics (2022)&#34;. Here&#8217;s the description: What is a &#8220;modern&#8221; metric anyway? An exploration on how to measure and evaluate popular (and experimental) web performance metrics, and how they affect user happiness and business goals. We&#8217;ll talk about how data can be biased, and how best to [&#8230;]</p>
<p>The post <a href="https://nicj.net/modern-metrics/" target="_blank">Modern Metrics</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>At <a href="https://perfnow.nl/speakers">performance.now() 2022</a>, I gave a talk titled &quot;Modern Metrics (2022)&quot;.</p>
<p><a href="https://youtu.be/IDM32xJmFjo"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2022/11/modern-metrics-2022-youtube-840x449.png" alt="Modern Metrics (2022)" width="840" height="472" class="aligncenter size-large" /></a></p>
<p>Here&#8217;s the description:</p>
<div style="margin-left: 20px;"><i>What is a &#8220;modern&#8221; metric anyway? An exploration on how to measure and evaluate popular (and experimental) web performance metrics, and how they affect user happiness and business goals.</p>
<p>We&#8217;ll talk about how data can be biased, and how best to interpret performance data given those biases. We&#8217;ll look at a broad set of RUM data we&#8217;ve captured to see how the Core Web Vitals correlate (or not) to other performance and business metrics. Finally, we&#8217;ll share a new way that others can research modern metrics and RUM data.<br />
</i></div>
<p>At the conference, we also announced a new project called the <a href="https://rumarchive.com">RUM Archive</a>.  Inspired by other projects like <a href="https://archive.org">archive.org</a> and <a href="https://httparchive.org">httparchive.org</a>, we want to make RUM data available for public research.  We&#8217;re regularly exporting aggregated RUM data from <a href="https://akamai.com">Akamai mPulse</a> to start!</p>
<p><a href="https://rumarchive.com"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2022/11/rum-archive-logo-color-268x400.png" alt="RUM Archive" width="268" height="400" class="aligncenter size-medium wp-image-2857" srcset="https://o.nicj.net/wp-content/uploads/2022/11/rum-archive-logo-color-268x400.png 268w, https://o.nicj.net/wp-content/uploads/2022/11/rum-archive-logo-color-687x1024.png 687w, https://o.nicj.net/wp-content/uploads/2022/11/rum-archive-logo-color-768x1145.png 768w, https://o.nicj.net/wp-content/uploads/2022/11/rum-archive-logo-color.png 798w" sizes="(max-width: 268px) 100vw, 268px" /></a></p>
<p>I&#8217;ll blog more about the RUM Archive later!</p>
<p>You can watch the presentation on <a href="https://youtu.be/IDM32xJmFjo">YouTube</a> or <a href="https://docs.google.com/presentation/d/1EMv1BRC8DYtu2LBDJ0b9-mOsixBG8WmE9gKNS5fjzQk/edit?usp=sharing">catch the slides</a>.</p><p>The post <a href="https://nicj.net/modern-metrics/" target="_blank">Modern Metrics</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/modern-metrics/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>JS Self-Profiling API In Practice</title>
		<link>https://nicj.net/js-self-profiling-api-in-practice/</link>
					<comments>https://nicj.net/js-self-profiling-api-in-practice/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 31 Dec 2021 19:16:36 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2838</guid>

					<description><![CDATA[<p>Table of Contents The JS Self-Profiling API What is Sampled Profiling? Downsides to Sampled Profiling API Document Policy API Shape Sample Interval Buffer Who to Profile When to Profile Specific Operations User Interactions Page Load Overhead Anatomy of a Profile Beaconing Size Compression Analyzing Profiles Individual Profiles Bulk Profile Analysis Gotchas Minified JavaScript Named Functions [&#8230;]</p>
<p>The post <a href="https://nicj.net/js-self-profiling-api-in-practice/" target="_blank">JS Self-Profiling API In Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table of Contents</h2>
<ul>
<li><a href="#js-self-profiling-the-api">The JS Self-Profiling API</a></li>
<li><a href="#js-self-profiling-what-is-sampled-profiling">What is Sampled Profiling?</a>
<ul>
<li><a href="#js-self-profiling-downsides-to-sampled-profiling">Downsides to Sampled Profiling</a></li>
</ul>
</li>
<li><a href="#js-self-profiling-api">API</a>
<ul>
<li><a href="#js-self-profiling-document-policy">Document Policy</a></li>
<li><a href="#js-self-profiling-api-shape">API Shape</a></li>
<li><a href="#js-self-profiling-sample-interval">Sample Interval</a></li>
<li><a href="#js-self-profiling-buffer">Buffer</a></li>
</ul>
</li>
<li><a href="#js-self-profiling-who-to-profile">Who to Profile</a></li>
<li><a href="#js-self-profiling-when-to-profile">When to Profile</a>
<ul>
<li><a href="#js-self-profiling-specific-operations">Specific Operations</a></li>
<li><a href="#js-self-profiling-user-interactions">User Interactions</a></li>
<li><a href="#js-self-profiling-page-load">Page Load</a></li>
</ul>
</li>
<li><a href="#js-self-profiling-overhead">Overhead</a></li>
<li><a href="#js-self-profiling-anatomy-of-a-profile">Anatomy of a Profile</a></li>
<li><a href="#js-self-profiling-beaconing">Beaconing</a>
<ul>
<li><a href="#js-self-profiling-size">Size</a></li>
<li><a href="#js-self-profiling-compression">Compression</a></li>
</ul>
</li>
<li><a href="#js-self-profiling-analyzing-profiles">Analyzing Profiles</a>
<ul>
<li><a href="#js-self-profiling-individual-profiles">Individual Profiles</a></li>
<li><a href="#js-self-profiling-bulk-analysis">Bulk Profile Analysis</a></li>
</ul>
</li>
<li><a href="#js-self-profiling-gotchas">Gotchas</a>
<ul>
<li><a href="#js-self-profiling-minified-javascript">Minified JavaScript</a></li>
<li><a href="#js-self-profiling-named-functions">Named Functions</a></li>
<li><a href="#js-self-profiling-cross-origin-scripts">Cross-Origin Scripts</a></li>
<li><a href="#js-self-profiling-sending-from-unload-events">Sending from Unload Events</a></li>
<li><a href="#js-self-profiling-non-javascript-browser-work">Non-JavaScript Browser Work</a></li>
</ul>
</li>
<li><a href="#js-self-profiling-conclusion">Conclusion</a></li>
</ul>
<p><a name="js-self-profiling-the-api"></a></p>
<h2>The JS Self-Profiling API</h2>
<p>The <a href="https://wicg.github.io/js-self-profiling/">JavaScript Self-Profiling API</a> allows you to take performance <em>profiles</em> of your JavaScript web application in the <em>real world</em> from <em>real customers</em> on <em>real devices</em>.  In other words, you&#8217;re no longer limited to only profiling your application on your personal machines (locally) from browser developer tools!  Profiling your application is a great way to get insight into its performance.  A profile will help you see what is running over time (its &quot;stack&quot;), and can identify &quot;hot spots&quot; in your code.</p>
<p>You may be familiar with profiling JavaScript if you&#8217;ve ever used a browser&#8217;s developer tools.  For example, in Chrome&#8217;s Developer Tools in the <em>Performance</em> tab, you can record a profile.  This profile provides (among other things) a view of what&#8217;s running in the application over time.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/developer-tools-profiling.png" alt="browser developer tools" /></p>
<p>In fact, this API actually reminds me a bit more of the simplicity of the old <em>JavaScript Profiler</em> tab, which is still available in Chrome, but <a href="https://developers.google.com/web/updates/2016/12/devtools-javascript-cpu-profile-migration">hidden in favor</a> of the new <em>Performance</em> tab.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/developer-tools-javascript-profiler.png" alt="Chrome&#039;s Developer Tools&#039; old JavaScript Profiler tab" /></p>
<p>The JS Self-Profiling API is a <strong>new API</strong>, currently only available in Chrome versions 94+ (on Desktop and Android).  It provides a <strong>sampling profiler</strong> that you can enable, from JavaScript, for any of your visitors.</p>
<p>The API is a currently a <a href="https://www.w3.org/groups/cg/wicg">WICG</a> draft, and is being evaluated by browsers before possibly being adopted by a W3C Working Group such as the <a href="https://www.w3.org/webperf/">Web Performance WG</a>.</p>
<p><a name="js-self-profiling-what-is-sampled-profiling"></a></p>
<h2>What is Sampled Profiling?</h2>
<p>There are two common types of performance profilers in use today:</p>
<ol>
<li><strong>Instrumented</strong> (or &quot;structured&quot; or &quot;tracing&quot;) Profilers, in which an application is invasively instrumented (modified) to add hooks at <strong>every function entry and exit</strong>, so the exact time spent in each function is known</li>
<li><strong>Sampled</strong> Profilers, which <strong>temporarily pause execution</strong> of the application at a fixed frequency to note (&quot;sample&quot;) what is running on the call stack at that time</li>
</ol>
<p>The JS Self-Profiling API starts a <strong>sampled profiler</strong> in the browser.  This is the same profiler that records traces in browser developer tools.</p>
<p>The &quot;sampling&quot; part of the profiler means that the browser is basically taking a snapshot at regular intervals, checking what&#8217;s currently running on the stack.  This is a lightweight way of tracing an application&#8217;s performance, as long as the sampling interval isn&#8217;t too frequent.  Each regularly-spaced sampling interrupt quickly inspects the running stack and notes it for later.  Over time, these sampled stacks can give you a <em>indication</em> of what was commonly running during the trace, though sometimes samples can also mislead (see <a href="#js-self-profiling-downsides-to-sampled-profiling"><em>Downsides</em></a> below).</p>
<p>Consider a diagram of the function stacks running in an application over time. A sampling profiler will attempt to inspect the currently-running stack at regular intervals (the vertical red lines), and report on what it sees:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks.svg" alt="sampled profiler stacks" /></p>
<p>The other common method of profiling an application, often called a <em>instrumented</em> or <em>tracing</em> or <em>structured</em> profiler, relies on invasively modifying the application so that the profiler knows exactly when every function is called, begins and ends.  This invasive measurement has a lot of overhead, and can slow down the application being measured.  However, it provides an exact measurement of the relative time being spent in every function, as well as exact function call-counts.  Due to the overhead that comes from invasively hooking every function entry and exit, the app will be slowed down (spending time in instrumentation).</p>
<p>Instrumented profiling has a time and place, but it&#8217;s generally not performed in the &quot;real world&quot; on your visitors &#8212; as it will slow down their experience.  This is why sampled profiling is more popular on the web, as it has a smaller performance impact on the application being sampled.</p>
<p>With this API, you can choose the sampling frequency.  In my testing, Chrome currently doesn&#8217;t let you sample any more frequently than once every 16ms (Windows) or 10ms (Mac / Android).</p>
<p>If you want to learn more about the different types of profiling, I highly recommend viewing <a href="https://www.igvita.com/slides/2012/structural-and-sampling-javascript-profiling-in-chrome.pdf">Ilya Grigorik&#8217;s <em>Structural and Sampling JavaScript Profiling<br />
in Google Chrome</em></a> slides from 2012.  It goes into further details about when to use the two types of profilers and how they complement each other.</p>
<p>Note: further in this document I may use the term &quot;traces&quot; to describe the data from a Sampled Profiler, not from a Tracing Profiler.</p>
<p><a name="js-self-profiling-downsides-to-sampled-profiling"></a></p>
<h3>Downsides to Sampled Profiling</h3>
<p>Unlike Instrumented Profilers that trace each function&#8217;s entry and exit (which increases the measurement overhead significantly), Sampled Profilers simply poll the stack at regular intervals to determine what&#8217;s running.</p>
<p>This type of lightweight profiling is great for reducing overhead, but it can lead to some situations where the data it captures is <em>misleading</em> at best, or <em>wrong</em> at worst.</p>
<p>Let&#8217;s look at the previous call stack and the 8 samples it took, pretending the samples were 10ms apart:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks.svg" alt="sampled profiler stacks" /></p>
<p>Since the Sampled Profiler doesn&#8217;t know any better, it <em>guesses</em> that any hit during its regular sampling interval was running for that <em>entire</em> interval, i.e. 10ms.</p>
<p>If a Sampled Profiler was examining that stack at those regular intervals (the vertical red lines), it would report the overall time spent in these stacks as:</p>
<ul>
<li>A-&gt;B-&gt;C: 1 hit (10ms)</li>
<li>A-&gt;B: 2 hits (20ms)</li>
<li>A: 1 hit (10ms)</li>
<li>D: 2 hits (20ms)</li>
<li>idle: 2 (20ms)</li>
</ul>
<p>While this is a <em>decent</em> representation of what was running over those 80ms, it&#8217;s not entirely accurate:</p>
<ul>
<li>A-&gt;B-&gt;C is over-reported by 6ms</li>
<li>A-&gt;B is over-reported by 12ms</li>
<li>A is under-reported by 8ms</li>
<li>D is over-reported by 8ms</li>
<li>D-&gt;D-&gt;D is missing and under-reported by 4ms</li>
<li>idle is under-reported by 15ms</li>
</ul>
<p>This mis-reporting can get worse in a few canonical cases.  Most application stacks won&#8217;t be this simple, so it&#8217;s unlikely you&#8217;ll see this happen exactly as-is in the real world, but it&#8217;s useful to understand.</p>
<p>First, consider a case where your sampled profiler is taking samples every 10ms, and your application has a task that executes for 2ms approximately every 16ms.  Will the Sampled Profiler even notice it was running?</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks-bad-case-1.svg" alt="sampled profiler stacks - bad case" /></p>
<p>Maybe, or maybe not &#8212; depends on when the sampling happens, and the frequency/runtime of the function.  In this case, the function is executing for 12.5% of the runtime, but may get un-reported.</p>
<p>Taken to the extreme, this same function may have the exact same interval frequency as the profiler, but only execute for that 1ms that was being sampled:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks-bad-case-3.svg" alt="sampled profiler stacks - bad case" /></p>
<p>In this case, the function may be only running for 12.5% of the runtime, but may get reported as running 100% of the time.</p>
<p>To the other extreme, you could have a function which runs at 10ms intervals but only for 8ms:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks-bad-case-2.svg" alt="sampled profiler stacks - bad case" /></p>
<p>Depending on when the Sampling Profiler hits, it may not get reported at all, even though it&#8217;s executing for 80% of the time.</p>
<p>All of these are &quot;canonically bad&quot; examples, but you could see how some types of program behavior may get mis-represented by a Sampled Profiler.  Something to keep in mind as you&#8217;re looking at traces!</p>
<p><a name="js-self-profiling-api"></a></p>
<h2>API</h2>
<p><a name="js-self-profiling-document-policy"></a></p>
<h3>Document Policy</h3>
<p>In order to allow the JavaScript Self-Profiling API to be called, there needs to be a <a href="https://w3c.github.io/webappsec-permissions-policy/document-policy.html">Document Policy</a> on the HTML page, called <code>js-profiling</code>.  This is usually configured via a HTTP response header called <code>Document-Policy</code>, or via a <code>&lt;iframe policy=&quot;&quot;&gt;</code> attribute.</p>
<p>A simple example of enabling the API would be this HTTP response header (for the HTML page):</p>
<pre><code>Document-Policy: js-profiling</code></pre>
<p>Once enabled, <strong>any</strong> JavaScript on the page can start profiling, including third-party scripts!</p>
<p><a name="js-self-profiling-api-shape"></a></p>
<h3>API Shape</h3>
<p>The JS Self-Profiling API exposes a new <a href="https://wicg.github.io/js-self-profiling/#the-profiler-interface"><code>Profiler</code></a> object (in browsers that support it).</p>
<p>Creating the object starts the Sampled Profiler, and you can later call <code>.stop()</code> on the object to stop profiling and get the trace back (via a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a>).</p>
<pre><code class="language-js">if (typeof window.Profiler === &quot;function&quot;) {
  var profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });

  // do work
  profiler.stop().then(function(trace) {
    sendProfile(trace);
  });
}</code></pre>
<p>Or if you&#8217;re into whole <code>await</code> thing:</p>
<pre><code class="language-js">if (typeof window.Profiler === &quot;function&quot;) {
  const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });

  // do work
  var trace = await profiler.stop();
  sendProfile(trace);
}</code></pre>
<p>The two main options you can set when starting a profile are:</p>
<ul>
<li><a href="#js-self-profiling-sample-interval"><code>sampleInterval</code></a> is the application&#8217;s desired sample interval (in milliseconds)
<ul>
<li>Once started, the true sampling rate is accessible via <code>profiler.sampleInterval</code></li>
</ul>
</li>
<li><a href="#js-self-profiling-buffer-size"><code>maxBufferSize</code></a> is the desired sample buffer size limit, measured in number of samples</li>
</ul>
<p>There is usually a measurable delay to starting a <code>new Profiler()</code>, as the browser needs to prepare its profiler infrastructure.</p>
<p>In my testing, I&#8217;ve found that new profiles usually take 1-2ms to start (e.g. before <code>new Profiler()</code> returns) on both desktop and mobile.</p>
<p><a name="js-self-profiling-sample-interval"></a></p>
<h3>Sample Interval</h3>
<p>The <code>sampleInterval</code> you specify (in milliseconds) determines how frequently the browser wakes up to take samples of the JavaScript call stack.</p>
<p>Ideally, you would want to choose a small enough interval that gives you <a href="#js-self-profiling-downsides-to-sampled-profiling"><em>data as accurately as possible</em></a> without there being <a href="#js-self-profiling-overhead">measurement overhead</a>.</p>
<p>The <a href="https://wicg.github.io/js-self-profiling/#the-profilerinitoptions-dictionary">draft spec</a> suggests you need to simply specify a value <em>greater than or equal to zero</em> (though I&#8217;m not sure what zero would mean), though the User Agent may <a href="https://wicg.github.io/js-self-profiling/#profiling-sessions">choose the rate</a> that it ultimately samples at.</p>
<p>In practice, in Chrome 96+, I&#8217;ve found the following <a href="https://chromium.googlesource.com/chromium/src/+/751534e92f25e530da2172a4a446dd5258a50176/third_party/blink/renderer/core/timing/profiler_group.cc#26">minimum sampling rates</a> supported:</p>
<ul>
<li>Windows Desktop: 16ms</li>
<li>Mac/Linux Desktop, Android: 10ms</li>
</ul>
<p>Meaning, if you specify <code>sampleInterval: 1</code>, you will only get a sampling rate of 16ms on Windows.</p>
<p>You can verify the sampling rate that was chosen by the User Agent by inspecting the <code>.sampleInterval</code> of any started trace:</p>
<pre><code class="language-js">const profiler = new Profiler({ sampleInterval: 1, maxBufferSize: 10000 });
console.log(profiler.sampleInterval);</code></pre>
<p>In addition, it appears in Chrome that the chosen actual sample interval is rounded up to the next multiple of the minimum, so 16ms (Windows) or 10ms (Mac/Android).</p>
<p>For example, if you choose a <code>sampleInterval</code> of between 91-99ms on Android, you&#8217;ll get 100ms instead.</p>
<p><a name="js-self-profiling-buffer"></a></p>
<h3>Buffer</h3>
<p>The other knob you control when starting a trace is the <code>maxBufferSize</code>.  This is the maximum number of samples the Profiler will take before stopping on its own.</p>
<p>For example, if you specify a <code>sampleInterval: 100</code> and a <code>maxBufferSize: 10</code>, you will get 10 samples of 100ms each, so 1s of data.</p>
<p>If the buffer fills, the <code>samplebufferfull</code> event fires and no more samples are taken.</p>
<pre><code class="language-js">if (typeof window.Profiler === &quot;function&quot;)
{
  const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });

  function collectAndSendProfile() {
    if (profiler.stopped) return;

    sendProfile(await profiler.stop());
  }

  profiler.addEventListener(&#039;samplebufferfull&#039;, collectAndSendProfile);

  // do work, or listen for some other event, then:
  // collectAndSendProfile();
}</code></pre>
<p><a name="js-self-profiling-who-to-profile"></a></p>
<h2>Who to Profile</h2>
<p>Should you enable a Sampled Profiler for all of your visitors?  Probably not.  While the <a href="#js-self-profiling-overhead">observed overhead</a> appears to be small, it&#8217;s best not to burden all visitors with sampling and collecting this data.</p>
<p>Ideally, you would probably <strong>sample your Sampled Profiler activations</strong> as well.</p>
<p>You could consider turning it on for 10% or 1% or 0.1% of your visitors, for example.</p>
<p>The main reasons you wouldn&#8217;t want to enable this for all visitors are:</p>
<ul>
<li>While minimal, enabling sampling has some associated <a href="#js-self-profiling-overhead">cost</a>, so you probably don&#8217;t want to slow down all visitors</li>
<li>The <a href="#js-self-profiling-size">amount of data</a> produced by a sampled profiler trace is significant, and your probably don&#8217;t want your servers to have to deal with this data from every visitor</li>
<li>As of 2021-12, the only browser that supports this API is Chrome, so your profiles will be biased towards that browser, as well as the above downsides</li>
</ul>
<p>Enabling the profiler for a <strong>sample of specific page loads</strong>, or a <strong>sample of specific visitors</strong> seems ideal.</p>
<p><a name="js-self-profiling-when-to-profile"></a></p>
<h2>When to Profile</h2>
<p>Now that you&#8217;ve determined that this current page or visitor should be profiled, when should you turn it on?</p>
<p>There are a lot ways you can utilize profiling during a session: specific events, user interactions, the entire page load itself, and more.</p>
<p><a name="js-self-profiling-specific-operations"></a></p>
<h3>Specific Operations</h3>
<p>Your app probably has a few complex operations that it regularly executes for visitors.</p>
<p>Instrumenting these operations (on a sampled basis) may be useful in the cases where you don&#8217;t know how the code is flowing and performing in the real world.  It could also be useful if you&#8217;re calling into third-party scripts where you don&#8217;t fully understand their cost.</p>
<p>You could simply start the Profiler at the beginning of the operation and stop it once complete.</p>
<p>The trace data you capture won&#8217;t necessarily be <em>limited</em> to just the code you&#8217;re profiling, but that can also help you understand if your operations are competing with any other code.</p>
<pre><code class="language-js">function loadExpensiveThirdParty() {
  const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 1000 });

  loadThirdParty(async function onThirdPartyComplete() {
      var trace = await profiler.stop();
      sendProfile(trace);
  });
}</code></pre>
<p><a name="js-self-profiling-user-interactions"></a></p>
<h3>User Interactions</h3>
<p>User interactions are great to profile from time to time, especially if metrics like <a href="https://web.dev/fid/">First Input Delay</a> are important to you.</p>
<p>There are a couple approaches you could take regarding <strong>when</strong> to start the profiler when measuring user interactions:</p>
<ul>
<li>Have one always running.  When a user interacts, trim the profile to a short amount of time before and after the events
<ul>
<li>If you&#8217;re using EventTiming and have an active Profiler, you could measure from the event&#8217;s <code>startTime</code> to <code>processingEnd</code> to understand what was running before, during and as a result of the event</li>
</ul>
</li>
<li>Turn on a Profiler once the mouse starts moving, or moving towards a known click-able target</li>
<li>Turn on a Profiler once there&#8217;s an event like <code>mousedown</code> where you expect the user to follow through with their interaction</li>
</ul>
<p>If you wish to wait for a user interaction to start a profiler, note that creating a <code>new Profiler()</code> has a measurable cost (1-2ms) in many cases.</p>
<p>Here&#8217;s an example of having a long-running Profiler available for when there are user interactions, via <a href="https://wicg.github.io/event-timing/">EventTiming</a>:</p>
<pre><code class="language-js">// start a profiler to be monitoring all times
let profiler = new Profiler({ sampleInterval: interval, maxBufferSize: 10000 });

// when there are impactful EventTiming events like &#039;click&#039;, filter to those samples and start a new Profiler
const observer = new PerformanceObserver(function(list) {
    const perfEntries = list.getEntries().forEach(entry =&gt; {
        if (profiler &amp;&amp; !profiler.stopped &amp;&amp; entry.name === &#039;click&#039;) {
            profiler.stop().then(function(trace) {
                const filteredSamples = trace.samples.filter(function(sample) {
                    return sample.timestamp &gt;= entry.startTime &amp;&amp; sample.timestamp &lt;= entry.processingEnd;
                });

                // do something with the filteredSamples and the event

                // start a new profiler
                profiler = new Profiler({ sampleInterval: interval, maxBufferSize: 10000 });
            });
        }
    });
})
.observe({type: &#039;event&#039;, buffered: true});</code></pre>
<p><a name="js-self-profiling-page-load"></a></p>
<h3>Page Load</h3>
<p>If you want to profile the entire Page Load process, it&#8217;s best to start the Profiler via an inline <code>&lt;script&gt;</code> tag before any other Scripts in the <code>&lt;head&gt;</code> of your document.</p>
<p>You could then wait for the page&#8217;s <code>onload</code> event, plus a delay, before processing/sending the trace.</p>
<p>You may also want to listen to the <code>pagehide</code> or <code>visibilitychange</code> events to determine if the visitor <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-avoiding-abandons">abandons</a> the page before it fully loads, and send the profile then.  Note there are <a href="#js-self-profiling-sending-from-unload-events">challenges</a> when sending from unload events.</p>
<p>If you&#8217;re measuring other important aspects, metrics and events of the Page Load process, like Long Tasks or EventTiming events, having a Sampled Profiler trace to understand what was running during those events can be very enlightening.</p>
<p><a name="js-self-profiling-overhead"></a></p>
<h2>Overhead</h2>
<p>Any time you enable a profiler, the browser will be doing extra work to capture the performance data.  Luckily a Sampled Profiler is a bit cheaper to do than an Instrumented Profiler, but what is its cost in the real-world?</p>
<p>Facebook, one of the primary drivers of this API, has <a href="https://github.com/WICG/js-self-profiling/blob/main/doc/tpac-2021-slides.pdf">reported</a> that <code>initial data suggests enabling profiling slows load time by &lt;1% (p=0.05)</code>.</p>
<p>In my own experimentation on one of my websites, there was no noticeable difference in Page Load times between sessions with profiling enabled and those without.</p>
<p>This is great news, though I would love to see more experimentation and evaluation of the performance impacts of this API.  If you&#8217;ve used the JS Self-Profiling API, please share your experimentation results!</p>
<p><a name="js-self-profiling-anatomy-of-a-profile"></a></p>
<h2>Anatomy of a Profile</h2>
<p>The profile trace object returned from the <code>Profiler.stop()</code> Promise callback is described in the <a href="https://github.com/WICG/js-self-profiling/blob/main/README.md#appendix-profile-format">spec&#8217;s appendix</a>, and contains four main sections:</p>
<ul>
<li><code>frames</code> contains an array of frames, i.e. individual functions that could be part of a stack
<ul>
<li>You may see DOM functions (such as <code>set innerHTML</code>) or even <code>Profiler</code> (for work the Sampled Profiler is doing) here</li>
<li>If a frame is missing a <code>name</code> it&#8217;s likely JavaScript executing in the root of a <code>&lt;script&gt;</code> tag or external JavaScript file, see <a href="#js-self-profiling-named-functions">this note</a> for a workaround</li>
</ul>
</li>
<li><code>resources</code> contains an array of all of the resources that contained functions that have a frame in the trace
<ul>
<li>The page itself is often (always?) the first in the array, with any other external JavaScript files or pages following</li>
</ul>
</li>
<li><code>samples</code> are the actual profiler samples, with a corresponding <code>timestamp</code> for when the sample occurred and a <code>stackId</code> pointing at the stack executing at that time
<ul>
<li>If there is no <code>stackId</code>, nothing was executing at that time</li>
</ul>
</li>
<li><code>stacks</code> contains an array of frames that were running on the top of the stack
<ul>
<li>Each stack may have an optional <code>parentId</code>, which maps into the next node of the tree for the function that called it (and so forth)</li>
</ul>
</li>
</ul>
<p>This format is unique to the JS Self-Profiling API, and cannot be used directly in any other tool (<a href="#js-self-profiling-individual-profiles">at the moment</a>).</p>
<p>Here&#8217;s a full example:</p>
<pre><code class="language-json">{
  &quot;frames&quot;: [
    { &quot;name&quot;: &quot;Profiler&quot; }, // the Profiler itself
    { &quot;column&quot;: 0, &quot;line&quot;: 100, &quot;name&quot;: &quot;&quot;, &quot;resourceId&quot;: 0 }, // un-named function in root HTML page
    { &quot;name&quot;: &quot;set innerHTML&quot; }, // DOM function
    { &quot;column&quot;: 10, &quot;line&quot;: 10, &quot;name&quot;: &quot;A&quot;, &quot;resourceId&quot;: 1 } // A() in app.js
    { &quot;column&quot;: 20, &quot;line&quot;: 20, &quot;name&quot;: &quot;B&quot;, &quot;resourceId&quot;: 1 } // B() in app.js
  ],
  &quot;resources&quot;: [
    &quot;https://example.com/page&quot;,
    &quot;https://example.com/app.js&quot;,
  ],
  &quot;samples&quot;: [
      { &quot;stackId&quot;: 0, &quot;timestamp&quot;: 161.99500000476837 }, // Profiler
      { &quot;stackId&quot;: 2, &quot;timestamp&quot;: 182.43499994277954 }, // app.js:A()
      { &quot;timestamp&quot;: 197.43499994277954 }, // nothing running
      { &quot;timestamp&quot;: 213.32999992370605 }, // nothing running
      { &quot;stackId&quot;: 3, &quot;timestamp&quot;: 228.59999990463257 }, // app.js:A()-&gt;B()
  ],
  &quot;stacks&quot;: [
    { &quot;frameId&quot;: 0 }, // Profiler
    { &quot;frameId&quot;: 2 }, // set innerHTML
    { &quot;frameId&quot;: 3 }, // A()
    { &quot;frameId&quot;: 4, &quot;parentId&quot;: 2 } // A()-&gt;B()
  ]
}</code></pre>
<p>To figure out what was running over time, you look at the <code>samples</code> array, each entry containing a <code>timestamp</code> of when the sample occurred.</p>
<p>For example:</p>
<pre><code class="language-json">&quot;samples&quot;: [
  ...
  { &quot;stackId&quot;: 3, &quot;timestamp&quot;: 228.59999990463257 }, // app.js:A()-&gt;B()
  ...
]</code></pre>
<p>If that sample does not contain a <code>stackId</code>, nothing was executing.</p>
<p>If that sample contains a <code>stackId</code>, you look it up in the <code>stacks: []</code> array by the index (<code>3</code> in the above):</p>
<pre><code class="language-json">&quot;stacks&quot;: [
  ...
  2: { &quot;frameId&quot;: 3 }, // A()
  3: { &quot;frameId&quot;: 4, &quot;parentId&quot;: 2 } // A()-&gt;B()
]</code></pre>
<p>We see that <code>stackId: 3</code> is <code>frameId: 4</code> with a <code>parentId: 2</code>.</p>
<p>If you follow the <code>parentId</code> chain recursively, you can see the full stack.  In this case, there are only two frames in this stack:</p>
<pre><code>frameId:4
frameId:3</code></pre>
<p>From those <code>frameId</code>s, look into the <code>frames: []</code> array to map them to functions:</p>
<pre><code>&quot;frames&quot;: [
...
  3: { &quot;column&quot;: 10, &quot;line&quot;: 10, &quot;name&quot;: &quot;A&quot;, &quot;resourceId&quot;: 1 } // A() in app.js
  4: { &quot;column&quot;: 20, &quot;line&quot;: 20, &quot;name&quot;: &quot;B&quot;, &quot;resourceId&quot;: 1 } // B() in app.js
],</code></pre>
<p>So the stack for the sample at <code>228.59999990463257</code> above is:</p>
<pre><code>B()
A()</code></pre>
<p>Meaning, <code>A()</code> called <code>B()</code>.</p>
<p><a name="js-self-profiling-beaconing"></a></p>
<h2>Beaconing</h2>
<p>Once a Sampled Profile trace is stopped, what should you do with the data?  You probably want to <a href="https://nicj.net/beaconing-in-practice/">exfiltrate the data</a> somehow.</p>
<p>Depending on the size of the trace, you could either process it locally first (in the browser), or just send it raw to your back-end servers for further analysis.</p>
<p>If you will be sending the trace elsewhere for processing, you will probably want to gather supporting evidence with it to make the trace more actionable.</p>
<p>For example, you could gather alongside the trace:</p>
<ul>
<li>Performance metrics, such as <em>Page Load Time</em> or any of the <em>Core Web Vitals</em>
<ul>
<li>These can help you understand if the Sampled Profile trace is measuring a user experience that was &quot;good&quot; vs. &quot;bad&quot;</li>
</ul>
</li>
<li>Supporting performance events, such as <em>Long Tasks</em> or <em>EventTiming</em> events
<ul>
<li>These can help you understand what was happening during &quot;bad&quot; events by correlating samples with events such as Long Tasks</li>
</ul>
</li>
<li>User Experience characteristics, such as User Agent / Device information, page dimensions, etc
<ul>
<li>These can help you slice-and-dice your data, and help narrow down your search if you come across patterns of &quot;bad&quot; experiences</li>
</ul>
</li>
</ul>
<p>Sampled Profiles are most helpful when you can understand the circumstances under which they were taken, so make sure you have enough information to know whether the trace is a &quot;good&quot; user experience or a &quot;bad&quot; one.</p>
<p><a name="js-self-profiling-size"></a></p>
<h3>Size</h3>
<p>Depending on the frequency (<code>sampleInterval</code>) and duration (or <code>maxBufferSize</code>) of your profiles, the resulting trace data can be 10s or 100s of KB!  Simply taking the <code>JSON.stringify()</code> representation of the data may not be the best choice if you intend on uploading the raw trace to your server.</p>
<p>In a sample of ~50,000 profiles captured from my website, where I was profiling from the start of the page through 5 seconds after Page Load, the traces averaged about <strong>25 KB</strong> in size.  The median page load time on this site is about 2 seconds, so these traces captured about 7 seconds of data.  These traces are essentially the <code>JSON.stringify()</code> output of the trace data.</p>
<p>The good news is 25 KB is reasonable where you could just take the simplest approach and upload it directly to a server for processing.</p>
<p><a name="js-self-profiling-compression"></a></p>
<h2>Compression</h2>
<p>You also have a few other options for reducing the size of this data before you upload, if you&#8217;re willing to trade some CPU time.</p>
<p>One option is the <a href="https://wicg.github.io/compression/">Compression Stream API</a>, which gives you the ability to get a gzip-compressed stream of data from your string input.  It should be available (in Chrome) whenever the JS Self-Profiling API is available.  One downside is that it is (currently) async-only, so you will need to wait for a callback with the compressed bytes, before you can upload your compressed profile data.</p>
<p>If you expect to send this data via the <code>application/x-www-form-urlencoded</code> encoding, be aware that URL-encoding <code>JSON.stringify()</code> strings results in a much larger string.  For example, a 25 KB JSON object from <code>JSON.stringify()</code> grows to about <strong>36 KB</strong> if <code>application/x-www-form-urlencoded</code> encoded.</p>
<p>To avoid this bloat, you could alternatively consider something like <a href="https://github.com/Sage/jsurl">JSURL</a>.  JSURL is an interesting library that looks similar to JSON, but encodes a bit smaller for URL-encoded data (like <code>application/x-www-form-urlencoded</code> data).</p>
<p>Besides these generic compression methods that can be applied to any string data, <strong>someone smart</strong> could probably come up with a domain-specific compression scheme for this data if they desired!  Please!</p>
<p><a name="js-self-profiling-analyzing-profiles"></a></p>
<h2>Analyzing Profiles</h2>
<p>Once you&#8217;ve started capturing these profiles from your visitors and have been beaconing them to your servers, now what?</p>
<p>Assuming you&#8217;re sending the full trace data (and not doing profile analysis in the browser before beaconing), you have a lot of data to work with.</p>
<p>Let&#8217;s split the discussion between looking at individual profiles (for debugging) and in bulk (aggregate analysis).</p>
<p><a name="js-self-profiling-individual-profiles"></a></p>
<h3>Individual Profiles</h3>
<p>As far as I&#8217;m aware, there aren&#8217;t any openly-available ways of visualizing this trace data in any of the common browser developer tools.</p>
<p>While the <a href="https://github.com/WICG/js-self-profiling#visualization">JS Self-Profiling API Readme</a> mentions that <code>Mozilla&#039;s perf.html visualization tool for Firefox profiles or Chrome&#039;s trace-viewer (chrome://tracing) UI could be trivially adapted to visualize the data produced by this profiling API.</code>, I do not believe this had been done yet.</p>
<p>Ideally, someone could either update one of the existing visualization tools, or write a converter to change the JS Self-Profiling API format into one of the existing formats.  I have seen a <a href="https://github.com/WICG/js-self-profiling/issues/59#issue-1033397566">comment</a> from a developer that the <a href="https://www.specto.dev/">Specto</a> visualization tool may be able to display this data soon, which would be great!</p>
<p>Until then, I don&#8217;t think it&#8217;s very feasible to review individual traces &quot;by hand&quot;.</p>
<p>With the <a href="#js-self-profiling-anatomy-of-a-profile">knowledge of the trace format</a> and just a little bit of code, you could easily post-process these traces to pick out interesting aspects of the traces.  Which brings us to&#8230;</p>
<p><a name="js-self-profiling-bulk-analysis"></a></p>
<h3>Bulk Profile Analysis</h3>
<p>Given a large number of sampled profiles, what insights could you gain from them?</p>
<p>This is an inherently challenging problem.  Given a sample of visitors with tracing enabled, and each trace containing KB or MB of trace data, knowing how to effectively use that data to narrow down performance problems is no easy feat.</p>
<p>The infrastructure required to do this type of bulk analysis is not insignificant, though it really boils down to post-processing the traces and aggregating those insights in ways that make sense.</p>
<p>As a starting point, there are at least a few ways of distilling sampled profile traces down into smaller data points.  By aggregating this type of information for each trace, you may be able to spot patterns, such as which hot functions are more often seen in slower scenarios.</p>
<p>For example, given a single sampled profile trace, you may be able to extract its:</p>
<ul>
<li>Top N function(s) (by exclusive time)</li>
<li>Top N function(s) (by inclusive time)</li>
<li>Top N file(s)</li>
</ul>
<p>If you captured other supporting information alongside the profile, such as Long Tasks or EventTiming events, you could provide more context to why those events were slow as well!</p>
<p>Aggregating this information into a traditional analytics engine, and you may be able to gain insight into which code to focus on.</p>
<p><a name="js-self-profiling-gotchas"></a></p>
<h2>Gotchas</h2>
<p>Of course, no API is perfect, and there are a few ways this API can be confusing, misleading, or hard to use.</p>
<p>Here are a few gotchas I&#8217;ve encountered.</p>
<p><a name="js-self-profiling-minified-javascript"></a></p>
<h3>Minified JavaScript</h3>
<p>If your application contains minified JavaScript, the Sampled Profiles will report the minified function <code>name</code>s.</p>
<p>If you will be processing profiles on your server, you may want to un-minify them via the <a href="https://sourcemaps.info/spec.html">Source Map</a> artifacts from the build.</p>
<p><a name="js-self-profiling-named-functions"></a></p>
<h3>Named Functions</h3>
<p>One issue that I came across while testing this API on personal websites was that I was finding a lot of work triggered by &quot;un-named&quot; functions:</p>
<pre><code class="language-json">{
  &quot;frames&quot;: [
    ...
    { &quot;column&quot;: 0, &quot;line&quot;: 10, &quot;name&quot;: &quot;&quot;, &quot;resourceId&quot;: 0 }, // un-named function in root HTML page
    { &quot;column&quot;: 0, &quot;line&quot;: 52, &quot;name&quot;: &quot;&quot;, &quot;resourceId&quot;: 0 }, // another un-named function in root HTML page
    ...
  ],</code></pre>
<p>These frames were coming from the page itself (<code>resourceId: 0</code>), i.e. inline <code>&lt;script&gt;</code> tags.</p>
<p>They&#8217;re hard to map back to the original function in the HTML, since the page&#8217;s HTML may differ by URL or by visitor.</p>
<p>One thing that helped me group these frames better was to change the inline <code>&lt;script&gt;</code>&#8216;s JavaScript so that they run from named anonymous functions.</p>
<p>e.g. instead of:</p>
<pre><code class="language-html">&lt;script&gt;
// start some work
&lt;/script&gt;</code></pre>
<p>Simply wrap it in a named <a href="https://developer.mozilla.org/en-US/docs/Glossary/IIFE">IIFE</a> (Immediately Invoked Function Expression):</p>
<pre><code class="language-html">&lt;script&gt;
(function initializeThirdPartyInHTML() {
  // start some work
})();
&lt;/script&gt;</code></pre>
<p>Then the <code>frames</code> array provides better context:</p>
<pre><code class="language-json">{
  &quot;frames&quot;: [
    ...
    { &quot;column&quot;: 0, &quot;line&quot;: 10, &quot;name&quot;: &quot;initializeThirdPartyInHtml&quot;, &quot;resourceId&quot;: 0 }, // now with 100% more name!
    { &quot;column&quot;: 0, &quot;line&quot;: 52, &quot;name&quot;: &quot;doOtherWorkInHtml&quot;, &quot;resourceId&quot;: 0 },
    ...
  ],</code></pre>
<p><a name="js-self-profiling-cross-origin-scripts"></a></p>
<h3>Cross-Origin Scripts</h3>
<p>When the API was first being developed and experimented with, it came with a requirement that the page being profiled have <a href="https://web.dev/cross-origin-isolation-guide/">cross-origin isolation (COI)</a> via <a href="https://web.dev/coop-coep/">COOP and COEP</a>.  If <strong>any</strong> third-party script did not enable COOP/COEP, then the API could not be used.</p>
<p>This requirement unfortunately made the API nearly useless for any site that includes third-party content, as forcing those third-parties into COOP/COEP compliance is tricky at best.</p>
<p>Thankfully, after some <a href="https://github.com/WICG/js-self-profiling/issues/41">discussion</a>, the implementation in Chrome was updated, and the COI requirement was dropped.</p>
<p>However, there are still major challenges when you utilize third-party scripts.  In order to not leak private information from third-party scripts, they are treated as opaque unless they opt-in to CORS.   This is primarily to ensure their call stacks aren&#8217;t unintentionally leaked, which may include private information.  Any <strong>cross-origin JavaScript</strong> that is in a call-stack will have its <strong>entire frame removed</strong> unless it has a CORS header.</p>
<p>This is analogous to the protections that cross-origin scripts have in JavaScript error events, where detailed information (line/column number) is only available if the script is same-origin or CORS-enabled.</p>
<p>When applied to Sampled Profiles, this has some strange side-effects.</p>
<p>For <strong>any</strong> cross-origin script (that is not opt-in to CORS) that has a frame in a sample, its entire frame will be removed, <strong>without any indication that this has been done</strong>.  As a result, this means that some of the stacks may be misleading or confusing.</p>
<p>Consider a case where your same-origin JavaScript calls into one or more cross-origin function:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks-cross-origin-top.svg" alt="sampled profiler with cross-origin content" /></p>
<p>Guess what the profiler will report?</p>
<ul>
<li><code>sameOriginFunction()</code> 20ms</li>
</ul>
<p>Even though the two functions <code>crossOriginFunctionA()</code> and <code>crossOriginFunctionB()</code> accounted for a most of the runtime, the JS Self-Profiling API will remove those frames entirely from the report, and limit its reporting to <code>sameOriginFunction()</code>.</p>
<p>It&#8217;s even stranger if those cross-origin functions call back into same-origin functions.  Consider a third-party utility library like jQuery that might do this?</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/12/sampled-profiler-stacks-cross-origin-middle.svg" alt="sampled profiler with cross-origin content" /></p>
<p>The profiler will report:</p>
<ul>
<li><code>sameOriginFunction()</code> 10ms</li>
<li><code>sameOriginFunction() -&gt; sameOriginCallback()</code> 10ms</li>
</ul>
<p>In other words, it pretends the cross-origin functions don&#8217;t even exist.  This could make debugging these types of stacks very confusing!</p>
<p>To ensure your third-party scripts are CORS-enabled, you need to do two things:</p>
<ol>
<li>The origin serving the third-party JavaScript needs to have the <code>Access-Control-Allow-Origin</code> HTTP response header set</li>
<li>The embedding HTML page needs to set <code>&lt;script src=&quot;...&quot; crossorigin=&quot;anonymous&quot;&gt;&lt;/script&gt;</code></li>
</ol>
<p>Once these have been set, the third-party JavaScript will be treated the same as any same-origin content and its frame/function/line/column numbers available.</p>
<p><a name="js-self-profiling-sending-from-unload-events"></a></p>
<h3>Sending from Unload Events</h3>
<p>One challenge with using the JS Self-Profiling API is that to get the trace data, you need to rely on a Promise (callback) from <code>.stop()</code>.</p>
<p>As a result, you really can&#8217;t use this function in page unload handlers like <code>beforeunload</code> or <code>unload</code>, where promises and callbacks may not get the chance to fire before the DOM is destroyed.</p>
<p>So if you want to use the JS Self-Profiling API, you won&#8217;t be able to wait until the page is being unloaded to send your profiles.  If you want to profile a session for a long time, you would need to consider breaking up the profiles into multiple pieces and beacon at a regular interval to ensure you received most (but probably not the final) trace.</p>
<p>This is unfortunate for one scenario, which is page loads that are delayed due to a third-party resource or other heavy site execution.  I would expect many consumers of this API to trace from the beginning of the page to the <code>load</code> event.  But if the visitor leaves the page before it fully loads (say due to a delayed third-party resource), the <code>unload</code> event will fire before the <code>load</code> event, and there will be no opportunity to get the callback from the <code>Profiler.stop()</code>.</p>
<p>I&#8217;ve filed an <a href="https://github.com/WICG/js-self-profiling/issues/65">issue</a> to see if there are any better ways of addressing unload scenarios.</p>
<p><a name="js-self-profiling-non-javascript-browser-work"></a></p>
<h3>Non-JavaScript Browser Work</h3>
<p>One of the issues with the current profiler is that non-JavaScript execution isn&#8217;t represented in profiles.</p>
<p>As a result, top-level User Agent work like HTML Parsing, CSS Style and Layout Calculation, and Painting will appear as &quot;empty&quot; samples.</p>
<p>Other activity like JavaScript garbage collection (GC) will also be &quot;empty&quot; in samples.</p>
<p>There is a <a href="https://github.com/WICG/js-self-profiling/blob/main/markers.md">proposal</a> for the User Agent to add optional &quot;markers&quot; for specific samples, if it wants the profiler to know about non-JavaScript work:</p>
<pre><code class="language-js">enum ProfilerMarker { &quot;script&quot;, &quot;gc&quot;, &quot;style&quot;, &quot;layout&quot;, &quot;paint&quot;, &quot;other&quot; };

...
&quot;samples&quot; : [
  { &quot;timestamp&quot; : 100, &quot;stackId&quot;: 2, &quot;marker&quot;: &quot;script&quot; },
  { &quot;timestamp&quot; : 110, &quot;stackId&quot;: 2, &quot;marker&quot;: &quot;gc&quot; },
  { &quot;timestamp&quot; : 120, &quot;stackId&quot;: 3, &quot;marker&quot;: &quot;layout&quot; },
  { &quot;timestamp&quot; : 130, &quot;stackId&quot;: 2, &quot;marker&quot;: &quot;script&quot; },
  { &quot;timestamp&quot; : 140, &quot;stackId&quot;: 2, &quot;marker&quot;: &quot;script&quot; },
}
...</code></pre>
<p>This is still just a <strong>proposal</strong>, but if implemented it will provide a lot more context of what the browser is doing in profiles.</p>
<p><a name="js-self-profiling-conclusion"></a></p>
<h2>Conclusion</h2>
<p>The JS Self-Profiling API is still under heavy development, experimentation and testing.  There are <a href="https://github.com/WICG/js-self-profiling/issues/36">open issues</a> in the Github repository where work is being tracked, and I would encourage anyone utilizing the API to post feedback there.</p>
<p>We&#8217;ve heard feedback from Facebook and <a href="https://github.com/WICG/js-self-profiling/issues/24#issue-697222098">Microsoft</a> and others that the API has been useful in identifying and fixing performance issues from customers.</p>
<p>Looking forward to hearing others giving the API a try and their results!</p><p>The post <a href="https://nicj.net/js-self-profiling-api-in-practice/" target="_blank">JS Self-Profiling API In Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/js-self-profiling-api-in-practice/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Beaconing In Practice</title>
		<link>https://nicj.net/beaconing-in-practice/</link>
					<comments>https://nicj.net/beaconing-in-practice/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Mon, 28 Dec 2020 17:35:39 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2745</guid>

					<description><![CDATA[<p>Table of Contents Introduction What are Beacons? Beaconing Stages Sending Data at Startup Gathering Data through the Page Load Incrementally Gathering Telemetry throughout a Page&#8217;s Lifetime Gathering Data up to the End of the Page &#34;Whenever&#34; How Many Beacons? A Single Beacon Multiple Beacons Mechanisms Image XMLHttpRequest sendBeacon() Fetch API Fallback Strategies Payload Limits URL [&#8230;]</p>
<p>The post <a href="https://nicj.net/beaconing-in-practice/" target="_blank">Beaconing In Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a name="beaconing-toc"></a></p>
<h2>Table of Contents</h2>
<ul>
<li><a href="#beaconing-introduction">Introduction</a></li>
<li><a href="#beaconing-beacons">What are Beacons?</a></li>
<li><a href="#beaconing-stages">Beaconing Stages</a>
<ul>
<li><a href="#beaconing-sending-data-at-startup">Sending Data at Startup</a></li>
<li><a href="#beaconing-gathering-data-through-page-load">Gathering Data through the Page Load</a></li>
<li><a href="#beaconing-incrementally-gathering-telemetry">Incrementally Gathering Telemetry throughout a Page&#8217;s Lifetime</a></li>
<li><a href="#beaconing-gathering-data-up-to-the-end">Gathering Data up to the End of the Page</a></li>
<li><a href="#beaconing-gathering-data-whenever">&quot;Whenever&quot;</a></li>
</ul>
</li>
<li><a href="#beaconing-how-many-beacons">How Many Beacons?</a>
<ul>
<li><a href="#beaconing-single-beacons">A Single Beacon</a></li>
<li><a href="#beaconing-multiple-beacons">Multiple Beacons</a></li>
</ul>
</li>
<li><a href="#beaconing-mechanisms">Mechanisms</a>
<ul>
<li><a href="#beaconing-image-beacon"><code>Image</code></a></li>
<li><a href="#beaconing-xhr-beacon"><code>XMLHttpRequest</code></a></li>
<li><a href="#beaconing-sendbeacon-beacon"><code>sendBeacon()</code></a></li>
<li><a href="#beaconing-fetch-api">Fetch API</a></li>
<li><a href="#beaconing-beacon-fallback-strategies">Fallback Strategies</a></li>
</ul>
</li>
<li><a href="#beaconing-payload">Payload</a>
<ul>
<li><a href="#beaconing-payload-limits">Limits</a></li>
<li><a href="#beaconing-payload-url">URL (Query String)</a></li>
<li><a href="#beaconing-payload-body-payload">Body Payload</a></li>
<li><a href="#beaconing-payload-compression">Compression</a></li>
</ul>
</li>
<li><a href="#beaconing-reliability">Reliability</a>
<ul>
<li><a href="#beaconing-reliability-page-load">Page Load event</a></li>
<li><a href="#beaconing-reliability-delayed-page-load">Delayed Page Load event</a></li>
<li><a href="#beaconing-reliability-unload">Unload events</a></li>
<li><a href="#beaconing-reliability-avoiding-abandons">Avoiding Abandons</a></li>
<li><a href="#beaconing-one-beacon-tradeoffs">One Beacon Trade-offs</a></li>
<li><a href="#beaconing-advanced-techniques">Advanced Techniques</a></li>
</ul>
</li>
<li><a href="#beaconing-misc">Misc</a>
<ul>
<li><a href="#beaconing-cleanup">Cleanup</a></li>
<li><a href="#beaconing-during-prerender-hidden">During Prerender or Hidden</a></li>
</ul>
</li>
<li><a href="#beaconing-the-future">The Future</a></li>
<li><a href="#beaconing-tldr">TL;DR Summary</a></li>
</ul>
<p><a name="beaconing-introduction"></a></p>
<h2>Introduction</h2>
<p><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2021/01/beaconing-in-practice.svg" alt="Lighthouse modified via vecteezy.com" width="840" height="1024" class="aligncenter size-large wp-image-2755" /></p>
<ul>
<li>Step 1: Gather the data!</li>
<li>Step 2: ???</li>
<li>Step 3: Profit!</li>
</ul>
<p>Let&#8217;s say you have a website, and you want to find out how long it takes your visitors to see the Largest Contentful Paint on your homepage.</p>
<p>Or, let&#8217;s say you want to track how frequently your visitors are clicking a button during the Checkout process.</p>
<p>Or, let&#8217;s say you want to use the new <a href="https://wicg.github.io/performance-measure-memory/">Measure Memory API</a> to track JavaScript memory usage over time, because you&#8217;re concerned that your Single Page App might have a leak.</p>
<p>Or, let&#8217;s say your work on a <a href="https://github.com/akamai/boomerang">performance analytics library</a> that automatically captures performance metrics all throughout the Page Load and beyond.</p>
<p>For each of those scenarios, you may end up using one of the many exciting JavaScript APIs or libraries to capture, query, track or observe key metrics.</p>
<p>That&#8217;s the easy part!</p>
<p>The hard part is making sure your back-end actually <strong>receives</strong> that data in a <strong>reliable</strong> way.  If your telemetry hasn&#8217;t been received, the experience never happened!  What&#8217;s worse, <strong>you may not even know that you don&#8217;t know</strong> it happened!</p>
<p>So, I&#8217;d argue that Step 2 is just as important as Step 1:</p>
<ul>
<li>Step 1: Gather the data!</li>
<li>Step 2: <strong>Beacon the data!</strong></li>
<li>Step 3: Profit!</li>
</ul>
<p>This article will look at several strategies for reliably exfiltrating telemetry &#8212; aka <strong>beaconing</strong>.  We will cover <strong>when</strong> and <strong>how</strong> to send beacons, and gotchas you should watch out for.</p>
<p>This article was written by one of the authors of <a href="https://github.com/akamai/boomerang">Boomerang</a>, an open-source RUM performance monitoring library that sends a <em>lot</em> of beacons (1 billion+ a day!).  We were taking a look at <em>how</em> and <em>when</em> we send beacons to make sure we&#8217;re sending them as optimally as possible, especially to make sure we&#8217;re not missing beacons due to listening to the wrong (or too many) events.  See our findings in the <a href="#beaconing-tldr">TL;DR</a> section!</p>
<p><a name="beaconing-beacons"></a></p>
<h2>Beacons</h2>
<p>Each of the scenarios above cover different ways that websites can collect <strong>telemetry</strong>.  What is telemetry?  Wikipedia <a href="https://en.wikipedia.org/wiki/Telemetry">says</a>:</p>
<blockquote>
<p>Telemetry is the in situ collection of measurements or other data at remote points and their <strong>automatic transmission</strong> to receiving equipment (telecommunication) for monitoring</p>
</blockquote>
<p>Any sort of measurement, whether it&#8217;s for performance, marketing or just curiosity, is telemetry data.  We generally collect telemetry to improve our websites, our services and our visitor&#8217;s experiences.</p>
<p>Your website may have its own internal telemetry that tracks application health, or you may rely on third-party marketing or performance analytics libraries to collect data for you automatically.</p>
<p>An essential part of <strong>collecting</strong> telemetry is making sure that it is reliably <strong>sent</strong> (exfiltrated) so you can actually <strong>use</strong> it (in bulk).</p>
<p>In analytics terms, we often call sending telemetry <strong>beaconing</strong>, and the HTTPS payload that carries the data the <strong>beacon</strong>.</p>
<p><a name="beaconing-stages"></a></p>
<h2>Beaconing Stages</h2>
<p>Every time you collect some data, you should have a strategy for when you&#8217;re going to get that data out of the browser.</p>
<p>This sounds simple, but depending on the type of data you&#8217;re tracking, <em>when</em> you send it matters just as much as collecting it.</p>
<p>Let&#8217;s look at some common scenarios:</p>
<p><a name="beaconing-sending-data-at-startup"></a></p>
<h3>Sending Data at Startup</h3>
<p>Sometimes, you just want to log that a thing happened.  For example, you can log when a Page Load occurred and maybe include a few extra bits of details, like the URL that was loaded or characteristics of the browser.</p>
<p>As long as you&#8217;re not waiting on anything else, in this case, it makes sense to <strong>beacon  immediately</strong> after the analytics code has loaded.</p>
<p>Many marketing analytics scripts, such as Google or Adobe Analytics fall into this bucket.  As soon as their JavaScript libraries are loaded, they may immediately send a beacon noting that &quot;this Page Load happened&quot; with supporting details about the Page Load&#8217;s dimensions.</p>
<pre><code class="language-javascript">// pseudo code
function onStartup() {
    // gather the data
    sendBeacon();
}</code></pre>
<p>Good for:</p>
<ul>
<li>Quick marketing-level analytics</li>
<li>Highly reliable</li>
</ul>
<p>Bad for:</p>
<ul>
<li>Collecting any Page Load <strong>performance</strong> data</li>
<li>Measuring anything that happens after the page has loaded (e.g. user interactions or post-Load content)</li>
</ul>
<p><a name="beaconing-gathering-data-through-page-load"></a></p>
<h3>Gathering Data through the Page Load</h3>
<p>Some websites use Real User Monitoring (RUM) to track the performance of each Page Load.  Since you&#8217;re waiting for the Page Load to finish, you can&#8217;t immediately send a beacon when the JavaScript starts up.  Generally, you&#8217;ll need to wait for at least the Page Load (<code>onload</code>) event, and possibly longer if you have a Single Page App.</p>
<p>To do so, you would normally register for an <code>onload</code> handler, then send your data immediately after the <code>onload</code> event has finished.</p>
<p>Performance analytics libraries such as <a href="https://github.com/akamai/boomerang">boomerang.js</a> or <a href="https://speedcurve.com/features/lux/">SpeedCurve&#8217;s LUX</a> will wait until the Page Load (or SPA Page Load) events before beaconing their data.</p>
<pre><code class="language-javascript">// pseudo code
function onStartup() {
    window.addEventListener(&#039;load&#039;, function(event) {
        // you may want to capture more data now, such as the total Page Load time
        gatherMoreData();

        sendBeacon();
    });

    // you could collect some details now, such as the page URL
    gatherSomeData();
}</code></pre>
<p>Note: You may want to delay your beacon until slightly <em>after</em> <code>onload</code> to ensure your analytics tool doesn&#8217;t cause a lot of work at the same time other <code>onload</code> handlers are executing:</p>
<pre><code class="language-javascript">// pseudo code
function onStartup() {
    window.addEventListener(&#039;load&#039;, function(event) {
        // wait a little bit until Page Load activity dies down
        setTimeout(function() {
            // you may want to capture more data now, such as the total Page Load time
            gatherMoreData();

            sendBeacon();
        }, 500);
    });

    // you could collect some details now, such as the page URL
    gatherSomeData();

    // ALSO!  Have an unload strategy
}</code></pre>
<p>Good for:</p>
<ul>
<li>Gathering performance analytics</li>
</ul>
<p>Bad for:</p>
<ul>
<li>Measuring anything that happens after the page has loaded (e.g. user interactions or post-Load content)</li>
<li>Waiting <em>only</em> for the Page Load event means you will miss data from any user that abandons the page <em>prior</em> to Page Load</li>
<li>Make sure you have an <a href="#beaconing-reliability-avoiding-abandons"><em>unload strategy</em></a> to capture abandons.</li>
</ul>
<p><a name="beaconing-incrementally-gathering-telemetry"></a></p>
<h3>Incrementally Gathering Telemetry throughout a Page&#8217;s Lifetime</h3>
<p>After the page has loaded, there may be user interactions or other periodic changes to the page that you want to track.</p>
<p>For example, you may want to measure how many times a button is clicked, or how long it takes for that button click to result in a UI change.</p>
<p>This type of on-the-fly data collection can often be exfiltrated immediately, especially if you&#8217;re tracking events in real-time:</p>
<pre><code class="language-javascript">// pseudo code
myButton.addEventListener(&#039;click&#039;, function(event) {
    sendBeacon();
});</code></pre>
<p>You could also consider <em>batching</em> these types of events and sending the data periodically.  This may save a bit of CPU and network activity:</p>
<pre><code class="language-javascript">// pseudo code
var dataBuffer = [];
myButton.addEventListener(&#039;click&#039;, function(event) {
    dataBuffer.push(...);
});

// send every 10 seconds if there&#039;s new data
setInterval(function() {
    if (dataBuffer.length) {
        sendBeacon(dataBuffer);
        dataBuffer = [];
    }
}, 10000);</code></pre>
<p>Good for:</p>
<ul>
<li>Real time event tracking</li>
</ul>
<p>Bad for:</p>
<ul>
<li>If you&#8217;re batching data, you should have an <a href="#beaconing-reliability-avoiding-abandons"><em>unload strategy</em></a> to ensure it goes out before the user leaves</li>
</ul>
<p><a name="beaconing-gathering-data-up-to-the-end"></a></p>
<h3>Gathering Data up to the End of the Page</h3>
<p>Some types of metrics are continuous, happening or updating throughout the page&#8217;s lifecycle. You don&#8217;t necessarily want to send a beacon for every update to those metrics &#8212; you just want to know the &quot;final&quot; result.</p>
<p>One simple example of this is when measuring Page View Duration, i.e. how long the user spent reading or viewing the page.  Sure, you could send a beacon every minute (&quot;they&#8217;ve been viewing for [n] minutes!&quot;), but it&#8217;s a lot more efficient to just send the final value (&quot;they were here for 5 minutes!&quot;) <strong>once</strong>, when the user is navigating away.</p>
<p>If you&#8217;re interested in Google&#8217;s Core Web Vitals metrics, you should probably track <a href="https://nicj.net/cumulative-layout-shift-in-practice/">Cumulative Layout Shift (CLS)</a> beyond just the Page Load event.  If Layout Shifts happen post-page-load, those also affect the user experience.  CLS is a score that incrementally updates with each Layout Shift, so you shouldn&#8217;t necessarily beacon on each Layout Shift &#8212; you just want the final CLS value, after the user leaves the page.</p>
<p>Another example would be for the <a href="https://wicg.github.io/performance-measure-memory/">Measure Memory API</a>, which lets you track memory usage over time.  If your Single Page App is alive for 3 hours (over many interactions), you may only want to send one final beacon with how the memory behaved over that lifetime.</p>
<p>For these cases, your best bet is to listen for a page lifecycle indicator like the <code>pagehide</code> event, and send data as the user is navigating away.  The <a href="#beaconing-reliability-unload">specific events you want to listen for</a> are a little complex, so read up on <a href="#beaconing-reliability-avoiding-abandons">unload strategies</a> later.</p>
<pre><code class="language-javascript">// pseudo code
var clsScore = 0;

// don&#039;t listen for just pagehide!  see unload strategies section
window.addEventListener(&#039;pagehide&#039;, function(event) {
    sendBeacon();
});

// Listen for each Layout Shift
var po = new PerformanceObserver(function(list) {
  var entries = list.getEntries();
  for (var i = 0; i &lt; entries.length; i++) {
    if (!entries[i].hadRecentInput) {
      clsScore += entries[i].value;
    }
  }
});

po.observe({type: &#039;layout-shift&#039;, buffered: true});</code></pre>
<p>Good for:</p>
<ul>
<li>Continuous metrics that are updated over time, and you only want the final value</li>
</ul>
<p>Bad for:</p>
<ul>
<li>Real time metrics &#8212; these will be delayed until the user actually navigates away</li>
<li>Reliability &#8212; you will lose some of this data just because unload events aren&#8217;t as <a href="#beaconing-reliability">reliable</a>, so have an <a href="#beaconing-reliability-avoiding-abandons">unload strategy</a></li>
</ul>
<p><a name="beaconing-gathering-data-whenever"></a></p>
<h3>&quot;Whenever&quot;</h3>
<p>Sometimes you may want track metrics or events, but you don&#8217;t necessarily need to send the data immediately (because it doesn&#8217;t need to be <em>Real Time</em> data).  In fact, it may be advantageous to delay sending until <em>another beacon</em> has to go out.  For example, as a later beacon is flushed, you can tack on additional data as needed.</p>
<p>In this case, you may want to:</p>
<ul>
<li>Send data on the next outgoing beacon, if any</li>
<li>Send batched data periodically, if desired</li>
<li>Send any un-sent data at the end of the page</li>
</ul>
<p>To do this, you would use a combination of the strategies above &#8212; using queuing/batching and unload beacons.</p>
<p>Good for:</p>
<ul>
<li>Minimizing beacon counts</li>
</ul>
<p>Bad for:</p>
<ul>
<li>Real-time metrics</li>
<li>Reliability &#8212; you will lose some of this data just because unload events aren&#8217;t as <a href="#beaconing-reliability">reliable</a>, so have an <a href="#beaconing-reliability-avoiding-abandons">unload strategy</a></li>
</ul>
<p><a name="beaconing-how-many-beacons"></a></p>
<h2>How Many Beacons?</h2>
<p>Depending on the data you&#8217;re collecting, and how you&#8217;re considering exfiltrating it, you may have the choice to send a single beacon, or multiple beacons.  Each has its own advantages and disadvantages, from the client&#8217;s (browser&#8217;s) perspective, as well as the server&#8217;s.</p>
<p><a name="beaconing-single-beacons"></a></p>
<h3>A Single Beacon</h3>
<p>A single beacon is the simplest way to send your data.  Collect all of your data, and when you&#8217;re done, send out a single beacon and stop processing.  This is frequently how marketing and performance analytics beacons are implemented, when sending the results of a single Page Load.</p>
<p>Good for:</p>
<ul>
<li>Less processing (CPU) time in the client</li>
<li>Less network egress bytes (less protocol overhead of a single network request vs. multiple requests)</li>
<li>Easier on the back-end &#8212; all data relating to the user experience is in one beacon payload, so the server doesn&#8217;t have to stitch it back together later</li>
</ul>
<p>Bad for:</p>
<ul>
<li>Real-time metrics, unless you&#8217;re sending the beacon early in the Page Load cycle (<a href="#beaconing-sending-data-at-startup">immediately</a> or at <a href="#beaconing-gathering-data-through-page-load"><code>onload</code></a>).</li>
<li>Capturing data after the beacon has been sent</li>
</ul>
<p><a name="beaconing-multiple-beacons"></a></p>
<h3>Multiple Beacons</h3>
<p>If you&#8217;re collecting data at multiple stages throughout the page lifecycle, or due to user interactions, you may want to send that data on multiple beacons.</p>
<p>The main downside to multiple beacons is that it <em>costs more</em> from several perspectives: more JavaScript CPU time building the beacons, more network overhead sending the beacons, more server CPU time processing the beacons.</p>
<p>In addition, depending on how the back-end server infrastructure is setup, you may want to &quot;link&quot; or &quot;stitch&quot; those beacons together.  For example, let&#8217;s say you&#8217;re interested in tracking the Load Time of a Page, as well as the final Cumulative Layout Shift Score.  You may send a beacon out at the <code>onload</code> event with the Load Time, but wait until the <code>unload</code> event to send the final CLS Score.</p>
<p>Later, when you&#8217;re analyzing the data, you may want to group or compare Page Load times with their final CLS Scores.  To do that, you would need to link the beacons together through some sort of GUID, and probably spend time on the back-end <em>joining</em> those beacons together (at your database layer).</p>
<p>An alternative strategy, once the Page Load beacon arrives, is holding it in memory until the final CLS Score arrives, before &quot;stitching&quot; it together on the back-end and sending to the database as a &quot;combined&quot; beacon with all of the data of that Page Load Experience.  Doing this would result in additional server complexity, memory usage, and probably less reliability.  You&#8217;d also need to figure out what happens if one of the partial beacons never arrives (data gets lost in-transit all the time, and sometimes events like <code>unload</code> never fire).</p>
<p>If you&#8217;ll never be looking at or comparing the data from those multiple beacons, these concerns may not matter.  But if you&#8217;re doing more advanced analytics where joining data from multiple beacons would be common, you should weigh the pros and cons of multiple beacons as part of your strategy.</p>
<p>Good for:</p>
<ul>
<li>Real-time capturing/reporting of events, events don&#8217;t &quot;wait&quot; for a later beacon to be sent</li>
<li>Capturing data beyond a single event, throughout a Page Load lifecycle</li>
</ul>
<p>Bad for:</p>
<ul>
<li>Generally more processing time on the client (preparing the beacon)</li>
<li>Generally more network usage (HTTP protocol overhead, repeated dimensions or IDs to stitch to other beacons)</li>
<li>Generally more processing on the server (multiple incoming requests)</li>
<li>Harder to keep context of the same user experience together &#8212; multiple beacons may need to be &quot;joined&quot; for querying or held in-memory until they all arrive</li>
</ul>
<p><a name="beaconing-mechanisms"></a></p>
<h2>Mechanisms</h2>
<p>Once you&#8217;ve figured out when you&#8217;d like to send your beacon(s), and how many you&#8217;ll send, you need to convince the browser to send it.  There&#8217;s at least 4 common APIs to send beacons: <code>Image</code>, <code>XMLHttpRequest</code>, <code>sendBeacon()</code> and Fetch API.</p>
<p><a name="beaconing-image-beacon"></a></p>
<h3>Image</h3>
<p>The simplest method of beaconing data is by using a HTML <code>Image</code>, commonly called a &quot;pixel&quot;.  This is generally done via a HTTP GET request by creating a hidden DOM <code>Image</code>, setting its <code>Image.url</code>, and including your beacon data in the query string.</p>
<p>Often, the server will respond with a <code>204 No Content</code> or a simple/transparent 1&#215;1 pixel image.</p>
<pre><code class="language-javascript">var img = new Image();
img.src = &#039;https://site.com/beacon/?a=1&amp;b=2&#039;;</code></pre>
<p>You can&#8217;t include any data in the &quot;body&quot; of the <code>Image</code>, as you only have the URL (query string) to work with.  This limits you to how much actual data can be sent, depending on both the browser and server configuration.</p>
<p>From the browser&#8217;s point of view, most modern browsers support URL lengths of at least 64 KB:</p>
<ul>
<li>Chrome: ~ 100 KB</li>
<li>Firefox (3.x): &gt;= 5 MB</li>
<li>Firefox (recent): ~ 100 KB</li>
<li>Safari 4, 5: &gt;= 5 MB</li>
<li>Safari 13: ~ 64 KB</li>
<li>Mobile Safari 13: ~ 64 KB</li>
<li>Internet Explorer 6, 7: 2083 bytes</li>
<li>Internet Explorer 8, 9, 10, 11: &gt;= 5 MB</li>
<li>Edge (EdgeHTML 20-44): &gt;= 5 MB</li>
<li>Edge (Chromium 79+): ~ 100 KB</li>
<li>Opera (Presto &lt;= 12): &gt;= 5 MB</li>
<li>Opera (Chromium): ~ 100 KB</li>
</ul>
<p>Notably small exceptions are Internet Explorer 6 and 7 (&#8230; does anyone still care?).</p>
<p>One thing to keep in mind is that serializing data onto the URL is usually inefficient.  Strings need to be <a href="https://en.wikipedia.org/wiki/Percent-encoding">URI-encoded</a>, which bloats the size of characters due to &quot;percent encoding&quot;.  Especially if you&#8217;re trying to tack on raw JSON, like this:</p>
<pre><code>{&quot;abc&quot;:123,&quot;def&quot;:&quot;ghi&quot;}</code></pre>
<p>It gets expanded on the URL by 69% to:</p>
<pre><code>%7B%22abc%22:123,%22def%22:%22ghi%22%7D</code></pre>
<p>You may be able to minimize this type of bloat by using <a href="#beaconing-compression">compression</a> or things like <a href="https://github.com/felixhayashi/jsurl">JSURL</a>.</p>
<p>The browser&#8217;s URL limits are just part of the story.  Most web servers also have their own max request URL size:</p>
<ul>
<li>Apache: <a href="https://httpd.apache.org/docs/2.4/mod/core.html#limitrequestline">Defaults to 8190 bytes</a> and can be increased via the <code>LimitRequestLine</code> directive</li>
<li>TomCat has a default limit of 8 KB, and can be increased up to 64 KB via <code>maxHttpHeaderSize</code></li>
<li>Jetty has a default limit of 8 KB, and can be increased via <code>requestHeaderSize</code></li>
<li>CDNs will have their own URL length limits, which are usually not configurable. <a href="https://community.akamai.com/customers/s/question/0D50f00005RtpqGCAR/ending-up-with-bad-request-when-posted-with-long-urls-header-vs-uri-limit-extension?language=en_US">Akamai</a>, <a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RequestAndResponseBehaviorCustomOrigin.html">CloudFront</a> and <a href="https://docs.fastly.com/en/guides/resource-limits">Fastly</a> all seem to have limits around 8KB.</li>
<li>Users may have proxies installed that have their own limits</li>
</ul>
<p>At the end of the day, it&#8217;s safest to limit <code>Image</code> beacon URLs to under 2,000 bytes, if you care about Internet Explorer 6 and 7.  If not, you can probably go up to 8,190 bytes unless you&#8217;ve specifically configured and tested all of the parts of your CDN and server infrastructure.</p>
<p>I&#8217;m not specifically aware of any user proxies with URL limits, but my guess is there are some out there that may have limits around the same sizes (of 2 or 8 KB), so even if your server infrastructure supports longer request URLs, <em>some</em> users may not be able to send requests that long.</p>
<p><code>Image</code> Beacon Pros:</p>
<ul>
<li>Simplest API</li>
<li>Least amount of overhead</li>
<li>Largest browser support</li>
<li>Will not be rejected or delayed by CORS</li>
</ul>
<p><code>Image</code> Beacon Cons:</p>
<ul>
<li>Does not support HTTP POST</li>
<li>Does not support any payload other than the URL</li>
<li>Does not support more than ~2 KB of data, depending on the browser</li>
<li>Not as reliable as <code>sendBeacon()</code></li>
</ul>
<p><a name="beaconing-xhr-beacon"></a></p>
<h3>XMLHttpRequest</h3>
<p>Once the <code>XMLHttpRequest</code> (XHR) API was <a href="https://caniuse.com/mdn-api_xmlhttprequest">added to browsers</a>, it created a way for developers to use the API to send raw data to any URL, instead of pretending we were fetching <code>Images</code> from everywhere.</p>
<p>XHRs are a lot more flexible than <code>Image</code> beacons.  They can use any HTTP method, including POST.  They can also include a body payload (of any <code>Content-Type</code>), so we can avoid the URL length concerns of <code>Image</code> beacons.</p>
<p>To avoid the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> performance penalty of a <code>OPTIONS</code> Pre-Flight, you should make sure your XHR beacon is a <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests">simple request</a>: only GET/POST/HEAD, no fancy headers, and a <code>Content-Type</code> of either:</p>
<ul>
<li><code>application/x-www-form-urlencoded</code></li>
<li><code>multipart/form-data</code></li>
<li><code>text/plain</code></li>
</ul>
<p>Make sure to review the <a href="#beaconing-beacon-fallback-strategies">fallback strategies</a> in case <code>XMLHttpRequest</code> isn&#8217;t available, or if it fails.</p>
<p>XHR allows you to send data <em>synchronously</em> or <em>asynchronously</em>.  There&#8217;s really no reason to send synchronous XHRs these days.  Some websites used to send synchronous XHRs on <code>unload</code> to make sure the beacon data was sent prior to the browser closing the page.  These days, you should use <code>sendBeacon()</code> instead for even more reliability and better performance.</p>
<p>Here&#8217;s an example of using XHR to send a beacon with multiple key-value pairs:</p>
<pre><code class="language-javascript">// data to send
var data = {
    a: 1,
    b: 2
};

// open a POST
var xhr = new XMLHttpRequest();
xhr.open(&#039;POST&#039;, &#039;https://site.com/beacon/&#039;);
xhr.setRequestHeader(&#039;Content-type&#039;, &#039;application/x-www-form-urlencoded&#039;);

// prepare to send our data as FORM encoded
var params = [];
for (var name in data) {
    if (data.hasOwnProperty(name)) {
        params.push(encodeURIComponent(name) + &#039;=&#039; + encodeURIComponent(data[name]));
    }
}

var paramsJoined = params.join(&#039;&amp;&#039;);

// send!
xhr.send(paramsJoined);</code></pre>
<p><code>XMLHttpRequest</code> Beacon Pros:</p>
<ul>
<li>Simple API</li>
<li>Supports HTTP POST and other methods</li>
<li>Supports a payload in the body of any <a href="#beaconing-content-type">content type</a></li>
<li>Supports any size payload (up to server limits)</li>
</ul>
<p><code>XMLHttpRequest</code> Beacon Cons:</p>
<ul>
<li>May require consideration around CORS to avoid Pre-Flights</li>
<li>Not as reliable as <code>sendBeacon()</code></li>
</ul>
<p><a name="beaconing-sendbeacon-beacon"></a></p>
<h3>sendBeacon</h3>
<p>The <code>navigator.sendBeacon(url, payload)</code> API provides a mechanism to asynchronously send beacon data more performantly and reliably than using <code>XMLHttpRequest</code> or <code>Image</code>.  When using the <code>sendBeacon()</code> API, even if the page is about to unload, the browser will make a best effort attempt to send the data.  The request is always a HTTP POST.</p>
<p><code>sendBeacon()</code> was built for telemetry, analytics and beaconing, and we should use it if available!  According to <a href="https://caniuse.com/beacon">caniuse.com</a>, over 95% of browser marketshare supports <code>sendBeacon()</code> today (the end of 2020).</p>
<p>The API is <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon">fairly simple to use on its own</a>, but has a few gotcha&#8217;s and limits.</p>
<p>First, the return value of <code>navigator.sendBeacon()</code> should be checked.  If it returned <code>true</code>, you&#8217;ve successfully handed data off to the browser and you&#8217;re good to go!  Note this doesn&#8217;t mean the data <em>arrived</em> at the server &#8212; you&#8217;ll never be able to see the server&#8217;s response to the beacon with the <code>sendBeacon()</code> API.</p>
<p>The <code>sendBeacon()</code> API will return <code>false</code> if the UA could not queue the request. This generally happens if the payload size has tripped over certain beacon limits that the browser has set for the page.  Here&#8217;s what the Beacon API spec says about these limits:</p>
<blockquote>
<p>The user agent imposes limits on the amount of data that can be sent via this API: this helps ensure that such requests are delivered successfully and with minimal impact on other user and browser activity. If the amount of data to be queued exceeds the user agent limit, this method returns false; a return value of true implies the browser has queued the data for transfer. However, since the actual data transfer happens asynchronously, this method does not provide any information whether the data transfer has succeeded or not.</p>
</blockquote>
<p>In practice today, the following limits are observed:</p>
<ul>
<li>Firefox does not appear to impose any limits</li>
<li>Chromium-based browsers and Safari have:
<ul>
<li>A payload size limit: this is defined in the <a href="https://fetch.spec.whatwg.org/#http-network-or-cache-fetch">Fetch API spec</a> as 64 KB</li>
<li>An outstanding-beacon payload limit: if there are other <code>navigator.sendBeacon()</code> requests in progress (from any script), and the sum of their payload sizes is over 64 KB, the limit is breached</li>
</ul>
</li>
<li>In Chrome versions earlier than 66, if the total size of <strong>previous calls</strong> to <code>sendBeacon()</code> was over 64 KB, subsequent calls would fail</li>
</ul>
<p>Besides these limits, the URL itself <em>could</em> also contain data, and would adhere to the same URL limits seen in the <a href="#beaconing-image-beacon"><code>Image</code> beacon</a> section.</p>
<p>If the <code>navigator.sendBeacon()</code> returns <code>false</code>, it means the browser <em>will not</em> be sending the beacon.  If so, it&#8217;s best to <a href="#beaconing-beacon-fallback-strategies">fallback</a> to <code>XMLHttpRequest</code> or <code>Image</code> beacons.</p>
<p>This sample code will check that <code>sendBeacon()</code> exists and works, and if not, fallback to XHR/Image beacons:</p>
<pre><code class="language-javascript">function sendData(payload) {
    if (window &amp;&amp;
        window.navigator &amp;&amp;
        typeof window.navigator.sendBeacon === &quot;function&quot; &amp;&amp;
        typeof window.Blob === &quot;function&quot;) {

        var blobData = new window.Blob([payload], {
            type: &quot;application/x-www-form-urlencoded&quot;
        });

        try {
            if (window.navigator.sendBeacon(&#039;https://site.com/beacon/&#039;, blobData)) {
                // sendBeacon was successful!
                return;
            }
        } catch (e) {
            // fallback below
        }
    }

    // Fallback to XHR or Image
    sendXhrOrImageBeacon();
}</code></pre>
<p>Note there are only 3 <a href="https://fetch.spec.whatwg.org/#cors-safelisted-request-header">CORS safelisted</a> <code>Content-Type</code>s you can send:</p>
<ul>
<li><code>application/x-www-form-urlencoded</code></li>
<li><code>multipart/form-data</code></li>
<li><code>text/plain</code></li>
</ul>
<p>Any other content type will result in a CORS pre-flight for cross-origin requests, which isn&#8217;t desired for a beacon that you&#8217;re trying to get out reliably.  So if you&#8217;re wanting to send <code>application/json</code> content to another domain, you may consider encoding it as just <code>text/plain</code>.</p>
<p><code>sendBeacon</code> Pros:</p>
<ul>
<li>Simple API, but beware of fallbacks</li>
<li>Most reliable</li>
<li>Should not be rejected or delayed by CORS (using the correct <code>Content-Type</code>s)</li>
<li>Supports any size payload, though the browser may reject larger sizes (stick to under 64 KB)</li>
</ul>
<p><code>sendBeacon</code> Cons:</p>
<ul>
<li>Calling it does not guarantee the API will &quot;accept&quot; the call &#8212; you may need to fallback to other metrics</li>
<li>Only supports HTTP POST</li>
<li>Supports only some <a href="#beaconing-content-type">Content Types</a> to avoid CORS pre-flight</li>
</ul>
<p><a name="beaconing-fetch-api"></a></p>
<h3>Fetch API</h3>
<p>Similar to using an <code>XMLHttpRequest</code>, the modern <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"><code>fetch()</code> API</a> could be used to send beacons.  If you&#8217;re already using Fetch in your app, you could use that interchangeably with <code>XMLHttpRequest</code> as a fallback.</p>
<p>In addition, there&#8217;s a recent Fetch API option called <code>keepalive: true</code>.  This option is likely what <code>sendBeacon()</code> is using under the hoods in most browsers.</p>
<p>This is supported by <a href="https://www.chromestatus.com/feature/5760375567941632">Chrome 66+</a>, Safari 11+, and is being <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1342484">considered by Firefox</a>.</p>
<p>There are some caveats and limitations around using <a href="https://github.com/whatwg/fetch/issues/679"><code>keepalive</code></a> so I&#8217;d encourage you to review that issue if you&#8217;re using the Fetch API.</p>
<p>At this point, I&#8217;d suggest using <code>sendBeacon()</code> over the Fetch API.</p>
<p><a name="beaconing-beacon-fallback-strategies"></a></p>
<h3>Fallback Strategies</h3>
<p>Not every beaconing method is available in every browser.  You&#8217;ll want to try to fallback to older methods if <code>sendBeacon()</code> isn&#8217;t available:</p>
<p>Generally, use:</p>
<ol>
<li><code>sendBeacon()</code> if available (for reliability) and if it returns <code>true</code></li>
<li><code>XMLHttpRequest</code> (or Fetch API) if you need to use HTTP POST <strong>or</strong> have a body payload <strong>or</strong> if the data is &gt; 2 KB</li>
<li><code>Image</code> otherwise</li>
</ol>
<p><a name="beaconing-payload"></a></p>
<h2>Payload</h2>
<p>What does your data look like?  How big is it?</p>
<p>Ideally, you should minimize the outgoing request size as much as possible to avoid overtaxing your visitor&#8217;s network.  To do this, you could consider various forms of data minification or compression.</p>
<p><a name="beaconing-payload-limits"></a></p>
<h3>Limits</h3>
<p>It would be wise to first look at your expected minimum, median and maximum payload size.  This may dictate what <a href="#beaconing-mechanisms">kind of beacon</a> you can send, i.e. <code>Image</code> vs <code>XMLHttpRequest</code> vs <code>sendBeacon()</code>, and whether any sort of minification/compression is needed.</p>
<p>Briefly:</p>
<ul>
<li>If your data is under 2 KB, you can use any type of beacon, and probably don&#8217;t need to compress it</li>
<li>If your data is under 8 KB, you can use any type of beacon, but won&#8217;t support IE 6 or 7</li>
<li>If your data is under 64 KB, you can use <code>sendBeacon()</code> or <code>XMLHttpRequest</code>, and you may want to consider compressing it</li>
<li>If your data is over 64 KB, you can only use <code>XMLHttpRequest</code>, and you may want to consider compressing it</li>
</ul>
<p><a name="beaconing-payload-url"></a></p>
<h3>Payload via URL (Query String)</h3>
<p>The simplest beacons can include all of their data in the Query String of a URL, i.e.:</p>
<pre><code>https://mysite.com/beacon/?a=1&amp;b=2...</code></pre>
<p>As we saw with the <a href="#beaconing-image-beacon"><code>Image</code> beacon</a> section, in practice this is limited to a total URL length of 2 KB (if you support IE 6/7) or 8 KB (unless your server infrastructure supports more).</p>
<p>One complication is that characters outside of the range below will need to be <a href="https://en.wikipedia.org/wiki/Percent-encoding">URI-encoded</a> by <code>encodeURIComponent</code>:</p>
<pre><code>A-Z a-z 0-9 - _ . ! ~ * &#039; ( )</code></pre>
<p>Depending on your data, this could <a href="#beaconing-image-beacon">bloat</a> the size of your URL significantly!  You may want to consider <a href="#beaconing-compression">JSURL</a> or another compression technique to help offset this if you&#8217;re sticking to a URL payload.</p>
<p><a name="beaconing-payload-body-payload"></a></p>
<h3>Payload via Request Body</h3>
<p>For <code>XMLHttpRequest</code> and <code>sendBeacon</code> calls, you&#8217;ll often specify the bulk of your data in the <strong>payload</strong> of the beacon (instead of the URL).</p>
<p>Common ways of encoding your beacon data include:</p>
<ul>
<li>
<p><code>multipart/form-data</code> via <code>FormData</code>, which is pretty inefficient for sending multiple small key-value pairs due to the &quot;boundary&quot; and <code>Content-Disposition</code> overhead:</p>
<pre><code>------WebKitFormBoundaryeZAm2izbsZ6UAnS8
Content-Disposition: form-data; name="a"

1
------WebKitFormBoundaryeZAm2izbsZ6UAnS8
Content-Disposition: form-data; name="b"

2
------WebKitFormBoundaryeZAm2izbsZ6UAnS8--</code></pre>
</li>
<li><code>application/x-www-form-urlencoded</code> (via <code>UrlSearchParams</code>), which suffers from the same <a href="https://en.wikipedia.org/wiki/Percent-encoding">percentage encoding</a> bloat as URLs if you have many non-alpha-numeric characters.</li>
<li><code>text/plain</code> with whatever text content you want, if your server knows how to parse it</li>
</ul>
<p>Any other content type may trigger a CORS pre-flight for cross-origin requests in  <code>XMLHttpRequest</code> and <code>sendBeacon</code>.</p>
<p><a name="beaconing-compression"></a></p>
<h3>Compression</h3>
<p>You may want to consider reducing the size of your URL or Body payloads, if possible.  There are always trade-offs in doing so, as minification/compression generally use CPU (JavaScript) to reduce outgoing byte sizes.</p>
<p>Some common techniques include:</p>
<ul>
<li>Using a data-specific compression technique to reduce or minify data.  We have some examples for data compression in Boomerang for <a href="https://nicj.net/compressing-resourcetiming/">ResourceTiming</a> and <a href="https://nicj.net/compressing-usertiming/">UserTiming</a>.</li>
<li>URL and <code>application/x-www-form-urlencoded</code> body payloads can benefit from being minified by <a href="https://github.com/Sage/jsurl">JSURL</a>, which swaps out characters that must be encoded for URL-safe characters.</li>
<li>The <a href="https://wicg.github.io/compression/">Compression Streams API</a> could be used to compress large payloads for browsers that support it</li>
</ul>
<p><a name="beaconing-reliability"></a></p>
<h2>Reliability</h2>
<p>As described above, there are many different stages of the page lifecycle that you can send data.  Often, you&#8217;ll want to send data during one of the lifecycle events like <code>onload</code> or <code>unload</code>.</p>
<p>Browsers give us a lot of lifecycle events to listen to, and depending on which of these events you use, you may be more-or-less likely to <strong>receive</strong> data if you send a beacon then.</p>
<p>Let&#8217;s look at some examples, and find a strategy for when to send our beacons, so we can have the best reliability of the data reaching our servers.</p>
<h3>Methodology</h3>
<p>I recently ran a study on one of my websites, collecting data over a week from a large set (millions+) of Page Loads.</p>
<p>For each of these visitors, I sent multiple beacons: as soon as the page started up, at <code>onload</code>, during <code>unload</code> and several other events.</p>
<p>The goal was to see how reliable beaconing is at each of those events, and to see what combination of events would be the most reliable way to receive beacons.</p>
<p>The percentages below reflect <strong>how frequently a beacon arrived if sent during that event</strong>, as compared to the &quot;startup&quot; beacon that was sent as soon as the page&#8217;s <code>&lt;head&gt;</code> was parsed.</p>
<p>This test was done on a <strong>single site</strong> so results from other sites <strong>will differ</strong>.</p>
<p><a name="beaconing-reliability-page-load"></a></p>
<h3>Page Load (<code>onload</code>) Event</h3>
<p>Besides sending a beacon as soon as the page starts up, the most frequent opportunity to send data is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event"><code>window</code> <code>load</code></a> event (aka <code>onload</code>).</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/onload-event.svg" alt="onload event" loading="lazy" /></p>
<p>When sending data <em>just</em> at <code>onload</code>, beacons arrive only <strong>86.4% of the time</strong> (on this site).</p>
<p>This of course varies by browser:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/onload-event-by-browser.svg" alt="onload event - by browser" loading="lazy" /></p>
<p>A large percentage of those &quot;missing&quot; beacons are due to page <em>abandons</em>, i.e. when the visitor leaves before the <code>onload</code> event has fired.</p>
<p>This abandon rate will vary by site, but for this particular site, nearly 14% of visits would not be tracked if you only listened to <code>onload</code>.</p>
<p>Thus, if your data requires waiting until the <code>onload</code> event, you should also listen to page lifecycle &quot;unload&quot; events, to get the opportunity to send a beacon if the user is leaving the page.  See <a href="#beaconing-reliability-avoiding-abandons">avoiding abandons</a> below.</p>
<p><a name="beaconing-reliability-delayed-page-load"></a></p>
<h3>Delayed Page Load (<code>onload</code>) Event</h3>
<p>Sometimes, you may not want to send data immediately at the <code>onload</code> event.  It could make sense to wait a little bit.</p>
<p>You could consider waiting a pre-defined amount of time, say 1 or 5 or 10 seconds after <code>onload</code> before sending the beacon.</p>
<p>Alternatively, if you have page components that are delay-loaded until the <code>onload</code> event, you may want to wait until they load to measure them.</p>
<p>Any amount of time you&#8217;re waiting <em>beyond</em> the Page Load will <strong>decrease beacon rates</strong>, unless you&#8217;re also listening to unload events (see below).</p>
<p>For example, artificially adding a delay after <code>onload</code> before sending the beacon resulted in a clear drop-off of reliability:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/onload-waiting-n-seconds.svg" alt="Waiting N seconds after onload to send a beacon" loading="lazy" /></p>
<p>Again, these rates are if you <em>only</em> listen to the <code>onload</code> (and send a beacon N seconds after that) &#8212; you&#8217;d ideally pair this with <a href="#beaconing-reliability-avoiding-abandons">avoiding abandons</a> below to make sure you send a beacon if the visitor leaves first.</p>
<p><a name="beaconing-reliability-unload"></a></p>
<h3>Unload Events</h3>
<p>There are several events that are all related to the page &quot;unloading&quot;, such as <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event">visibilitychange</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/Events/pagehide">pagehide</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload">beforeunload</a>, and <a href="https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload">unload</a>.  They are all used for specific purposes, and not all browsers support each event.</p>
<p><code>unload</code> and <code>beforeunload</code> are two events that are fired as the page is being unloaded:</p>
<ul>
<li><code>beforeunload</code> happens first, and gives JavaScript the opportunity to cancel the unload</li>
<li><code>unload</code> happens next, and there is no turning back</li>
</ul>
<p>While the <code>unload</code> and <code>beforeunload</code> events have been with us since the beginning of the web, they&#8217;re not the most reliable events to use for beaconing:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/unload-and-beforeunload-events.svg" alt="onload event" loading="lazy" /></p>
<p>The <code>unload</code> event is significantly more reliable than the <code>beforeunload</code> event.  This discrepancy is primarily due to browser differences:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/unload-event-by-browser.svg" alt="unload event - by browser" loading="lazy" /><br />
<img src="https://o.nicj.net/wp-content/uploads/2020/12/beforeunload-event-by-browser.svg" alt="beforeunload event - by browser" loading="lazy" /></p>
<p>Notably, on Safari Mobile, <code>beforeunload</code> is not fired at all (while <code>unload</code> is).</p>
<p><code>pagehide</code> and <code>visibilitychange</code> are more &quot;modern&quot; events:</p>
<ul>
<li><code>visibilitychange</code> can happen when a user switches to another tab (so the current tab is not <em>unloading</em> yet).   This <em>may not</em> be the time you want to send a beacon, as a change to <code>hidden</code> doesn&#8217;t preclude the page coming back to <code>visible</code> later &#8212; the user hasn&#8217;t navigated away, just gone away (possibly) temporarily.  But it&#8217;s possibly the last opportunity you&#8217;ll have to send data, so it&#8217;s a good time to send a beacon if you can.</li>
<li><code>pagehide</code> was introduced as a more reliable &quot;this page is going away&quot; event than the original <code>unload</code> events, which have some caveats and scenarios where they aren&#8217;t expected to fire.</li>
</ul>
<p>Here&#8217;s how often beacons sent during those events arrived:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/pagehide-vischange.svg" alt="onload event" loading="lazy" /></p>
<p>As seen above, we find <code>pagehide</code> (the modern version of <code>unload</code>) to be slightly more reliable than <code>unload</code> (74.8% vs. 72.2%).  <code>visibilitychange</code> (hidden) alone doesn&#8217;t send beacons as often, but if combined with <code>pagehide</code> events, we&#8217;re up to 82.3% reliability which is superior to the combined 73.4% of <code>beforeunload|unload</code>.</p>
<p>By browser:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/pagehide-event-by-browser.svg" alt="pagehide event - by browser" loading="lazy" /><br />
<img src="https://o.nicj.net/wp-content/uploads/2020/12/visibilitychange-event-by-browser.svg" alt="visibilitychange event - by browser" loading="lazy" /></p>
<p>Not coincidentally, listening for these two events <code>pagehide</code> and <code>visibilitychange</code> to save state or to send a beacon is the <a href="https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/">recommendation from Ilya Grigorik</a> from back in 2015.  This is still a great recommendation.  However, if you&#8217;re sending only a <a href="#beaconing-one-beacon-tradeoffs">single beacon</a> (and not just saving state), I recommend considering the <a href="#beaconing-one-beacon-tradeoffs">trade-offs</a> of attempting to beacon earlier in the process.</p>
<p>Below are all of the unload-style events in a single chart.  If for some reason you want to listen to all of these events, you gain the most reliability (82.94%):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/all-unload.svg" alt="onload event" loading="lazy" /></p>
<p>Listening to all events gives you 0.64% more reliability (82.94%) than just <code>pagehide</code>/<code>visibilitychange</code> (at 82.3%).</p>
<p>However, there is a major downside to registering for the <code>unload</code> handler: it breaks <a href="https://web.dev/bfcache/#never-use-the-unload-event">BFCache</a> in Chrome , Safari and Firefox!  BFCache is a browser performance optimization that&#8217;s been available in Firefox and Safari for a while, and was recently added to Chrome 86+.  The <code>beforeunload</code> handler also breaks BFCache in Firefox.</p>
<p>Depending on your site (or if you&#8217;re a third-party analytics provider), you should consider the trade-off of more beacons vs. breaking BFCache when deciding which events to listen for.</p>
<p>Note: Not all browsers support <code>pagehide</code> or <code>visibilitychange</code>, so you&#8217;ll want to detect support for those and if not, fallback to listening for <code>unload</code> and <code>beforeunload</code> as well.</p>
<p>Wrapping this all together, here&#8217;s my recommendation for listening for unload-style events to get the most reliability:</p>
<pre><code class="language-javascript">// pseudo-code

// prefer pagehide to unload events
if (&#039;onpagehide&#039; in self) {
    addEventListener(&#039;pagehide&#039;, sendBeacon, { capture: true} );
} else {
    // only register beforeunload/unload in browsers that don&#039;t support
    // pagehide to avoid breaking bfcache
    addEventListener(&#039;unload&#039;, sendBeacon, { capture: true} );
    addEventListener(&#039;beforeunload&#039;, sendBeacon, { capture: true} );
}

// visibilitychange may be your last opportunity to beacon,
// though the user could come back later
addEventListener(&#039;visibilitychange&#039;, function() {
    if (document.visibilityState === &#039;hidden&#039;) {
        sendBeacon();
    }
}, { capture: true} );</code></pre>
<p><a name="beaconing-reliability-avoiding-abandons"></a></p>
<h3>Avoiding Abandons</h3>
<p>If your primary beaconing event is the Page Load (<code>onload</code>) event, but you want to also respond to users abandoning the page before the page reaches <code>onload</code>, you&#8217;ll want to combine listening for both <code>onload</code> and Unload events.</p>
<p>When the page is abandoned prematurely, the page may not have all of the data you track for &quot;full&quot; navigations.  However, there are often useful things you&#8217;ll still want to track, such as:</p>
<ul>
<li>That the Page Load happened at all</li>
<li>Characteristics of the page, user, browser</li>
<li>What &quot;phase&quot; of the Page Load they reached</li>
</ul>
<p>Combining <code>onload</code> plus the <a href="#beaconing-reliability-unload">two recommended Unload events</a> <code>pagehide</code> and <code>visibilitychange</code> (hidden) gives you the best possible opportunity for tracking the Page Load:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/avoiding-abandons.svg" alt="Avoiding Abandons" loading="lazy" /></p>
<p>By listening to those three events, we see beacons arriving 92.6% of the time.</p>
<p>This rate:</p>
<ul>
<li>Decreases by just 0.6% to 92.0% if you don&#8217;t listen for <code>visibilitychange</code> (if you don&#8217;t want to beacon if the user might come back after a tab switch)</li>
<li>Increases by just 0.2% to 92.8% if you listen for <code>beforeunload</code> (which would break BFCache in Firefox)</li>
<li>Does <strong>not increase</strong> in any meaningful way if you also listened for <code>unload</code> (which breaks BFCache anyway).</li>
</ul>
<p>By browser:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/12/avoiding-abandons-by-browser.svg" alt="Avoiding Abandons" loading="lazy" /></p>
<p>Notably Safari and Safari Mobile seem less reliably for measuring, likely due to not firing the <code>pagehide</code> and <code>visibilitychange</code> events as often.</p>
<p>So if your primary use case is just sending out <strong>one beacon</strong> by the <code>onload</code> (or Unload) event:</p>
<pre><code class="language-javascript">// pseudo-code

// prefer pagehide to unload event
if (&#039;onpagehide&#039; in self) {
    addEventListener(&#039;pagehide&#039;, sendBeacon, { capture: true} );
} else {
    // only register beforeunload/unload in browsers that don&#039;t support
    // pagehide to avoid breaking bfcache
    addEventListener(&#039;unload&#039;, sendBeacon, { capture: true} );
    addEventListener(&#039;beforeunload&#039;, sendBeacon, { capture: true} );
}

// visibilitychange may be your last opportunity to beacon,
// though the user could come back later
addEventListener(&#039;visibilitychange&#039;, function() {
    if (document.visibilityState === &#039;hidden&#039;) {
        sendBeacon();
    }
}, { capture: true} );

// send data at load!
addEventListener(&#039;load&#039;, sendBeacon, { capture: true} );

// track if we&#039;ve sent this beacon or not
var sentBeacon = false;
function sendBeacon() {
    if (sentBeacon) {
        return;
    }

    // 1. call navigator.sendBeacon or XHR or Image
    // 2. cleanup after yourself, e.g. handlers

    sentBeacon = true;
}</code></pre>
<p><a name="beaconing-one-beacon-tradeoffs"></a></p>
<h3>One Beacon Trade-offs</h3>
<p>Many analytics scripts prefer to send a <a href="#beaconing-single-beacons">single beacon</a>.  Taking <a href="https://github.com/akamai/boomerang">boomerang</a> as an example, we measure the performance of the user experience up to the Page Load (<code>onload</code>) event, and attempt to send our performance beacon immediately afterwards.</p>
<p>There are some continuous performance metrics, such as <a href="https://nicj.net/cumulative-layout-shift-in-practice/">Cumulative Layout Shift (CLS)</a> where it may be desirable to continue measuring the metric throughout the page&#8217;s lifetime, right up to the unloading of the page.  Doing so would track the &quot;full page&quot; CLS score, instead of just the CLS score snapshotted at the <code>onload</code> event.</p>
<p>There&#8217;s an inherent trade-off when trying to decide to send a beacon immediately (at <code>onload</code>) instead of waiting until the unload event.  Sending earlier is better for reliability, sending later is better for measuring &quot;more&quot; of the user experience.</p>
<p>Through this study we were able to quantify what this trade-off is (at least for the study&#8217;s website):</p>
<ul>
<li>Sending a beacon at Page Load, with an <a href="#beaconing-reliability-avoiding-abandons">abandonment strategy</a>: 92.8% of beacons arrive</li>
<li>Sending <strong>only</strong> a <a href="#beaconing-reliability-unload">beacon at Unload</a>: 82.3% of beacons arrive</li>
</ul>
<p>So the &quot;cost&quot; of sending a single beacon at Unload instead of Page Load is about <strong>10% of beacons don&#8217;t arrive</strong>.  Depending on your priorities, that decrease in beacons may be worth measuring for &quot;longer&quot; before you send your data?</p>
<p>One important thing to remember when some beacons don&#8217;t arrive is that their characteristics may not be evenly distributed.  In other words, those 10% of beacons may be more frequently &quot;good&quot; experiences, or &quot;bad&quot; experiences, or a particular class of devices or browsers.  Those missing beacons aren&#8217;t a representative sample of the entire class of visitors, and could be hiding some real issues!</p>
<p>Bringing it back to <a href="https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/">Ilya&#8217;s advice</a> about saving app state via the unloading events: this is still suitable if you&#8217;re saving app state or sending multiple beacons, but I&#8217;d suggest considering the reliability  drop-off of not sending the beacon earlier, depending on the data you&#8217;re measuring.</p>
<p><a name="beaconing-advanced-techniques"></a></p>
<h3>Advanced Techniques</h3>
<p>If your goal is to capture as many user experiences as possible, there are a few more things you can try.</p>
<h4>Persisting Beacon Data in Local Storage</h4>
<p>If your goal is to send a single beacon, and you want to wait as long as possible to send it, you may want to only register for Unload events.</p>
<p>Since not beaconing earlier has a <a href="#beaconing-one-beacon-tradeoffs">trade-off</a> of being less reliable, you could consider temporarily storing your upcoming beacon data into <code>localStorage</code> until you send it.</p>
<p>If your Unload events fire properly and you&#8217;re able to send a beacon, great!  You can remove that data from <code>localStorage</code> too.</p>
<p>However, if your application starts up and finds orphan beacon data from a previous Page Load, you could send it on that page instead.</p>
<p>This works best if you&#8217;re concerned about losing data for users navigating across your site &#8212; obviously if a user navigates away to another website, you may never get the opportunity to send data again (unless they come back later).</p>
<h4>Service Workers</h4>
<p>You could also consider using a ServiceWorkers as a &quot;network buffer&quot; for your beacon data.</p>
<p>If you&#8217;re goal is to send a single beacon but want to wait until as late as possible, you can reduce some of the <a href="#beaconing-one-beacon-tradeoffs">reliability trade-offs</a> by &quot;sending&quot; the data to a ServiceWorker for the domain, and letting it transmit at its leisure.</p>
<p>You could have a communications channel with your ServiceWorker where you keep updating its beacon data throughout the page&#8217;s lifetime, and rely on the ServiceWorker to send when it detects the user is no longer on the page</p>
<p>The reason this works is often a ServiceWorker will persist beyond the page&#8217;s lifetime, even if the user navigates to another domain entirely.  This won&#8217;t work if the browser is closed (or crashes), but ServiceWorkers often live a little beyond the page unload.</p>
<p>Using a ServiceWorker would be best suited for first-party beacons (i.e. capturing data on your own site) &#8212; most third-party analytics tools would have a hard time convincing a domain to install a ServiceWorker  just to improve their beacon reliability.</p>
<p><a name="beaconing-misc"></a></p>
<h2>Misc</h2>
<p><a name="beaconing-cleanup"></a></p>
<h3>Cleanup</h3>
<p>After you&#8217;ve successfully sent your data, it&#8217;s a good opportunity to consider cleaning up after yourself if you don&#8217;t anticipate any additional work.</p>
<p>For example, you could:</p>
<ul>
<li>Remove any event listeners, such as click handlers or unload events</li>
<li>Discard any shared state (local variables)</li>
</ul>
<p>You may not need to do this if you&#8217;re sending a beacon as the result of an unload event firing, but if you&#8217;re sending data earlier in the Page Load process, make sure you JavaScript won&#8217;t continue doing work even though it&#8217;ll never send a beacon again.</p>
<p><a name="beaconing-during-prerender-hidden"></a></p>
<h3>During Prerender or when Hidden?</h3>
<p>You should consider whether it makes sense for you to send a beacon if the user hasn&#8217;t seen the page yet.</p>
<p>The most likely scenario is when the page is loaded completely hidden.  This can happen when a user opens a link into a new (background) tab, or loads a page and tabs/switches away before it loads.</p>
<p>Is this experience something you want to track?  Does the experience matter if the user never saw the page?  If you do want to send a beacon, do you send it at <code>onload</code> or wait until the page becomes <code>visible</code> first?  These are all questions you should consider when capturing telemetry.</p>
<p>In Boomerang for example, we still measure those &quot;Always Hidden&quot; user experiences (where the user never sees the page before <code>onload</code>), and send a beacon right away.  However, the beacon is also tagged with a special parameter, so the back-end (like mPulse) can &quot;bucket&quot; those user experiences so they can be excluded (or reviewed independently) from regular Page Loads.</p>
<p>There used to be some user agents that would also implement a &quot;prerender&quot; mode, but that was abandoned a few years ago.  There&#8217;s a new <a href="https://github.com/jeremyroman/alternate-loading-modes">privacy-focused prerender proposal</a> that may come back at some point that you should consider similar to the &quot;hidden&quot; case above.</p>
<p><a name="beaconing-the-future"></a></p>
<h2>The Future</h2>
<p>Because of the limitations we mentioned in this article around the <a href="#beaconing-one-beacon-tradeoffs">trade-offs for a &quot;one beacon&quot; approach versus its reliability</a>, there have been recent discussions around using something like the <a href="https://www.w3.org/TR/reporting/">Reporting API</a> as a better &quot;beacon data queuing mechanism&quot; that would reliably send your beacon data when the user leaves the page.</p>
<p>You can see a <a href="https://docs.google.com/presentation/d/1Wu2hK3SKKE9mMgFULLZV7u17XGe862-ZXPBvzayUZ_s/edit#slide=id.p">presentation from Yoav Weiss</a> from this year&#8217;s 2020 W3C WebPerf TPAC event.</p>
<p>This could enable better capturing of continuous metrics (like CLS) via a single beacon sent <em>just</em> at the end of the Page Load in a reliable way.</p>
<p>Hoping the discussion continues!</p>
<p><a name="beaconing-tldr"></a></p>
<h2>TL;DR Summary</h2>
<p>There are many reason <em>why</em> and <em>when</em> you may want to send beacons, but here are some high level tips:</p>
<ul>
<li>Use <code>navigator.sendBeacon()</code> when possible, but listen to its return codes and fallback to <code>XMLHttpRequest</code> or <code>Image</code> beacons when needed</li>
<li>Send your beacon(s) as early as possible to ensure as many can reach your endpoints</li>
<li>If you&#8217;re waiting for a specific event to send your beacon, like Page Load, make sure you also have an <a href="#beaconing-reliability-avoiding-abandons">abandonment strategy</a></li>
<li>There are several browser events that happen near the <a href="#beaconing-reliability-unload">unloading</a> of a page &#8212; listen to <code>pagehide</code> and <code>visibilitychange</code> (hidden) (and not <code>unload</code> or <code>beforeunload</code> which can break BFCache)</li>
<li>Be aware of your content and look for ways of minimizing payload size via <a href="#beaconing-compression">compression</a> or other means if it makes sense</li>
</ul>
<p>Finally, we started this research by looking into our <a href="https://github.com/akamai/boomerang">own beaconing strategy in Boomerang</a>.  We&#8217;ve found a few key changes we should make:</p>
<ul>
<li>We currently listen for the <code>unload</code> and <code>beforeunload</code> events to try to make sure we capture all abandons/unloads.  This is not only unnecessary (it does not meaningfully increase reliability rate), it also breaks BFCache in nearly all modern browsers</li>
<li>We do not currently listen for <code>visibilitychange</code> (hidden) to send our beacon, and we should consider it as it would increase our reliability (by 0.6% points)</li>
<li>Boomerang generally sends its Page Load beacon right at <code>onload</code> if possible, as we were concerned with losing measurements if we waited later.  This study found we&#8217;d miss around 10% of all Page Loads if we only sent our beacon during Unload instead.  This may be a tradeoff some RUM customers want, so we can add that as an option.</li>
</ul><p>The post <a href="https://nicj.net/beaconing-in-practice/" target="_blank">Beaconing In Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/beaconing-in-practice/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Cumulative Layout Shift in the Real World</title>
		<link>https://nicj.net/cumulative-layout-shift-in-the-real-world/</link>
					<comments>https://nicj.net/cumulative-layout-shift-in-the-real-world/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 09 Oct 2020 19:49:22 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2655</guid>

					<description><![CDATA[<p>Table of Contents Introduction Across the Web By Industry By Page Group Desktop vs. Mobile vs. Bounce Rate vs. Session Length vs. Page Load Time vs. Rage Clicks What&#8217;s Next? Introduction This is a companion post to Cumulative Layout Shift in Practice, which goes over what Cumulative Layout Shift (CLS) is, how it can be [&#8230;]</p>
<p>The post <a href="https://nicj.net/cumulative-layout-shift-in-the-real-world/" target="_blank">Cumulative Layout Shift in the Real World</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table of Contents</h2>
<ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#real-world-data-all">Across the Web</a></li>
<li><a href="#real-world-by-industry">By Industry</a></li>
<li><a href="#real-world-by-page-group">By Page Group</a></li>
<li><a href="#real-world-desktop-vs-mobile">Desktop vs. Mobile</a></li>
<li><a href="#real-world-vs-bounce-rate">vs. Bounce Rate</a></li>
<li><a href="#real-world-vs-session-length">vs. Session Length</a></li>
<li><a href="#real-world-vs-page-load-time">vs. Page Load Time</a></li>
<li><a href="#real-world-vs-rage-clicks">vs. Rage Clicks</a></li>
<li><a href="#real-world-whats-next">What&#8217;s Next?</a></li>
</ul>
<p><a name="introduction"></a></p>
<h2>Introduction</h2>
<p>This is a companion post to <a href="https://nicj.net/cumulative-layout-shift-in-practice/">Cumulative Layout Shift in Practice</a>, which goes over what <a href="https://nicj.net/cumulative-layout-shift-in-practice/#definition">Cumulative Layout Shift (CLS) is</a>, how it can be measured in lab (<a href="https://nicj.net/cumulative-layout-shift-in-practice/#synthetic">synthetic</a>) and real-world (<a href="https://nicj.net/cumulative-layout-shift-in-practice/#rum">RUM</a>) environments, and <a href="https://nicj.net/cumulative-layout-shift-in-practice/#why-important">why it matters</a>.</p>
<p>This article will review <strong>real world</strong> Cumulative Layout Shift data, taken by analyzing billions of individual page load experiences collected via <a href="http://www.akamai.com/mpulse">Akamai mPulse&#8217;s</a> RUM performance analytics.  It is written from the point of view of an author of <a href="https://github.com/akamai/boomerang">Boomerang</a> and an employee working on mPulse, which measures CLS via the Boomerang JavaScript RUM library.</p>
<p><a name="real-world-data"></a></p>
<h2>Real World Data</h2>
<p>What do Cumulative Layout Shift scores look like in the real world?</p>
<p>The <a href="https://github.com/akamai/boomerang">boomerang.js</a> JavaScript RUM library has support for capturing Cumulative Layout Shift in <a href="https://akamai.github.io/boomerang/tutorial-version-history.html">version 1.700.0+</a>.  Boomerang measures CLS up to the Page Load or SPA Page Load event, as well as for each <a href="https://nicj.net/cumulative-layout-shift-in-practice/#spas">SPA Soft Navigation</a>.</p>
<p>Akamai&#8217;s mPulse RUM product uses boomerang.js to gather performance metrics for Akamai&#8217;s customers.</p>
<p>As part of their <a href="https://web.dev/vitals/">Core Web Vitals</a>, Google recommends a CLS score under <code>0.1</code> for a Good experience, and under <code>0.25</code> for Needs Improvement.  Anything above <code>0.25</code> falls under the Poor category.  They explain how they came up with these thresholds in a <a href="https://web.dev/defining-core-web-vitals-thresholds/">blog post</a> with more details.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-google-scores.svg" alt="Google's Suggested CLS Values" /></p>
<p><a name="real-world-data-all"></a></p>
<h2>Across the Web</h2>
<p>Let&#8217;s take a look at a sample of the Cumulative Layout Shift distribution over <em>all</em> mPulse websites.  This histogram reflects hundreds of millions of page load experiences captured over a week in September 2020:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-by-bucket.svg" alt="CLS all Akamai mPulse websites" /></p>
<p>We see what looks like a logarithmic distribution with higher occurrences of CLS near <code>0.00</code> and a long tail out towards <code>2.0</code>+.  Note all CLS values over <code>2.0</code> are limited to <code>2.0</code> for these graphs.</p>
<p>Some interesting findings from this dataset:</p>
<ul>
<li>7.5% of page loads have CLS scores of <code>0.00</code> to <code>0.01</code> (the most common bucket)</li>
<li>50% of page loads have CLS under <code>0.10</code> (the median)</li>
<li>75% of page loads have CLS under <code>0.28</code> (what Google recommends to measure at)</li>
<li>90% of page loads have CLS under <code>0.63</code></li>
<li>95% of page loads have CLS under <code>0.93</code></li>
<li>99% of page loads have CLS under <code>1.60</code></li>
<li>0.5% of page loads have CLS over <code>2.00</code></li>
</ul>
<p>There also seems to be a strange spike around <code>1.00</code> &#8212; I wonder if there&#8217;s a common scenario or type of website that shifts all visible content once?  I hope to investigate this more later.</p>
<p>The 75th percentile (which <a href="https://web.dev/cls/">Google recommends measuring at</a>) shows a CLS score of <code>0.28</code> for these websites &#8212; just outside of the <em>Needs Improvement</em> range (<code>0.1</code> to <code>0.25</code>), and in to the <em>Poor</em> bucket (<code>0.25</code> and higher).  Luckily the median experience is at <code>0.10</code>, right at the edge of the threshold for <em>Good</em> experiences (according to Google).</p>
<p>Note that all of the data you see in the chart above (and sections below) will be <strong>biased</strong> towards the websites mPulse measures, which skews more towards North America and European websites, across retail, financial and other sectors.  It is <strong>not</strong> a representative sample of all websites.</p>
<p>This data may also be biased towards the higher-traffic websites that mPulse measures (it is not normalized by traffic rates).</p>
<p><a name="real-world-by-industry"></a></p>
<h2>By Industry</h2>
<p>Let&#8217;s break down Cumulative Layout Shift by industry.</p>
<p>For these charts, we&#8217;re taking a sample of at least 5 websites for each industry, split by the top-level categories of <em>Retail</em>, <em>News</em> and <em>Travel</em>:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-by-industry-retail.svg" alt="CLS Retail" /><br />
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-by-industry-news.svg" alt="CLS News" /><br />
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-by-industry-travel.svg" alt="CLS Travel" /></p>
<p>These three graphs highlight how different industries (and for that matter, different websites) may have different <em>page styles</em>, and as a result, different user experiences.</p>
<p>These sample Retail websites show a relatively smooth logarithmic decrease from <code>0.00</code> towards <code>2.00</code>.  The 75th percentile user experience is <code>0.23</code> &#8212; in the <em>Needs Improvement</em> bucket, but better than the other sectors.</p>
<p>These sample News websites look similar to Retail, but also have a few spikes of data around <code>0.10</code> (and a smaller one at <code>1.00</code>).  The 75th percentile user experience is at <code>0.29</code>, and it appears these experiences shift more towards the <em>Poor</em> bucket than Retail.</p>
<p>These sample Travel websites show a much different distribution, with spikes at a lot of different score buckets.  These sites have a 75th percentile CLS score of <code>0.41</code>, which is worse than the other two industries.  In fact, this is the only sector with a median (<code>0.29</code>) in the <em>Poor</em> category.</p>
<p>(Obviously, the exact websites that go into each of these samples will have a dramatic effect on the shape of the graphs, but we tried to use similarly sized and traffic&#8217;d websites so one particular website doesn&#8217;t overly skew the data.)</p>
<p><a name="real-world-by-page-group"></a></p>
<h2>By Page Group</h2>
<p>A <em>Page Group</em>, for a specific website, is a set of pages with a common design or layout.  Common Page Groups might be called <em>Home</em>, <em>Product Page</em>, <em>Product List</em>, <em>Cart</em>, etc. for a Retail website.</p>
<p>Each Page Group may be constructed differently, with varying content.  While we can&#8217;t really compare Page Groups across <em>different</em> websites, it can be interesting to see how dramatically Cumulative Layout Shift scores may differ by Page Group on a single website.</p>
<p>For this example Retail website, we can see CLS scores for two unique Page Groups.  The first Page Group shows a majority of <em>Good</em> experiences, while the second Page Group has mainly <em>Poor</em> experiences:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket-pagegroup-1.svg" alt="Example CLS Distribution - Page Group 1" /></p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket-pagegroup-2.svg" alt="Example CLS Distribution - Page Group 2" /></p>
<p>When looking at CLS for a website, or <em>your</em> website, make sure you <a href="https://nicj.net/cumulative-layout-shift-in-practice/#rum">understand all of the experiences</a> going into the reported value, and that you have enough data to be able to <a href="https://nicj.net/cumulative-layout-shift-in-practice/#how-to-improve-it">target and reduce</a> the largest factors of that score.</p>
<p><a name="real-world-desktop-vs-mobile"></a></p>
<h2>Desktop vs. Mobile</h2>
<p>Breaking down CLS from all mPulse websites by device type shows slightly different curves:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-by-bucket-devicetype-desktop-scaled.svg" alt="CLS Desktop" /></p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-by-bucket-devicetype-mobile-scaled.svg" alt="CLS Mobile" /></p>
<p>Desktop CLS scores are skewed more towards <code>0.00</code> and logarithmically decrease towards <code>2.0</code>+, while mobile CLS scores still have a spike around <code>0.00</code> but have additional peaks around <code>0.01</code> and drop off more slowly towards <code>1.00</code>.</p>
<p>There&#8217;s also a noticeable spike for mobile CLS around <code>1.0</code>, which we don&#8217;t see as pronounced in desktop.  Maybe there is a subset of mobile pages or ads or widgets that shift all content at once?</p>
<p>The 75th percentile CLS for mobile (<code>0.39</code>) is notably worse than for desktop (<code>0.23</code>), and are in different ranking buckets (<em>Poor</em> vs. <em>Needs Improvement</em>).  Mobile websites are often built differently than desktop layouts, but it&#8217;s a shame mobile users see such an increase in layout shifts.  Shifting content can be frustrating and cause users to loose their place or mis-click on the wrong content, and those frustrations can be amplified on smaller screens.</p>
<p><a name="real-world-vs-bounce-rate"></a></p>
<h2>vs. Bounce Rate</h2>
<p>How does Cumulative Layout Shift affect Bounce Rate?</p>
<p>Bounce Rate is a measure of whether your visitors bounce (leave) after visiting a single page.  Any user that visits two or more pages is considered a non-Bounce.</p>
<p>Since the first page will help decide whether the user navigates elsewhere, let&#8217;s take a look at <strong>Landing Page</strong> Cumulative Layout Shift vs. that user&#8217;s Bounce Rate (whether they left the site after the first page or not).</p>
<p>The theory is that if a user has a high Cumulative Layout Shift (i.e. negative experience) on the first page, they may be <em>more likely</em> to bounce.</p>
<p>Here&#8217;s one example Retail website.  CLS (from <code>0.0</code> to <code>2.0</code> max) is on the X axis, Bounce Rate (as a percentage of users who bounced after one page at that CLS) on the Y axis.  The size of the circle is the relative number of data points for that bucket:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-landing-page-cls-vs-bounce.svg" alt="Landing Page CLS vs. Bounce Rate Retail 1" /></p>
<p>We can see <em>correlation</em> (ρ=0.74) between Cumulative Layout Shift and Bounce Rate.  There are obviously outliers, but the Poor (<code>&gt; 0.25</code>) CLS scores generally increase Bounce Rate as the CLS increases.</p>
<p>Here&#8217;s a second retail website, which seems to show a similar correlation (ρ=0.83) to Bounce Rate:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer3-landing-page-cls-vs-bounce.svg" alt="Landing Page CLS vs. Bounce Rate Retail 2" /></p>
<p>Let&#8217;s look at a different sector of websites.  Here&#8217;s a News website that shows less of a correlation (ρ=0.53):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer2-landing-page-cls-vs-bounce.svg" alt="Landing Page CLS vs. Bounce Rate News" /></p>
<p>(note the Y scale has changed)</p>
<p>The lowest CLS scores (Good experiences) show a relatively low Bounce Rate.  As soon as the CLS goes out of the range of Good (<code>0.1</code>) towards Needs Improvement (<code>0.25</code>) and beyond, Bounce Rate stays relatively the same.</p>
<p>For this site, why doesn&#8217;t the Bounce Rate change much as the CLS increases?  Honestly, I&#8217;m not sure, though if I had time I could <a href="https://nicj.net/cumulative-layout-shift-in-practice/#how-to-improve-it">dig into the data</a>.  It&#8217;s possible the lowest-CLS experiences are pages that entice the user to stay more.</p>
<p>For the retail websites, obviously CLS is just one measure of the user experience, and we just see a <em>correlation</em> with Bounce Rate.  Improving CLS alone may not improve bounce rates.  It&#8217;s probable that some of the lower-bouncing pages have lower CLS because of how they&#8217;re designed.  Or, those lower-CLS pages are crafted &#8220;landing pages&#8221; that try to get the visitor to go to more pages on the site.</p>
<p>It&#8217;s also possible other factors like ad-blockers are affecting things here &#8212; maybe an ad-free non-shifting user experience keeps visitors longer?  It would take a bit more research into the specific sites to understand this better.</p>
<p><a name="real-world-vs-session-length"></a></p>
<h2>vs. Session Length</h2>
<p>Similar to <a href="#real-world-vs-bounce-rate">Bounce Rate</a>, <em>Session Length</em> is a measure of how many pages a visitor accesses over a specific period of time (e.g. 30 minutes).</p>
<p>Here&#8217;s the same retailer&#8217;s Session Length vs. Landing Page CLS.  Like how Bounce Rate <em>increased</em> with CLS, let&#8217;s look to see if the Session Length <em>decreases</em> with higher CLS scores:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-landing-page-cls-vs-session-length.svg" alt="Landing Page CLS vs. Session Length Retail 1" /></p>
<p>As expected, the higher the Landing Page Cumulative Layout Shift, the fewer number of pages those visitors go to.</p>
<p>As we saw before with the same News website, lower CLS values seem to give a <em>slightly</em> higher Session Length (e.g. more pages were visited) for <em>Good</em> experiences, but the drop in Session Length isn&#8217;t as pronounced for higher CLS scores (the difference between ~1.5 and ~2.0 pages per Session).</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer2-landing-page-cls-vs-session-length.svg" alt="Landing Page CLS vs. Session Length Retail 1" /></p>
<p>Also note this graph is just comparing the <em>Landing Page</em> CLS score &#8212; i.e. their first experience on the site &#8212; not the subsequent CLS scores from additional pages.</p>
<p>This data just shows a <em>correlation</em>, not <em>causation</em>.  When looking at data like this, try to consider what is causing the shifts in the first place.  Was it ads?  Social widgets?  Removing the content that causes the shifts will help multiple aspects of performance, including network activity, runtime tasks, layout shifts, and more.</p>
<p><a name="real-world-vs-page-load-time"></a></p>
<h2>vs. Page Load Time</h2>
<p>Does Cumulative Layout Shift correlate with Page Load times?</p>
<p>Using <a href="https://github.com/akamai/boomerang">Boomerang</a>, we can collect Page Load times (for regular and <a href="https://nicj.net/cumulative-layout-shift-in-practice/#spas">Single Page Apps</a>) as well as the Cumulative Layout Shift score (<em>at the time of the load</em>).</p>
<p>Here&#8217;s a plot of hundreds of millions of CLS score buckets versus the median Page Load times:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-vs-page-load-time.svg" alt="CLS vs. Page Load Time" /></p>
<p>There appears to be strong correlation (ρ=0.84) for Cumulative Layout Shift increasing with increased Page Load time.</p>
<p>Intuitively, I would expect this to be the case &#8212; the more content that is added to a page (which increases its Load Time), the more likely that content will cause layout shifts.</p>
<p>Again, this is just showing a <em>correlation</em>.  Some layout shifts may be caused by simple layout inefficiencies or bugs.  Other layout shifts may be directly caused by third-party content, which is also increasing Page Load time.</p>
<p><a name="real-world-vs-rage-clicks"></a></p>
<h2>vs. Rage Clicks</h2>
<p>Does Cumulative Layout Shift correlate with Rage Clicks?</p>
<p>Using <a href="https://github.com/akamai/boomerang">Boomerang</a>, we can collect <a href="https://akamai.github.io/boomerang/BOOMR.plugins.Continuity.html#toc6__anchor">Rage Clicks</a>, which are a measure of how commonly a visitor clicks the same area repeatedly.  One of the cases where this may happen is when a website stops reacting to user input, and the user repeats the same clicks in the same region.</p>
<p>Here&#8217;s a plot of hundreds of millions of CLS score buckets versus average Rage Clicks per page:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-all-vs-rage-clicks.svg" alt="CLS vs. Page Load Time" /></p>
<p>We again see a decent correlation (ρ=0.77) between Cumulative Layout Shifts and Rage Clicks.</p>
<p>There is a strange spike of Rage Clicks around CLS values of ~<code>0.10</code>, and I haven&#8217;t had a chance to investigate why.  That could be an over-representation of some website that has a lot of CLS values around <code>0.10</code> and higher Rage Click occurrences.  Or it could be a common design pattern or widget/ad that is causing issues!  Something to dig into in the future.</p>
<p>Rage Clicks can frustrate your users, and cause them to bounce.  Even if you&#8217;re not measuring Rage Clicks directly, your CLS scores may give a hint toward how often it happens.  It&#8217;s intuitive that the worst CLS scores (over <code>1.0</code>) have a strong correlation with users (mis)clicking, if content is shifting around a lot.</p>
<p><a name="real-world-whats-next"></a></p>
<h2>What&#8217;s Next</h2>
<p>Cumulative Layout Shift is still a relatively new metric for websites to measure.  At mPulse, we capture billions of performance metrics a day, and there are still are a lot of aspects of CLS that we haven&#8217;t dug into yet.  Over time, I hope to share more insights and graphs around CLS (and other performance metrics) in this post or others.</p>
<p>Being a relatively new metric, there is still a lot of opportunity to understand how closely CLS reflects the user experience.  From the above data, we see correlations with business and performance metrics, but on its own, CLS scores may just be a side effect of how the rest of the site is built and the third party components or ads you include.  If you&#8217;re interested in improving your own CLS score, you really need to <a href="https://nicj.net/cumulative-layout-shift-in-practice/#how-to-improve-it">dig into your own data</a> and use <a href="https://nicj.net/cumulative-layout-shift-in-practice/#synthetic-developer-tools">developer tools</a> to find and fix the shifts.</p>
<p>If you want to learn more about CLS in general, you can read the companion post <a href="https://nicj.net/cumulative-layout-shift-in-practice/">Cumulative Layout Shift in Practice</a>.</p>
<p>If you have any interesting insights into your own CLS data, please share below!</p><p>The post <a href="https://nicj.net/cumulative-layout-shift-in-the-real-world/" target="_blank">Cumulative Layout Shift in the Real World</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/cumulative-layout-shift-in-the-real-world/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Cumulative Layout Shift in Practice</title>
		<link>https://nicj.net/cumulative-layout-shift-in-practice/</link>
					<comments>https://nicj.net/cumulative-layout-shift-in-practice/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 09 Oct 2020 19:49:17 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2538</guid>

					<description><![CDATA[<p>Table of Contents Introduction What is Cumulative Layout Shift? Why is it important? Definition When Does it End? Single Page Apps (SPAs) IFRAMEs How to Improve It How to Measure It RUM Example Code Attribution Fallbacks Browser Support Gotchas Open-Source / Free RUM Commercial RUM Synthetic Free Synthetic Developer Tools Commercial Synthetic Monitoring Tools RUM [&#8230;]</p>
<p>The post <a href="https://nicj.net/cumulative-layout-shift-in-practice/" target="_blank">Cumulative Layout Shift in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table of Contents</h2>
<ul>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#what-is-cls">What is Cumulative Layout Shift?</a></li>
<li><a href="#why-important">Why is it important?</a></li>
<li><a href="#definition">Definition</a>
<ul>
<li><a href="#when-does-it-end">When Does it End?</a></li>
<li><a href="#spas">Single Page Apps (SPAs)</a></li>
<li><a href="#iframes">IFRAMEs</a></li>
</ul>
</li>
<li><a href="#how-to-improve-it">How to Improve It</a></li>
<li><a href="#how-to-measure-it">How to Measure It</a>
<ul>
<li><a href="#rum">RUM</a>
<ul>
<li><a href="#example-code">Example Code</a></li>
<li><a href="#attribution">Attribution</a></li>
<li><a href="#fallbacks">Fallbacks</a></li>
<li><a href="#browser-support">Browser Support</a></li>
<li><a href="#gotchas">Gotchas</a></li>
<li><a href="#open-source-rum">Open-Source / Free RUM</a></li>
<li><a href="#commercial-rum">Commercial RUM</a></li>
</ul>
</li>
<li><a href="#synthetic">Synthetic</a>
<ul>
<li><a href="#synthetic-developer-tools">Free Synthetic Developer Tools</a></li>
<li><a href="#commercial-synthetic-monitoring-tools">Commercial Synthetic Monitoring Tools</a></li>
</ul>
</li>
<li><a href="#rum-vs-synthetic">RUM vs. Synthetic</a></li>
</ul>
</li>
<li><a href="#real-world-data">Real World Data</a></li>
<li><a href="#whats-next">What&#8217;s Next?</a></li>
<li><a href="#references">References</a></li>
</ul>
<p><a name="introduction"></a></p>
<h2>Introduction</h2>
<p>Cumulative Layout Shift (CLS) is a user experience metric that measures how <em>unstable</em> content is for your visitors.  Layout shifts occur when page content moves after being presented to the user.  These unexpected shifts can lead to a frustrating visual and user experience, such as misplaced clicks or rendered content being scrolled out of view.</p>
<p>Trying to read or interact with a page that has a high CLS can be a frustrating experience!  A common example of layout shifts occurs when reading an article on a mobile device, and you see your content jumping up or down as ads are dynamically inserted when you scroll:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-reading-ad-demo.gif" alt="Layout Shifts while reading content" class="blog-screenshot" style="width: 250px" />
</div>
<p><strong>Cumulative</strong> Layout Shift is a <a href="#definition">measure</a> of how much content shifts on a page, and is one of Google&#8217;s <a href="https://web.dev/vitals/">Core Web Vitals</a> metrics, so there has been a lot of attention on it lately.  It will soon be used as a <a href="https://webmasters.googleblog.com/2020/05/evaluating-page-experience.html">signal</a> in Google&#8217;s Search Engine Optimization (SEO) rankings, meaning lower CLS scores may give higher search rankings.</p>
<p>As of September 2020, Cumulative Layout Shift is part of a <a href="https://wicg.github.io/layout-instability/">draft specification</a> of the Web Platform Incubator Community Group (WICG), and not yet a part of the W3C Standards track.  It is only <a href="#browser-support">supported</a> in Blink-based browsers (Chrome, Opera, Edge) at the moment.</p>
<p>This article will review <a href="#definition">what Cumulative Layout Shift is</a>, how it can be measured in lab (<a href="#synthetic">synthetic</a>) and real-world (<a href="#rum">RUM</a>) environments, and <a href="#why-important">why it matters</a>.  A <a href="https://nicj.net/cumulative-layout-shift-in-the-real-world/">companion post</a> dives into what CLS looks like in the <a href="#real-world-data">real world</a> by looking at mPulse RUM data.</p>
<p>This article is written from the point of view of an author of <a href="https://github.com/akamai/boomerang">Boomerang</a> and an employee working on Akamai&#8217;s mPulse RUM product, which measures CLS via the Boomerang JavaScript RUM library.</p>
<p><a name="what-is-cls"></a></p>
<h2>What is Cumulative Layout Shift?</h2>
<p>Cumulative Layout Shift is a score that starts at <code>0.0</code> (for no unexpected shifts) and grows incrementally for each unexpected layout shift that happens on the page.</p>
<p>The score is <strong>unitless</strong> and unbound &#8212; theoretically, you could see CLS scores over <code>1.0</code> or <code>10.0</code> or <code>100.0</code> on highly shifting pages.  In the <a href="#real-world-data">real world</a>, 99.5% of CLS scores are under <code>2.0</code>.</p>
<p>As part of their <a href="https://web.dev/vitals/">Core Web Vitals</a>, Google recommends a CLS score under <code>0.10</code> for a Good experience, and under <code>0.25</code> for Needs Improvement.  Anything above <code>0.25</code> falls under the Poor category.  They explain how they came up with these thresholds in a <a href="https://web.dev/defining-core-web-vitals-thresholds/">blog post</a> with more details.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-google-scores.svg" alt="Google's Suggested CLS Values" /></p>
<p>Note that Google&#8217;s recommended CLS value of <code>0.10</code> is for the <strong>75th percentile</strong> of your users on both mobile <strong>and</strong> desktop.</p>
<p>For this blog post, we will generally use Google&#8217;s recommended thresholds above when talking about Good, Needs Improvement, or Poor categories.</p>
<p>Importantly, just like any performance metric, Cumulative Layout Shift is a distribution of values across your entire site.  While individual synthetic tests (like WebPagetest or Lighthouse) may only measure a single (or few) test runs, when looking at your CLS scores from the wild in RUM data, you may have thousands or millions of individual data points.  CLS will be different for different page types, visitors, devices, and screens.</p>
<p>Let&#8217;s say, through some sort of aggregate data (like mPulse RUM or CrUX), you know that your site has a Cumulative Layout Shift score of <code>0.31</code> at the 75th percentile.</p>
<p>Here&#8217;s what that distribution could look like:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket.svg" alt="Example CLS Distribution" /></p>
<p>The frequency distribution above shows real data (via mPulse) for a retail website over a single day, comprising of 7+ million user experiences.  Note that while the 75th percentile is <code>0.31</code> (Poor), the median (50th percentile) is <code>0.16</code> (Needs Improvement).</p>
<p>The distribution is not normal, and shows that there are a few &#8220;groups&#8221; of common CLS scores, i.e. around <code>0.00</code>, <code>0.10</code>, <code>0.17</code> and <code>1.00</code>.  It&#8217;s possible those humps represent different subsets of the data, such as different device types or page groups.</p>
<p>For example, let&#8217;s breakdown the data into Desktop vs. Mobile:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket-devicetype-desktop.svg" alt="Example CLS Distribution - Desktop" /></p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket-devicetype-mobile.svg" alt="Example CLS Distribution - Mobile" /></p>
<p>As you can see, your experience varies by the type of device that you&#8217;re on.</p>
<p>Desktop users have a lot of CLS scores between <code>0.0</code> and <code>0.4</code>, with a small bump around <code>1.0</code>.</p>
<p>Mobile users have some experiences around <code>0.0</code>, a spike at <code>0.06-0.10</code>, then a fairly even distribution all the way to <code>1.0</code>.</p>
<p>Of course, different parts of a website may be constructed differently, with varying content.  Reviewing CLS scores for two unique page groups shows a Good experience for the first type of page, and a lot of Poor experiences for the second type of page:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket-pagegroup-1.svg" alt="Example CLS Distribution - Page Group 1" /></p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-query-customer1-cls-by-bucket-pagegroup-2.svg" alt="Example CLS Distribution - Page Group 2" /></p>
<p>All of this is to say, when looking at CLS for a website, make sure you <a href="#rum">understand all of the experiences</a> going into the reported value, and that you have enough data to be able to <a href="#how-to-improve-it">target and reduce</a> the largest factors of that score.</p>
<p><a name="why-important"></a></p>
<h2>Why is it important?</h2>
<p>Why does Cumulative Layout Shift matter?</p>
<p>CLS is one measurement of the user experience.  There are many ways your website can frustrate or delight your users, and CLS is a measurement that <em>may</em> highlight some of those negative experiences.</p>
<p>A bad (high) CLS score may indicate that users are seeing content shift and flow around as they&#8217;re trying to interact with your site, which can be frustrating.  Users who get frustrated may leave your site, and never come back!</p>
<p>Some of those frustrating experiences may be:</p>
<ul>
<li>Reading an article and having the content <strong>jump</strong> down below the viewport, causing the visitor to lose their place (see demo at the start of this article)</li>
<li><strong>Mis-clicking</strong> or clicking the <strong>wrong</strong> button:</li>
</ul>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-mis-click-demo.gif" alt="Mis-click demo" class="blog-screenshot" style="width: 250px" />
</div>
<p>See the data below in the <a href="#real-world-data">real-world data</a> section for how Cumulative Layout Shift correlates with other performance and business metrics, such as Bounce Rate, Session Length, Rage Clicks and more.</p>
<p>In some ways, Cumulative Layout Shift is more of a user experience / web design metric than a web performance metric.  It measures what the user sees, not how long something takes.</p>
<p>It&#8217;s good for the websites to move away from <em>just</em> measuring network- and DOM-based metrics and towards measuring more of the overall user experience.  We need to understand what delights and frustrates our users.</p>
<p>Finally, Google is putting their weight behind the metric and will be using it as a <a href="https://webmasters.googleblog.com/2020/05/evaluating-page-experience.html">signal</a> in Google&#8217;s Search Engine Optimization (SEO) rankings.  Search ranking have a direct impact on visitors, and a lot of attention has been going into CLS as a result.</p>
<p><a name="definition"></a></p>
<h2>Definition</h2>
<p>The Cumulative Layout Shift score is a <em>sum</em> of the <em>impact</em> of each <em>unexpected layout shift</em> that happens to a user over a period of time.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-multiple-shifts.svg" alt="Multiple Layout Shifts" class="blog-screenshot" style="width: 700px" />
</div>
<p>Only shifts of visible content from within in the viewport matter.  Content that moves below the fold (or currently scrolled viewport) does not degrade the user experience.</p>
<p>As a visitor loads and interacts with a site, they may encounter these layout shifts.  The sum of the &#8220;scores&#8221; of each individual layout shift results in the <em>Cumulative</em> Layout Shift score.</p>
<p>To calculate the score from an individual layout shift, we need to look at two components of that shift: its <em>impact fraction</em> and <em>distance fraction</em>.</p>
<p>The <strong>impact fraction</strong> measures how much of the viewport changed from one frame (moment) to the next.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-impact-fraction.svg" alt="CLS - Impact Fraction" class="blog-screenshot" style="width: 500px" />
</div>
<p>In the above screenshot, the green frame shows the portion of the viewport changing from the previous frame.</p>
<p>The <strong>distance fraction</strong> measures the greatest distance moved by any of those unstable elements, as a portion of the viewport&#8217;s size.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-distance-fraction.svg" alt="CLS - Distance Fraction" class="blog-screenshot" style="width: 500px" />
</div>
<p>In the above screenshot, the blue arrow shows the distance fraction (from the new Ads/Widgets coming in).</p>
<p>Multiplied together, you get a single layout shift score:</p>
<pre><code>layout shift score = impact fraction * distance fraction
</code></pre>
<p>Each layout shift is then <em>accumulated</em> into the Cumulative Layout Shift score over time.</p>
<p>Both HTML <code>Element</code>s (such as images, videos, etc.) as well as text nodes may be affected by layout shifts.  Under <a href="https://github.com/WICG/layout-instability/issues/61">discussion</a> is whether some types of hidden elements (such as <code>visibility:hidden</code>) would be considered.</p>
<p>For further details, the <a href="https://web.dev/cls/">web.dev article on CLS</a> has a great explanation on how CLS is calculated as well.</p>
<p><a name="when-does-it-end"></a></p>
<h3>When does it End?</h3>
<p>The point at which individual layout shifts stop being added to the Cumulative Layout Shift score may differ depending on what you or your tool is measuring.</p>
<p>Tools may measure up to one of the following events:</p>
<ul>
<li>When the browser&#8217;s <code>onload</code> event fires</li>
<li>For <a href="#spas">Single Page Apps (SPAs)</a>, when all SPA content is loaded</li>
<li>For the life of the page (even after the user interacts with the page)</li>
</ul>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-when-does-it-end.svg" alt="When does CLS end?" /></p>
<p>If your main concern is just the Page Load experience, you can accumulate layout shifts into the Cumulative Layout Shift score until the browser&#8217;s <code>onload</code> event fires (or a <a href="https://akamai.github.io/boomerang/BOOMR.plugins.SPA.html">similar event</a> for Single Page Apps).</p>
<p>These <code>onload</code> (and SPA &#8220;load&#8221;) events are measuring until a pre-defined and consistent phase of the page load.  Once that phase has been reached (e.g. most/all content is loaded), the Cumulative Layout Shift score accumulated from the start of the navigation through that event is finalized.</p>
<p>This type of &#8220;<em>load-limited</em>&#8221; Cumulative Layout Shift is often what pure synthetic tools such as Lighthouse or WebPagetest measure, in the absence of any user interactions on the page.  RUM tools, such as Boomerang.js also generally send their beacon right after the load events, so will stop their CLS measurements there.</p>
<p>Alternatively, CLS can be measured beyond just the &#8220;load&#8221; event, continually accumulating as the user interacts on the page.  Layout shifts that happen after the result of scrolling (e.g. dynamic ad loads) can be especially frustrating users.  It&#8217;s worthwhile measuring the page&#8217;s entire lifetime CLS if you can.  You would generally accumulate layout shifts until something like the <a href="https://wicg.github.io/layout-instability/#cumulative-layout-shift"><code>visibilitychange</code></a> event (when the page goes hidden or unloads).</p>
<p>As a result, these &#8220;<em>page lifetime</em>&#8221; CLS scores will likely be higher than &#8220;load-limited&#8221; CLS scores.  See the <a href="#rum-vs-synthetic">RUM vs. Synthetic</a> section for more details on why different tools may report a different CLS.</p>
<p>If your page is a <a href="#spas">Single Page App (SPA)</a>, it&#8217;s probably best to &#8220;restart&#8221; the Cumulative Layout Shift score each time an in-page (&#8220;Soft&#8221;) SPA navigation starts.  This way the score will reflect each view change and will not just keep growing indefinitely as users interacts with the page over time.  More details in <a href="#spas">the SPA section</a>.</p>
<p><a name="spas"></a></p>
<h3>Single Page Apps (SPAs)</h3>
<p>Measuring the user experience in a Single Page App (SPA) is a unique challenge. SPAs rewrite and may completely change the DOM and visuals as the user navigates throughout a website.</p>
<p>For example, when <a href="https://github.com/akamai/boomerang">Boomerang</a> is on a SPA website with <a href="https://akamai.github.io/boomerang/BOOMR.plugins.SPA.html">SPA monitoring enabled</a>, it takes additional steps to measure the page&#8217;s performance and user experience:</p>
<ul>
<li>Instead of waiting for just the <code>onload</code> event to gather performance data, it waits for the dynamic visual content to be fetched.  This is called a &#8220;<a href="https://akamai.github.io/boomerang/BOOMR.plugins.SPA.html#toc2__anchor">SPA Hard Navigation</a>&#8220;.</li>
<li>Boomerang monitors for state and view changes from the SPA framework as the user clicks around, and tracks the resources fetched as part of a &#8220;SPA Soft Navigation&#8221;</li>
</ul>
<p>Both types of SPA navigations can shift content around on the page, <em>potentially</em> causing unexpected layout shifts.  The <a href="#how-to-measure-it">definition</a> of Cumulative Layout Shift actually <em>excludes</em> content changes right after direct user input such as clicks (since those types of changes to the view are intentional and <em>expected</em> by the user), but additional dynamic content (ads, widgets) after the initial shifts may be unexpected and frustrating.</p>
<p>Since the <code>onload</code> event in SPAs <a href="https://www.slideshare.net/nicjansma/measuring-the-performance-of-single-page-applications">doesn&#8217;t matter as much</a>, it&#8217;s worthwhile to keep accumulating the Cumulative Layout Score beyond just <code>onload</code>.  For example, Boomerang in SPA mode measures CLS up to the end of the SPA Hard Navigation (when all dynamic content has loaded), when it sends its beacon.</p>
<p>After the SPA Hard Navigation, it&#8217;s also useful to know about the user experience during subsequent Soft Navigations.  Resetting the CLS value for each Soft Navigation lets you understand how each individual view change affects the user experience.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-spas.svg" alt="CLS with SPA Navigations" /></p>
<p>Not all measurement tools will be able to split out CLS by Soft Navigation.  For example, the Chrome User Experience (CrUX) data measures <em>all</em> layout shifts until the page goes hidden (or unloads), which means the Hard navigation and all Soft navigations are combined and Cumulative Layout Shift is just the sum of all of those experiences.</p>
<p><a name="iframes"></a></p>
<h3>IFRAMEs</h3>
<p>The <a href="https://wicg.github.io/layout-instability/">Layout Instability spec</a> mentions that:</p>
<blockquote><p>
  The cumulative layout shift (CLS) score is the sum of every layout shift value that is reported inside a top-level browsing context, <strong>plus a fraction (the subframe weighting factor) of each layout shift value that is reported inside any descendant browsing context</strong>.
</p></blockquote>
<p>and</p>
<blockquote><p>
  The <strong>subframe weighting factor</strong> for a layout shift value in a child browsing context is the fraction of the top-level viewport that is occupied by the viewport of the child browsing context.
</p></blockquote>
<p>In other words, shifts in IFRAMEs should affect the top-level document&#8217;s CLS score.</p>
<p>This seems logical, right? IFRAMEs that are in the viewport also have the chance to shift visible content.  End-users don&#8217;t necessarily know which content is in a frame versus the top-level page, so IFRAME layout shifts should be able to affect the top-level document&#8217;s Cumulative Layout Shift Score.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-frames.svg" alt="CLS In IFRAMEs" class="blog-screenshot" style="width: 500px" />
</div>
<p>In the above image, let&#8217;s pretend the content in the blue box is in an <code>&lt;iframe&gt;</code> taking approximately 50% of the viewport.  If an <em>Annoying Ad</em> pops it, it may cause a Layout Shift with a <a href="#definition">value</a> of <code>0.10</code> within the IFRAME itself.  That layout shift could theoretically affect its&#8217; parent&#8217;s Cumulative Layout Shift as well.  Since the IFRAME is 50% of the viewport of its parent, the parent&#8217;s Cumulative Layout Shift core would increase by <code>0.05</code>.</p>
<p>Here&#8217;s the complication:</p>
<ul>
<li>While the <a href="https://wicg.github.io/layout-instability/">Layout Instability spec</a> proposes this behavior, as of October 2020, IFRAME layout shifts <strong>do not</strong> affect the Cumulative Layout Shift scores in <strong>most</strong> current synthetic and RUM tools</li>
<li>Chrome Lighthouse (in browser Developer Tools, as well as powering PageSpeed Insights and WebPagetest&#8217;s CLS scores) does <em>not currently</em> track Layout Shifts in frames.
<ul>
<li>While Lighthouse reports a CLS of <code>0.0</code> for shifts from IFRAMEs, it will still suggest <em>Avoid large layout shifts</em> for any shifts in those frames (<a href="https://github.com/GoogleChrome/lighthouse/issues/11500">bug</a> tracking this), which can be confusing:</li>
</ul>
</li>
</ul>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-frames.png" alt="CLS in IFRAMEs in Dev Tools" class="blog-screenshot" />
</div>
<ul>
<li>All current <a href="#rum">RUM</a> tools only track Layout Shifts in the top-level page, not accounting for any shifts from IFRAMEs
<ul>
<li>If they wanted to do this, they would need to crawl for all IFRAMEs and register <code>PerformanceObserver</code>s for those</li>
<li>It&#8217;s hard to do this for dynamically added or removed IFRAMEs</li>
<li>This cannot be done for any cross-origin IFRAMEs due to frame restrictions</li>
<li>Here&#8217;s an <a href="https://github.com/WICG/layout-instability/issues/80">issue</a> discussing this discrepancy</li>
</ul>
</li>
<li>On the other hand, Google&#8217;s Chrome User Experience (<a href="https://developers.google.com/web/tools/chrome-user-experience-report">CrUX</a>) report <em>does</em> factor in IFRAME layout shifts for CLS</li>
</ul>
<p>As a result, if you have content shifting in IFRAMEs <em>today</em>, those might (or might-not) not be affecting your top-level Cumulative Layout Shift scores, depending on what data you&#8217;re looking at.</p>
<p>In the future, if Lighthouse and other synthetic tools are <a href="https://github.com/GoogleChrome/lighthouse/issues/11500">updated</a> to include layout shifts from IFRAMEs, it is likely they will always differ from RUM CLS which <em>cannot easily</em> (or at all) get layout shifts from IFRAMEs.</p>
<p>We should strive to keep RUM CLS as close as possible to synthetic CLS, so I&#8217;ve filed an <a href="https://github.com/WICG/layout-instability/issues/80">issue</a> to try to get the same IFRAME details in RUM easily.</p>
<p><a name="how-to-improve-it"></a></p>
<h2>How to Improve It</h2>
<p>This article won&#8217;t dive too deeply into how to improve a site&#8217;s CLS score, as there is already a lot of great content from other sources, such as Google&#8217;s <a href="https://web.dev/optimize-cls/">Optimize Cumulative Layout Shift</a> article on <a href="https://web.dev">web.dev</a>.</p>
<p>However, it&#8217;s important to take time to understand and investigate <em>why</em> your CLS score is the way it is before you try to fix or improve anything.</p>
<p>The first step of improving any performance metric is making sure you understand precisely how that metric is being measured.  Whether you&#8217;re looking at <a href="#synthetic">synthetic</a> or <a href="#rum">RUM</a> data, make sure you understand how it&#8217;s being calculated and how much data the CLS value represents.</p>
<p>For example, make sure you know <a href="#when-does-it-end">how much of the page&#8217;s lifetime</a> layout shifts are being measured for, as it varies by tool.</p>
<p>If you&#8217;re looking at a CLS score from a <a href="#synthetic">synthetic</a> test like Lighthouse or WebPagetest, you can probably get a trace, or breakdown, of the content that contributed to that score.  From there, you can look for opportunities to improve.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-lighthouse-details.png" alt="CLS in Lighthouse" class="blog-screenshot" />
</div>
<p>Remember, <a href="#synthetic-developer-tools">synthetic developer tools</a> often just measure a single test case on your developer machine, and may not be representative of what your users are seeing across devices, browsers, screens and locations!  <a href="#commercial-synthetic-monitoring-tools">Synthetic monitoring tools</a> are useful for getting repeatable measurements from a lab-like environment, but won&#8217;t be representative of your real visitors.</p>
<p>If you have <a href="#rum">RUM</a> data, see if you can break down CLS by Page Group, Mobile/Desktop, and other dimensions to see which segments of your visitors are having the worst experiences.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-dashboard-vgt-cls-only.png" alt="CLS in RUM" class="blog-screenshot" style="width: 500px" />
</div>
<p>Intuitively, Cumulative Layout Shift scores may differ significantly for each page group (e.g. different types of pages such as Home, Product, or List pages) of a site.</p>
<p>Tim Vereecke <a href="">confirms</a> this is what he found for his site:</p>
<div class="blog-screenshot-wrap">
<a href="https://twitter.com/TimVereecke/status/1234945516657573888"><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-rum-tweet.png" alt="RUM Data Tweet" class="blog-screenshot" /></a>
</div>
<p>RUM data can also contain <a href="#attribution">attribution</a> that has details about which elements moved for each layout shift.</p>
<p>Once you&#8217;ve narrowed down the largest scenarios and population segments that are contributing to your CLS, you can use a local debugger or synthetic testing tools to try to reproduce the layout shifts.</p>
<p>From there, at a high-level, layout shifts occur when content is inserted <em>above</em> or <em>at</em> where the current viewport is.</p>
<p>Many times, this can be caused by:</p>
<ul>
<li>Scroll bars needing to be added by additional content (which can reduce the width of the page, which can shift content to the left or down)
<ul>
<li>For example, add <a href="https://twitter.com/TimVereecke/status/1239454788116533248"><code>html { overflow-y:scroll }</code></a> to commit to scroll bars right away</li>
</ul>
</li>
<li>CSS animations
<ul>
<li>Use <code>transform</code> properties instead</li>
</ul>
</li>
<li>Image sliders
<ul>
<li>Make sure you&#8217;re using <code>transform</code>s instead of changing dimension / placement properties</li>
</ul>
</li>
<li>Ads
<ul>
<li>If possible, define dimensions ahead of time</li>
</ul>
</li>
<li>Images without dimensions
<ul>
<li>Add <code>width</code> and <code>height</code> dimensions</li>
<li>Get a report on which images don&#8217;t have dimensions from <a href="https://github.com/w3c/webappsec-feature-policy/blob/master/policies/unsized-media.md">Feature Policy Reporting</a></li>
</ul>
</li>
<li>Content that only gets included or initialized after the user scrolls to it
<ul>
<li>Add placeholders with the correct dimensions</li>
</ul>
</li>
<li>Fonts
<ul>
<li>Unstyled fonts being drawn before the final font (which may have slightly different dimensions) can lead to layout shifts</li>
<li><code>font-display: swap</code> in conjunction with a good <a href="https://meowni.ca/font-style-matcher/">matching font fallback</a> can help</li>
</ul>
</li>
</ul>
<p>More details on the above fixes are on Google&#8217;s <a href="https://web.dev/optimize-cls/">Optimize Cumulative Layout Shift</a> page.</p>
<p>Taking a video as you load and interact with a page can highlight specific cases where CLS increases, and <a href="#synthetic-developer-tools">Chrome Developer Tools</a> has an option to see which regions shifted in real-time.</p>
<p>One note is that a lot of today&#8217;s modern performance best practices may potentially have a <em>negative</em> effect on CLS, such as lazy-loading CSS, images, fonts, etc.  When those components are loaded asynchronously, it&#8217;s possible for them to introduce layout shifts as they need to be drawn with the  proper dimensions.</p>
<p>In other cases, websites that are tuning for performance may be exposing themselves to more layout shifts unintentionally.  Besides lazy-loading, fast-loading sites optimize for a quick first-paint, to get something on-screen for the visitor&#8217;s eyes.  As additional content comes in, it may be shifting the page around significantly, even though the user may think it&#8217;s possible for them to start interacting with the site.</p>
<p>That&#8217;s why it can be important to keep an eye on CLS every time major performance changes are being considered.  There are always trade-offs between delivering content quickly and delivering it <em>too quickly</em>, where it will need to be shuffled around before the page has reached its final form.</p>
<p><a name="how-to-measure-it"></a></p>
<h2>How to Measure It</h2>
<p>CLS can be measured <a href="#synthetic">synthetically</a> (in the lab) or for real users (via <a href="#rum">RUM</a>).  Lab measurements may only capture layout shifts from a single or repeated Page Load experience, while RUM measurements will be more reflective of what real users see as they experience and interact with a site.</p>
<p><a name="rum"></a></p>
<h3>RUM</h3>
<p>Cumulative Layout Shift can be measured via the browser&#8217;s <a href="https://github.com/WICG/layout-instability">Layout Instability API</a>.  This experimental API reports individual <code>layout-shift</code> entries to any registered <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver">PerformanceObserver</a> on the page.</p>
<p>Each <code>layout-shift</code> entry represents an occurrence where an element in the viewport changes its starting position between two frames.  An element simply changing its size or being added to the DOM for the first time won&#8217;t necessarily trigger a layout shift, if it doesn&#8217;t affect other visible DOM elements in the viewport.</p>
<p>Not all layout shifts are necessarily bad.  For instance, if a user is interacting with the page, such as clicking a button in a Single Page App, they may be expecting the viewport to change.  Each <code>layout-shift</code> event has a <code>hadRecentInput</code> flag that tells whether there was input within the last 500ms of the shift.  If so, that layout shift can probably be excluded from the Cumulative Layout Shift score.</p>
<p>Inputs that trigger <code>hadRecentInput</code> are <code>mousedown</code>, <code>keydown</code>, and <code>pointerdown</code>.  Simple <code>mousemove</code> and <code>pointermove</code> events and scrolls are not counted.</p>
<p>How long should layout shifts be added to the Cumulative Layout Shift score?  That depends on how much of the user experience you&#8217;re trying to measure.</p>
<p>See <a href="#when-does-it-end">When does it End?</a> for more details.</p>
<p><a name="example-code"></a></p>
<h4>Example Code</h4>
<p>There are many open-source libraries that capture CLS today, such as <a href="https://github.com/akamai/boomerang">Boomerang</a> or the <a href="https://github.com/GoogleChrome/web-vitals">web-vitals</a> library.</p>
<p>See the <a href="#open-source-rum">open-source RUM</a> section for more examples.</p>
<p>If you want to experiment with the raw layout shifts via the <a href="https://github.com/WICG/layout-instability">Layout Instability API</a>, the first thing is to create a <code>PerformanceObserver</code>:</p>
<pre><code class="js">var clsScore = 0;

try {
  var po = new PerformanceObserver(function(list) {
    var entries = list.getEntries();
    for (var i = 0; i &lt; entries.length; i++) {
      if (!entries[i].hadRecentInput) {
        clsScore += entries[i].value;
      }
    }
  });

  po.observe({type: 'layout-shift', buffered: true});
} catch (e) {
  // not supported
}
</code></pre>
<p><code>buffered:true</code> is used to gather any <code>layout-shift</code>s that occurred before the <code>PerformanceObserver</code> was initialized.  This is especially useful for scripts, libraries, or third-party analytics that load asynchronously on the page.</p>
<p>Each callback to the above <code>PerformanceObserver</code> will have a list of entries, via <code>list.getEntries()</code>.</p>
<p>Each entry is a <code>LayoutShift</code> object:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-object.png" alt="LayoutShift Object" /></p>
<p>Here are its attributes:</p>
<ul>
<li><code>duration</code> will always be <code>0</code></li>
<li><code>entryType</code> will always be <code>layout-shift</code></li>
<li><code>hadRecentInput</code> is whether there was user input in the last 500ms</li>
<li><code>lastInputTime</code> is the time of the most recent input</li>
<li><code>name</code> should be <code>layout-shift</code> (though Chrome appears to currently put the empty string <code>""</code>)</li>
<li><code>sources</code> is a sampling of attribution for what caused the layout shift (see <a href="#attribution">attribution</a> below)</li>
<li><code>startTime</code> is the high resolution timestamp when the shift occurred</li>
<li><code>value</code> is the layout shift contribution (see <a href="#definition">definition</a> above)</li>
</ul>
<p>If you&#8217;re just interested in calculating the Cumulative Layout Score, you can add the <code>value</code> of each <code>layout-shift</code> as long as it doesn&#8217;t have <code>hadRecentInput</code> set.</p>
<p>For more details on the shifts, you could capture the <code>sources</code> to see top contributors.</p>
<p>There are a few edge-cases to be aware of, so it&#8217;s best to look at one of the example <a href="#open-source-rum">libraries</a> for details.</p>
<p>If you want to browse the web and watch CLS entries as they happen live, you can try this <a href="https://github.com/nicjansma/tampermonkey/blob/master/cls.js">simple script</a> for <a href="https://www.tampermonkey.net/">Tampermonkey</a>, or the <a href="https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?hl=en">Web Vitals Chrome Extension</a>.</p>
<p><a name="attribution"></a></p>
<h4>Attribution</h4>
<p>So, your site has a CLS score of <code>0.3</code>.  Great!?  Now what?</p>
<p>You probably want to know <em>why</em>.  Besides the raw <code>value</code> that each <code>layout-shift</code> generates, it has a <code>sources</code> attribute that can give an indication of the top elements that shifted.</p>
<p>The <code>sources</code> attribute of the <code>layout-shift</code> entry is sampling of up to five DOM elements whose layout shifts most substantially contributed to the layout shift value:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-object-sources.png" alt="LayoutShift Object" /></p>
<p>Note <code>sources</code> are the <em>elements that shifted</em>, not necessarily the element(s) that <em>caused the shift</em>.  For example, an element that is inserted above the current viewport could cause elements within the viewport to shift (and contribute to the CLS score), though the inserted element itself may not be in the <code>sources</code> list.</p>
<p>Attribution via <code>sources</code> is only available in Chrome 84+.</p>
<p><a name="fallbacks"></a></p>
<h4>Fallbacks</h4>
<p>Unfortunately, it would be challenging to measure Layout Shifts without the <a href="https://github.com/WICG/layout-instability">Layout Instability API</a>, which today is only <a href="https://caniuse.com/mdn-api_layoutshift">supported</a> in Blink-based browsers.</p>
<p>Theoretically, a polyfill might be able to calculate the placement and dimensions of every element within the viewport every frame and how they change&#8230; seems like that would be rather inefficient to do in JavaScript.</p>
<p>Maybe someone will prove me wrong!</p>
<p>For now, it&#8217;s best to capture CLS via browsers that support the Layout Instability API and use other spot checks to make sure other browsers have similar layout behavior.</p>
<p><a name="browser-support"></a></p>
<h4>Browser Support</h4>
<p><a href="https://caniuse.com/mdn-api_layoutshift">CanIUse.com</a> tracks browser support for the Layout Instability API.</p>
<p>As of 2020-10, only Blink-based browsers support it, which is about 69% of global market share:</p>
<ul>
<li>Chrome 77+</li>
<li>Opera</li>
<li>Edge 80+ (based on Chromium &#8212; no support in EdgeHTML)</li>
</ul>
<p>Note that Chrome has done a great job <a href="https://chromium.googlesource.com/chromium/src/+/master/docs/speed/metrics_changelog/cls.md">documenting</a> any changes they&#8217;ve made to the Layout Instability API or CLS measurement.</p>
<p>Based on recent feedback to the <a href="https://github.com/WICG/layout-instability/issues/">Layout Instability GitHub Issues Page</a> it seems that Mozilla engineers are reviewing the specification (but have not yet shown a public commitment to implementing it).</p>
<p><a name="gotchas"></a></p>
<h4>Gotchas</h4>
<p>When measuring and reporting on Cumulative Layout Shift, there are a lot of gotchas and caveats to understand:</p>
<ul>
<li>Layout Shifts are affected by the viewport and size of the viewport.  Only content that is within the viewport is visible.  Shifts that happen below the fold will have no effect on Cumulative Layout Shift:</li>
</ul>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-outside-viewport.svg" alt="CLS in boomerang.js" class="blog-screenshot" style="width: 300px" />
</div>
<ul>
<li>Layout Shifts <em>may</em> <a href="#real-world-data">happen more frequently</a> on mobile vs. desktop due to responsive layouts that are more vertical, with a lot of dynamically added content from scrolling.  When analyzing CLS data, investigate Desktop and Mobile layouts separately.</li>
<li>The point at which you &#8220;stop&#8221; accumulating layout shifts into the Cumulative Layout Shift score matters, and different measurement tools may stop at different points. See the <a href="#when-does-it-end">When does it End?</a> section for more details.</li>
<li>There are bugs (with developer tools) and inconsistencies (between synthetic and RUM) with measuring layout shifts happening in <a href="#iframes">IFRAMEs</a>.</li>
<li>There are some known canonical cases that might provide high CLS values but still present a good user experience.  For example, some types of image carousels (not using <code>transform</code>) might cause a large shift every time the image changes.</li>
<li>CLS can&#8217;t distinguish elements that don&#8217;t paint any content (but have non-zero fixed size), see <a href="https://github.com/WICG/layout-instability/issues/45">this discussion</a>.</li>
<li>Anytime there&#8217;s a new performance metric, there will be places it breaks down or doesn&#8217;t work well.  It&#8217;s useful to browse (and possibly subscribe) to the <a href="https://github.com/WICG/layout-instability/issues/">Layout Instability&#8217;s Issue Page</a> if you&#8217;re interested in this metric.</li>
</ul>
<p><a name="open-source-rum"></a></p>
<h4>Open-Source / Free RUM</h4>
<p>Cumulative Layout Shift is already supported in many popular open-source JavaScript libraries:</p>
<h5><a href="https://github.com/akamai/boomerang">boomerang.js</a></h5>
<p><a href="https://github.com/akamai/boomerang">boomerang.js</a> is an open-source performance monitoring JavaScript library.  (I am one of its authors).</p>
<p>It has support for <a href="https://akamai.github.io/boomerang/BOOMR.plugins.Continuity.html#toc8__anchor">Cumulative Layout Shift</a>, which was added as part of the <a href="https://akamai.github.io/boomerang/BOOMR.plugins.Continuity.html">Continuity plugin</a> in version <a href="https://akamai.github.io/boomerang/tutorial-version-history.html">1.700.0</a>.</p>
<p>CLS is measured up to the point the beacon is sent.  For traditional apps, this is right after the <code>onload</code> event.  For Single Page Apps (SPAs), CLS is measured up to the SPA Hard beacon is sent, which can include dynamically loaded content.  CLS is also measured for each SPA Soft navigation.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-rum-boomerang.png" alt="CLS in boomerang.js" class="blog-screenshot" />
</div>
<h5><a href="https://zizzamia.github.io/perfume/">perfume.js</a></h5>
<p><a href="https://zizzamia.github.io/perfume/">perfume.js</a> is an open-source web performance monitoring JavaScript library that reports field data back to your favorite analytics tool.</p>
<p>Perfume added support for <a href="https://zizzamia.github.io/perfume/#/cumulative-layout-shift/">Cumulative Layout Shift</a> in version <a href="https://github.com/Zizzamia/perfume.js/releases/tag/v4.8.0">4.8.0</a>.</p>
<p>Perfume is measured up to two points: when First Input Delay happens (as <code>cls</code>), and when the page&#8217;s lifecycle state changes to hidden (as <code>clsFinal</code>).</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-rum-perfume.png" alt="CLS in perfume.js" class="blog-screenshot" />
</div>
<h5><a href="https://github.com/GoogleChrome/web-vitals">web-vitals</a> from Google</h5>
<p>Google&#8217;s official <a href="https://github.com/GoogleChrome/web-vitals">web-vitals</a> open-source JavaScript library measures all of Google&#8217;s Web Vitals metrics, in a way that accurately matches how they&#8217;re measured by Chrome and reported to other Google tools.</p>
<p>web-vitals can measure CLS throughout the page load process, and will also report CLS as the page is being unloaded or backgrounded.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-rum-webvitals.png" alt="CLS in Web Vitals" class="blog-screenshot" />
</div>
<h5><a href="https://developers.google.com/web/tools/chrome-user-experience-report">CrUX</a></h5>
<p>The Chrome User Experience (<a href="https://developers.google.com/web/tools/chrome-user-experience-report">CrUX</a>) Report provides real-user monitoring (RUM) data for Chrome users as they navigate across the web.</p>
<p>Its data is available via <a href="https://developers.google.com/speed/pagespeed/insights">PageSpeed Insights</a> and in raw form via the <a href="https://bigquery.cloud.google.com/dataset/chrome-ux-report:all">Public Google Big Query Project</a>.  It&#8217;s data is also used in <a href="https://support.google.com/webmasters/answer/9205520">Google Search Console&#8217;s Core Web Vitals report</a>.</p>
<p>If you&#8217;re interested in setting up a CrUX report for your own domain, you can <a href="https://web.dev/chrome-ux-report-data-studio-dashboard/">follow this guide</a>.</p>
<p>CrUX always reports on the last 28 days of data.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-crux-vgt.png" alt="CLS in CrUX" class="blog-screenshot" />
</div>
<p><a name="commercial-rum"></a></p>
<h4>Commercial RUM</h4>
<p>Commercial Real User Monitoring (RUM) providers measure the experiences of real-world page loads.  They can aggregate millions (or billions) of page loads into dashboards where you can slice and dice the data.</p>
<h5><a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp">Akamai mPulse</a></h5>
<p><a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp">Akamai mPulse</a> (which I work on) has added full support for Cumulative Layout Shift (and other Web Vitals):</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-dashboard-2-vgt.png" alt="CLS in mPulse" class="blog-screenshot" />
</div>
<h5><a href="https://speedcurve.com/">SpeedCurve&#8217;s LUX</a></h5>
<p><a href="https://speedcurve.com">SpeedCurve</a>&#8216;s <em>LUX</em> RUM tool has full support for Web Vitals, including CLS:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-speedcurve.png" alt="CLS in SpeedCurve" class="blog-screenshot" />
</div>
<h5><a href="https://blog.newrelic.com/product-news/w3c-context-trace-cumulative-layout-shift-measurement/">New Relic Browser</a></h5>
<p><a href="https://blog.newrelic.com/product-news/w3c-context-trace-cumulative-layout-shift-measurement/">New Relic Browser</a> is New Relic&#8217;s RUM monitoring, and has added support for Cumulative Layout Shift in Browser Agent <a href="https://blog.newrelic.com/product-news/w3c-context-trace-cumulative-layout-shift-measurement/">v1177</a>.</p>
<h5><a href="https://requestmetrics.com/">RequestMetrics</a></h5>
<p><a href="https://requestmetrics.com/">RequestMetrics</a> provides website performance monitoring and has <a href="https://requestmetrics.com/web-performance/cumulative-layout-shift">support for Web Vitals</a>:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-rum-requestmetrics.png" alt="CLS in RequestMetrics" class="blog-screenshot" />
</div>
<p><a name="synthetic"></a></p>
<h3>Synthetic</h3>
<p>Synthetic tests are run in a lab-like environment or on developer machines.  In general, synthetic tests allow for repeated testing of a URL in a consistent environment.</p>
<p>Synthetic <a href="#synthetic-developer-tools"><em>developer tools</em></a> take traces of <em>individual</em> page loads, and are fantastic for diving into and fixing CLS scores.</p>
<p>Synthetic <a href="#commercial-synthetic-monitoring-tools"><em>monitoring tools</em></a> help measure and monitor a URL (or set of URLs) over time, to ensure performance metrics don&#8217;t regress.</p>
<p><a name="synthetic-developer-tools"></a></p>
<h4>Free Synthetic Developer Tools</h4>
<p>The following free synthetic developer tools can help you dive into individual URLs to understand what is causing layout shifts and how to fix them.</p>
<h5><a href="https://developers.google.com/web/tools/chrome-devtools">Chrome Developer Tools</a> and Lighthouse</h5>
<p><a href="https://developers.google.com/web/tools/chrome-devtools">Chrome Developer Tools</a> (and the <a href="https://developers.google.com/web/tools/lighthouse">Lighthouse</a> browser extension/CLI) provide a wealth of information about Cumulative Layout Shift and the individual layout shifts that go into the score.</p>
<p>Within the Chrome Developer Tools, you have access to Lighthouse performance audits.  Head to the <em>Lighthouse</em> tab, and run a <em>Performance Audit</em>:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-1.png" alt="CLS in Chrome Developer Tools" class="blog-screenshot" />
</div>
<p>In addition to the top-level Cumulative Layout Shift score, you can get a breakdown of the contributing layout shifts:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-2.png" alt="CLS in Chrome Developer Tools - Contributions" class="blog-screenshot" />
</div>
<p>(Note there&#8217;s a <a href="https://github.com/GoogleChrome/lighthouse/issues/11500">bug</a> where <a href="#iframes">IFRAME shifts</a> aren&#8217;t accounted for in the score but are shown in the breakdown)</p>
<p>If you click on <em>View Original Trace</em> in the Audit, it will automatically open the <em>Performance</em> tab:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-7.png" alt="CLS in Chrome Developer Tools - Performance Tab" class="blog-screenshot" />
</div>
<p>Within the <em>Performance</em> tab, there is now a new <em>Experience</em> row in the timeline that highlights individual layout shifts and their details:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-6.png" alt="CLS in Chrome Developer Tools - Experience Row" class="blog-screenshot" style="width: 482px" />
</div>
<p>Outside of taking a trace, you can browse while getting visual indicators that layout shifts are happening.</p>
<p>To do this, open the <em>Rendering</em> option in <em>More Tools</em>:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-3.png" alt="CLS in Chrome Developer Tools - Rendering Options" class="blog-screenshot" style="width: 382px" />
</div>
<p>Then enable <em>Layout Shift Regions</em>:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-4.png" alt="CLS in Chrome Developer Tools - Layout Shift Regions" class="blog-screenshot" />
</div>
<p>And when you browse, you&#8217;ll see light-blue highlights of content that had layout shifts:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dev-tools-5.gif" alt="CLS in Chrome Developer Tools - Highlights" class="blog-screenshot" style="width: 250px" />
</div>
<p>All of these tools can be used to help find, fix, and verify layout shifts.</p>
<h5><a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a></h5>
<p><a href="https://developers.google.com/speed/pagespeed/insights/">PageSpeed Insights</a> is a free tool from Google.  It analyzes the content of a web page, then generates suggestions to make that page faster.</p>
<p>Behind the scenes, it runs Lighthouse as the analysis engine, so you&#8217;ll get similar results.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-pagespeedinsights.png" alt="CLS in PageSpeed Insights" class="blog-screenshot" />
</div>
<h5><a href="https://webpagetest.org/">WebPagetest</a></h5>
<p><a href="https://webpagetest.org/">WebPagetest.org</a>, the gold standard in free synthetic performance testing, has a <em>Web Vitals</em> section that calculates CLS (for Chrome browser tests).</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-wpt-vgt.png" alt="CLS in WebPagetest" class="blog-screenshot" />
</div>
<h5><a href="https://layoutstability.rocks/">layoutstability.rocks</a></h5>
<p><a href="https://layoutstability.rocks/">layoutstability.rocks</a> provides a simple form where you can enter a URL to get the CLS of a page:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-layoutstability-rocks-vgt.png" alt="CLS in LayoutStability.rocks" class="blog-screenshot" />
</div>
<h5><a href="https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?hl=en">Web Vitals Chrome Extension</a></h5>
<p>The <a href="https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?hl=en">Web Vitals Chrome Extension</a> shows a page&#8217;s Largest Contentful Paint (LCP) in the extension bar, plus a popup with LCP, First Input Delay (FID) and Cumulative Layout Shift.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-web-vitals-extension.png" alt="CLS Web Vitals Chrome extension" class="blog-screenshot" />
</div>
<p><a name="commercial-synthetic-monitoring-tools"></a></p>
<h4>Commercial Synthetic Monitoring Tools</h4>
<p>There are several commercial synthetic performance monitoring solutions that help measure Cumulative Layout Shift over time.  Here is a sample of some of the best:</p>
<h5><a href="https://speedcurve.com">SpeedCurve</a></h5>
<p><a href="https://speedcurve.com">SpeedCurve</a> has full support for Web Vitals, including CLS:</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-speedcurve.png" alt="CLS in SpeedCurve" class="blog-screenshot" />
</div>
<h5><a href="https://calibreapp.com/">Calibre</a></h5>
<p><a href="https://calibreapp.com/">Calibre</a> is a synthetic performance monitoring product, and has full support for Web Vitals, <a href="https://calibreapp.com/docs/metrics/cumulative-layout-shift">including CLS</a>.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-calibre.png" alt="CLS in Calibre" class="blog-screenshot" />
</div>
<h4><a href="https://rigor.com">Rigor</a></h4>
<p><a href="https://rigor.com">Rigor</a> offers synthetic performance monitoring and supports <a href="https://rigor.com/blog/what-are-web-vitals/">Web Vitals</a>.</p>
<h5><a href="https://www.dareboost.com/">DareBoost</a></h5>
<p><a href="https://www.dareboost.com/">DareBoost</a> is a synthetic performance monitoring and website analysis product, and has <a href="https://blog.dareboost.com/en/2020/09/cumulative-layout-shift-visual-instability/">recently added support for CLS</a>.</p>
<div class="blog-screenshot-wrap">
<img src="https://o.nicj.net/wp-content/uploads/2020/10/cls-synthetic-dareboost.png" alt="CLS in Dareboost" class="blog-screenshot" />
</div>
<p><a name="rum-vs-synthetic"></a></p>
<h3>Why does CLS differ between Synthetic and RUM?</h3>
<p><em>(or even between tools?)</em></p>
<p>CLS scores reported by synthetic tests (such as Lighthouse, WebPagetest or PageSpeed Insights) may be different than CLS scores coming from real-world (RUM) data.  RUM libraries (such as  <code>boomerang.js</code> or <code>web-vitals.js</code>) may also report different CLS scores than browser data (such as from the Chrome User Experience (CrUX) report).</p>
<p>Here are some reasons why:</p>
<ul>
<li>Each tool may measure layout shifts until a different &#8220;end&#8221; point
<ul>
<li>See the <a href="#when-does-it-end">When does it End?</a> section for more details</li>
<li>This is especially important for <a href="#spas">Single Page Apps</a>.  For example, the Chrome User Experience (CrUX) data measures until the visibility state changes (i.e. when the page goes <code>hidden</code> or unloads), while other RUM tools (like Boomerang) more frequently measure just up to the Page Load event, and each individual in-page Soft Navigation separately</li>
</ul>
</li>
<li>A single testcase (run) of a synthetic tool (e.g. one Lighthouse run) may report dramatically different results than <a href="#real-world-data">real-world</a> aggregated data (e.g. RUM or CrUX)</li>
<li>Aggregated data may be reflective of a specific date or period in time, while other tools may focus on other date ranges.  For example:
<ul>
<li>CrUX always shows the last <a href="https://developers.google.com/web/tools/chrome-user-experience-report/api/reference">28 days</a></li>
<li>mPulse RUM can report any period from last 5 minutes to up to 18 months ago</li>
</ul>
</li>
<li>Google generally recommends measuring CLS at the 75th percentile across mobile and desktop devices.  Make sure your tool has the capability of measuring different percentiles (and not just averages or only the median)</li>
<li>Some tools throw out, or limit CLS scores over a certain value.  For example:
<ul>
<li><a href="https://github.com/WICG/layout-instability/issues/4#issuecomment-503280704">CrUX caps values at 10</a></li>
<li>mPulse caps values at 20</li>
</ul>
</li>
<li>While <em>today</em>, layout shifts are not counted from <a href="#iframes">IFRAMEs</a>, the <a href="https://wicg.github.io/layout-instability/">spec</a> and <a href="https://github.com/GoogleChrome/lighthouse/issues/11500">synthetic tools</a> suggest they should affect CLS.  RUM tools <a href="#iframes">may not be able to easily get</a> layout shifts from IFRAMEs, causing RUM to under-report versus synthetic.</li>
</ul>
<p><a name="real-world-data"></a></p>
<h2>Real World Data</h2>
<p>What do Cumulative Layout Shift scores look like in the real world?</p>
<p>I&#8217;ve written a companion post to this titled <a href="https://nicj.net/cumulative-layout-shift-in-the-real-world/">Cumulative Layout Shift in the Real World</a>, which dives into CLS data by looking at data from Akamai mPulse&#8217;s RUM.</p>
<p><a href="https://nicj.net/cumulative-layout-shift-in-the-real-world/">Head there</a> for insights into how Cumulative Layout Shift scores correlate with business metrics, bounce rates, load times, rage clicks, and more!</p>
<p><a name="whats-next"></a></p>
<h2>What&#8217;s Next?</h2>
<p>Cumulative Layout Shift is a relatively new metric, and it is still evolving.  You can see some of the discussions happening in its <a href="https://github.com/WICG/layout-instability/issues">GitHub issue tracker</a> as well as through discussions in the <a href="https://www.w3.org/webperf/">Web Performance Working Group</a>.</p>
<p>While it is only supported in Chromium-based browsers today, we hope that it is being considered for other engines as we&#8217;ve seen that the metric can provide a <a href="#real-world-data">good measurement of user experience</a> and correlates with other business metrics.</p>
<p>However, there is still a lot of work to be done to better understand where it&#8217;s working, <a href="#gotchas">when it doesn&#8217;t work</a>, and how we should improve its usefulness over time.  As more sites start paying attention to CLS, we will probably learn about its good and bad uses.</p>
<p>Will it be included as part of Google&#8217;s Core Vitals metrics next year?  We&#8217;ll see!  They&#8217;ve indicated that they&#8217;ll evaluate and evolve the primary metrics each year as they gather feedback.</p>
<p><a name="references"></a></p>
<h2>References</h2>
<ul>
<li><a href="https://wicg.github.io/layout-instability/">Layout Instability API</a> (WICG Draft)</li>
<li><a href="https://github.com/WICG/layout-instability/issues/">Layout Instability API GitHub Issues</a></li>
<li><a href="https://youtu.be/zIJuY-JCjqw">Understanding Cumulative Layout Shift</a> by Annie Sullivan and Steve Kobes</li>
<li><a href="https://web.dev/optimize-cls/">Optimize Cumulative Layout Shift</a></li>
<li><a href="https://web.dev/cls/">Cumulative Layout Shift @ web.dev</a></li>
<li><a href="https://blog.dareboost.com/en/2020/09/cumulative-layout-shift-visual-instability/">Cumulative Layout Shift, The Layout Instability Metric @ dareboost.com</a></li>
</ul>
<h2>Thanks</h2>
<p>A few words of thanks&#8230;</p>
<p>Thanks to the <a href="https://github.com/akamai/boomerang">Boomerang</a> development team (funded by Akamai as part of mPulse), and other mPulse and Akamai employees, and specifically <a href="https://github.com/ashenoy2014">Avinash Shenoy</a> for his work adding CLS support to Boomerang.</p>
<p>The Google engineering team has put a lot of thought and research into Web Vitals, the Layout Shift API and Cumulative Layout Shift scores.  Kudos to them for driving for a new performance metric that helps reflect the user experience.</p>
<h2>Updates</h2>
<ul>
<li>2020-10-21: Updated the <a href="#iframes">IFRAMEs</a> section to note that CrUX does factor in IFRAME layout shifts into their CLS scores</li>
</ul><p>The post <a href="https://nicj.net/cumulative-layout-shift-in-practice/" target="_blank">Cumulative Layout Shift in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/cumulative-layout-shift-in-practice/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>Check Yourself Before You Wreck Yourself: Auditing and Improving the Performance of Boomerang</title>
		<link>https://nicj.net/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang/</link>
					<comments>https://nicj.net/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 04 Feb 2020 19:03:02 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2511</guid>

					<description><![CDATA[<p>At FOSDEM 2020, I talked about our recent Boomerang Performance Audit and the improvements we&#8217;ve made since: Here&#8217;s the description: Boomerang is an open-source Real User Monitoring (RUM) JavaScript library used by thousands of websites to measure their visitor&#8217;s experiences. The developers behind Boomerang take pride in building a reliable and performant third-party library that [&#8230;]</p>
<p>The post <a href="https://nicj.net/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang/" target="_blank">Check Yourself Before You Wreck Yourself: Auditing and Improving the Performance of Boomerang</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>At <a href="https://fosdem.org/2020/schedule/event/webperf_boomerang_optimisation/">FOSDEM 2020</a>, I talked about our recent Boomerang Performance Audit and the improvements we&#8217;ve made since:</p>
<p><a href="https://youtu.be/p0hCBB9N2qI"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2020/02/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang-slides-840x472.png" alt="Check Yourself Before You Wreck Yourself: Auditing and Improving the Performance of Boomerang on YouTube" width="840" height="472" class="aligncenter size-large wp-image-2509" srcset="https://o.nicj.net/wp-content/uploads/2020/02/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang-slides-840x472.png 840w, https://o.nicj.net/wp-content/uploads/2020/02/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang-slides-400x225.png 400w, https://o.nicj.net/wp-content/uploads/2020/02/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang-slides-768x431.png 768w, https://o.nicj.net/wp-content/uploads/2020/02/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang-slides.png 1548w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>Here&#8217;s the description:</p>
<div style="margin-left: 20px;"><i><br />
Boomerang is an open-source Real User Monitoring (RUM) JavaScript library used by thousands of websites to measure their visitor&#8217;s experiences. The developers behind Boomerang take pride in building a reliable and performant third-party library that everyone can use without being concerned about its measurements affecting their site. We recently performed and shared an audit of Boomerang&#8217;s performance, to help communicate its &#8220;cost of doing business&#8221;, and in doing so we found several areas of code that we wanted to improve. We&#8217;ll discuss how we performed the audit, some of the improvements we&#8217;ve made, how we&#8217;re testing and validating our changes, and the real-time telemetry we capture for our library to ensure we&#8217;re having as little of an impact as possible on the sites we&#8217;re included on.</p>
<p>Boomerang is an open-source Real User Monitoring (RUM) JavaScript library used by thousands of websites to measure their visitor&#8217;s experiences.</p>
<p>Boomerang runs on billions of page loads a day, either via the open-source library or as part of Akamai&#8217;s mPulse RUM service. The developers behind Boomerang take pride in building a reliable and performant third-party library that everyone can use without being concerned about its measurements affecting their site.</p>
<p>Recently, we performed and shared an audit of Boomerang&#8217;s performance, to help communicate the &#8220;cost of doing business&#8221; of including Boomerang on a page while it takes its measurements. In doing the audit, we found several areas of code that we wanted to improve and have been making continuous improvements ever since. We&#8217;ve taken ideas and contributions from the OSS community, and have built a Performance Lab that helps &#8220;lock in&#8221; our improvements by continuously measuring the metrics that are important to us.</p>
<p>We&#8217;ll discuss how we performed the audit, some of the improvements we&#8217;ve made, how we&#8217;re testing and validating our changes, and the real-time telemetry we capture on our library to ensure we&#8217;re having as little of an impact as possible on the sites we&#8217;re included on.</i></div>
<p>You can watch the presentation on <a href="https://youtu.be/p0hCBB9N2qI">YouTube</a> or <a href="https://docs.google.com/presentation/d/1oB-AHPEB1lmQkV23ABu7rTAvR2egD3JqPZtsT5IFJwU/edit?usp=sharing">catch the slides</a>.</p><p>The post <a href="https://nicj.net/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang/" target="_blank">Check Yourself Before You Wreck Yourself: Auditing and Improving the Performance of Boomerang</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/check-yourself-before-you-wreck-yourself-auditing-and-improving-the-performance-of-boomerang/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Boomerang Performance Update</title>
		<link>https://nicj.net/boomerang-performance-update/</link>
					<comments>https://nicj.net/boomerang-performance-update/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 06 Dec 2019 00:20:33 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2471</guid>

					<description><![CDATA[<p>Table Of Contents Introduction Boomerang Loader Snippet Improvements ResourceTiming Compression Optimization Debug Messages Minification Cookie Size Cookie Access MD5 plugin SPA plugin Brotli Performance Test Suite Next Boomerang is an open-source JavaScript library that measures the page load experience of real users, commonly called RUM (Real User Measurement). Boomerang is used by thousands of websites [&#8230;]</p>
<p>The post <a href="https://nicj.net/boomerang-performance-update/" target="_blank">Boomerang Performance Update</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table Of Contents</h2>
<ol>
<li><a href="#boomerang-performance-update-intro">Introduction</a></li>
<li><a href="#boomerang-performance-update-loader-snippet-improvements">Boomerang Loader Snippet Improvements</a></li>
<li><a href="#boomerang-performance-update-resourcetiming-compression-optimization">ResourceTiming Compression Optimization</a></li>
<li><a href="#boomerang-performance-update-debug-messages">Debug Messages</a></li>
<li><a href="#boomerang-performance-update-minification">Minification</a></li>
<li><a href="#boomerang-performance-update-cookie-size">Cookie Size</a></li>
<li><a href="#boomerang-performance-update-cookie-access">Cookie Access</a></li>
<li><a href="#boomerang-performance-update-md5-plugin">MD5 plugin</a></li>
<li><a href="#boomerang-performance-update-spa-plugin">SPA plugin</a></li>
<li><a href="#boomerang-performance-update-brotli">Brotli</a></li>
<li><a href="#boomerang-performance-update-performance-test-suite">Performance Test Suite</a></li>
<li><a href="#boomerang-performance-update-next">Next</a></li>
</ol>
<p><a name="boomerang-performance-update-intro"></a></p>
<p>Boomerang is an <a href="https://github.com/SOASTA/boomerang">open-source</a> JavaScript library that measures the page load experience of real users, commonly called RUM (Real User Measurement).</p>
<p>Boomerang is used by thousands of websites large and small, either via the open-source library or as part of Akamai&#8217;s <a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp">mPulse</a> RUM service.  With Boomerang running on billions of page loads a day, the developers behind Boomerang take pride in building a reliable and performant third-party library that everyone can use without being concerned about its measurements affecting their site.</p>
<p>Two years ago, we performed an <a href="https://calendar.perfplanet.com/2017/an-audit-of-boomerangs-performance/">audit of Boomerang&#8217;s performance</a>, to help communicate the &#8220;cost&#8221; of including Boomerang on a page as a third-party script.  In doing the audit, we found several areas of code that we wanted to improve, and have been working steadily to make it better for our customers.</p>
<p>This article highlights some of the improvements we&#8217;ve made in the last two years, and what we still want to work on next!</p>
<p><a name="boomerang-performance-update-loader-snippet-improvements"></a></p>
<h2>Boomerang Loader Snippet Improvements</h2>
<p>We recommended loading Boomerang via our custom <a href="https://akamai.github.io/boomerang/tutorial-loader-snippet.html">loader snippet</a>.  The snippet ensures Boomerang loads asynchronously and non-blocking, so it won&#8217;t affect the Page Load time.  Version 10 (v10) of this snippet utilizes an IFRAME to host Boomerang, which gets it out of the critical path.</p>
<p>When we <a href="https://nicj.net/an-audit-of-boomerangs-performance/#loader-snippet">reviewed the performance impact of the snippet</a> we found that on modern devices and browsers the snippet itself should take less than 10ms of CPU, but on older or slower devices it could take 20-40ms.</p>
<p>A significant portion of this CPU cost was creating a dynamic IFRAME and <code>document.write()</code>&#8216;ing into it.</p>
<p>Last year, our team <a href="https://calendar.perfplanet.com/2018/a-csp-compliant-non-blocking-script-loader/">developed an updated version of the loader snippet we call Version 12 (v12)</a>, which utilizes Preload on modern browsers (instead of an IFRAME) to load Boomerang asynchronously.  This avoids the majority of the CPU cost we saw when profiling the snippet.</p>
<p>As long as your browser <a href="https://caniuse.com/#feat=link-rel-preload">supports Preload</a>, the new loader snippet should have negligible CPU cost:</p>
<table>
<thead>
<tr>
<th align="left">Device</th>
<th align="left">OS</th>
<th align="left">Browser</th>
<th align="left">Snippet v10 (ms)</th>
<th align="left">Snippet v12 (ms)</th>
<th align="left">Method</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Chrome 73</td>
<td align="left">7</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Firefox 66</td>
<td align="left">3</td>
<td align="left">3</td>
<td align="left">IFRAME</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 10</td>
<td align="left">12</td>
<td align="left">12</td>
<td align="left">IFRAME</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 11</td>
<td align="left">14</td>
<td align="left">14</td>
<td align="left">IFRAME</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Edge 44</td>
<td align="left">8</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
<tr>
<td align="left">MacBook Pro (2017)</td>
<td align="left">macOS High Sierra</td>
<td align="left">Safari 12</td>
<td align="left">3</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
<tr>
<td align="left">Galaxy S4</td>
<td align="left">Android 4</td>
<td align="left">Chrome 56</td>
<td align="left">37</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
<tr>
<td align="left">Galaxy S8</td>
<td align="left">Android 8</td>
<td align="left">Chrome 73</td>
<td align="left">9</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
<tr>
<td align="left">Galaxy S10</td>
<td align="left">Android 9</td>
<td align="left">Chrome 73</td>
<td align="left">7</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
<tr>
<td align="left">iPhone 4</td>
<td align="left">iOS 7</td>
<td align="left">Safari 7</td>
<td align="left">19</td>
<td align="left">19</td>
<td align="left">IFRAME</td>
</tr>
<tr>
<td align="left">iPhone 5s</td>
<td align="left">iOS 11</td>
<td align="left">Safari 11</td>
<td align="left">9</td>
<td align="left">9</td>
<td align="left">IFRAME</td>
</tr>
<tr>
<td align="left">iPhone 5s</td>
<td align="left">iOS 12</td>
<td align="left">Safari 12</td>
<td align="left">9</td>
<td align="left"><strong>1</strong></td>
<td align="left"><strong>Preload</strong></td>
</tr>
</tbody>
</table>
<p>Browsers which don&#8217;t support Preload (such as IE and Firefox) will still use the IFRAME fallback, but it should still take minimal time to execute.</p>
<p>In addition, the new loader snippet is CSP-compliant, and brings some SEO improvements (i.e. creating an IFRAME in the <code>&lt;head&gt;</code> can confuse some web crawlers).</p>
<p>You can review the difference between the two versions <a href="https://github.com/akamai/boomerang/commit/1f58d200cf8336bbf8cbbc21793127e95b88c879#diff-c5b2b99f98d7ee81f6b2120d3e8ed7ef">here</a>.</p>
<p><a name="boomerang-performance-update-resourcetiming-compression-optimization"></a></p>
<h2>ResourceTiming Compression Optimization</h2>
<p>Boomerang can collect <a href="https://www.w3.org/TR/resource-timing-2/">ResourceTiming</a> data for all of the resources on the page.  We <a href="https://nicj.net/compressing-resourcetiming/">compress the data</a> to reduce its size, but we found this was one of the most <a href="https://github.com/akamai/boomerang/issues/174">expensive things we did</a>.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/12/boomerang-resource-timing-cpu.png" alt="Boomerang's ResourceTiming Compression in CPU Profiles" /></p>
<p>On some sites &#8212; especially those with hundreds of resources &#8212; our ResourceTiming compression could take 20-100ms or more.  Most of the cost is in our <code>optimizeTrie()</code> function.  Let&#8217;s take a look at what it does.</p>
<p>Say you have a list of ResourceTiming entries, i.e. resources fetched from a website:</p>
<pre><code>http://site.com/
http://site.com/assets/js/main.js
http://site.com/assets/js/lib.js
http://site.com/assets/css/screen.css
</code></pre>
<p>We convert this list of URLs into an <a href="http://en.wikipedia.org/wiki/Trie">optimized Trie</a>, a data structure that compresses common prefixes (i.e. <code>http://site.com/</code> for all URLs above):</p>
<pre><code>{"http://site.com/": {
    "|": "[data]",
    "assets/": {
        "js/": {
            "main.js": "[data]",
            "lib.js": "[data]"
        },
        "css/screen.css": "[data]"
    }
}
</code></pre>
<p>Originally, we were compressing a perfectly-optimized Trie &#8212; we would evaluate every character of every URL to find if there are other URLs that share the same prefix.  This character iteration would create call stacks N deep, where N is the longest URL on the page.  This is pretty costly to execute as you can imagine!</p>
<p>We switched this Trie optimization to instead split each URL at every <code>"/"</code> instead of every character.  This leads to a <em>slightly</em> less optimized Trie (meaning, a few bytes larger), but it&#8217;s <em>significantly</em> faster.  Call stacks are now only as deep as the number of slashes in the URL.</p>
<p>These optimizations are most significant on large sites (i.e. 100+ URLs): on sites where the ResourceTiming optimization was taking > 100ms (on desktop CPUs), changing to splitting at <code>"/"</code> reduced CPU time to ~25ms at only a 4% growth in data.</p>
<p>On less complex sites that used to take 25-35ms to compress this data, changing our algorithm reduced CPU time to just ~10ms at only 2-3% data growth.</p>
<p>Collecting ResourceTiming is still one of the more expensive operations that Boomerang does, but its costs are much less noticeable!</p>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/7834819ec087948ae789d5a7306fb83f3c52e4dc">here</a>.</p>
<p><a name="boomerang-performance-update-debug-messages"></a></p>
<h2>Debug Messages</h2>
<p>Our <a href="https://github.com/akamai/boomerang/issues/233">community noticed</a> that even in the production builds of Boomerang, the <code>BOOMR.debug()</code> debug messages were included in the minified JavaScript, even though they would never be echo&#8217;d to the console.</p>
<p>Stripping these messages from the build saw immediate size improvements:</p>
<ul>
<li>Original size (minified): 205,769 bytes</li>
<li><code>BOOMR.debug()</code> and related messages removed: 196,125 bytes (-5%)</li>
</ul>
<p>Gzipped:</p>
<ul>
<li>Original size (minified, <strong>gzipped</strong>): 60,133 bytes</li>
<li><code>BOOMR.debug()</code> and related messages removed (minified, <strong>gzipped</strong>): 57,123 bytes (-6%)</li>
</ul>
<p>Removing dead code is always a win!</p>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/d7c485df923e6af7e16379ff50970cb97442d0f6">here</a>.</p>
<p><a name="boomerang-performance-update-minification"></a></p>
<h2>Minification</h2>
<p>For production builds, we used <a href="https://github.com/mishoo/UglifyJS2/tree/v2.x">UglifyJS-2</a> to minify Boomerang.</p>
<p>Minification is incredibly powerful &#8212; in our case, our &#8220;debug&#8221; builds are over 800 KB, and are reduced to 191 KB on minification:</p>
<ul>
<li>Boomerang with all comments and debug code: 820,130 bytes</li>
<li>Boomerang minified: 196,125 bytes (76% savings)</li>
</ul>
<p>When Boomerang was first created, it used the <a href="https://yui.github.io/yuicompressor/">YUI Compressor</a> for minification.  We <a href="https://github.com/akamai/boomerang/commit/7d369d7d4f027f5678eed53b2b4642c61cd5cebd">switched to UglifyJS-2</a> in 2015 as it offered the best minification rate at the time.</p>
<p>UgifyJS-3 is now out, so we wanted to <a href="https://github.com/akamai/boomerang/issues/177">investigate</a> if it (or any other tool) offered better minification in 2019.</p>
<p>After some experimentation, we changed from UglifyJS-2 to UglifyJS-3, while also enabling the <code>ie8:true</code> option for compatibility and <code>compress.keep_fnames</code> option to improve Boomerang Error reporting.</p>
<p>After all of these changes, Boomerang is 2,074 bytes <em>larger</em> uncompressed (because of <code>ie8</code> and <code>keep_fnames</code>), though 723 bytes smaller once gzipped.  Had we not enabled those compatibility options, Uglify-3 would&#8217;ve been 2,596 bytes <em>smaller</em> than Uglify-2.  We decided the compatibility changes were worth it.</p>
<p>You can review this change <a href="https://github.com/akamai/boomerang/commit/65faac0d7248d2f6af3a6f4e5e17147bc54e4416">here</a>.</p>
<p>Our team also looked at the <a href="https://developers.google.com/closure/compiler">Closure compiler</a>, and we estimate it would give us additional savings:</p>
<ul>
<li>UglifyJS-3: 47 KB brotli, 55 KB gzip</li>
<li>Closure: 42 KB brotli, 47 KB gzip</li>
</ul>
<p>Unfortunately, there are some optimizations in the Closure compiler that complain about parts of the Boomerang source code.  Boomerang today can collect performance metrics on IE6+, and switching to Closure might reduce our support for older browsers.</p>
<p><a name="boomerang-performance-update-cookie-size"></a></p>
<h2>Cookie Size</h2>
<p>mPulse uses a first-party cookie <code>RT</code> to track mPulse sessions between pages, and for tracking Page Load time on browsers that don&#8217;t support NavigationTiming.</p>
<p>Here&#8217;s an example of what the cookie might look like in a modern browser.  Our cookie consists of name-value pairs (~322 bytes):</p>
<pre><code>dm=virtualglobetrotting.com
si=9563ee29-e809-4844-88df-d4e84697f475
ss=1537818004420
sl=1
tt=7556
obo=0
sh=1537818012102%3D1%3A0%3A7556
bcn=%2F%2F17d98a5a.akstat.io%2F
ld=1537818012103
nu=https%3A%2F%2Fvirtualglobetrotting.com%maps%2F
cl=1537818015199
r=https%3A%2F%2Fvirtualglobetrotting.com%2F
ul=1537818015211
</code></pre>
<p>We reviewed everything that we were storing in the cookie, and found a few areas for improvement.</p>
<p>The two biggest contributors to its size are the <code>"nu"</code> and <code>"r"</code> values, which are the URL of the next page the user is navigating to, and the referring page, respectively.  This means the cookie will be at least as long as those two URLs.</p>
<p>We decided that we didn&#8217;t need to store the full URL in the cookie: we could use a hash instead, and compare the hashes on the next page.  This can save a lot of bytes on longer-URL sites.</p>
<p>We were also able to reduce the size of all of the timestamps in the cookie by switching to <a href="https://en.wikipedia.org/wiki/Base36">Base36</a> encoding, and offsetting each timestamp by the start time (<code>"ss"</code>). We also removed some unused debugging data (<code>"sh"</code>).</p>
<p>Example cookie after all of the above changes (~189 bytes, 41% smaller):</p>
<pre><code>dm=virtualglobetrotting.com
si=9563ee29-e809-4844-88df-d4e84697f475
ss=jmgp4udg
sl=1
tt=5tw
obo=0
bcn=%2F%2F17d98a5a.akstat.io%2F
ld=5xf
nu=13f91b339af573feb2ad5f66c9d65fc7
cl=8bf
ul=8br
z=1
</code></pre>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/8eea1d7002d96e8af63f896d5790d1a2d3fdf9a8">here</a>.</p>
<p><a name="boomerang-performance-update-cookie-access"></a></p>
<h2>Cookie Access</h2>
<p>As part of the <a href="https://github.com/akamai/boomerang/issues/173">2017 Boomerang Performance Audit</a> we found that Boomerang was reading/writing the Cookie multiple times during the page load, and the relevant functions were showing up frequently in CPU profiles:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/12/boomerang-cookie-access.png" alt="Boomerang Cookie Access" /></p>
<p>Upon review, we found that Boomerang was reading the <code>RT</code> cookie up to 21 times during a page load and setting it a further 8 times.  This seemed&#8230; excessive.  Cookie accesses may show up on CPU profiles because it can be accessing the disk to persist the data.</p>
<p>Through an audit of these reads/writes and some optimizations, we&#8217;ve been able to reduce this down to 2 reads and 4 writes, which is the minimal number we think are required to be reliable.</p>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/204a9175a9a5582f7a54c7bebf0288c06adf99b0">here</a>.</p>
<p><a name="boomerang-performance-update-md5-plugin"></a></p>
<h2>MD5 plugin</h2>
<p>This improvement comes from our new Boomerang developer <a href="https://github.com/ceckoslab">Tsvetan Stoychev</a>.  In his words:</p>
<p>&#8220;About 2 months ago we received a <a href="https://github.com/akamai/boomerang/issues/268">ticket</a> in our GitHub repository about an issue developers experienced when they tried to build a Boomerang bundle that does not contain the Boomerang <a href="https://github.com/akamai/boomerang/blob/1.650.0/plugins/md5.js">MD5</a> plugin. It was nothing critical but I started working on a small fix to make sure the issue doesn’t happen again in newer versions of Boomerang.</p>
<p>While working on the fix, I managed to understand the bigger picture and the use cases where we used the MD5 plugin. I saw an opportunity to replace MD5 implementation with an algorithm that doesn’t generate hashes as strong as MD5 but would still work in our use case.</p>
<p>The goal was to reduce the Boomerang bundle size and to keep things backward-compatible. I asked around in a group of professionals who do programming for IoT devices and received some good ideas about lightweight algorithms that could save a lot of bytes.</p>
<p>I looked at 2 candidates: CRC32 and FNV-1. The winner was a slightly modified version of FNV-1 and in terms of bytes it was 0.33 KB (uncompressed) compared to MD5, which was 8.17 KB (uncompressed). I also experimented with CRC32 but the JavaScript implementation was 2.55 KB (uncompressed) because the CRC32 source code contains a string of a CRC table that adds quite a few bytes. As a matter of fact, there is a way to use a smaller version that is 0.56 KB (uncompressed) where the CRC table is generated on the fly, but I found this version was adding more complexity and had a performance penalty when the CRC table was generated.</p>
<p>Let’s look at the data I gathered during my research where I compare performance, bytes and chances for collisions:</p>
<table>
<thead>
<tr>
<th align="left">#</th>
<th align="left">Collisions</th>
<th align="left">Size (uncompressed)</th>
<th align="left">Hash Length</th>
<th align="left">Hashes / Sec</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">MD5</td>
<td align="left">0</td>
<td align="left">8.17 KB</td>
<td align="left">32</td>
<td align="left">35,397</td>
</tr>
<tr>
<td align="left">CRC32</td>
<td align="left">8</td>
<td align="left">2.55 KB</td>
<td align="left">9-10</td>
<td align="left">253,680</td>
</tr>
<tr>
<td align="left">FNV-1 (original)</td>
<td align="left">3</td>
<td align="left">0.33 KB</td>
<td align="left">9-10</td>
<td align="left">180,056</td>
</tr>
<tr>
<td align="left">FNV-1 (modified)</td>
<td align="left">0</td>
<td align="left">0.34 KB</td>
<td align="left">6-8</td>
<td align="left">113,532</td>
</tr>
</tbody>
</table>
<ul>
<li>Collision testing was performed on 500,000 unique URLs.</li>
<li>Performance benchmark was performed with the help of <a href="https://jsperf.com/fnv-alorithm">jsPerf</a></li>
</ul>
<p>FNV-1 modifications:</p>
<ul>
<li>In order to achieve better entropy for the FNV-1 algorithm, we concatenate the length of our input string with the end of the original FNV-1 generated hash.</li>
<li>The FNV-1 output is actually a number that we convert to base 36 in the modified version in order to make the number’s string representation shorter.</li>
</ul>
<p>Below is the final result in case you would like to test the modified version of FNV-1:</p>
<pre><code class="js">var fnv = function(string) {
    string = encodeURIComponent(string);
    var hval = 0x811c9dc5;
    for (var i = 0; i &lt; string.length; i++) {
        hval = hval ^ string.charCodeAt(i);
        hval += (hval &lt;&lt; 1) + (hval &lt;&lt; 4) + (hval &lt;&lt; 7) + (hval &lt;&lt; 8) + (hval &lt;&lt; 24);
    }
    var hash = (hval &gt;&gt;&gt; 0).toString() + string.length;
    return parseInt(hash).toString(36);
}
</code></pre>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/9c66ccba62ebf18ad7b8602cf44d0cbad247ce9b">here</a>.</p>
<p><a name="boomerang-performance-update-spa-plugin"></a></p>
<h2>SPA Plugin</h2>
<p>This improvement comes from our Boomerang developer <a href="https://github.com/querymetrics">Nigel Heron</a>, who&#8217;s been working on a large refactor (and simplification) of our Single Page App monitoring code.  In his words:</p>
<p>&#8220;Boomerang had 4 SPA plugins: <code>Angular</code>, <code>Ember</code>, <code>Backbone</code> and <code>History</code>. The first 3 are SPA framework specific and hook into special functions in their respective frameworks to detect route changes (eg. Angular&#8217;s <code>$routeChangeStart</code>).</p>
<p>The <code>History</code> plugin could be used in 2 ways, either by hooking into the React Router framework&#8217;s <code>history</code> object or by hooking into the browser&#8217;s <code>window.history</code> object for use by any other framework that calls the window History API before issuing route changes.</p>
<p>As SPA frameworks have evolved over the years, calling into the History API before route changes has become standard. We determined that the timings observed by hooking History API calls vs hooking into framework specific events was negligible.</p>
<p>We modified the History plugin to remove React specific code and removed the 3 framework specific plugins from Boomerang.</p>
<p>This has simplified Boomerang&#8217;s SPA logic, simplified the configuration of Boomerang on SPA sites and as a bonus, has dropped the size of Boomerang!</p>
<table>
<thead>
<tr>
<th align="left"></th>
<th align="left">unminified</th>
<th align="left">minified</th>
<th align="left">gzip</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">before</td>
<td align="left">796 KB</td>
<td align="left">202 KB</td>
<td align="left">57.27 KB</td>
</tr>
<tr>
<td align="left">after</td>
<td align="left">779 KB</td>
<td align="left">200 KB</td>
<td align="left">57.07 KB</td>
</tr>
</tbody>
</table>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/8b6b73ec93d5b7a1580828a5b367a6c53312714f">here</a>.</p>
<p><a name="boomerang-performance-update-brotli"></a></p>
<h2>Brotli</h2>
<p>This is a fix that could be applied to any third-party library, but when we ran our <a href="https://nicj.net/an-audit-of-boomerangs-performance/#boomerang-size">audit</a> two years ago, we had not yet enabled Brotli compression for boomerang.js delivery when using mPulse.</p>
<p>mPulse&#8217;s boomerang.js is delivered via the Akamai CDN, and we were able to utilize <a href="https://developer.akamai.com/ion/adaptive-acceleration">Akamai&#8217;s Resource Optimizer</a> to automatically enable Brotli compression for our origin-gzip-compressed JavaScript URLs, with minimal effort.  We should have done this sooner!</p>
<p>The over-the-wire byte savings of Brotli vs gzip are pretty significant.  Taking the most recent build of Boomerang with all plugins enabled:</p>
<ul>
<li>gzip: 54,803 bytes</li>
<li>brotli: 48,663 bytes (11.2% savings)</li>
</ul>
<p>Brotli is now enabled for all boomerang.js downloads for mPulse customers.</p>
<p>(Over-the-wire byte size does not reduce the parse/compile/initialization time of Boomerang, and we&#8217;re still looking for additional ways of making Boomerang and its plugins smaller and more efficient)</p>
<p><a name="boomerang-performance-update-performance-test-suite"></a></p>
<h2>Performance Test Suite</h2>
<p>After spending so much time improving the above issues, it would be a shame if we were working on unrelated changes and accidentally regressed our improvements!</p>
<p>To assist us in this, we built a lightweight performance test suite into the Boomerang build infrastructure that can be executed on-demand and the results can be compared to previous runs.</p>
<p>We built the performance tests on top of our existing <a href="https://akamai.github.io/boomerang/tutorial-tests.html">End-to-End (E2E)</a> tests, which are basic HTML pages we use to test Boomerang in various scenarios.  Today we have over 550+ E2E tests that run on every build.</p>
<p>The new <a href="https://akamai.github.io/boomerang/tutorial-perf-tests.html">Boomerang Performance Tests</a> utilize a similar test infrastructure and also track metrics built into the debug builds of Boomerang, such as the scenario&#8217;s CPU time or how many times the cookie was set.</p>
<p>Example results of running one Boomerang Test scenario:</p>
<pre><code>&gt; grunt perf

{
  "00-basic": {
    "00-empty": {
      "page_load_time": 30,
      "boomerang_javascript_time": 29.5,
      "total_javascript_time": 43,
      "mark_startup_called": 1,
      "mark_check_doc_domain_called": 4,
  ...
}
</code></pre>
<p>If we re-run the same test later after making changes, we can directly compare the results:</p>
<pre><code>&gt; grunt perf:compare

Running "perf-compare" task
Results comparison to baseline:
00-basic.00-empty.page_load_time  :  30 -6 (-20%)
</code></pre>
<p>We still need to be attentive to our changes and to run these test automatically to &#8220;hold the line&#8221;.</p>
<p>We also need to try to be mindful and put in instrumentation (metrics) when we&#8217;re making fixes to make sure we have something to track over time.</p>
<p>You can review our change <a href="https://github.com/akamai/boomerang/commit/e3af081b367ebfcdd4c446009afd76e1adbda197">here</a>.</p>
<p><a name="boomerang-performance-update-next"></a></p>
<h2>Next</h2>
<p>With all of the above improvements, where are we at?</p>
<p>We&#8217;ve:</p>
<ul>
<li>Reduced the overhead and increased the compatibility of the Loader Snippet</li>
<li>Reduced the CPU cost of gathering ResourceTiming data</li>
<li>Reduced the size of Boomerang by stripping debug messages, applying smarter minification, and enabling Brotli</li>
<li>Reduced the size of and overhead of cookies for Session tracking</li>
<li>Reduced the size of Boomerang and complexity of hashing URLs by switching to FNV, and by simplifying our SPA plugins</li>
<li>Created a Performance Test Suite to track our improvements over time</li>
</ul>
<p>All of these changes had to be balanced against the <a href="https://github.com/akamai/boomerang/pull/230">other</a> <a href="https://github.com/akamai/boomerang/pull/258">work</a> we do to improve the library itself.  We still have a <a href="https://github.com/akamai/boomerang/issues?q=is%3Aissue+is%3Aopen+label%3Aperformance">large list</a> of other performance-related items we hope to tackle in 2020.</p>
<p>One of the main areas we continue to focus on is Boomerang&#8217;s size.  As more features, reliability and compatibility fixes get added, Boomerang continues to increase in size.  While we&#8217;ve done a lot of work to keep it under 50 KB (over the wire), we know there is more we can do.</p>
<p>One thing we&#8217;re looking into for our mPulse customers is to deliver builds of Boomerang with the minimal features necessary for that application&#8217;s configuration.  For example, if Single Page App support is not needed, all of the related SPA plugins can be omitted.  We&#8217;re exploring ways of quickly switching between different builds of Boomerang for mPulse customers, while still having a high browser cache hit rate and yet allowing customers to change their configuration and see it &#8220;live&#8221; within 5 minutes.</p>
<p>Note that open-source users of Boomerang can always build a custom build with only the plugins they care about.  Depending on the features needed, open-source builds can be under 30 KB with the majority of the plugins included.</p>
<h2>Thanks</h2>
<p>Boomerang is built by the developers at Akamai and the broader performance community on <a href="https://github.com/SOASTA/boomerang">Github</a>.  Many people have a hand in building and improving Boomerang, including the changes mentioned above by: <a href="https://github.com/querymetrics">Nigel Heron</a>, <a href="https://github.com/nicj">Nic Jansma</a>, <a href="https://github.com/andreas-marschke">Andreas Marschke</a>, <a href="https://github.com/ashenoy2014">Avinash Shenoy</a>, <a href="https://github.com/ceckoslab">Tsvetan Stoychev</a>, <a href="https://github.com/bluesmoon">Philip Tellis</a>, <a href="https://twitter.com/timvereecke">Tim Vereecke</a>, <a href="https://github.com/manofearth">Aleksey Zemlyanskiy</a>, and all of the other <a href="https://github.com/akamai/boomerang/graphs/contributors">open-source contributors</a>.</p><p>The post <a href="https://nicj.net/boomerang-performance-update/" target="_blank">Boomerang Performance Update</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/boomerang-performance-update/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Side Effects of Boomerang&#8217;s JavaScript Error Tracking</title>
		<link>https://nicj.net/side-effects-of-boomerangs-javascript-error-tracking/</link>
					<comments>https://nicj.net/side-effects-of-boomerangs-javascript-error-tracking/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 09 Apr 2019 12:29:26 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2432</guid>

					<description><![CDATA[<p>Table Of Contents Introduction What is Boomerang Doing Fixing Script Error Workarounds for Third Parties that Aren&#8217;t Sending ACAO Disabling Wrapping Side Effects of Wrapping Overhead Console Logs Browser CPU Profiling Chrome Lighthouse and Page Speed Insights WebPagetest Summary Introduction TL;DR: If you don&#8217;t have time to read this article, head down to the summary. [&#8230;]</p>
<p>The post <a href="https://nicj.net/side-effects-of-boomerangs-javascript-error-tracking/" target="_blank">Side Effects of Boomerang's JavaScript Error Tracking</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table Of Contents</h2>
<ol>
<li><a href="#side-effects-intro">Introduction</a></li>
<li><a href="#what-is-boomerang-doing">What is Boomerang Doing</a></li>
<li><a href="#fixing-script-error">Fixing Script Error</a></li>
<li><a href="#workarounds">Workarounds for Third Parties that Aren&#8217;t Sending ACAO</a></li>
<li><a href="#disabling-wrapping">Disabling Wrapping</a></li>
<li><a href="#side-effects">Side Effects of Wrapping</a>
<ol>
<li><a href="#overhead">Overhead</a></li>
<li><a href="#console-logs">Console Logs</a></li>
<li><a href="#browser-cpu-profiling">Browser CPU Profiling</a></li>
<li><a href="#chrome-lighthouse">Chrome Lighthouse and Page Speed Insights</a></li>
<li><a href="#webpagetest">WebPagetest</a></li>
</ol>
</li>
<li><a href="#side-effects-summary">Summary</a></li>
</ol>
<p><a name="side-effects-intro"></a></p>
<h2>Introduction</h2>
<p><strong>TL;DR</strong>: If you don&#8217;t have time to read this article, head down to the <a href="#side-effects-summary">summary</a>.  If you&#8217;re specifically interested in how Boomerang may affect <a href="#chrome-lighthouse">Chrome Lighthouse</a>, <a href="#page-speed-insights">Page Speed Insights</a> or <a href="#webpagetest">WebPagetest</a>, check those sections.</p>
<p><a href="https://github.com/akamai/boomerang">Boomerang</a> is an open-source JavaScript library that measures the page load time experienced by real users, commonly called RUM (Real User Measurement).  Boomerang is easily integrated into personal projects, enterprise websites, and also powers Akamai&#8217;s mPulse RUM monitoring.  Boomerang optionally includes a JavaScript Error Tracking <a href="http://akamai.github.io/boomerang/BOOMR.plugins.Errors.html">plugin</a>, which monitors the page for JavaScript errors and includes those error messages on the beacon.</p>
<p>When JavaScript Error Tracking is enabled for Boomerang, it can provide real-time telemetry (analytics) on the health of your application.  However, due to the way Boomerang gathers important details about each error (e.g. the full message and stack), it might be incorrectly blamed for <em>causing</em> errors, or as the culprit for high CPU usage.</p>
<p>This unintentional side-effect can be apparent in tools like browser developer tools&#8217; consoles, WebPagetest, Chrome Lighthouse and other benchmarks that report on JavaScript CPU usage.  For example, Lighthouse may blame Boomerang under <em>Reduce JavaScript Execution Time</em> due to how Boomerang &quot;wraps&quot; functions to gather better error details.  This unfortunately &quot;blames&quot; Boomerang for more work than it is actually causing.</p>
<p>Let&#8217;s explore why, and how you can ensure those tools report on the correct source of errors and JavaScript CPU usage.</p>
<p>This article is applicable to both the <a href="https://github.com/akamai/boomerang">open-source Boomerang</a> as well as the Boomerang used with <a href="https://akamai.com">Akamai mPulse</a>.  Where appropriate, differences will be mentioned.</p>
<p><span id="more-2432"></span></p>
<p><a name="what-is-boomerang-doing"></a></p>
<h2>What is Boomerang Doing?</h2>
<p>Once enabled, the Boomerang <a href="http://akamai.github.io/boomerang/BOOMR.plugins.Errors.html">Errors</a> plugin configures itself to listen for errors on the page.  It first hooks into the Browser&#8217;s <code>onerror</code> event, which the browser fires whenever there is a JavaScript error on the page.</p>
<p>Unfortunately, the <code>onerror</code> event has a major limitation: if an error originates in a cross-origin (third-party) library, the message reported by <code>onerror</code> will simply be <code>&quot;Script error.&quot;</code>.</p>
<p>As an example, let&#8217;s say your site at <code>mysite.com</code> loads a JavaScript library from the CDN <code>//othercdn.com/library.js</code> as well as jQuery (from <code>//code.jquery.com</code>):</p>
<pre><code class="language-html">&lt;html&gt;
    &lt;head&gt;
        &lt;script src=&quot;//othercdn.com/library.js&quot;&gt;&lt;/script&gt;
        &lt;script src=&quot;//code.jquery.com/jquery-3.3.1.min.js&quot;&gt;&lt;/script&gt;
    &lt;/head&gt;
    ...</code></pre>
<p>Once <code>library.js</code> is loaded, it performs some initialization.  Let&#8217;s pretend it does something like the following:</p>
<pre><code class="language-js">// library.js
function init() {
    setTimeout(function myCallback() {
        if (jQuery(&quot;abc&quot;)) {
            ...
        }
    }, 100);
}</code></pre>
<p>In this scenario, let&#8217;s pretend jQuery has not yet loaded fast enough before the <code>setTimeout()</code> runs, so attempting to call <code>jQuery()</code> in this <code>init()</code> function would fail.  An exception will be thrown, something similar to <code>&quot;jQuery is not defined&quot;</code>.</p>
<p>You would see this message in your developer tools console:</p>
<pre><code>ReferenceError: jQuery is not defined</code></pre>
<p>Here&#8217;s an example of what Chrome&#8217;s Developer Tools looks like:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/simple-error.png" alt="Chrome Developer Tools&#039;s Console message" /></p>
<p>Unfortunately, because the library is cross-origin, anything listening for <code>onerror</code> (e.g. Boomerang) merely gets the message <code>&quot;Script error.&quot;</code>:</p>
<pre><code class="language-js">// in Boomerang or the site itself
window.addEventListener(&quot;error&quot;, function(errEvent) {
    console.log(&quot;onerror called:&quot;, errEvent);
    var message = errEvent.message; // Script error.
    var stack = errEvent.error; // null, would normally contain error.stack
});</code></pre>
<p>Here&#8217;s what the <code>errEvent</code> object looks like in Chrome&#8217;s Developer Tools in the above scenario:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/simple-error-dev-tools.png" alt="onerror event data" /></p>
<p>The message (<code>message: &quot;Script error.&quot;</code>) and stack (<code>error: null</code>) have been obscured by the browser to protect against potentially sensitive information in the error message coming from the cross-origin JavaScript.</p>
<p>The <code>&quot;Script Error.&quot;</code> string is given instead of the real error message and does not contain any useful information about what caused the error. In addition, there is no stack associated with the message, so it’s impossible to know where or why the error occurred.</p>
<p>Note that if <strong>you</strong> have Developer Tools open when an error occurs, <strong>you</strong> can see the full error message and stack in the Console.  However, JavaScript libraries that have registered for <code>onerror</code> only get redacted information (<code>&quot;Script error.&quot;</code>). This is because there aren’t any security or privacy concerns for a developer looking at their own machine’s information.</p>
<p><a name="fixing-script-error"></a></p>
<h2>Fixing Script Error</h2>
<p>As the owner of <code>mysite.com</code>, you can &quot;give access&quot; to the full error information (message and stack) by including the <code>library.js</code> with the <code>Access-Control-Allow-Origin</code> HTTP response header and a <code>crossorigin=&quot;anonymous&quot;</code> HTML attribute. Unfortunately, it requires both <code>mysite.com</code> <em>and</em> <code>othercdn.com</code> to opt-in to sharing the full message and stack.</p>
<p>To ensure a cross-origin script shares full error details with <code>onerror</code> listeners, you&#8217;ll need to do <strong>two</strong> things:</p>
<ol>
<li>
<p>Add <code>crossorigin=&quot;anonymous&quot;</code> to the <code>&lt;script&gt;</code> tag</p>
<ul>
<li>The <a href="https://www.w3.org/TR/html5/infrastructure.html#cors-settings-attribute"><code>crossorigin=&quot;anonymous&quot;</code> attribute</a> tells the browser that the script should be fetched without sending any cookies or HTTP authentication</li>
</ul>
</li>
<li>
<p>Add the <code>Access-Control-Allow-Origin</code> (ACAO) header to the JavaScript file&#8217;s response.</p>
<ul>
<li>The <code>Access-Control-Allow-Origin</code> header is part of the <a href="https://www.w3.org/TR/cors/">Cross Origin Resource Sharing</a> (CORS) standard.</li>
<li>The ACAO header <strong>must</strong> be set in the JavaScript&#8217;s HTTP response headers.</li>
<li>An example header that sets ACAO for all calling origins would be: <code>Access-Control-Allow-Origin: *</code></li>
</ul>
</li>
</ol>
<p>If both conditions are true, cross-origin JavaScript files will report errors to <code>onerror</code> listeners with the correct error message and full stack.</p>
<p>The biggest challenge to getting this working in practice is that (1) is within the <em>site&#8217;s</em> control while (2) can only be configured by the <em>owner</em> of the JavaScript.  If you&#8217;re loading JavaScript from a third-party, you will need to encourage them to add the ACAO header if it&#8217;s not already set.  The good news is that many CDNs and third-parties set the ACAO header already.</p>
<p>Unfortunately, not all third-party libraries (or the site itself) will be able, or willing, to opt-into this full error information.  If not, the error message is stuck at just <code>&quot;Script error.&quot;</code>.</p>
<p>Unless&#8230;</p>
<p><a name="workarounds"></a></p>
<h2>Workarounds for Third Parties that Aren’t Sending ACAO</h2>
<p>However, there is a workaround for third-party domains that don&#8217;t set <code>Access-Control-Allow-Origin</code>.</p>
<p>If the page&#8217;s JavaScript (or a library like Boomerang) opts to, it can &quot;wrap&quot; all calls to the third-party script in a <code>try/catch</code> block.  If an exception is thrown, the <code>catch (e) { ... }</code> still gets access to the full error message and stack, even from cross-origin scripts.</p>
<p>For example:</p>
<pre><code class="language-js">try {
    // calls a cross-origin script that doesn&#039;t have ACAO
    initLibrary();
} catch (e) {
    // report on error with e.message and e.stack
}</code></pre>
<p>It may not be practical to wrap every invocation of cross-origin functions.  Boomerang includes some <a href="http://akamai.github.io/boomerang/BOOMR.plugins.Errors.html">helper functions</a> making wrapping easier.</p>
<p>In addition, Boomerang will also provide some assistance for wrapping events and callbacks initiated by third-party scripts, so the site doesn&#8217;t have to do this on its own.  Boomerang &quot;wraps&quot; top-level JavaScript functions, forwarding all original calls and callbacks to its own wrapping function instead.  This is done on <code>setTimeout()</code>, <code>setInterval()</code>, <code>addEventListener()</code>, and similar functions so any <em>callbacks</em> from those functions &quot;originate&quot; in the page and are wrapped.</p>
<p>This sounds complicated, but let&#8217;s see how Boomerang can do this, and how it can benefit error reporting.</p>
<p><a name="side-effects-boomerang-wrap-example"></a><br />
First, Boomerang will wrap top-level functions that register callbacks, like <code>setTimeout()</code>, <code>setInterval()</code>, <code>addEventListener()</code>:</p>
<pre><code class="language-js">var origSetTimeout = window.setTimeout;
window.setTimeout = function wrappedFunction() {
    try {
        origSetTimeout.apply(this, arguments);
    } catch (e) {
        // An exception happened!
        // Boomerang gets the full message and stack
    }
}</code></pre>
<p>(Note the actual wrapping is a bit more complicated, see the Boomerang <a href="https://github.com/akamai/boomerang/blob/master/plugins/errors.js"><code>Errors.js</code></a> plugin for more details).</p>
<p>Why does this help?  If <em>any</em> exception happens within the callback, the wrapped function&#8217;s <code>try/catch</code> is at the top of the stack, so it gets the full error message!  This include the original error message (instead of <code>&quot;Script error.&quot;</code>), and the full stack.</p>
<p>Putting this wrapping in place can help reduce occurrences of <code>&quot;Script error.&quot;</code> from third-party scripts within a page, if those scripts can&#8217;t otherwise be <a href="#fixing-script-error">opted-in</a> via ACAO and the <code>crossorigin=&quot;anonymous&quot;</code> attribute.</p>
<p><a name="disabling-wrapping"></a></p>
<h2>Disabling Wrapping</h2>
<p>Wrapping is currently enabled by default when enabling JavaScript Error Tracking in Boomerang.  You can have <code>Errors.js</code> opt-out of wrapping by setting the following configuration options to <code>false</code> (for open-source Boomerang):</p>
<pre><code class="language-js">BOOMR.init({
    Errors: {
        monitorTimeout: false, // disable setTimeout and setInterval wrapping
        monitorEvents: false   // disable addEventListener and removeEventListener wrapping
    }
})</code></pre>
<p>For users of Akamai mPulse, wrapping can be disabled by including the following JavaScript snippet on the page prior to Boomerang being loaded:</p>
<pre><code class="language-js">window.BOOMR_config = window.BOOMR_config || {};
window.BOOMR_config.Errors = window.BOOMR_config.Errors || {};
window.BOOMR_config.Errors.monitorTimeout = false; // disable setTimeout and setInterval wrapping
window.BOOMR_config.Errors.monitorEvents = false; // disable addEventListener and removeEventListener wrapping</code></pre>
<p>Or via the following Configuration Override in Luna:</p>
<pre><code class="language-json">{
    &quot;Errors&quot;: {
        &quot;monitorTimeout&quot;: false,
        &quot;monitorEvents&quot;: false
    }
}</code></pre>
<p>You may also enable or disable JavaScript Error Tracking entirely within the Akamai mPulse app configuration.</p>
<p><a name="side-effects"></a></p>
<h2>Side Effects of Wrapping</h2>
<p>While doing this wrapping to get additional error details for cross-origin scripts sounds ideal, there are some notable downsides to be aware of, as Boomerang being at the &quot;bottom&quot; of the stack may now confuse several well-intentioned tools.</p>
<p>Here are some of the side-effects:</p>
<p><a name="overhead"></a></p>
<h3>Overhead</h3>
<p>Since Boomerang has replaced top-level function such as <code>setTimeout()</code> with its own wrapper function, there will be non-zero overhead when those functions are called and Boomerang&#8217;s code forwards the arguments to the native functions.</p>
<p>The good news is the wrapped function is minimal and efficient, and generally won&#8217;t appear in profiling traces.  In a experiment of calling <code>setTimeout()</code> (after being wrapped) 10,000 times, a modern (2017 Macbook Pro) laptop&#8217;s JavaScript profile shows only 4.3ms of sampled profile hits in the wrapped function (<code>BOOMR_plugins_errors_wrapped_function</code>), so 0.43 <em>micro</em>-seconds of overhead:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/wrap-overhead.png" alt="Chrome Developer Tools&#039;s Console message" /></p>
<p><a name="console-logs"></a></p>
<h3>Console Logs</h3>
<p>Once Boomerang&#8217;s JavaScript Error Tracking with wrapping is enabled, errors that are triggered by other libraries that have been wrapped will now look like they come from Boomerang instead, as Boomerang is now on the bottom of the call stack.</p>
<p>For example, the above error where <code>jQuery()</code> was undefined may look like this before Boomerang has wrapped <code>setTimeout()</code>:</p>
<pre><code class="language-js">Uncaught ReferenceError: jQuery is not defined at myCallback @ library.js
setTimeout (async)
init @ library.js</code></pre>
<p>Browser Developer Tools report the &quot;cause&quot; of JavaScript errors as the last non-async callback.  In this case, it will report <code>library.js</code> in the right side of Chrome Developer Tools&#8217; Console:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/simple-error.png" alt="Chrome Developer Tools&#039;s Console message" /></p>
<p>After Boomerang wraps <code>setTimeout()</code>, the error&#8217;s message says the same, but the stack has an additional function from Boomerang at the bottom:</p>
<pre><code class="language-js">Uncaught ReferenceError: jQuery is not defined at myCallback @ library.js
BOOMR_plugins_errors_wrap @ boomerang.js
setTimeout (async)
init @ library.js</code></pre>
<p>Because <code>BOOMR_plugins_errors_wrap</code> is now the last non-async function, Chrome Developer Tools&#8217; Console now lists boomerang.js as the cause (the file on the right side):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/simple-error-with-wrap.png" alt="Chrome Developer Tools&#039;s Console message after wrapping" /></p>
<p>Boomerang&#8217;s <a href="http://akamai.github.io/boomerang/BOOMR.plugins.Errors.html"><code>Errors.js</code></a> JavaScript Error Tracking plugin automatically looks for its own functions on the stack of error messages and will try to exclude its own wrapped functions when it reports on errors.  Tools that visualize error messages, such as Akamai&#8217;s mPulse, may also be able to &quot;de-prioritize&quot; Boomerang on the stack, so the true culprit is correctly reported:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/mpulse-boomerang-stack.png" alt="Akamai mPulse Boomerang in stack" /></p>
<p>To be clear: Boomerang is not causing these errors, developer tools are just confused because of the wrapping.</p>
<p>You can confirm that Boomerang is not the cause of the errors by <a href="#disabling-wrapping">disabling wrapping</a>.</p>
<p><a name="browser-cpu-profiling"></a></p>
<h3>Browser CPU Profiling</h3>
<p>Once Boomerang&#8217;s JavaScript Error Tracking with wrapping is enabled, browser developer tools such as Chrome&#8217;s <em>Performance</em> and <em>Profiler</em> tabs may be confused about JavaScript CPU attribution.  In other words, they may think Boomerang is the cause of more work than it is.</p>
<p>In addition, because Boomerang&#8217;s wrapped functions are now at the bottom of the stack, reports such as <em>By Domain</em> might get attributed to Boomerang&#8217;s domain (or the root site), instead of third-party libraries.</p>
<p>Let&#8217;s look at a slightly different example of a third-party library doing work.  In this case, let&#8217;s busy-loop for 5 seconds to show exaggerated work from a library:</p>
<pre><code class="language-js">// library.js
function init() {
    setTimeout(function myCallback() {
        var startTime = Date.now();
        while (Date.now() - startTime &lt; 5000) {
            // NOP
        }
    }, 100);
}</code></pre>
<p>Without wrapping, Chrome&#8217;s JavaScript Profiler shows the <code>myCallback()</code> function doing ~5 seconds of busy work:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/profiler.png" alt="Chrome Developer Tools&#039;s Profiler" /></p>
<p>The <code>myCallback()</code> function above is also at the &quot;bottom of the stack&quot;.</p>
<p>When Boomerang wraps the top-level <code>setTimeout()</code> and its callbacks, we see the function <code>BOOMR_plugins_errors_wrap</code> show up as the top cause of CPU usage:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/profiler-with-wrap-first.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping" /></p>
<p>However, once expanded, we see the real cause of CPU usage (as evidence by <em>Self Time</em>) as the original <code>myCallback()</code> function:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/profiler-with-wrap.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping expanded" /></p>
<p>This is also apparent in Chrome&#8217;s <em>Performance</em> tab.  Here we see the un-wrapped function show at the top of the charts:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/performance-tab.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping" /></p>
<p>And when <em>Grouped By Domain</em>, the library (<code>//othercdn.com</code>) shows up at the top as well:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/performance-tab-by-domain.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping" /></p>
<p>However, once Boomerang wrapping has happened, we see Boomerang getting attributed to all of the work in both views:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/performance-tab-with-wrap.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping" /></p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/performance-tab-by-domain-with-wrap-1.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping" /></p>
<p>Once you dive into the stack, you can see the original function is causing nearly all of the <em>Self Time</em>:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/performance-tab-by-domain-with-wrap-2.png" alt="Chrome Developer Tools&#039;s Profiler after wrapping" /></p>
<p>Due to wrapping, Boomerang&#8217;s error wrapping functions are being included in the <em>Total Time</em>.</p>
<p>You can confirm that Boomerang is not the cause of the work by <a href="#disabling-wrapping">disabling wrapping</a> temporarily.</p>
<p><a name="chrome-lighthouse"></a><br />
<a name="page-speed-insights"></a></p>
<h3>Chrome Lighthouse and Page Speed Insights</h3>
<p>Once Boomerang&#8217;s JavaScript Error Tracking with wrapping is enabled, Chrome Lighthouse may be confused about JavaScript CPU attribution, due to the same reasons <a href="#browser-cpu-profiling">as above</a>.</p>
<p>Lighthouse comes in multiple flavors, but the two most commonly used are as a <a href="https://developers.google.com/web/tools/lighthouse/">Chrome browser extension</a> and as part of <a href="https://developers.google.com/speed/pagespeed/insights/">Page Speed Insights</a>.  Both use Chrome under the hood to analyze the performance impact of various components of the page, including JavaScript libraries.</p>
<p>When Boomerang&#8217;s wrapping is enabled, the wrapping functions often get incorrectly blamed for work being done in other libraries or the root page itself.</p>
<p>Using the same exaggerated expensive function:</p>
<pre><code class="language-js">// library.js
function init() {
    setTimeout(function myCallback() {
        var startTime = Date.now();
        while (Date.now() - startTime &lt; 5000) {
            // NOP
        }
    }, 100);
}</code></pre>
<p>With Boomerang Error wrapping enabled, when run through Lighthouse&#8217;s Chrome extension, we see Lighthouse reporting Boomerang taking 20,111ms (simulated 4x CPUs slowdown):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/lighthouse-with-wrap.png" alt="Chrome Lighthouse" /></p>
<p>Once Boomerang&#8217;s wrapping was turned off, the attribution was corrected to show the <code>library.js</code> as the real cause of work (20,011 ms) with Boomerang&#8217;s CPU usage only at 116ms (simulated 4x CPU slowdown):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/lighthouse.png" alt="Chrome Lighthouse Without Wrapping" /></p>
<p>We have <a href="https://github.com/GoogleChrome/lighthouse/issues/7005">opened an issue with Lighthouse</a> to see if the attribution can be better explained.</p>
<p>You can <a href="#disabling-wrapping">disable wrapping</a> on your local development machine to verify the changes.  When using Lighthouse through Page Speed Insights, you may need to disable Boomerang JavaScript Error Tracking entirely via your <code>BOOMR.init()</code> call (open-source Boomerang) or application configuration (Akamai mPulse).</p>
<p><a name="webpagetest"></a></p>
<h3>WebPagetest</h3>
<p>Once Boomerang&#8217;s JavaScript Error Tracking with wrapping is enabled, WebPagetest may be confused about JavaScript CPU attribution, due to the same reasons <a href="#browser-cpu-profiling">as above</a>.</p>
<p>When Boomerang&#8217;s wrapping is enabled, the wrapping functions often get incorrectly blamed for work being done in other libraries or the root page itself.</p>
<p>Using the same exaggerated expensive function:</p>
<pre><code class="language-js">// library-5s.js
function init() {
    setTimeout(function myCallback() {
        var startTime = Date.now();
        while (Date.now() - startTime &lt; 5000) {
            // NOP
        }
    }, 100);
}</code></pre>
<p>When run through WebPagetest, we see <code>library-5s.js</code> heavy on the execution for around 5 seconds (according to the pink <em>JS Execution</em> line in the waterfall).</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/wpt.png" alt="WebPagetest without Wrapping" /></p>
<p>When Boomerang Error wrapping is turned on, we see WebPagetest now blames all of that work on <code>boomerang.js</code> instead of <code>library-5s.js</code>, even though the later is doing the majority of the work:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2019/04/wpt-with-wrap.png" alt="WebPagetest with Wrapping" /></p>
<p>You can <a href="#disabling-wrapping">disable wrapping</a> on your local development machine to verify the changes.  When using WebPagetest, you may need to disable Boomerang JavaScript Error Tracking entirely via your <code>BOOMR.init()</code> call (open-source Boomerang) or application configuration (Akamai mPulse), or using WebPagetest&#8217;s options to <a href="#disabling-wrapping">disable <code>monitorEvents</code></a>.</p>
<p><a name="side-effects-summary"></a></p>
<h2>Summary</h2>
<p>Boomerang&#8217;s Error Tracking <a href="http://akamai.github.io/boomerang/BOOMR.plugins.Errors.html">plugin</a> is a great way to get telemetry on application health.  In order to gather <a href="#what-is-boomerang-doing">better error messages</a>, Boomerang may <a href="#side-effects-boomerang-wrap-example">wrap</a> top-level functions such as <code>setTimeout()</code>, <code>setInterval()</code> and <code>addEventListener()</code>.</p>
<p>Once this happens, <a href="#console-logs">developer tools</a> and performance audits such as <a href="#chrome-lighthouse">Chrome Lighthouse</a>, <a href="#page-speed-insights">Page Speed Insights</a> or <a href="#webpagetest">WebPagetest</a> may get confused about the &quot;cause&quot; of JavaScript activity.  As a result, it may look like Boomerang is causing errors when the true reason for the error is another library or the page itself.  In addition, performance audits may assign more &quot;work&quot; to Boomerang than is accurate, as the work is really happening inside a library or the page itself (that Boomerang had wrapped).</p>
<p>If the wrapping is causing confusion or concern, you can <a href="#disabling-wrapping">disable wrapping</a> entirely through the Errors plugin.</p><p>The post <a href="https://nicj.net/side-effects-of-boomerangs-javascript-error-tracking/" target="_blank">Side Effects of Boomerang's JavaScript Error Tracking</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/side-effects-of-boomerangs-javascript-error-tracking/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>3rdParty.io</title>
		<link>https://nicj.net/3rdparty-io/</link>
					<comments>https://nicj.net/3rdparty-io/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 01 Aug 2018 18:03:30 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2385</guid>

					<description><![CDATA[<p>3rdParty.io is a new tool I&#8217;ve released to evangelize best-practices for third-party scripts and libraries: 3rdParty.io is a YSlow-like developer tool that&#8217;s designed to run best-practice checklists on third-party scripts and libraries. While YSlow and other great synthetic testing tools (such as Chrome&#8217;s Lighthouse) perform audits on entire websites, 3rdParty.io focuses on individual third-party JavaScript [&#8230;]</p>
<p>The post <a href="https://nicj.net/3rdparty-io/" target="_blank">3rdParty.io</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="https://3rdparty.io/">3rdParty.io</a> is a new tool I&#8217;ve released to evangelize best-practices for third-party scripts and libraries:</p>
<p><a href="https://3rdparty.io"><img loading="lazy" class="aligncenter size-large wp-image-2387" src="https://o.nicj.net/wp-content/uploads/2018/07/home-840x363.png" alt="3rdParty.io" width="840" height="363" srcset="https://o.nicj.net/wp-content/uploads/2018/07/home-840x363.png 840w, https://o.nicj.net/wp-content/uploads/2018/07/home-400x173.png 400w, https://o.nicj.net/wp-content/uploads/2018/07/home-768x332.png 768w, https://o.nicj.net/wp-content/uploads/2018/07/home.png 1207w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>3rdParty.io is a YSlow-like developer tool that&#8217;s designed to run best-practice checklists on third-party scripts and libraries. While YSlow and other great synthetic testing tools (such as Chrome&#8217;s <a href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a>) perform audits on entire websites, 3rdParty.io focuses on individual third-party JavaScript files, validating that they won&#8217;t slow down your site or cause compatibility or security concerns.</p>
<p>What exactly is a third party? Any JavaScript script, library, widget, snippet, tag, ad, analytics, CSS, font, or resource that you include in your site that comes from somewhere else.</p>
<p>Third-party scripts have the ability to break your site, cause performance problems, leave you vulnerable, and cost you money.</p>
<p>There are many great providers of third-party scripts out there who are following best practices to ensure that their content is secure and performant. However, not every third-party has followed best practices, and these mistakes can wreak havoc on your site.</p>
<p>3rdParty.io&#8217;s goals are to monitor popular third-party scripts, promote best practices, and to ensure that content providers can feel comfortable including third-party scripts and libraries in their site without worry.</p>
<p>There&#8217;s an existing <a href="https://3rdparty.io/products">library of third-parties</a> that are being constantly validated (with many more being included soon).  You can click on any of them to get a breakdown of what they&#8217;re doing right, and how they can improve:</p>
<p><a href="https://3rdparty.io"><img loading="lazy" class="aligncenter size-large wp-image-2388" src="https://o.nicj.net/wp-content/uploads/2018/07/lib-840x327.png" alt="3rdParty.io mPulse" width="840" height="327" srcset="https://o.nicj.net/wp-content/uploads/2018/07/lib-840x327.png 840w, https://o.nicj.net/wp-content/uploads/2018/07/lib-400x156.png 400w, https://o.nicj.net/wp-content/uploads/2018/07/lib-768x299.png 768w, https://o.nicj.net/wp-content/uploads/2018/07/lib.png 1188w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>Each check (there are about 60 today) can be expanded to get details of why the check is important, and how it can be fixed:</p>
<p><a href="https://3rdparty.io"><img loading="lazy" class="aligncenter size-large wp-image-2386" src="https://o.nicj.net/wp-content/uploads/2018/07/check-840x417.png" alt="3rdParty.io TAO check" width="840" height="417" srcset="https://o.nicj.net/wp-content/uploads/2018/07/check-840x417.png 840w, https://o.nicj.net/wp-content/uploads/2018/07/check-400x199.png 400w, https://o.nicj.net/wp-content/uploads/2018/07/check-768x381.png 768w, https://o.nicj.net/wp-content/uploads/2018/07/check.png 1164w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>You can run any third-party JavaScript library through the site via the home page.  Simply paste the JavaScript URL into the <em>Check!</em> bar:</p>
<p><a href="https://3rdparty.io"><img loading="lazy" class="aligncenter size-large wp-image-2392" src="https://o.nicj.net/wp-content/uploads/2018/07/on-demand-1-840x59.png" alt="3rdParty.io On Demand" width="840" height="59" srcset="https://o.nicj.net/wp-content/uploads/2018/07/on-demand-1-840x59.png 840w, https://o.nicj.net/wp-content/uploads/2018/07/on-demand-1-400x28.png 400w, https://o.nicj.net/wp-content/uploads/2018/07/on-demand-1-768x54.png 768w, https://o.nicj.net/wp-content/uploads/2018/07/on-demand-1.png 1045w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>Once you hit <em>Check!</em>, we will run an automated checkup within a few moments:</p>
<p><a href="https://3rdparty.io"><img loading="lazy" class="aligncenter size-large wp-image-2390" src="https://o.nicj.net/wp-content/uploads/2018/07/on-demand-results-840x526.png" alt="3rdParty.io On Demand Results" width="840" height="526" srcset="https://o.nicj.net/wp-content/uploads/2018/07/on-demand-results-840x526.png 840w, https://o.nicj.net/wp-content/uploads/2018/07/on-demand-results-400x251.png 400w, https://o.nicj.net/wp-content/uploads/2018/07/on-demand-results-768x481.png 768w, https://o.nicj.net/wp-content/uploads/2018/07/on-demand-results.png 1165w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>It&#8217;s still a work-in-progress, with a lot still to be done: adding more checks, making it more reliable, loading more third-parties into the database.  I would love your feedback!</p>
<p>Please check out the tool at <a href="https://3rdparty.io/">3rdParty.io</a>!</p><p>The post <a href="https://nicj.net/3rdparty-io/" target="_blank">3rdParty.io</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/3rdparty-io/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>When Third Parties Stop Being Polite&#8230; and Start Getting Real</title>
		<link>https://nicj.net/when-third-parties-stop-being-polite-and-start-getting-real/</link>
					<comments>https://nicj.net/when-third-parties-stop-being-polite-and-start-getting-real/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 24 Jul 2018 02:20:31 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">https://nicj.net/?p=2405</guid>

					<description><![CDATA[<p>At Fluent 2018, Charlie Vazac and I gave a talk titled When Third Parties Stop Being Polite&#8230; and Start Getting Real. Here’s the abstract: Would you give the Amazon Prime delivery robot the key to your house, just because it stops by to deliver delicious packages every day? Even if you would, do you still [&#8230;]</p>
<p>The post <a href="https://nicj.net/when-third-parties-stop-being-polite-and-start-getting-real/" target="_blank">When Third Parties Stop Being Polite... and Start Getting Real</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="https://youtu.be/L3LKtFh1HkQ"><img loading="lazy" class="aligncenter wp-image-2403 size-large" src="https://o.nicj.net/wp-content/uploads/2018/07/when-third-parties-stop-being-polite-and-start-getting-real-talk-840x471.png" alt="When Third Parties Stop Being Polite... and Start Getting Real" width="840" height="471" srcset="https://o.nicj.net/wp-content/uploads/2018/07/when-third-parties-stop-being-polite-and-start-getting-real-talk-840x471.png 840w, https://o.nicj.net/wp-content/uploads/2018/07/when-third-parties-stop-being-polite-and-start-getting-real-talk-400x224.png 400w, https://o.nicj.net/wp-content/uploads/2018/07/when-third-parties-stop-being-polite-and-start-getting-real-talk-768x431.png 768w, https://o.nicj.net/wp-content/uploads/2018/07/when-third-parties-stop-being-polite-and-start-getting-real-talk.png 1918w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>At Fluent 2018, <a href="https://twitter.com/vazac">Charlie Vazac</a> and I gave a talk titled <i>When Third Parties Stop Being Polite&#8230; and Start Getting Real</i>. Here’s the abstract:</p>
<div style="margin-left: 20px;">
<p>Would you give the Amazon Prime delivery robot the key to your house, just because it stops by to deliver delicious packages every day? Even if you would, do you still have 100% confidence that it wouldn’t accidentally drag in some mud, let the neighbor in, steal your things, or burn your house down? Worst-case scenarios such as these are what you should be planning for when deciding whether or not to include third-party libraries and services on your website. While most libraries have good intentions, by including them on your site, you have given them complete control over the kingdom. Once on your site, they can provide all of the great services you want—or they can destroy everything you’ve worked so hard to build.It’s prudent to be cautious: we’ve all heard stories about how third-party libraries have caused slowdowns, broken websites, and even led to downtime. But how do you evaluate the actual costs and potential risks of a third-party library so you can balance that against the service it provides? Every library requires nonzero overhead to provide the service it claims. In many cases, the overhead is minimal and justified, but we should quantify it to understand the real cost. In addition, libraries need to be carefully crafted so they can avoid causing additional pain when the stars don’t align and things go wrong.</p>
<p>Nic Jansma and Charles Vazac perform an honest audit of several popular third-party libraries to understand their true cost to your site, exploring loading patterns, SPOF avoidance, JavaScript parsing, long tasks, runtime overhead, polyfill headaches, security and privacy concerns, and more. From how the library is loaded, to the moment it phones home, you’ll see how third-parties can affect the host page and discover best practices you can follow to ensure they do the least potential harm.</p>
<p>With all of the great performance tools available to developers today, we’ve gained a lot of insight into just how much third-party libraries are impacting our websites. Nic and Charles detail tools to help you decide if a library’s risks and unseen costs are worth it. While you may not have the time to perform a deep dive into every third-party library you want to include on your site, you’ll leave with a checklist of the most important best practices third-parties should be following for you to have confidence in them.</p>
</div>
<p>You can watch the presentation on <a href="https://youtu.be/L3LKtFh1HkQ">YouTube</a> or catch the <a href="https://docs.google.com/presentation/d/174EE6e7sV_SXPug_gK5GH4Jk5wB35xjqPrvDUG0E6FA">slides</a>.</p><p>The post <a href="https://nicj.net/when-third-parties-stop-being-polite-and-start-getting-real/" target="_blank">When Third Parties Stop Being Polite... and Start Getting Real</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/when-third-parties-stop-being-polite-and-start-getting-real/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ResourceTiming Visibility: Third-Party Scripts, Ads and Page Weight</title>
		<link>https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/</link>
					<comments>https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 28 Mar 2018 01:20:32 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=2192</guid>

					<description><![CDATA[<p>Table Of Contents Introduction How to gather ResourceTiming data How cross-origins play a role 3.1 Cross-Origin Resources 3.2 Cross-Origin Frames Why does this matter? 4.1 Third-Party Scripts 4.2 Ads 4.3 Page Weight Real-world data Workarounds Summary 1. Introduction ResourceTiming is a specification developed by the W3C Web Performance working group, with the goal of exposing [&#8230;]</p>
<p>The post <a href="https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/" target="_blank">ResourceTiming Visibility: Third-Party Scripts, Ads and Page Weight</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table Of Contents</h2>
<ol>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#how-to-gather-resourcetiming-data">How to gather ResourceTiming data</a></li>
<li><a href="#how-cross-origins-play-a-role">How cross-origins play a role</a><br />
3.1 <a href="#cross-origin-resources">Cross-Origin Resources</a><br />
3.2 <a href="#cross-origin-frames">Cross-Origin Frames</a></li>
<li><a href="#why-does-this-matter">Why does this matter?</a><br />
4.1 <a href="#third-party-scripts">Third-Party Scripts</a><br />
4.2 <a href="#advertisements">Ads</a><br />
4.3 <a href="#page-weight">Page Weight</a></li>
<li><a href="#real-world-data">Real-world data</a></li>
<li><a href="#workarounds">Workarounds</a></li>
<li><a href="#summary">Summary</a></li>
</ol>
<p><a name="introduction"></a></p>
<h2>1. Introduction</h2>
<p><a href="https://www.w3.org/TR/resource-timing/">ResourceTiming</a> is a specification developed by the <a href="https://www.w3.org/2010/webperf">W3C Web Performance working group</a>, with the goal of exposing accurate performance metrics about all of the resources downloaded during the entire user experience, such as images, CSS and JavaScript.</p>
<p>For a detailed background on ResourceTiming, you can see my post on <a href="https://nicj.net/resourcetiming-in-practice/">ResourceTiming in Practice</a>.</p>
<p>One important caveat when working with ResourceTiming data is that in most cases, it will <strong>not</strong> give you the full picture of all of the resources fetched during a page load.</p>
<p>Why is that?  Four reasons:</p>
<ol>
<li>Assets served from a different domain &#8212; known as <em>cross-origin resources</em> &#8212; have <a href="https://nicj.net/resourcetiming-in-practice/#timing-allow-origin">restrictions</a> applied to them (on timing and size data), for privacy and security reasons, unless they have the <code>Timing-Allow-Origin</code> HTTP response header</li>
<li>Each <code>&lt;iframe&gt;</code> on the page keeps track of the resources that it fetched, and the <code>frame.performance</code> API that exposes the ResourceTiming data is blocked in a cross-origin <code>&lt;iframe&gt;</code></li>
<li>Some asset types (e.g. <a href="https://github.com/w3c/resource-timing/issues/130"><code>&lt;video&gt;</code> tags</a>) may be affected by browser bugs where they don&#8217;t generate ResourceTiming entries</li>
<li>If the number of resources loaded in a frame exceeds 150, and the page hasn&#8217;t called <code>performance.setResourceTimingBufferSize()</code> to increase the buffer size, ResourceTiming data will be limited to the first 150 resources</li>
</ol>
<p>The combination of these restrictions &#8212; some in our control, some out of our control &#8212; leads to an unfortunate caveat when analyzing ResourceTiming data: <strong>In the wild, you will rarely be able to access the full picture of everything that was fetched on the page</strong>.</p>
<p>Given these blind-spots, you may have a harder time answering these important questions:</p>
<ul>
<li>Are there any third-party JavaScript libraries that are significantly affecting my visitor&#8217;s page load experience?</li>
<li>How many resources are being fetched by my advertisements?</li>
<li>What is my <a href="https://speedcurve.com/blog/web-performance-page-bloat/">Page Weight</a>?</li>
</ul>
<p>We&#8217;ll explore what ResourceTiming Visibility means for your page, and how you can work around these limitations.  We will also sample ResourceTiming Visibility data across the web by looking at the Alexa Top 1000, to see how frequently resources get hidden from our view.</p>
<p>But first, some background:</p>
<p><a name="how-to-gather-resourcetiming-data"></a></p>
<h2>2. How to gather ResourceTiming Data</h2>
<p>ResourceTiming is a straightforward <a href="https://www.w3.org/TR/resource-timing-2/">API</a> to use:  <code>window.performance.getEntriesByType("resource")</code> returns a list of all resources fetched in the current frame:</p>
<pre><code class="javascript">var resources = window.performance.getEntriesByType("resource");

/* eg:
[
    {
        name: "https://www.foo.com/foo.png",
        entryType: "resource",
        startTime: 566.357000003336,
        duration: 4.275999992387369,
        initiatorType: "img",
        redirectEnd: 0,
        redirectStart: 0,
        fetchStart: 566.357000003336,
        domainLookupStart: 566.357000003336,
        domainLookupEnd: 566.357000003336,
        connectStart: 566.357000003336,
        secureConnectionStart: 0,
        connectEnd: 566.357000003336,
        requestStart: 568.4959999925923,
        responseStart: 569.4220000004862,
        responseEnd: 570.6329999957234,
        transferSize: 10000,
        encodedBodySize: 10000,
        decodedBodySize: 10000,
    }, ...
]
*/
</code></pre>
<p>There is a bit of complexity added when you have frames on the site, e.g. from third-party content such as social widgets or advertising.</p>
<p>Since each <code>&lt;iframe&gt;</code> has its own buffer of ResourceTiming entries, you will need to crawl all of the frames on the page (and sub-frames, etc), and join their entries to the main window&#8217;s.</p>
<p><a href="https://gist.github.com/nicjansma/10dc57bae1f6cf139630c6b055d23f98">This gist</a> shows a naive way of doing this.  For a version that deals with all of the complexities of the crawl, such as adjusting resources in each frame to the correct <code>startTime</code>, you should check out <a href="https://github.com/SOASTA/boomerang/blob/master/plugins/restiming.js">Boomerang&#8217;s <code>restiming.js</code> plugin</a>.</p>
<p><a name="how-cross-origins-play-a-role"></a></p>
<h2>3. How cross-origins play a role</h2>
<p>The main challenge with ResourceTiming data is that <strong>cross-origin resources</strong> and <strong>cross-origin frames</strong> will affect the data you have access to.</p>
<p><a name="cross-origin-resources"></a></p>
<h3>3.1 Cross-Origin Resources</h3>
<p>For each window / frame on your page, a resource will either be considered &#8220;same-origin&#8221; or &#8220;cross-origin&#8221;:</p>
<ul>
<li><strong>same-origin</strong> resources share the same protocol, hostname and port of the page</li>
<li><strong>cross-origin</strong> resources have a different protocol, hostname (or subdomain) or port from the page</li>
</ul>
<p>ResourceTiming data <a href="https://www.w3.org/TR/resource-timing-2/#cross-origin-resources">includes all same-origin <em>and</em> cross-origin resources</a>, but there are restrictions applied to cross-origin resources specifically:</p>
<ul>
<li><a href="https://www.w3.org/TR/resource-timing-2/#performanceresourcetiming">Detailed timing information</a> will always be <code>0</code>:
<ul>
<li><code>redirectStart</code></li>
<li><code>redirectEnd</code></li>
<li><code>domainLookupStart</code></li>
<li><code>domainLookupEnd</code></li>
<li><code>connectStart</code></li>
<li><code>connectEnd</code></li>
<li><code>requestStart</code></li>
<li><code>responseStart</code></li>
<li><code>secureConnectionStart</code></li>
<li>That leaves you with just <code>startTime</code> and <code>responseEnd</code> containing timestamps</li>
</ul>
</li>
<li><a href="https://www.w3.org/TR/resource-timing-2/#dom-performanceresourcetiming-transfersize">Size information</a> will always be <code>0</code>:
<ul>
<li><code>transferSize</code></li>
<li><code>encodedBodySize</code></li>
<li><code>decodedBodySize</code></li>
</ul>
</li>
</ul>
<p>For cross-origin assets that you control, i.e. images on your own CDN, you can add a special HTTP response header to force the browser to expose this information:</p>
<pre><code>Timing-Allow-Origin: *
</code></pre>
<p>Unfortunately, for most websites, you will not be in control of all of third-party (and cross-origin) assets being served to your visitors.  Some third party domains have been adding the <code>Timing-Allow-Origin</code> header to their responses, but not all &#8212; <a href="https://discuss.httparchive.org/t/how-many-and-which-resources-have-timing-allow-origin-for-resource-timing/152/6">current usage is around 13%</a>.</p>
<p><a name="cross-origin-frames"></a></p>
<h3>3.2 Cross-Origin Frames</h3>
<p>In addition, every <code>&lt;iframe&gt;</code> you have on your page will have its own list of ResourceTiming entries.  You will be able to crawl any same-origin and anonymous frames to gather their ResourceTiming entries, but cross-origin frames block access to the the <code>contentWindow</code> (and thus the <code>frame.performance</code>) object &#8212; so any resources fetched in cross-origin frames will be <strong>invisible</strong>.</p>
<p>Note that adding <code>Timing-Allow-Origin</code> to a cross-origin <code>&lt;iframe&gt;</code> (HTML) response will give you full access to the ResourceTiming data for that HTML response, but will <em>not</em> allow you to access <code>frame.performance</code>, so all of the <code>&lt;iframe&gt;</code>&#8216;s ResourceTimings remain unreachable.</p>
<p>To recap:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/resourcetiming-visibility-diagram-1.png" alt="ResourceTiming Visibility Diagram" /></p>
<p><a name="why-does-this-matter"></a></p>
<h2>4. Why does this matter?</h2>
<p>Why does this all matter?</p>
<p>When you, a developer, are looking at a browser&#8217;s networking panel (such as Chrome&#8217;s <em>Network</em> tab), you have full visibility into all of the page&#8217;s resources:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/resourcetiming-chrome-network-panel.png" alt="Chrome Network Panel" /></p>
<p>Unfortunately, if you were to <a href="#how-to-gather-resourcetiming-data">use the ResourceTiming API</a> at the same time, you may only be getting a subset of that data.</p>
<p>ResourceTiming isn&#8217;t giving you the full picture.</p>
<p>Without the same visibility into the resources being fetched on a page as a developer has using browser developer tools, if we are <em>just</em> looking at ResourceTiming data, we may be misleading ourselves about what is really happening in the wild.</p>
<p>Let&#8217;s look at a couple scenarios:</p>
<p><a name="third-party-scripts"></a></p>
<h3>4.1 Third-Party Scripts</h3>
<p>ResourceTiming has given us fantastic insight into the resources that our visitors are fetching when visiting our websites.  One of the key benefits of ResourceTiming is that it can help you understand what third-party libraries are doing on your page when you&#8217;re not looking.</p>
<p>Third-party libraries cover the spectrum of:</p>
<ul>
<li>JavaScript libraries like jQuery, Modernizr, Angular, and marketing/performance analytics libraries</li>
<li>CSS libraries such as Bootstrap, animate.css and normalize.css</li>
<li>Social widgets like the Facebook, Twitter and Google+ icons</li>
<li>Fonts like FontAwesome, Google Fonts</li>
<li>Ads (see next section)</li>
</ul>
<p>Each of the above categories bring in different resources.  A JavaScript library may come alone, or it may fetch more resources and send a beacon.  A CSS library may trigger downloads of images or fonts.  Social widgets will often create frames on the page that load even more scripts, images and CSS.  Ads may bring in 100 megabytes of video <em>just because</em>.</p>
<p>All of these third-party resources can be loaded in a multitude of ways as well.  Some of may be embedded (bundled) directly into your site; some may be loaded from a CDN (such as <a href="https://cdnjs.com">cdnjs.com</a>); some libraries may be loaded directly from a service (such a Google Fonts or Akamai mPulse).</p>
<p>The true cost of each library is hard to judge, but ResourceTiming can give us <em>some</em> insight into the content being loaded.</p>
<p>It&#8217;s also important to remember that just because a third-party library behaves one way on your development machine (when you have Developer Tools open) doesn&#8217;t mean that it&#8217;s not going to behave differently for other users, on different networks, with different cookies set, when it detects your Developer Tools aren&#8217;t open.  It&#8217;s always good to keep an eye on the cost of your third-party libraries in the wild, making sure they are behaving as you expect.</p>
<p>So how can we use ResourceTiming data to understand third-party library behavior?</p>
<ul>
<li>We can get detailed timing information: how long does it take to DNS resolve, TCP connect, wait for the first byte and take to download third-party libraries.  All of this information can help you judge the cost of a third-party library, especially if it is for a render-blocking critical resource like non-async JavaScripts, CSS or Fonts.</li>
<li>We can understand the weight (byte cost) of the third party, and the dependencies it brings in, by looking at the <code>transferSize</code>.  We can also see if it&#8217;s properly compressed by comparing <code>encodedBodySize</code> to <code>decodedBodySize</code>.</li>
</ul>
<p>In order to get the detailed timing information and weight of third-party libraries, they need to be either served same-domain (bundled with your other assets), or with a <code>Timing-Allow-Origin</code> HTTP response header.  Otherwise, all you get is the overall duration (without DNS, TCP, request and response times), and no size information.</p>
<p>Take the below screenshot as an example.  In it, there are third-party (cross-origin) images on <code>vgtstatic.com</code> that have <code>Timing-Allow-Origin</code> set, so we get a detailed breakdown of the how long they took.  We can determine Blocking, DNS, TCP, SSL, Request and Response phases of the requests.  We see that many of these resources were Blocked (the grey phase) due to connection limits to <code>*.vgtstatic.com</code>.  Once TCP (green) was established, the Request (yellow) was quick and the Response (purple) was nearly instant:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/third-party-scripts-tao-1.png" alt="ResourceTiming Waterfall with Cross-Origin Resources" /></p>
<p>Contrast all of this information to a cross-origin request to <code>http://www.google-analytics.com/collect</code>.  This resources <em>does not</em> have <code>Timing-Allow-Origin</code> set, so we only see its total Duration (deep blue).   For some reason this beacon took 648 milliseconds, and it is impossible for us to know why.  Was it delayed (Blocked) by other requests to the same domain?  Did <code>google-analytics.com</code> take  a long time to first byte?  Was it a huge download?  Did it redirect from <code>http://</code> to <code>https://</code>?</p>
<p>Since the URL is cross-origin without <code>Timing-Allow-Origin</code>, we also do not have any byte information about it, so we don&#8217;t know how big the response was, or if it was compressed.</p>
<p>The great news is that many of the common third-party domains on the internet are setting the <code>Timing-Allow-Origin</code> header, so you can get full details:</p>
<pre><code>&gt; GET /analytics.js HTTP/1.1
&gt; Host: www.google-analytics.com
&gt;
&lt; HTTP/1.1 200 OK
&lt; Strict-Transport-Security: max-age=10886400; includeSubDomains; preload
&lt; Timing-Allow-Origin: *
&lt; ...
</code></pre>
<p>However, there are still some very common scripts that show up without <code>Timing-Allow-Origin</code> (in the Alexa Top 1000), even when they&#8217;re setting TAO on other assets:</p>
<ul>
<li><code>https://connect.facebook.net/en_US/fbevents.js</code> (200 sites)</li>
<li><code>https://sb.scorecardresearch.com/beacon.js</code> (133 sites)</li>
<li><code>https://d31qbv1cthcecs.cloudfront.net/atrk.js</code> (104 sites)</li>
<li><code>https://www.google-analytics.com/plugins/ua/linkid.js</code> (54 sites)</li>
</ul>
<p>Also remember that any third-party library that loads a cross-origin frame will be effectively hiding all of its cost from ResourceTiming.  See the next section for more details.</p>
<p><a name="advertisements"></a></p>
<h3>4.2 Ads</h3>
<p>Ads are a special type of third-party that requires some additional attention.</p>
<p>Most advertising on the internet loads rich media such as images or videos.  Most often, you&#8217;ll include an ad platform on your website by putting a small JavaScript snippet somewhere on your page, like the example below:</p>
<pre><code>&lt;script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"&gt;&lt;/script&gt;
&lt;ins class="adsbygoogle" style="display:block;width: 120px; height: 600px" data-ad-client="ca-pub-123"&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
</code></pre>
<p>Looks innocent enough right?</p>
<p>Once the ad platform&#8217;s JavaScript is on your page, it&#8217;s going to bootstrap itself and try to fetch the media it wants to display to your visitors.  Here&#8217;s the general lifecycle of an ad platform:</p>
<ul>
<li>The platform is loaded by a single JavaScript file</li>
<li>Once that JavaScript loads, it will often load another library or load a &#8220;configuration file&#8221; that tells the library where to get the media for the ad</li>
<li>Many ad platforms then create an <code>&lt;iframe&gt;</code> on your page to load a HTML file from an advertisor</li>
<li>That <code>&lt;iframe&gt;</code> is often in control of a separate third-party advertisor, which can (and will) load <strong>whatever content it wants</strong>, including additional JavaScript files (for tracking viewing habits), media and CSS.</li>
</ul>
<p>In the end, the original JavaScript ad library (e.g. <code>adsbygoogle.js</code> at 25 KB) might be responsible for loading a total of 250 KB of content, or more &#8212; a 10x increase.</p>
<p>How much of this content is visible from ResourceTiming?  It all depends on a few things:</p>
<ol>
<li>How much content is loaded within your page&#8217;s top level window and has <code>Timing-Allow-Origin</code> set?</li>
<li>How much content is loaded within same-origin (anonymous) frames and has <code>Timing-Allow-Origin</code> set?</li>
<li>How much content is loaded from a cross-origin frame?</li>
</ol>
<p>Let&#8217;s take a look at one example Google AdSense ad:</p>
<ul>
<li>In the screenshot below, the green content was loaded into the top-level window.  Thankfully, most of Google AdSense&#8217;s resources have <code>Timing-Allow-Origin</code> set, so we have complete visibility into how long they took and how many bytes were transferred.  These 3 resources accounted for about 26 KB of data.</li>
<li>If we crawl into accessible same-origin frames, we&#8217;re able to find another 4 resources that accounted for 115 KB of data.  Only one of these was missing <code>Timing-Allow-Origin</code>.</li>
<li>AdSense then loaded the advertiser&#8217;s content in a cross-origin frame.  We&#8217;re unable to look at any of the resources in the cross-origin frame.  There were 11 resources that accounted for 98 KB of data (40.8% of the total)</li>
</ul>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/alayout.png" alt="Google Ads layout" /></p>
<p>Most advertising platforms&#8217;s ads are loaded into cross-origin frames so they are less likely to affect the page itself.  Unfortunately, as you&#8217;ve seen, this means it&#8217;s also easy for advertisers to unintentionally hide their true cost to ResourceTiming.</p>
<p><a name="page-weight"></a></p>
<h3>4.3 Page Weight</h3>
<p>Another challenge is that it makes it really hard to measure <a href="https://speedcurve.com/blog/web-performance-page-bloat/">Page Weight</a>.  Page Weight measures the total <em>cost</em>, in bytes, of loading a page and all of its resources.</p>
<p>Remember that the byte fields of ResourceTiming (<code>transferSize</code>, <code>decodedBodySize</code> and <code>encodedBodySize</code>) are hidden if the resource is <em>cross-origin</em>.  In addition, any resources fetched from a <em>cross-origin frame</em> will be completely invisible to ResourceTiming.</p>
<p>So in order for ResourceTiming to expose the accurate Page Weight of a page, you need to ensure the following:</p>
<ul>
<li>All of your resources are fetched from the same origin as the page
<ul>
<li>Unless: If any resources are fetched from other origins, they must have <code>Timing-Allow-Origin</code></li>
</ul>
</li>
<li>You must not have any cross-origin frames
<ul>
<li>Unless: If you are in control of the cross-origin frame&#8217;s HTML (or you can convince third-parties to <a href="#workarounds">add a snippet to their HTML</a>), you might be able to &#8220;bubble up&#8221; ResourceTiming data from cross-origin frames</li>
</ul>
</li>
</ul>
<p>Anything less than the above, and you&#8217;re not seeing the full Page Weight.</p>
<p><a name="real-world-data"></a></p>
<h2>5. Real World data</h2>
<p>How many of your resources are fully visible to ResourceTiming?  How many are visible, but don&#8217;t contain the detailed timing and size information due to cross-origin restrictions?  How many are completely hidden from your view?</p>
<p>There&#8217;s been previous <a href="https://developer.akamai.com/blog/2017/07/26/measuring-performance-third-party-contributors/">research</a> and <a href="https://discuss.httparchive.org/t/how-many-and-which-resources-have-timing-allow-origin-for-resource-timing/152/6">HTTP Archive stats</a> on <code>Timing-Allow-Origin</code> usage, and I wanted to expand on that research by also seeing how significantly the cross-origin <code>&lt;iframe&gt;</code> issue affects visibility.</p>
<p>To test this, I&#8217;ve built a small <a href="https://github.com/nicjansma/resourcetiming-visibility">testing tool</a> that runs Chrome headless (via <a href="https://github.com/GoogleChrome/puppeteer">puppetteer</a>).</p>
<p>The tool loads the Alexa Top 1000 websites, monitoring <em>all</em> resources fetched in the page by the Chrome native networking APIs.  It then executes a crawl of the ResourceTiming data, going into all of the frames it has access to, and compares what the browser fetched versus what is visible to ResourceTiming.</p>
<p>Resources are put into three visibility buckets:</p>
<ul>
<li><strong>Full</strong>: Same-origin resources, or cross-origin resources that have <code>Timing-Allow-Origin</code> set.  These have all of the timing and size fields available.</li>
<li><strong>Restricted</strong>: Cross-origin resources without <code>Timing-Allow-Origin</code> set.  These only have <code>startTime</code> and <code>responseEnd</code>, and no size fields (so can&#8217;t be used for Page Weight calculations).</li>
<li><strong>Missing</strong>: Any resource loaded in a cross-origin <code>&lt;iframe&gt;</code>.  Completely invisible to ResourceTiming.</li>
</ul>
<h3>Overall</h3>
<p>Across the Alexa Top 1000 sites, <strong>only 22.3% of resources have Full visibility from ResourceTiming</strong>.</p>
<p>A whopping 31.7% of resources are Missing from ResourceTiming, and the other 46% are Restricted, showing no detailed timing or size information:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/resourcetiming-visibility-overall-requests-1.png" alt="ResourceTiming Visibility - Overall by Request Count" /><br />
<em>Overall ResourceTiming Visibilty by Request Count</em></p>
<p>If you&#8217;re interested in measuring <strong>overall byte count</strong> for Page Weight, even more data is missing from ResourceTiming.  <strong>Over 50% of all bytes transferred when loading the Alexa Top 1000 were Missing from ResourceTiming</strong>.  In order to calculate Page Weight, you need Full Visibility, so if you used ResourceTiming to calculate Page Weight, you would only be (on average) including 18% of the actual bytes:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/resourcetiming-visibility-overall-bytes.png" alt="ResourceTiming Visibility - Overall by Byte Count" /><br />
<em>Overall ResourceTiming Visibilty by Byte Count</em></p>
<h3>By Site</h3>
<p>Obviously each site is different, and due to how a site is structured, each site will have a differing degrees of Visibility.  You can see that Alexa Top 1000 websites vary across the Visibility spectrum:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/resourcetiming-visibility-missing-resources.png" alt="ResourceTiming Visibility Missing Percentage by Site" /></p>
<p>There are some sites with 100% Full Visibility.  They&#8217;re serving nearly all of the content from their primary domain, or, have <code>Timing-Allow-Origin</code> on all resources, and don&#8217;t contain any cross-origin frames.  For example, a lot of simple landing pages such as search engines are like this:</p>
<ul>
<li>google.com</li>
<li>sogou.com</li>
<li>telegram.org</li>
</ul>
<p>Other sites have little or no Missing resources but are primarily Restricted Visibility.  This often happens when you serve your homepage on one domain and all static assets on a secondary domain (CDN) <em>without</em> <code>Timing-Allow-Origin</code>:</p>
<ul>
<li>stackexchange.com</li>
<li>chess.com</li>
<li>ask.fm</li>
<li>steamcommunity.com</li>
</ul>
<p>Finally, there are some sites with over 50% Missing resources.  After reviewing these sites, it appears the majority of this is due to advertising being loaded from cross-origin frames:</p>
<ul>
<li>weather.com</li>
<li>time.com</li>
<li>wsj.com</li>
</ul>
<h3>By Content</h3>
<p>It&#8217;s interesting how Visibility commonly differs by the type of content.  For example, CSS and Fonts have a good chance of being Full or at least Restricted Visibility, while Tracking Pixels and Videos are often being loaded in cross-origin frames so are completely Missing.</p>
<p>Breaking down the requests by type, we can see that different types of content are available at different rates:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2018/03/resourcetiming-visibility-by-type.png" alt="ResourceTiming Visibility by Type" /><br />
<em>ResourceTiming Visibility by Type</em></p>
<p>Let&#8217;s look at a couple of these types.</p>
<h4>HTML Content</h4>
<p>For the purposes of this analysis, HTML resources have an <code>initiatorType</code> of <code>iframe</code> or were a URL ending in <code>.htm*</code>.  Note that the page itself is not being reported in this data.</p>
<p>From the previous chart, HTML content is:</p>
<ul>
<li>20% Full Visibility</li>
<li>25% Restricted</li>
<li>53% Missing</li>
</ul>
<p>Remember that all top-level frames will show up in ResourceTiming, either as Full (same-origin) or Restricted (cross-origin without TAO).  Thus, HTML content that is being reported as <em>Missing</em> must be an <code>&lt;iframe&gt;</code> loaded <em>within</em> a cross-origin <code>&lt;iframe&gt;</code>.</p>
<p>For example, <code>https://pagead2.googlesyndication.com/pagead/s/cookie_push.html</code> is often Missing because it&#8217;s loaded within <code>container.html</code> (a cross-origin frame):</p>
<pre><code class="html">&lt;iframe src="https://tpc.googlesyndication.com/safeframe/1-0-17/html/container.html"&gt;
    &lt;html&gt;
        &lt;iframe src="https://pagead2.googlesyndication.com/pagead/s/cookie_push.html"&gt;
    &lt;/html&gt;
&lt;/iframe&gt;
</code></pre>
<p>Here are the top 5 HTML URLs seen across the Alexa Top 1000 that are Missing in ResourceTiming data:</p>
<table>
<thead>
<tr>
<th>URL</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>https://staticxx.facebook.com/connect/xd_arbiter/r/Ms1VZf1Vg1J.js?version=42</code></td>
<td>221</td>
</tr>
<tr>
<td><code>https://pagead2.googlesyndication.com/pagead/s/cookie_push.html</code></td>
<td>195</td>
</tr>
<tr>
<td><code>https://static.eu.criteo.net/empty.html</code></td>
<td>106</td>
</tr>
<tr>
<td><code>https://platform.twitter.com/jot.html</code></td>
<td>83</td>
</tr>
<tr>
<td><code>https://tpc.googlesyndication.com/sodar/6uQTKQJz.html</code></td>
<td>62</td>
</tr>
</tbody>
</table>
<p>All of these appear to be <code>&lt;iframes&gt;</code> injected for cross-frame communication.</p>
<h4>Video Content</h4>
<p>Video content is:</p>
<ul>
<li>2% Full Visibility (0.8% by byte count)</li>
<li>22% Restricted (1.9% by byte count)</li>
<li>75% Missing (97.3% by byte count)</li>
</ul>
<p>Missing video content appears to be pretty site-specific &#8212; there aren&#8217;t many shared video URLs across sites (which isn&#8217;t too surprising).</p>
<p>What is surprising is how often straight <code>&lt;video&gt;</code> tags don&#8217;t often show up in ResourceTiming data.  From my experimentation, this appears to be because of <em>when</em> ResourceTiming data surfaces for <code>&lt;video&gt;</code> content.</p>
<p>In the <a href="https://github.com/nicjansma/resourcetiming-visibility">testing tool</a>, I capture ResourceTiming data at the <code>onload</code> event &#8212; the point which the page appears ready.  In Chrome, Video content can start playing before <code>onload</code>, and it won&#8217;t delay the onload event while it loads the full video.  So the user might be seeing the first frames of the video, but not the entire content of the video by the <code>onload</code> event.</p>
<p>However, it looks like the ResourceTiming entry isn&#8217;t added until <em>after</em> the full video &#8212; which may be several megabytes &#8212; has been loaded from the network.</p>
<p>So unfortunately Video content is especially vulnerable to being Missing, simply because it hasn&#8217;t loaded all frames by the <code>onload</code> event (if that&#8217;s when you&#8217;re capturing all of the ResourceTiming data).</p>
<p>Also note that <a href="https://github.com/w3c/resource-timing/issues/130">some browsers</a> will (currently) <em>never</em> add ResourceTiming entries for <code>&lt;video&gt;</code> tags.</p>
<h3>Most Missing Content</h3>
<p>Across the Alexa Top 1000, there are some URLs that are frequently being loaded in cross-origin frames.</p>
<p>The top URLs appear to be a mixture of JavaScript, IFRAMEs used for cross-frame communication, and images:</p>
<table>
<thead>
<tr>
<th>URL</th>
<th>Count</th>
<th>Total Bytes</th>
</tr>
</thead>
<tbody>
<tr>
<td>tpc.googlesyndication.com/pagead/js/r20180312/r20110914/client/ext/m_window_focus_non_hydra.js</td>
<td>291</td>
<td>350946</td>
</tr>
<tr>
<td>staticxx.facebook.com/connect/xd_arbiter/r/Ms1VZf1Vg1J.js?version=42</td>
<td>221</td>
<td>3178502</td>
</tr>
<tr>
<td>tpc.googlesyndication.com/pagead/js/r20180312/r20110914/activeview/osd_listener.js</td>
<td>219</td>
<td>5789703</td>
</tr>
<tr>
<td>tpc.googlesyndication.com/pagead/js/r20180312/r20110914/abg.js</td>
<td>215</td>
<td>4850400</td>
</tr>
<tr>
<td>pagead2.googlesyndication.com/pagead/s/cookie_push.html</td>
<td>195</td>
<td>144690</td>
</tr>
<tr>
<td>tpc.googlesyndication.com/safeframe/1-0-17/js/ext.js</td>
<td>178</td>
<td>1036316</td>
</tr>
<tr>
<td>pagead2.googlesyndication.com/bg/lSvH2GMDHdWiQ5txKk8DBwe8KHVpOosizyQXSe1BYYE.js</td>
<td>178</td>
<td>886084</td>
</tr>
<tr>
<td>tpc.googlesyndication.com/pagead/js/r20180312/r20110914/client/ext/m_js_controller.js</td>
<td>163</td>
<td>2114273</td>
</tr>
<tr>
<td>googleads.g.doubleclick.net/pagead/id</td>
<td>146</td>
<td>8246</td>
</tr>
<tr>
<td>www.google-analytics.com/analytics.js</td>
<td>126</td>
<td>1860568</td>
</tr>
<tr>
<td>static.eu.criteo.net/empty.html</td>
<td>106</td>
<td>22684</td>
</tr>
<tr>
<td>static.criteo.net/flash/icon/nai_small.png</td>
<td>106</td>
<td>139814</td>
</tr>
<tr>
<td>static.criteo.net/flash/icon/nai_big.png</td>
<td>106</td>
<td>234154</td>
</tr>
<tr>
<td>platform.twitter.com/jot.html</td>
<td>83</td>
<td>6895</td>
</tr>
</tbody>
</table>
<h3>Most Restricted Origins</h3>
<p>We can group Restricted requests by domain to see opportunities where getting a third-party to add the <code>Timing-Allow-Origin</code> header would have the most impact:</p>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Count</th>
<th>Total Bytes</th>
</tr>
</thead>
<tbody>
<tr>
<td>images-eu.ssl-images-amazon.com</td>
<td>714</td>
<td>13204306</td>
</tr>
<tr>
<td>www.facebook.com</td>
<td>653</td>
<td>31286</td>
</tr>
<tr>
<td>www.google-analytics.com</td>
<td>609</td>
<td>757088</td>
</tr>
<tr>
<td>www.google.com</td>
<td>487</td>
<td>3487547</td>
</tr>
<tr>
<td>connect.facebook.net</td>
<td>437</td>
<td>6353078</td>
</tr>
<tr>
<td>images-na.ssl-images-amazon.com</td>
<td>436</td>
<td>8001256</td>
</tr>
<tr>
<td>tags.tiqcdn.com</td>
<td>402</td>
<td>3070430</td>
</tr>
<tr>
<td>sb.scorecardresearch.com</td>
<td>365</td>
<td>140232</td>
</tr>
<tr>
<td>www.googletagmanager.com</td>
<td>264</td>
<td>7684613</td>
</tr>
<tr>
<td>i.ebayimg.com</td>
<td>260</td>
<td>5489475</td>
</tr>
<tr>
<td>http2.mlstatic.com</td>
<td>253</td>
<td>3434650</td>
</tr>
<tr>
<td>ib.adnxs.com</td>
<td>243</td>
<td>1313041</td>
</tr>
<tr>
<td>cdn.doubleverify.com</td>
<td>239</td>
<td>4950712</td>
</tr>
<tr>
<td>mc.yandex.ru</td>
<td>235</td>
<td>2108922</td>
</tr>
</tbody>
</table>
<p>Some of these domains are a bit surprising because the companies behind them have been leaders in adding <code>Timing-Allow-Origin</code> to their third-party content such as Google Analytics and the Facebook Like tag.  Looking at some of the most popular URLs, it&#8217;s clear that they forgot to / haven&#8217;t added TAO to a few popular resources that are often loaded cross-origin:</p>
<ul>
<li><code>https://www.google.com/textinputassistant/tia.png</code></li>
<li><code>https://www.google-analytics.com/plugins/ua/linkid.js</code></li>
<li><code>https://www.google-analytics.com/collect</code></li>
<li><code>https://images-eu.ssl-images-amazon.com/images/G/01/ape/sf/desktop/xyz.html</code></li>
<li><code>https://www.facebook.com/fr/b.php</code></li>
</ul>
<p>Let&#8217;s hope <code>Timing-Allow-Origin</code> usage continues to increase!</p>
<h3>Buffer Sizes</h3>
<p>By default, each frame will only collect up to 150 resources in the PerformanceTimeline.  Once full, no new entries will be added.</p>
<p>How often do sites change the default ResourceTiming buffer size for the main frame?</p>
<table>
<thead>
<tr>
<th>Buffer Size</th>
<th>Number of Sites</th>
</tr>
</thead>
<tbody>
<tr>
<td>150</td>
<td>854</td>
</tr>
<tr>
<td>200</td>
<td>2</td>
</tr>
<tr>
<td>250</td>
<td>2</td>
</tr>
<tr>
<td>300</td>
<td>15</td>
</tr>
<tr>
<td>400</td>
<td>5</td>
</tr>
<tr>
<td>500</td>
<td>5</td>
</tr>
<tr>
<td>1000</td>
<td>6</td>
</tr>
<tr>
<td>1500</td>
<td>1</td>
</tr>
<tr>
<td>100000</td>
<td>3</td>
</tr>
<tr>
<td>1000000</td>
<td>1</td>
</tr>
</tbody>
</table>
<p>85.4% of sites don&#8217;t touch the default ResourceTiming buffer size.  Many sites double it (to 300), and 1% pick numbers over 1000.</p>
<p>There are four sites that are even setting it over 10,000:</p>
<ul>
<li>messenger.com: 100,000</li>
<li>fbcdn.net: 100,000</li>
<li>facebook.com: 100,000</li>
<li>lenovo.com: 1,000,000</li>
</ul>
<p>(this isn&#8217;t recommended unless you&#8217;re actively clearing the buffer)</p>
<p>Should you increase the default buffer size?  Obviously the need to do so varies by site.</p>
<p>In our crawl of the Alexa Top 1000, we find that <strong>14.5% of sites exceed 150 resources in the main frame</strong>, while only 15% of <em>those</em> sites had called <code>setResourceTimingBufferSize()</code> to ensure all resources were captured.</p>
<p><a name="workarounds"></a></p>
<h2>6. Workarounds</h2>
<p>Given the restrictions we have on cross-origin resources and frames, what can we do to increase the visibility of requests on our pages?</p>
<h3>6.1 Timing-Allow-Origin</h3>
<p>To move Restricted Visibility resources to Full Visibility, you need to ensure all cross-origin resources have the <code>Timing-Allow-Origin</code> header.</p>
<p>This should be straightforward for any content you provide (e.g. on a CDN), but it may take convincing third-parties to also add this HTTP response header.</p>
<p>Most people specify <code>*</code> as the allowed origin list, so any domain can read the timing data.  All third party scripts should be doing this!</p>
<pre><code>Timing-Allow-Origin: *
</code></pre>
<h3>6.2 Ensuring a Proper ResourceTiming Buffer Size</h3>
<p>By default, each frame will only collect up to 150 resources in the PerformanceTimeline.  Once full, no new entries will be added.</p>
<p>Obviously this limitation will affect any site that loads more than 150 resources in the main frame (or over 150 resources in a <code>&lt;iframe&gt;</code>).</p>
<p>You can change the default buffer size by calling <a href="https://www.w3.org/TR/resource-timing-2/#dom-performance-setresourcetimingbuffersize"><code>setRessourceTimingBufferSize()</code></a>:</p>
<pre><code>(function(w){
  if (!w ||
    !("performance" in w) ||
    !w.performance ||
    !w.performance.setResourceTimingBufferSize) {
    return;
  }

  w.performance.setResourceTimingBufferSize(500);
})(window);
</code></pre>
<p>Alternatively, you can use a <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver"><code>PerformanceObserver</code></a> (in each frame) to ensure you&#8217;re not affected by the limit.</p>
<h3>6.3 Bubbling ResourceTiming</h3>
<p>It&#8217;s possible to &#8220;leak&#8221; ResourceTiming to parent frames by using <code>window.postMessage()</code> to talk between cross-origin frames.</p>
<p><a href="https://gist.github.com/nicjansma/6f3209a53079f483abb5226156a2054b">Here&#8217;s some sample code</a> that listens for new ResourceTiming entries coming in via a <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver"><code>PerformanceObserver</code></a>, then &#8220;bubbles&#8221; up the message to its parent frame.  The top-level frame can then use these bubbled ResourceTiming entries and merge them with its own list from itself and same-origin frames.</p>
<p>The challenge is obviously convincing all of your third-party / cross-origin frames to use the same bubbling code.</p>
<p>We are considering adding support for this to <a href="https://github.com/SOASTA/boomerang">Boomerang</a>.</p>
<h3>6.4 Synthetic Monitoring</h3>
<p>In the absence of having perfect Visibility via the above two methods, it makes sense to supplement your Real User Monitoring analytics with <a href="https://webpagetest.org">synthetic monitoring</a> as well, which will have access to 100% of the resources.</p>
<p>You could also use synthetic tools to understand the average percentage of Missing resources, and use this to mentally adjust any RUM ResourceTiming data.</p>
<p><a name="summary"></a></p>
<h2>7. Summary</h2>
<p>ResourceTiming is a fantastic API that has given us insight into previously invisible data.  With ResourceTiming, we can get a good sense of how long critical resources are taking on real page loads and beyond.</p>
<p>Unfortunately, due to security and privacy restrictions, ResourceTiming data is limited or missing in many real-world site configurations.  This makes it really hard to track your third party libraries and ads, or gather an accurate Page Weight,</p>
<p>My hope is that we get more third-party services to add <code>Timing-Allow-Origin</code> to all of the resources being commonly fetched.  It&#8217;s not clear if there&#8217;s anything we can do to get more visibility into cross-origin frames, other than convincing them to always &#8220;bubble up&#8221; their resources.</p>
<p>For more information on how this data was gathered, you can look at the <a href="https://github.com/nicjansma/resourcetiming-visibility">resourcetiming-visibility</a> Github repo.</p>
<p>I&#8217;ve also put the raw data into a BigQuery dataset: <code>wolverine-digital:resourcetiming_visibility</code>.</p>
<p>Thanks to <a href="https://twitter.com/vazac">Charlie Vazac</a> and the <a href="https://github.com/SOASTA/boomerang">Boomerang team</a> for feedback on this article.</p><p>The post <a href="https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/" target="_blank">ResourceTiming Visibility: Third-Party Scripts, Ads and Page Weight</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>An Audit of Boomerang&#8217;s Performance</title>
		<link>https://nicj.net/an-audit-of-boomerangs-performance/</link>
					<comments>https://nicj.net/an-audit-of-boomerangs-performance/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Sat, 30 Dec 2017 04:00:10 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=2093</guid>

					<description><![CDATA[<p>Table Of Contents Introduction Methodology Audit Boomerang Lifecycle Loader Snippet mPulse CDN boomerang.js size boomerang.js Parse Time boomerang.js init() config.json (mPulse) Work at onload The Beacon Work beyond onload Work at Unload TL;DR Summary Why did we write this article? 1. Introduction Boomerang is an open-source JavaScript library that measures the page load experience of [&#8230;]</p>
<p>The post <a href="https://nicj.net/an-audit-of-boomerangs-performance/" target="_blank">An Audit of Boomerang's Performance</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2>Table Of Contents</h2>
<ol>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#methodology">Methodology</a></li>
<li><a href="#audit">Audit</a>
<ol>
<li><a href="#boomerang-lifecycle">Boomerang Lifecycle</a></li>
<li><a href="#loader-snippet">Loader Snippet</a></li>
<li><a href="#mpulse-cdn">mPulse CDN</a></li>
<li><a href="#boomerang-size">boomerang.js size</a></li>
<li><a href="#boomerang-parse">boomerang.js Parse Time</a></li>
<li><a href="#boomerang-init">boomerang.js <code>init()</code></a></li>
<li><a href="#config-json"><code>config.json</code> (mPulse)</a></li>
<li><a href="#work-at-onload">Work at <code>onload</code></a></li>
<li><a href="#the-beacon">The Beacon</a></li>
<li><a href="#work-beyond-onload">Work beyond <code>onload</code></a></li>
<li><a href="#work-at-unload">Work at Unload</a></li>
</ol>
</li>
<li><a href="#summary">TL;DR Summary</a></li>
<li><a href="#why">Why did we write this article?</a></li>
</ol>
<p><a name="introduction"></a></p>
<h2>1. Introduction</h2>
<p>Boomerang is an <a href="https://github.com/SOASTA/boomerang">open-source</a> JavaScript library that measures the page load experience of real users, commonly called RUM (Real User Measurement).</p>
<p>Boomerang&#8217;s mission is to measure all aspects of the user&#8217;s experience, while not affecting the load time of the page or causing a degraded user experience in any noticeable way (avoiding the <a href="https://en.wikipedia.org/wiki/Observer_effect_(information_technology)">Observer Effect</a>).  It can be loaded in an asynchronous way that will not delay the page load even if boomerang.js is unavailable.</p>
<p>This post will perform an honest audit of the entire &#8220;cost&#8221; of Boomerang on a page that it is measuring.  We will review every aspect of Boomerang&#8217;s lifecycle, starting with the loader snippet through sending a beacon, and beyond. At each phase, we will try to quantify any overhead (JavaScript, network, etc) it causes.  The goal is to be open and transparent about the work Boomerang has to do when measuring the user&#8217;s experience.</p>
<p>While it is impossible for Boomerang to cause zero overhead, Boomerang strives to be as minimal and lightweight as possible.  If we find any inefficiencies along the way, we will investigate alternatives to improve the library.</p>
<p>You can skip down to the <a href="#summary">TL;DR</a> at the end of this article if you just want an executive summary.</p>
<p><strong>Update 2019-12</strong>: We&#8217;ve made some performance improvements since this article was originally written.  You can read about them <a href="https://nicj.net/boomerang-performance-update/">here</a> and some sections have been updated to note the changes.</p>
<h3>boomerang.js</h3>
<p>Boomerang is an <a href="https://github.com/SOASTA/boomerang">open-source</a> library that is maintained by the developers at <a href="https://akamai.com">Akamai</a>.</p>
<p><a href="https://www.akamai.com/us/en/products/web-performance/mpulse.jsp">mPulse</a>, the Real User Monitoring product from Akamai, utilizes a customized version of Boomerang for its performance measurements.  The differences between the open-source boomerang.js and mPulse&#8217;s boomerang.js are mostly in the form of additional plugins mPulse uses to fetch a domain&#8217;s configuration and features.</p>
<p>While this audit will be focused on mPulse&#8217;s boomerang.js, most of the conclusions we draw can be applied to the open-source boomerang.js as well.</p>
<p><a name="methodology"></a></p>
<h2>2. Methodology</h2>
<p>This audit will be done on mPulse boomerang.js version 1.532.0.</p>
<p>While Boomerang captures performance data for all browsers going back to IE 6 and onward, this audit will primarily be looking at modern browsers: Chrome, Edge, Safari and Firefox.  Modern browsers provide superior profiling and debugging tools, and theoretically provide the best performance.</p>
<p>Modern browsers also feature performance APIs such as NavigationTiming, ResourceTiming and PerformanceObserver.  Boomerang will utilize these APIs, if available, and it is important to understand the processing required to use them.</p>
<p>Where possible, we will share any performance data we can in older browsers and on slower devices, to help compare best- and worst-case scenarios.</p>
<p>mPulse uses a customized version of the open-source Boomerang project.  Specifically, it contains four extra mPulse-specific plugins.  The plugins enabled in mPulse&#8217;s boomerang.js v1.532.0 are:</p>
<ul>
<li><code>config-override.js</code> (mPulse-only)</li>
<li><code>page-params.js</code> (mPulse-only)</li>
<li><code>iframe-delay.js</code></li>
<li><code>auto-xhr.js</code></li>
<li><code>spa.js</code></li>
<li><code>angular.js</code></li>
<li><code>backbone.js</code></li>
<li><code>ember.js</code></li>
<li><code>history.js</code></li>
<li><code>rt.js</code></li>
<li><code>cross-domain.js</code> (mPulse-only)</li>
<li><code>bw.js</code></li>
<li><code>navtiming.js</code></li>
<li><code>restiming.js</code></li>
<li><code>mobile.js</code></li>
<li><code>memory.js</code></li>
<li><code>cache-reload.js</code></li>
<li><code>md5.js</code></li>
<li><code>compression.js</code></li>
<li><code>errors.js</code></li>
<li><code>third-party-analytics.js</code></li>
<li><code>usertiming-compression.js</code></li>
<li><code>usertiming.js</code></li>
<li><code>mq.js</code></li>
<li><code>logn.js</code> (mPulse-only)</li>
</ul>
<p>For more information on any of these plugins, you can read the <a href="https://docs.soasta.com/boomerang-api/">Boomerang API documentation</a>.</p>
<p><a name="audit"></a></p>
<h2>3. Audit</h2>
<p>With that, let&#8217;s get a brief overview of how Boomerang operates!</p>
<p><a name="boomerang-lifecycle"></a></p>
<h3>3.1 Boomerang Lifecycle</h3>
<p>From the moment Boomerang is loaded on a website to when it sends a performance data beacon, the following is a diagram and overview of Boomerang&#8217;s lifecycle (as seen by Chrome Developer Tools&#8217; Timeline):</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/lifecycle.png" alt="Boomerang Lifecycle" /></p>
<ol>
<li>The <em>Boomerang Loader Snippet</em> is placed into the host page.  This snippet loads boomerang.js in an asynchronous, non-blocking manner.</li>
<li>The browser fetches boomerang.js from the remote host.  In the case of mPulse&#8217;s boomerang.js, this is from the Akamai CDN.</li>
<li>Once boomerang.js arrives on the page, the browser must parse and execute the JavaScript.</li>
<li>On startup, Boomerang initializes itself and any bundled plugins.</li>
<li>mPulse&#8217;s boomerang.js then fetches <code>config.json</code> from the Akamai CDN.</li>
<li>At some point the page will have loaded.  Boomerang will wait until after all of the <code>load</code> event callbacks are complete, then it will gather all of the performance data it can.</li>
<li>Boomerang will ask all of the enabled plugins to add whatever data they want to the beacon.</li>
<li>Once all of the plugins have completed their work, Boomerang will build the beacon and will send it out the most reliable way it can.</li>
</ol>
<p>Each of these stages may cause work in the browser:</p>
<ul>
<li>JavaScript parsing time</li>
<li>JavaScript executing time</li>
<li>Network overhead</li>
</ul>
<p>The rest of this article will break down each of the above phases and we will dive into the ways Boomerang requires JavaScript or network overhead.</p>
<p><a name="loader-snippet"></a></p>
<h3>3.2 Loader Snippet</h3>
<p>(<strong>Update 2019-12</strong>: The Loader Snippet has been rewritten for modern browsers, see <a href="https://nicj.net/boomerang-performance-update/#boomerang-performance-update-loader-snippet-improvements">this update</a> for details.)</p>
<p>We recommend loading boomerang.js using the non-blocking script loader pattern.  This methodology, <a href="http://www.lognormal.com/blog/2012/12/12/the-script-loader-pattern/">developed by Philip Tellis</a> and others, ensures that no matter how long it takes boomerang.js to load, it will not affect the page&#8217;s <code>onload</code> event.  We recommend mPulse customers use this pattern to load boomerang.js whenever possible; open-source users of boomerang.js can use the same snippet, pointing at boomerang.js hosted on their own CDN.</p>
<p>The non-blocking script loader pattern is currently <a href="https://akamai.github.io/boomerang/tutorial-loader-snippet.html">47 lines of code and around 890 bytes</a>.  Essentially, an <code>&lt;iframe&gt;</code> is injected into the page, and once that IFRAME&#8217;s <code>onload</code> event fires, a <code>&lt;script&gt;</code> node is added to the IFRAME to load boomerang.js.  Boomerang is then loaded in the IFRAME, but can access the parent window&#8217;s DOM to gather performance data when needed.</p>
<p>For proof that the non-blocking script loader pattern does not affect page load, you can look at this <a href="http://dev.nicj.net/boomerang-audit/test-mpulse-loader-snippet-delayed.html">test case</a> and these <a href="https://www.webpagetest.org/result/171221_HD_bb090190517fa8dd101859e8c1f327fe/">WebPagetest results</a>:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-mpulse-loader-snippet-delayed-wpt-1.png" alt="WebPagetest results of a delayed script" /></p>
<p>In the example above, we&#8217;re using the non-blocking script loader pattern, and the JavaScript is being delayed by 5 seconds.  WebPagetest shows that the page still loaded in just 0.323s (the blue vertical line) while the delayed JavaScript request took 5.344s to complete.</p>
<p>You can review the <a href="https://akamai.github.io/boomerang/tutorial-loader-snippet.html">current version of the mPulse Loader Snippet</a> if you&#8217;d like.</p>
<h4>Common Ways of Loading JavaScript</h4>
<p>Why not just add a <code>&lt;script&gt;</code> tag to load <code>boomerang.js</code> directly into the page&#8217;s DOM?  Unfortunately, all of the common ways of adding <code>&lt;script&gt;</code> tags to a page <em>will block the onload</em> if the script loads slowly.  These include:</p>
<ul>
<li><code>&lt;script&gt;</code> tags embedded in the HTML from the server</li>
<li><code>&lt;script&gt;</code> tags added at runtime by JavaScript</li>
<li><code>&lt;script&gt;</code> tags with <code>async</code> or <code>defer</code></li>
</ul>
<p>Notably, <code>async</code> and <code>defer</code> remove the <code>&lt;script&gt;</code> tag from the critical path, but will still block the page&#8217;s <code>onload</code> event from firing until the script has been loaded.</p>
<p>Boomerang strives to not affect the host page&#8217;s performance timings, so we don&#8217;t want to affect when <code>onload</code> fires.</p>
<p>Examples of each of the common methods above follow:</p>
<h5><code>&lt;script&gt;</code> tags embedded in the HTML from the server</h5>
<p>The oldest way of including JavaScript in a page &#8212; directly via a <code>&lt;script src="..."&gt;</code> tag, will definitely block the <code>onload</code> event.</p>
<p>In this <a href="https://www.webpagetest.org/result/171221_6R_aec493ed2c410d6e2d867dca65473db8">WebPagetest result</a>, we see that the 5-second JavaScript delay causes the page&#8217;s load time (blue vertical line) to be 5.295 seconds:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-script-runtime-wpt-1.png" alt="WebPagetest loading SCRIPT tag embedded in HTML" /></p>
<p>Other events such as <code>domInteractive</code> and <code>domContentLoaded</code> are also delayed.</p>
<h5><code>&lt;script&gt;</code> tags added at runtime by JavaScript</h5>
<p>Many third-party scripts suggest adding their JavaScript to your page via a snippet similar to this:</p>
<pre><code class="javascript">&lt;script&gt;
var newScript = document.createElement('script');
var otherScript = document.getElementsByTagName('script')[0];
newScript.async = 1;
newScript.src = "...";
otherScript.parentNode.insertBefore(newScript, otherScript);
&lt;/script&gt;
</code></pre>
<p>This script creates a <code>&lt;script&gt;</code> node on the fly, then inserts it into the document adjacent to the first <code>&lt;script&gt;</code> node on the page.</p>
<p>Unfortunately, while this helps ensure the <code>domInteractive</code> and <code>domContentLoaded</code> events aren&#8217;t delayed by the 5-second JavaScript, the <code>onload</code> event is still delayed.  See the <a href="https://www.webpagetest.org/result/171221_E7_f37718539df5725a3768e46c86df6fea">WebPagetest result</a> for more details:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-script-embedded-wpt-1.png" alt="WebPagetest loading SCRIPT tag embedded in HTML" /></p>
<h5><code>&lt;script&gt;</code> tags with <code>async</code> or <code>defer</code></h5>
<p><code>&lt;script&gt;</code> <code>async</code> and <code>defer</code> attributes <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script">change how scripts are loaded</a>.  For both, while the browser will not block document parsing, the <code>onload</code> event will still be blocked until the JavaScript has been loaded.</p>
<p>See this <a href="https://www.webpagetest.org/result/171221_PP_6f93a99272188298e266ce805718236d/">WebPagetest result</a> for an example. <code>domInteractive</code> and <code>domContentLoaded</code> events aren&#8217;t delayed, but the <code>onload</code> event still takes 5.329s to fire:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-script-runtime-wpt-1.png" alt="WebPagetest loading SCRIPT tag embedded in HTML" /></p>
<h4>Loader Snippet Overhead</h4>
<p>Now that we&#8217;ve looked why you shouldn&#8217;t use any of the standard methods of loading JavaScript to load Boomerang, let&#8217;s see how the non-blocking script loader snippet works and what it costs.</p>
<p>The work being done in the snippet is pretty minimal, but this code needs to be run in a <code>&lt;script&gt;</code> tag in the host page, so it will be executed in the critical path of the page.</p>
<p>For most modern browsers and devices, the amount of work caused by the snippet should not noticeably affect the page&#8217;s load time.  When profiling the snippet, you should see it take only about 2-15 milliseconds of CPU (with an exception for Edge documented below).  This amount of JavaScript CPU time should be undetectable on most webpages.</p>
<p>Here&#8217;s a breakdown of the CPU time of the non-blocking loader snippet profiled from various devices:</p>
<table>
<thead>
<tr>
<th align="left">Device</th>
<th align="left">OS</th>
<th align="left">Browser</th>
<th align="left">Loader Snippet CPU time (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Chrome 62</td>
<td align="left">7</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Firefox 57</td>
<td align="left">2</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 10</td>
<td align="left">12</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 11</td>
<td align="left">46</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Edge 41</td>
<td align="left">66</td>
</tr>
<tr>
<td align="left">MacBook Pro (2017)</td>
<td align="left">macOS High Sierra</td>
<td align="left">Safari 11</td>
<td align="left">2</td>
</tr>
<tr>
<td align="left">Galaxy S4</td>
<td align="left">Android 4</td>
<td align="left">Chrome 56</td>
<td align="left">37</td>
</tr>
<tr>
<td align="left">Galaxy S8</td>
<td align="left">Android 7</td>
<td align="left">Chrome 63</td>
<td align="left">9</td>
</tr>
<tr>
<td align="left">iPhone 4</td>
<td align="left">iOS 7</td>
<td align="left">Safari 7</td>
<td align="left">19</td>
</tr>
<tr>
<td align="left">iPhone 5s</td>
<td align="left">iOS 11</td>
<td align="left">Safari 11</td>
<td align="left">9</td>
</tr>
<tr>
<td align="left">Kindle Fire 7 (2016)</td>
<td align="left">Fire OS 5</td>
<td align="left">Silk</td>
<td align="left">33</td>
</tr>
</tbody>
</table>
<p>(<strong>Update 2019-12</strong>: The Loader Snippet has been rewritten for modern browsers, and the overhead has been reduced.  See <a href="https://nicj.net/boomerang-performance-update/#boomerang-performance-update-loader-snippet-improvements">this update</a> for details.)</p>
<p>We can see that most modern devices / browsers will execute the non-blocking loader snippet in under 10ms.  For some reason, recent versions of IE / Edge on a fast Desktop still seem to take up to 66ms to execute the snippet &#8212; we&#8217;ve filed <a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15194745/">a bug against the Edge issue tracker</a>.</p>
<p>On older (slower) devices, the loader snippet takes between 20-40ms to execute.</p>
<p>Let&#8217;s take a look at profiles for Chrome and Edge on the PC Desktop.</p>
<h5>Chrome Loader Snippet Profile</h5>
<p>Here&#8217;s an example Chrome 62 (Windows 10) profile of the loader snippet in action.</p>
<p>In this example, the IFRAME doesn&#8217;t load anything itself, so we&#8217;re just profiling the wrapper loader snippet code:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-iframe-creation-desktop-chrome-windows.png" alt="Chrome Profile" /></p>
<p>In general, we find that Chrome spends:</p>
<ul>
<li>&lt;1ms parsing the loader snippet</li>
<li>~5-10ms executing the script and creating the IFRAME</li>
</ul>
<h5>Edge Loader Snippet Profile</h5>
<p>Here&#8217;s an example Edge 41 (Windows 10) profile of the loader snippet:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-iframe-creation-desktop-edge-windows.png" alt="Edge Profile" /></p>
<p>We can see that the loader snippet is more expensive in Edge (and IE 11) than any other platform or browser, taking up to 50ms to execute the loader snippet.</p>
<p>In experimenting with the snippet and looking at the profiling, it seems the majority of this time is caused by the <code>document.open()/write()/close()</code> sequence.  When removed, the rest of the snippet takes a mere 2-5ms.</p>
<p>We have <a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15194745/">filed a bug</a> against the Edge team to investigate.  We have also <a href="https://github.com/SOASTA/boomerang/issues/163">filed a bug against ourselves</a> to see if we can update the document in a way that doesn&#8217;t cause so much work in Edge.</p>
<h4>Getting the Loader Snippet out of the Critical Path</h4>
<p>If the overhead of the loader snippet is of a concern, you can also defer loading boomerang.js until after the <code>onload</code> event has fired.  We don&#8217;t recommend loading boomerang.js that late, if possible &#8212; the later it loads, the higher the possibility you will &#8220;lose&#8221; traffic because boomerang.js doesn&#8217;t load before the user navigates away.</p>
<p>We have example code for how to do this in the <a href="https://docs.soasta.com/boomerang-api/">Boomerang API docs</a>.</p>
<h4>Future Loader Snippet Work</h4>
<p><strike>We are currently experimenting with other ways of loading Boomerang in an asynchronous, non-blocking manner that does not require the creation of an anonymous IFRAME.</strike></p>
<p><strike>You can follow the progress of this research on the <a href="https://github.com/SOASTA/boomerang/issues/164">Boomerang Issues page</a>.</strike></p>
<p><strong>Update 2019-12</strong>: The Loader Snippet has been rewritten to improve performance in modern browsers.  See <a href="https://nicj.net/boomerang-performance-update/#boomerang-performance-update-loader-snippet-improvements">this update</a> for details.</p>
<p><a name="mpulse-cdn"></a></p>
<h3>3.3 mPulse CDN</h3>
<p>Now that we&#8217;ve convinced the browser to load boomerang.js from the network, how is it actually fetched?</p>
<p>Loading boomerang.js from a CDN is highly recommended.  The faster Boomerang arrives on the page, the higher chance it has to actually measure the experience.  Boomerang can arrive late on the page and still capture all of the performance metrics it needs (due to the NavigationTiming API), but there are downsides to it arriving later:</p>
<ul>
<li>The longer it takes Boomerang to load, the less likely it&#8217;ll arrive on the page before the user navigates away (missing beacons)</li>
<li>If the browser does not support NavigationTiming, and if Boomerang loads after the <code>onload</code> event, (and if the loader snippet doesn&#8217;t also save the <code>onload</code> timestamp), the Page Load time may not be accurate</li>
<li>If you&#8217;re using some of the Boomerang plugins such as <code>Errors</code> (JavaScript Error Tracking) that capture data throughout the page&#8217;s lifecycle, they will not capture the data until Boomerang is on the page</li>
</ul>
<p>For all of these reasons, we encourage the Boomerang Loader Snippet to be as early as possible in the <code>&lt;head&gt;</code> of the document and for boomerang.js to be served from a performant CDN.</p>
<h4>boomerang.js via the Akamai mPulse CDN</h4>
<p>For mPulse customers, boomerang.js is fetched via the <a href="https://www.akamai.com/">Akamai CDN</a> which provides lightning-quick responses from the closest location around the world.</p>
<p>According to our RUM data, the <code>boomerang.js</code> package loads from the Akamai CDN to visitors&#8217; browsers in about 191ms, and is cached by the browser for 50% of page loads.</p>
<p>Note that because of the non-blocking loader snippet, even if Boomerang loads slowly (or not at all), it should have zero effect on the page&#8217;s load time.</p>
<p>The URL will generally look something like this:</p>
<pre><code>https://c.go-mpulse.net/boomerang/API-KEY
https://s.go-mpulse.net/boomerang/API-KEY
</code></pre>
<p>Fetching boomerang.js from the above location will result in HTTP response headers similar to the following:</p>
<pre><code>Cache-Control: max-age=604800, s-maxage=604800, stale-while-revalidate=60, stale-if-error=3600
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/javascript;charset=UTF-8
Date: [date]
Expires: [date + 7 days]
Timing-Allow-Origin: *
Transfer-Encoding: chunked
Vary: Accept-Encoding
</code></pre>
<p>(Note: these domains are not currently HTTP/2 enabled.  However, HTTP/2 doesn&#8217;t provide a lot of benefit for a third-party service unless it needs to load multiple resources in parallel.  In the case of Boomerang, <code>boomerang.js</code>, <code>config.json</code> and the beacon are all independent requests that will never overlap)</p>
<p>The headers that affect performance are <code>Cache-Control</code>, <code>Content-Encoding</code> and <code>Timing-Allow-Origin</code>:</p>
<h5><code>Cache-Control</code></h5>
<pre><code>Cache-Control: max-age=604800, s-maxage=604800, stale-while-revalidate=60, stale-if-error=3600
</code></pre>
<p>The <code>Cache-Control</code> header tells the browser to cache boomerang.js for up to 7 days.  This helps ensure that subsequent visits to the same site do not need to re-fetch boomerang.js from the network.</p>
<p>For our mPulse customers, when the boomerang version is updated, using a 7-day cache header means it may take up to 7 days for clients to get the new boomerang.js version.  We think that a 7-day update time is a smart trade-off for cache-hit-rate vs. upgrade delay.</p>
<p>For open-source Boomerang users, we recommend using a versioned URL with a long expiry (e.g. years).</p>
<p>Using these headers, we generally see about 50% of page loads have boomerang.js in the cache.</p>
<h5><code>Content-Encoding</code></h5>
<pre><code>Content-Encoding: gzip
</code></pre>
<p><code>Content-Encoding</code> means boomerang.js is encoded via <code>gzip</code>, reducing its size over the wire.  This reduces the transfer cost of <code>boomerang.js</code> to about 28% of its original size.</p>
<p>The Akamai mPulse CDN also supports Brotli (<code>br</code>) encoding.  This reduces the transfer cost of <code>boomerang.js</code> to about 25% of its original size.</p>
<h5><code>Timing-Allow-Origin</code></h5>
<pre><code>Timing-Allow-Origin: *
</code></pre>
<p>The <code>Timing-Allow-Origin</code> header is part of the <a href="https://www.w3.org/TR/resource-timing-2/">ResourceTiming spec</a>.  If <em>not</em> set, cross-origin resources will only expose the <code>startTime</code> and <code>responseEnd</code> timestamps to ResourceTiming.</p>
<p>We add <code>Timing-Allow-Origin</code> to the HTTP response headers on the Akamai CDN so our customers can review the load times of boomerang.js from their visitors.</p>
<p><a name="boomerang-size"></a></p>
<h3>3.4 boomerang.js size</h3>
<p><strong>Update 2019-12</strong>: Boomerang&#8217;s size has been reduced from a few improvements.  See <a href="https://nicj.net/boomerang-performance-update/">this update</a> for details.</p>
<p><code>boomerang.js</code> generally comes packaged with multiple plugins.  See above for the plugins used in mPulse&#8217;s Boomerang.</p>
<p>The combined source input file size (with debug comments, etc) of <code>boomerang.js</code> and the plugins is 630,372 bytes.  We currently use <a href="https://github.com/mishoo/UglifyJS2">UglifyJS2</a> for minification, which reduces the file size down to 164,057 bytes.  Via the Akamai CDN, this file is then gzip compressed to 47,640 bytes, which is what the browser must transfer over the wire.</p>
<p>We are investigating whether other minifiers might shave some extra bytes off.  <a href="https://github.com/SOASTA/boomerang/issues/177">It looks like UglifyJS3 could reduce the minified file size</a> down to about 160,490 bytes (saving 3,567 bytes), though this only saves ~500 bytes after gzip compression.  We used YUI Compressor in the past, and have tested other minifiers such as Closure, but UglifyJS has provided the best and most reliable minification for our needs.</p>
<p>As compared to the <a href="https://docs.google.com/spreadsheets/d/1z4b533MhLWNA5YF-LGQM5kVHVnG4Wuk2K6uValXfbKM/edit?usp=sharing">top 100 most popular third-party scripts</a>, the minified / gzipped Boomerang package is in the 80th percentile for size.  For reference, Boomerang is smaller than many popular bundled libraries such as AngularJS (111 kB), Ember.js (111 kB), Knockout (58 kB), React (42 kB), D3 (81 kB), moment.js (59 kB), jQuery UI (64 kB), etc.</p>
<p>That being said, we&#8217;d like to find ways to reduce its size even further.  Some options we&#8217;re considering:</p>
<ul>
<li><strike>Enabling <a href="https://en.wikipedia.org/wiki/Brotli">Brotli</a> compression at the CDN.  This would reduce the package size to about 41,000 bytes over the wire.</strike> (implemented in 2019)</li>
<li>Only sending plugins to browsers that support the specific API.  For example, not sending the <code>ResourceTiming</code> plugin to browsers that don&#8217;t support it.</li>
<li>For mPulse customers, having different builds of Boomerang with different features enabled.  For customers wanting a smaller package, that are willing to give up support for things like JavaScript Error Tracking, they could use a customized build that is smaller.</li>
</ul>
<p>For open-source users of Boomerang, you have the ability to <a href="https://docs.soasta.com/boomerang-api/tutorial-building.html">build Boomerang</a> precisely for your needs by editing <code>plugins.json</code>.  See the <a href="https://docs.soasta.com/boomerang-api/tutorial-building.html">documentation</a> for details.</p>
<p><a name="boomerang-parse"></a></p>
<h3>3.5 boomerang.js Parse Time</h3>
<p>Once the browser has fetched the <code>boomerang.js</code> payload from the network, it will need to parse (compile) the JavaScript before it can execute it.</p>
<p>It&#8217;s a little complicated to reliably measure how long the browser takes to parse the JavaScript.  Thankfully there has been some great work from people like <a href="http://carlos.bueno.org/2010/02/measuring-javascript-parse-and-load.html">Carlos Bueno</a>, <a href="https://github.com/danielmendel/DeviceTiming">Daniel Espeset</a> and <a href="https://github.com/nolanlawson/optimize-js/issues/37#issuecomment-270583578">Nolan Lawson</a>, which can give us a good cross-browser approximation of parse/compile that matches up well to what browser profiling in developer tools shows us.</p>
<p>Across our devices, here&#8217;s the amount of time browsers are spending parsing boomerang.js:</p>
<table>
<thead>
<tr>
<th align="left">Device</th>
<th align="left">OS</th>
<th align="left">Browser</th>
<th align="left">Parse time (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Chrome 62</td>
<td align="left">11</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Firefox 57</td>
<td align="left">6</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 10</td>
<td align="left">7</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 11</td>
<td align="left">6</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Edge 41</td>
<td align="left">6</td>
</tr>
<tr>
<td align="left">MacBook Pro (2017)</td>
<td align="left">macOS High Sierra</td>
<td align="left">Safari 11</td>
<td align="left">6</td>
</tr>
<tr>
<td align="left">Galaxy S4</td>
<td align="left">Android 4</td>
<td align="left">Chrome 56</td>
<td align="left">47</td>
</tr>
<tr>
<td align="left">Galaxy S8</td>
<td align="left">Android 7</td>
<td align="left">Chrome 63</td>
<td align="left">13</td>
</tr>
<tr>
<td align="left">iPhone 4</td>
<td align="left">iOS 7</td>
<td align="left">Safari 7</td>
<td align="left">42</td>
</tr>
<tr>
<td align="left">iPhone 5s</td>
<td align="left">iOS 11</td>
<td align="left">Safari 11</td>
<td align="left">12</td>
</tr>
<tr>
<td align="left">Kindle Fire 7 (2016)</td>
<td align="left">Fire OS 5</td>
<td align="left">Silk</td>
<td align="left">41</td>
</tr>
</tbody>
</table>
<p>In our sample of modern devices, we see less than 15 milliseconds parsing the boomerang.js JavaScript.  Older (lower powered) devices such as the Galaxy S4 and Kindle Fire 7 may take up to 42 milliseconds to parse the JavaScript.</p>
<p>To measure the parse time, we&#8217;ve created a test suite that injects the boomerang.js source text into a <code>&lt;script&gt;</code> node on the fly, with random characters appended (to avoid the browser&#8217;s cache).  This is based on the idea from <a href="https://github.com/nolanlawson/optimize-js/issues/37#issuecomment-270583578">Nolan Lawson</a>, and matches pretty well to what is seen in Chrome Developer tools:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-boomerang-script-text-chrome-windows-1.png" alt="Chrome boomerang.js JavaScript Parse" /></p>
<h4>Possible Improvements</h4>
<p>There&#8217;s not a lot we can do to improve the browser&#8217;s parse time, and different browsers may do more work during parsing than others.</p>
<p>The primary way for us to reduce parse time is to reduce the overall complexity of the <code>boomerang.js</code> package.  Some of the changes we&#8217;re considering (which will especially help the slower devices) were discussed in the previous section on boomerang.js&#8217; size.</p>
<p><a name="boomerang-init"></a></p>
<h3>3.6 boomerang.js <code>init()</code></h3>
<p>After the browser parses the boomerang.js JavaScript, it should execute the bootstrapping code in the <code>boomerang.js</code> package.  Boomerang tries to do as little work as possible at this point &#8212; it wants to get off the critical path quickly, and defer as much work as it can to after the <code>onload</code> event.</p>
<p>When loaded, the <code>boomerang.js</code> package will do the following:</p>
<ul>
<li>Create the global <code>BOOMR</code> object</li>
<li>Create all embedded <code>BOOMR.plugins.*</code> plugin objects</li>
<li>Initialize the <code>BOOMR</code> object, which:
<ul>
<li>Registers event handlers for important events such as <code>pageshow</code>, <code>load</code>, <code>beforeunload</code>, etc</li>
<li>Looks at all of the plugins underneath <code>BOOMR.plugins.*</code> and runs <code>.init()</code> on all of them</li>
</ul>
</li>
</ul>
<p>In general, the Boomerang core and each plugin takes a small amount of processing to initialize their data structures and to setup any events that they will later take action on (i.e. gather data at <code>onload</code>).</p>
<p>Below is the measured cost of Boomerang initialization and all of the plugins&#8217; <code>init()</code>:</p>
<table>
<thead>
<tr>
<th align="left">Device</th>
<th align="left">OS</th>
<th align="left">Browser</th>
<th align="left"><code>init()</code> (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Chrome 62</td>
<td align="left">10</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Firefox 57</td>
<td align="left">7</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 10</td>
<td align="left">6</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 11</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Edge 41</td>
<td align="left">11</td>
</tr>
<tr>
<td align="left">MacBook Pro (2017)</td>
<td align="left">macOS High Sierra</td>
<td align="left">Safari 11</td>
<td align="left">3</td>
</tr>
<tr>
<td align="left">Galaxy S4</td>
<td align="left">Android 4</td>
<td align="left">Chrome 56</td>
<td align="left">12</td>
</tr>
<tr>
<td align="left">Galaxy S8</td>
<td align="left">Android 7</td>
<td align="left">Chrome 63</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">iPhone 4</td>
<td align="left">iOS 7</td>
<td align="left">Safari 7</td>
<td align="left">10</td>
</tr>
<tr>
<td align="left">iPhone 5s</td>
<td align="left">iOS 11</td>
<td align="left">Safari 11</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">Kindle Fire 7 (2016)</td>
<td align="left">Fire OS 5</td>
<td align="left">Silk</td>
<td align="left">15</td>
</tr>
</tbody>
</table>
<p>Most modern devices are able to do this initialization in under 10 milliseconds.  Older devices may take 10-20 milliseconds.</p>
<p>Here&#8217;s a sample Chrome Developer Tools visual of the entire initialization process:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-boomerang-script-init-chrome-windows-1.png" alt="Chrome boomerang.js Initialization" /></p>
<p>In the above trace, the initialization takes about 13 milliseconds.  Broken down:</p>
<ul>
<li>General script execution: 3ms</li>
<li>Creating the <code>BOOMR</code> object: 1.5ms</li>
<li>Creating all of the plugins: 2ms</li>
<li>Initializing <code>BOOMR</code>, registering event handlers, etc: 7.5ms</li>
<li>Initializing plugins: 2ms</li>
</ul>
<h4>Possible Improvements</h4>
<p>In looking at various Chrome and Edge traces of the creation/initialization process, we&#8217;ve found a few inefficiencies in our code.</p>
<p>Since initialization is done in the critical path to page load, we want to try to minimize the amount of work done here.  If possible, we should delay work to after the <code>onload</code> event, before we&#8217;re about to send the beacon.</p>
<p>In addition, the script currently executes all code sequentially.  We can considering breaking up this solid block of code into several chunks, executed on a <code>setTimeout(..., 0)</code>.  This will help reduce the possibility of a Long Task (which can lead to UI delays and visitor frustration).  The plugin creation and initialization is all done sequentially right now, and each could probably be executed after a short delay to allow for any other work that needs to run.</p>
<p>We&#8217;ve identified the following areas of improvement:</p>
<ul>
<li><a href="https://github.com/SOASTA/boomerang/issues/165">Create all plugins on a <code>setTimeout(..., 0)</code> to avoid Long Tasks</a></li>
<li><a href="https://github.com/SOASTA/boomerang/issues/165">Initialize all plugins on a <code>setTimeout(..., 0)</code> to avoid Long Tasks</a></li>
<li><a href="https://github.com/SOASTA/boomerang/issues/166">Defer work in Memory plugin to <code>beforebeacon</code></a></li>
<li><a href="https://github.com/SOASTA/boomerang/issues/167">Investigate areas during other plugin <code>init()</code> that could be deferred to later</a></li>
</ul>
<h3>Bootstrapping Summary</h3>
<p>So where are we at so far?</p>
<ol>
<li>The Boomerang Loader Snippet has executed, told the browser to fetch boomerang.js in an asynchronous, non-blocking way.</li>
<li><code>boomerang.js</code> was fetched from the CDN</li>
<li>The browser has parsed the <code>boomerang.js</code> package</li>
<li>The <code>BOOMR</code> object and all of the plugins are created and initialized</li>
</ol>
<p>On modern devices, this will take around 10-40ms.  On slower devices, this could take 60ms or more.</p>
<p>What&#8217;s next?  If you&#8217;re fetching Boomerang as part of mPulse, Boomerang will initiate a <code>config.json</code> request to load the domain&#8217;s configuration.  Users of the open-source Boomerang won&#8217;t have this step.</p>
<p><a name="config-json"></a></p>
<h3>3.7 config.json (mPulse)</h3>
<p>For mPulse, once boomerang.js has loaded, it fetches a configuration file from the mPulse servers.  The URL looks something like:</p>
<pre><code>https://c.go-mpulse.net/boomerang/config.json?...
</code></pre>
<p>This file is fetched via a <code>XMLHttpRequest</code> from the mPulse servers (on the Akamai CDN).  Browser are pros at sending XHRs &#8211; you should not see any work being done to send the XHR.</p>
<p>Since this fetch is a <code>XMLHttpRequest</code>, it is asynchronous and should not block any other part of the page load.  <code>config.json</code> might arrive before, or after, <code>onload</code>.</p>
<p>The size of the <code>config.json</code> response will vary by app.  A minimal <code>config.json</code> will be around 660 bytes (430 bytes gzipped).  Large configurations may be upward of 10 kB (2 kB gzipped).  <code>config.json</code> contains information about the domain, an Anti-Cross-Site-Request-Forgery token, page groups, dimensions and more.</p>
<p>On the Akamai CDN, we see a median download time of <code>config.json</code> of 240ms.  Again, this is an asynchronous XHR, so it should not block any work in the browser, but <code>config.json</code> is required before a beacon can be sent (due to the Anti-CSRF token), so it&#8217;s important that it&#8217;s fetched as soon as possible.  This takes a bit longer to download than boomerang.js because it cannot be cached at the CDN.</p>
<p><code>config.json</code> is served with the following HTTP response headers:</p>
<pre><code>Cache-Control: private, max-age=300, stale-while-revalidate=60, stale-if-error=120
Timing-Allow-Origin: *
Content-Encoding: gzip
</code></pre>
<p>Note that the <code>Cache-Control</code> header specifies <code>config.json</code> should only be cached by the browser for 5 minutes.  This allows our customers to change their domain&#8217;s configuration and see the results within 5 minutes.</p>
<p>Once <code>config.json</code> is returned, the browser must parse the JSON.  While the time it takes depends on the size of <code>config.json</code>, we were not able to convince any browser to take more than 1 millisecond parsing even the largest <code>config.json</code> response.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-boomerang-script-desktop-chrome-windows-config-json-1.png" alt="Chrome config.json parsing" /></p>
<p>After the JSON is parsed, Boomerang sends the configuration to each of the plugins again (calling <code>BOOMR.plugins.X.init()</code> a second time).  This is required as many plugins are disabled by default, and <code>config.json</code> turns features such as ResourceTiming, SPA instrumentation and others on, if enabled.</p>
<table>
<thead>
<tr>
<th align="left">Device</th>
<th align="left">OS</th>
<th align="left">Browser</th>
<th align="left"><code>config.json</code> <code>init()</code> (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Chrome 62</td>
<td align="left">5</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Firefox 57</td>
<td align="left">7</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 10</td>
<td align="left">5</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">IE 11</td>
<td align="left">5</td>
</tr>
<tr>
<td align="left">PC Desktop</td>
<td align="left">Win 10</td>
<td align="left">Edge 41</td>
<td align="left">5</td>
</tr>
<tr>
<td align="left">MacBook Pro (2017)</td>
<td align="left">macOS High Sierra</td>
<td align="left">Safari 11</td>
<td align="left">3</td>
</tr>
<tr>
<td align="left">Galaxy S4</td>
<td align="left">Android 4</td>
<td align="left">Chrome 56</td>
<td align="left">45</td>
</tr>
<tr>
<td align="left">Galaxy S8</td>
<td align="left">Android 7</td>
<td align="left">Chrome 63</td>
<td align="left">10</td>
</tr>
<tr>
<td align="left">iPhone 4</td>
<td align="left">iOS 7</td>
<td align="left">Safari 7</td>
<td align="left">30</td>
</tr>
<tr>
<td align="left">iPhone 5s</td>
<td align="left">iOS 11</td>
<td align="left">Safari 11</td>
<td align="left">10</td>
</tr>
<tr>
<td align="left">Kindle Fire 7 (2016)</td>
<td align="left">Fire OS 5</td>
<td align="left">Silk</td>
<td align="left">20</td>
</tr>
</tbody>
</table>
<p>Modern devices spend less than 10 milliseconds parsing the <code>config.json</code> response and re-initializing plugins with the data.</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/test-boomerang-script-desktop-chrome-windows-config-1.png" alt="Chrome config.json" /></p>
<h4>Possible Improvements</h4>
<p>The same investigations we&#8217;ll be doing for the first <code>init()</code> case apply here as well:</p>
<ul>
<li><a href="https://github.com/SOASTA/boomerang/issues/165">(re)Initialize all plugins on a <code>setTimeout(..., 0)</code> to avoid Long Tasks</a></li>
<li><a href="https://github.com/SOASTA/boomerang/issues/167">Investigate areas during other plugin <code>init()</code> that could be deferred to later</a></li>
</ul>
<p><a name="work-at-onload"></a></p>
<h3>3.8 Work at <code>onload</code></h3>
<p><strong>Update 2019-12</strong>: Boomerang&#8217;s work at <code>onload</code> has been reduced in several cases.  See <a href="https://nicj.net/boomerang-performance-update/">this update</a> for details.</p>
<p>By default, for traditional apps, Boomerang will wait until the <code>onload</code> event fires to gather, compress and send performance data about the user&#8217;s page load experience via a beacon.</p>
<p>For Single Page Apps, Boomerangs waits until the later of the <code>onload</code> event, or until all critical-path network activity (such as JavaScripts) has loaded.  See the <a href="https://docs.soasta.com/boomerang-api/BOOMR.plugins.SPA.html">Boomerang documentation</a> for more details on how Boomerang measures Single Page Apps.</p>
<p>Once the <code>onload</code> event (or SPA load event) has triggered, Boomerang will queue a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate"><code>setImmediate()</code></a> or <code>setTimeout(..., 0)</code> call before it continues with data collection.  This ensures that the Boomerang <code>onload</code> callback is instantaneous, and that it doesn&#8217;t affect the page&#8217;s &#8220;load time&#8221; &#8212; which is measured until all of the <code>load</code> event handlers are complete.  Since Boomerang&#8217;s load event handler is just a <code>setImmediate</code>, it should not affect the <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/loadEventEnd"><code>loadEventEnd</code></a> timestamp.</p>
<p>The <code>setImmediate()</code> queues work for the next period after all of the current tasks have completed.  Thus, immediately after the rest of the <code>onload</code> handlers have finished, Boomerang will continue with gathering, compressing and beaconing the performance data.</p>
<p>At this point, Boomerang notifies any plugins that are listening for the <code>onload</code> event that it has happened.  Each plugin is responsible for gathering, compressing and putting its data onto the Boomerang beacon.  Boomerang plugins that add data at <code>onload</code> include:</p>
<ul>
<li><code>RT</code> adds general page load timestamps (overall page load time and other timers)</li>
<li><code>NavigationTiming</code> gathers all of the <a href="https://www.w3.org/TR/navigation-timing-2/">NavigationTiming</a> timestamps</li>
<li><code>ResourceTiming</code> adds information about each downloaded resource via <a href="https://www.w3.org/TR/resource-timing/">ResourceTiming</a></li>
<li><code>Memory</code> adds statistics about the DOM</li>
<li>etc</li>
</ul>
<p>Some plugins require more work than others, and different pages will be more expensive to process than others.  We will sample the work being done during <code>onload</code> on 3 types of pages:</p>
<h4>Profiling <code>onload</code> on a Blank Page</h4>
<p>This is a page with only the minimal HTML required to load Boomerang.  On it, there are 3 resources and the total <code>onload</code> processing time is about 15ms:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/onload-profile-blank.png" alt="Chrome loading a blank page" /></p>
<p>The majority of the time is spent in 3 plugins: <code>PageParams</code>, <code>RT</code> and <code>ResourceTiming</code>:</p>
<ul>
<li><code>PageParams</code> handles domain configuration such as page group definitions.  The amount of work this plugin does depends on the complexity of the domain configuration.</li>
<li><code>RT</code> captures and calculates all of the timers on the page</li>
<li><code>ResourceTiming</code> <a href="https://github.com/nicjansma/resourcetiming-compression.js">compresses</a> all of the resources on the page, and is usually the most &#8220;expensive&#8221; plugin due to the compression.</li>
</ul>
<h4>Profiling <code>onload</code> on a Blog</h4>
<p>This is loading the <a href="https://nicj.net/">nicj.net</a> home page.  Currently, there are 24 resources and the <code>onload</code> processing time is about 25ms:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/onload-profile-blog.png" alt="Chrome loading a blog" /></p>
<p>The profile looks largely the same the empty page, with more time being spent in <code>ResourceTiming</code> compressing the resources.  The increase of 10ms can be directly attributed to the additional resources.</p>
<p>Profiling a Galaxy S4 (one of the slower devices we&#8217;ve been looking at) doesn&#8217;t look too differently, taking only 28ms:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/onload-profile-blog-mobile-1.png" alt="Chrome Galaxy S4 loading a blog" /></p>
<h4>Profiling <code>onload</code> on a Retail Home Page</h4>
<p>This is a load of the home page of a popular retail website.  On it, there were 161 resources and <code>onload</code> processing time took 37ms:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/onload-profile-retail.png" alt="Chrome loading a retail home page" /></p>
<p>Again, the profile looks largely the same as the previous two pages.  On this page, 25ms is spent compressing the resources.</p>
<p>On the Galaxy S4, we can see that work collecting the performance data (primarily, ResourceTiming data) has increased to about 310ms due to the slower processing power:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/onload-profile-retail-mobile-1.png" alt="Chrome Galaxy S4 loading a blank page" /></p>
<p>We will investigate ways of reducing this work on mobile devices.</p>
<h4>Possible Improvements</h4>
<p>While the work done for the beacon is outside of the critical path of the page load, we&#8217;ve still identified a few areas of improvement:</p>
<ul>
<li><strike>Boomerang and its plugins are getting/setting the page&#8217;s cookie several times during the page&#8217;s lifecycle.  In most browsers, getting and setting the cookie is a semi-expensive operation, since it often involves reading or writing state to the disk.  <a href="https://github.com/SOASTA/boomerang/issues/173">We should investigate if all of these gets/sets are necessary and combine/eliminate any extraneous ones.</a></strike> <strong>Update 2019-12</strong>: <a href="https://nicj.net/boomerang-performance-update/#boomerang-performance-update-cookie-access">See update</a>.</li>
<li><strike>The <code>ResourceTiming</code> plugin is often the most expensive plugin at <code>onload</code>.  This is due to the <a href="https://github.com/nicjansma/resourcetiming-compression.js">compression</a> we apply to ResourceTiming data, which can reduce the ResourceTiming payload to less than 10% of its original size.  <a href="https://github.com/SOASTA/boomerang/issues/174">However, there are opportunities to make this compression more efficient.</a></strike> <strong>Update 2019-12</strong>: <a href="https://nicj.net/boomerang-performance-update/#boomerang-performance-update-resourcetiming-compression-optimization">See update</a>.</li>
<li><a href="https://github.com/SOASTA/boomerang/issues/175">We can possibly break up the event callbacks for plugins that listen to the load event handler so that there&#8217;s not a solid block of code executing</a></li>
<li><a href="https://github.com/SOASTA/boomerang/issues/176"><code>Memory</code> plugin&#8217;s <code>nodeCount</code> is expensive</a></li>
</ul>
<p><a name="the-beacon"></a></p>
<h3>3.9 The Beacon</h3>
<p>After all of the plugins add their data to the beacon, Boomerang will prepare a beacon and send it to the specified cloud server.</p>
<p>Boomerang will send a beacon in one of 3 ways, depending on the beacon size and browser support:</p>
<ul>
<li>A hidden <code>&lt;img&gt;</code> tag</li>
<li><code>XMLHttpRequest</code></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon"><code>navigator.sendBeacon</code></a></li>
</ul>
<p>In general, the size of the beacon varies based on:</p>
<ul>
<li>What type of beacon it is (onload, error, unload)</li>
<li>What version of Boomerang</li>
<li>What plugins / features are enabled (especially whether <code>ResourceTiming</code> is enabled)</li>
<li>What the construction of the page is</li>
<li>What browser</li>
</ul>
<p>Here are some sample beacon sizes with 1.532.0, Chrome 63, all plugins enabled:</p>
<ul>
<li>Blank page:
<ul>
<li>Page Load beacon: 1,876 bytes</li>
<li>Unload beacon: 1,389 bytes</li>
</ul>
</li>
<li>Blog:
<ul>
<li>Page Load beacon: 3,814-4,230 bytes</li>
<li>Unload beacon: 1,390 bytes</li>
</ul>
</li>
<li>Retail:
<ul>
<li>Page Load beacon: 8,816-12,812 bytes</li>
<li>Unload beacon: 1,660 bytes</li>
</ul>
</li>
</ul>
<p>For the blog and retail site, here&#8217;s an approximate breakdown of the beacon&#8217;s data by type or plugin:</p>
<ul>
<li>Required information (domain, start time, beacon type, etc): 2.7%</li>
<li>Session information: 0.6%</li>
<li>Page Dimensions: 0.7%</li>
<li>Timers: 2%</li>
<li><code>ResourceTiming</code>: 85%</li>
<li><code>NavigationTiming</code>: 5%</li>
<li><code>Memory</code>: 1.7%</li>
<li>Misc: 2.3%</li>
</ul>
<p>As you can see, the <code>ResourceTiming</code> plugin dominates the beacon size.  This is even after the <a href="https://github.com/nicjansma/resourcetiming-compression.js">compression</a> reduces it down to less than 15% of the original size.</p>
<p>Boomerang still has to do a little bit of JavaScript work to create the beacon.  Once all of the plugins have registered the data they want to send, Boomerang must then prepare the beacon payload for use in the IMG/XHR/sendBeacon beacon:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/beacon.png" alt="Chrome loading a blank page" /></p>
<p>Once the payload has been prepared, Boomerang queues the <code>IMG</code>, <code>XMLHttpRequest</code> or <code>navigator.sendBeacon</code> call.</p>
<p>Browsers are pros at loading images, sending XHRs and beacons: sending the beacon data should barely register on CPU profiles (56 microseconds in the above example).  All of the methods of sending the beacon are asynchronous and will not affect the browser&#8217;s main thread or user experience.</p>
<p>The beacon itself is delivered quickly to the Akamai mPulse CDN.  We immediately return a 0-byte <code>image/gif</code> in a <code>204 No Content</code> response before processing any beacon data, so the browser immediately gets the response and moves on.</p>
<p><a name="work-beyond-onload"></a></p>
<h3>3.10 Work Beyond <code>onload</code></h3>
<p>Besides capturing performance data about the page load process, some plugins may also be active beyond <code>onload</code>.  For example:</p>
<ul>
<li>The <a href="https://docs.soasta.com/boomerang-api/BOOMR.plugins.Errors.html"><code>Errors</code></a> plugin optionally monitors for JavaScript errors after page load and will send beacons for batches of errors when found</li>
<li>The <a href="https://docs.soasta.com/boomerang-api/BOOMR.plugins.Continuity.html"><code>Continuity</code></a> plugin optionally monitors for user interactions after page load an will send beacons to report on these experiences</li>
</ul>
<p>Each of these plugins will have its own performance characteristics.  We will profile them in a future article.</p>
<p><a name="work-at-unload"></a></p>
<h3>3.11 Work at Unload</h3>
<p>In order to aid in monitoring Session Duration (how long someone was looking at a page), Boomerang will also attempt to send an <code>unload</code> beacon when the page is being navigated away from (or the browser is closed).</p>
<p>Boomerang listens to the <code>beforeunload</code>, <code>pagehide</code> and <code>unload</code> events, and will send a minimal beacon (with the <code>rt.quit=</code> parameter to designate it&#8217;s an unload beacon).</p>
<p>Note that not all of these events fire reliably on mobile browsers, and we see only about 30% of page loads send a successful unload beacon.</p>
<p>Here&#8217;s a Chrome Developer Tools profile of one <code>unload</code> beacon:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2017/12/unload.png" alt="Chrome loading a blank page" /></p>
<p>In this example, we&#8217;re spending about 10ms at <code>beforeunload</code> before sending the beacon.</p>
<h4>Possible Improvements</h4>
<p>In profiling a few examples of <code>unload</code> beacon, we&#8217;ve found some areas of improvement:</p>
<ul>
<li>Various plugins are reading/writing the cookie several times during unload.  <a href="https://github.com/SOASTA/boomerang/issues/169">We could possibly coalesce them into one or two calls.</a></li>
<li>The <code>NavigationTiming</code> unload handler is running, even if it already ran at <code>onload</code>.  We may want to run it again to <em>just</em> populate the <code>unload</code> event timestamp, but <a href="https://github.com/SOASTA/boomerang/issues/170">we probably don&#8217;t need to re-run all of the code.</a></li>
<li>The <code>Memory</code> plugin is updating node counts in the <code>unload</code> handler.  This can take a few milliseconds, <a href="https://github.com/SOASTA/boomerang/issues/171">so we may want to avoid doing it.</a></li>
<li>The <code>PageParams</code> plugin (mPulse) is running twice at <code>beforeunload</code> even if it ran at <code>onload</code>.  <a href="https://github.com/SOASTA/boomerang/issues/172">We can probably skip some of its work.</a></li>
<li><a href="https://github.com/SOASTA/boomerang/issues/168">Review unload beacon size</a></li>
</ul>
<p><a name="summary"></a></p>
<h2>4. TL;DR Summary</h2>
<p>So at the end of the day, what does Boomerang &#8220;cost&#8221;?</p>
<ul>
<li>During the page load critical path (loader snippet, boomerang.js parse, creation and initialization), Boomerang will require approximately 10-40ms CPU time for modern devices and 60ms for lower-end devices</li>
<li>Outside of the critical path, Boomerang may require 10-40ms CPU to gather and beacon performance data for modern devices and upwards of 300ms for lower-end devices</li>
<li>Loading boomerang.js and its plugins will generally take less than 200ms to download (in a non-blocking way) and transfer around 50kB</li>
<li>The mPulse beacon will vary by site, but may be between 2-20 kB or more, depending on the enabled features</li>
</ul>
<p>We&#8217;ve identified several areas for improvement.  They&#8217;re being tracked in the <a href="https://github.com/SOASTA/boomerang/issues?utf8=%E2%9C%93&amp;q=is%3Aissue+is%3Aopen+performance+audit">Boomerang Github Issues</a> page.</p>
<p>If you&#8217;re concerned about the overhead of Boomerang, you have some options:</p>
<ol>
<li>If you can&#8217;t have Boomerang executing on the critical path to <code>onload</code>, you can delay the loader snippet to execute after the <code>onload</code> event.</li>
<li>Disable or remove plugins
<ul>
<li>For mPulse customers, you should disable any features that you&#8217;re not using</li>
<li>For open-source users of Boomerang, you can remove plugins you&#8217;re not using by modifying the <code>plugins.json</code> prior to build.</li>
</ul>
</li>
</ol>
<p><a name="why"></a></p>
<h2>5. Why did we write this article?</h2>
<p>We wanted to gain a deeper understanding of how much impact Boomerang has on the host page, and to identify any areas we can improve on.  Reviewing all aspects of Boomerang&#8217;s lifecycle helps put the overhead into perspective, so we can identify areas to focus on.</p>
<p>We&#8217;d love to see this kind of analysis from other third-party scripts.  Details like these can help websites understand the cost/benefit analysis of adding a third-party script to their site.</p>
<p><a name="thanks"></a></p>
<h2>Thanks</h2>
<p>I&#8217;d like to thanks to everyone who has worked on Boomerang and who has helped with gathering data for this article: Philip Tellis, Charles Vazac, Nigel Heron, Andreas Marschke, Paul Calvano, Yoav Weiss, Simon Hearne, Ron Pierce and all of the <a href="https://github.com/SOASTA/boomerang/graphs/contributors">open-source contributors</a>.</p><p>The post <a href="https://nicj.net/an-audit-of-boomerangs-performance/" target="_blank">An Audit of Boomerang's Performance</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/an-audit-of-boomerangs-performance/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>Reliably Measuring Responsiveness in the Wild</title>
		<link>https://nicj.net/reliably-measuring-responsiveness-in-the-wild/</link>
					<comments>https://nicj.net/reliably-measuring-responsiveness-in-the-wild/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 20 Jul 2017 08:00:20 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=2086</guid>

					<description><![CDATA[<p>At Fluent 2017, Shubhie Panicker and I talked Reliably Measuring Responsiveness in the Wild. Here&#8217;s the abstract: Responsiveness to user interaction is crucial for users of web apps, and businesses need to be able to measure responsiveness so they can be confident that their users are happy. Unfortunately, users are regularly bogged down by frustrations [&#8230;]</p>
<p>The post <a href="https://nicj.net/reliably-measuring-responsiveness-in-the-wild/" target="_blank">Reliably Measuring Responsiveness in the Wild</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="https://www.slideshare.net/nicjansma/reliably-measuring-responsiveness"><img loading="lazy" class="wp-image-2014 size-large" src="https://o.nicj.net/wp-content/uploads/2017/11/reliably-measuring-responsiveness-slides-840x473.png" alt="Slides" width="840" height="473" srcset="https://o.nicj.net/wp-content/uploads/2017/11/reliably-measuring-responsiveness-slides-840x473.png 840w, https://o.nicj.net/wp-content/uploads/2017/11/reliably-measuring-responsiveness-slides-400x225.png 400w, https://o.nicj.net/wp-content/uploads/2017/11/reliably-measuring-responsiveness-slides-768x432.png 768w, https://o.nicj.net/wp-content/uploads/2017/11/reliably-measuring-responsiveness-slides.png 960w" sizes="(max-width: 840px) 100vw, 840px" /></a></p>
<p>At <a href="https://conferences.oreilly.com/fluent/fl-ca">Fluent 2017</a>, Shubhie Panicker and I talked <a href="https://www.slideshare.net/nicjansma/reliably-measuring-responsiveness"><i>Reliably Measuring Responsiveness in the Wild</i></a>.  Here&#8217;s the abstract:</p>
<div style="margin-left: 20px">
Responsiveness to user interaction is crucial for users of web apps, and businesses need to be able to measure responsiveness so they can be confident that their users are happy. Unfortunately, users are regularly bogged down by frustrations such as a delayed &#8220;time to interactive” during page load, high or variable input latency on critical interaction events (tap, click, scroll, etc.), and janky animations or scrolling. These negative experiences turn away visitors, affecting the bottom line. Sites that include third-party content (ads, social plugins, etc.) are frequently the worst offenders.</p>
<p>The culprit behind all these responsiveness issues are “long tasks,&#8221; which monopolize the UI thread for extended periods and block other critical tasks from executing. Developers lack the necessary APIs and tools to measure and gain insight into such problems in the wild and are essentially flying blind trying to figure out what the main offenders are. While developers are able to measure some aspects of responsiveness, it’s often not in a reliable, performant, or “good citizen” way, and it’s near impossible to correctly identify the perpetrators.</p>
<p>Shubhie Panicker and Nic Jansma share new web performance APIs that enable developers to reliably measure responsiveness and correctly identify first- and third-party culprits for bad experiences. Shubhie and Nic dive into real-user measurement (RUM) web performance APIs they have developed: standardized web platform APIs such as Long Tasks as well as JavaScript APIs that build atop platform APIs, such as Time To Interactive. Shubhie and Nic then compare these measurements to business metrics using real-world data and demonstrate how web developers can detect issues and reliably measure responsiveness in the wild—both at page load and postload—and thwart the culprits, showing you how to gather the data you need to hold your third-party scripts accountable.
</p></div>
<p>You can watch the presentation on <a href="https://www.youtube.com/watch?v=y5qPix1tdOE">YouTube</a>.</p><p>The post <a href="https://nicj.net/reliably-measuring-responsiveness-in-the-wild/" target="_blank">Reliably Measuring Responsiveness in the Wild</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/reliably-measuring-responsiveness-in-the-wild/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Measuring Real User Performance in the Browser</title>
		<link>https://nicj.net/measuring-real-user-performance-in-the-browser/</link>
					<comments>https://nicj.net/measuring-real-user-performance-in-the-browser/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 11 Oct 2016 23:54:02 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1949</guid>

					<description><![CDATA[<p>Philip Tellis and I gave a tutorial on Measuring Real User Performance in the Browser at Velocity New York 2016. Slides can be found at Slideshare: In the tutorial, we cover everything you need to know about measuring your visitors&#8217; experience (also known as Real User Monitoring, or RUM): History of Real User Measurement Browser Performance [&#8230;]</p>
<p>The post <a href="https://nicj.net/measuring-real-user-performance-in-the-browser/" target="_blank">Measuring Real User Performance in the Browser</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Philip Tellis and I gave a tutorial on <i>Measuring Real User Performance in the Browser</i> at <a href="http://conferences.oreilly.com/velocity/devops-web-performance-ny">Velocity New York 2016</a>. Slides can be found at <a href="http://www.slideshare.net/nicjansma/measuring-real-user-performance-in-the-browser">Slideshare</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/measuring-real-user-performance-in-the-browser"><img loading="lazy" class="aligncenter size-large wp-image-1954" src="https://o.nicj.net/wp-content/uploads/2016/10/Measuring-Real-User-Performance-in-the-Browser-1-600x337.png" alt="measuring-real-user-performance-in-the-browser" width="600" height="337" /></a></p>
<p>In the tutorial, we cover everything you need to know about measuring your visitors&#8217; experience (also known as Real User Monitoring, or RUM):</p>
<ul>
<li>History of Real User Measurement</li>
<li>Browser Performance APIs</li>
<li>Visual Experience</li>
<li>Beaconing</li>
<li>Single Page Apps</li>
<li>Continuity</li>
<li>Nixing Noise</li>
</ul>
<p>The 3-hour tutorial video is also available on <a href="https://youtu.be/yrWLi524YLM">YouTube</a>.</p><p>The post <a href="https://nicj.net/measuring-real-user-performance-in-the-browser/" target="_blank">Measuring Real User Performance in the Browser</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/measuring-real-user-performance-in-the-browser/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AMP: Does it Really Make Your Site Faster?</title>
		<link>https://nicj.net/amp-does-it-really-make-your-site-faster/</link>
					<comments>https://nicj.net/amp-does-it-really-make-your-site-faster/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 11 Oct 2016 23:44:55 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1945</guid>

					<description><![CDATA[<p>Nigel Heron and I gave a talk about Accelerated Mobile Pages (AMP) at Velocity New York 2016.  We have slides available on Slideshare: In this talk, we dig into AMP to determine whether or not it gives your visitors a better page load experience.  We cover: What is AMP? Why should AMP pages be faster? How do we measure the real [&#8230;]</p>
<p>The post <a href="https://nicj.net/amp-does-it-really-make-your-site-faster/" target="_blank">AMP: Does it Really Make Your Site Faster?</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Nigel Heron and I gave a talk about Accelerated Mobile Pages (AMP) at <a href="http://conferences.oreilly.com/velocity/devops-web-performance-ny">Velocity New York 2016</a>.  We have slides available on <a href="http://www.slideshare.net/nicjansma/amp-does-it-really-make-your-site-faster">Slideshare</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/amp-does-it-really-make-your-site-faster"><img loading="lazy" class="aligncenter size-large wp-image-1946" src="https://o.nicj.net/wp-content/uploads/2016/10/AMP-Does-it-really-make-your-site-faster-600x338.png" alt="amp-does-it-really-make-your-site-faster" width="600" height="338" /></a></p>
<p>In this talk, we dig into AMP to determine whether or not it gives your visitors a better page load experience.  We cover:</p>
<ul>
<li>What is AMP?</li>
<li>Why should AMP pages be faster?</li>
<li>How do we measure the real user experience for AMP pages?</li>
<li>A demo of how to use AMP analytics</li>
<li>Real-world performance data from AMP visitors</li>
<li>Real-world engagement / conversion data from AMP visitors</li>
</ul>
<p>The talk is also available on <a href="https://www.youtube.com/watch?v=dOiGcgw-r8g">YouTube</a>.</p><p>The post <a href="https://nicj.net/amp-does-it-really-make-your-site-faster/" target="_blank">AMP: Does it Really Make Your Site Faster?</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/amp-does-it-really-make-your-site-faster/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Measuring Continuity</title>
		<link>https://nicj.net/measuring-continuity/</link>
					<comments>https://nicj.net/measuring-continuity/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 23 Jun 2016 15:09:56 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1912</guid>

					<description><![CDATA[<p>Your site&#8217;s page load performance is important (and there are tools like Boomerang to measure it), but how good is your visitor&#8217;s experience as they continue to interact with your site after it has loaded? At Velocity 2016, Philip Tellis and I talked about how you can measure their experience (and emotion!) in Measuring Continuity: [&#8230;]</p>
<p>The post <a href="https://nicj.net/measuring-continuity/" target="_blank">Measuring Continuity</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Your site&#8217;s page load performance is important (and there are tools like <a href="http://github.com/SOASTA/boomerang/">Boomerang</a> to measure it), but how good is your visitor&#8217;s experience as they continue to interact with your site after it has loaded?</p>
<p>At <a href="http://conferences.oreilly.com/velocity/devops-web-performance-ca">Velocity 2016</a>, Philip Tellis and I talked about how you can measure their experience (and emotion!) in <a href="https://www.slideshare.net/nicjansma/measuring-continuity"><i>Measuring Continuity</i></a>:</p>
<p><a href="https://www.slideshare.net/nicjansma/measuring-continuity"><img loading="lazy" class="aligncenter size-large wp-image-1913" src="https://o.nicj.net/wp-content/uploads/2015/10/Screenshot-2016-06-23-08.03.04-600x337.png" alt="Measuring Continuity" width="600" height="337" /></a></p>
<p>We cover how to capture a variety of user experience metrics such as:</p>
<ul>
<li>Browser developer tools&#8217; Timeline metrics such as FPS, CPU, network and heap usage</li>
<li>Interactions like user input, page visibility and device orientation</li>
<li>Complexity metrics including document size, node counts and mutations</li>
<li>User experience metrics like jank, responsiveness and reliability</li>
<li>Tracking emotion with rage clicks, dead clicks and missed clicks</li>
</ul>
<p><a href="https://github.com/nicjansma/talks">Code samples</a> for the talk are also available, and you can watch it on on <a href="https://www.youtube.com/watch?v=dbAise49tWY">YouTube</a>.</p><p>The post <a href="https://nicj.net/measuring-continuity/" target="_blank">Measuring Continuity</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/measuring-continuity/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Particle Photon/Electron Remote Temperature and Humidity Logger</title>
		<link>https://nicj.net/particle-photonelectron-remote-temperature-and-humidity-logger/</link>
					<comments>https://nicj.net/particle-photonelectron-remote-temperature-and-humidity-logger/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Sun, 21 Feb 2016 17:14:54 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1884</guid>

					<description><![CDATA[<p>After how much fun I had building a cheap and simple Spark Core Water Sensor for my sump-pump, I&#8217;m now using a Photon (which is half of the price of the Spark Core) for remote temperature and humidity logging for my kegerator (keezer).  For just $24, you can have a remote sensor logging data to Adafruit.io, [&#8230;]</p>
<p>The post <a href="https://nicj.net/particle-photonelectron-remote-temperature-and-humidity-logger/" target="_blank">Particle Photon/Electron Remote Temperature and Humidity Logger</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="https://github.com/nicjansma/dht-logger" rel="attachment wp-att-1885"><img loading="lazy" class="alignright size-medium wp-image-1885" src="https://o.nicj.net/wp-content/uploads/2016/02/case-300x169.jpg" alt="case" width="300" height="169" /></a></p>
<p>After how much fun I had building a cheap and simple <a href="http://nicj.net/spark-core-water-sensor/">Spark Core Water Sensor</a> for my sump-pump, I&#8217;m now using a Photon (which is half of the price of the Spark Core) for remote temperature and humidity logging for my kegerator (keezer).  For just $24, you can have a remote sensor logging data to <a href="http://io.adafruit.com">Adafruit.io</a>, <a href="http://thingspeak.com">ThingSpeak</a>, <a href="https://aws.amazon.com/dynamodb/">Amazon DynamoDB</a> or any HTTP endpoint.</p>
<p>You can see how the whole project on <a href="https://github.com/nicjansma/dht-logger">Github</a>.</p><p>The post <a href="https://nicj.net/particle-photonelectron-remote-temperature-and-humidity-logger/" target="_blank">Particle Photon/Electron Remote Temperature and Humidity Logger</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/particle-photonelectron-remote-temperature-and-humidity-logger/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Compressing UserTiming</title>
		<link>https://nicj.net/compressing-usertiming/</link>
					<comments>https://nicj.net/compressing-usertiming/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 10 Dec 2015 15:00:36 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1861</guid>

					<description><![CDATA[<p>UserTiming is a modern browser performance API that gives developers the ability the mark important events (timestamps) and measure durations (timestamp deltas) in their web apps. For an in-depth overview of how UserTiming works, you can see my article UserTiming in Practice or read Steve Souders&#8217; excellent post with several examples for how to use [&#8230;]</p>
<p>The post <a href="https://nicj.net/compressing-usertiming/" target="_blank">Compressing UserTiming</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="http://www.w3.org/TR/user-timing/">UserTiming</a> is a modern browser performance API that gives developers the ability the mark important events (timestamps) and measure durations (timestamp deltas) in their web apps.  For an in-depth overview of how UserTiming works, you can see my article <a href="http://nicj.net/usertiming-in-practice/">UserTiming in Practice</a> or read <a href="https://speedcurve.com/blog/user-timing-and-custom-metrics/">Steve Souders&#8217; excellent post</a> with several examples for how to use UserTiming to measure your app.</p>
<p>UserTiming is very simple to use.  Let&#8217;s do a brief review.  If you want to mark an important event, just call <code>window.performance.mark(markName)</code>:</p>
<pre><code>// log the beginning of our task
performance.mark("start");
</code></pre>
<p>You can call <code>.mark()</code> as many times as you want, with whatever <code>markName</code> you want.  You can repeat the same <code>markName</code> as well.</p>
<p>The data is stored in the <a href="http://www.w3.org/TR/performance-timeline/">PerformanceTimeline</a>.  You query the PerformanceTimeline via methods like <code>performance.getEntriesByName(markName)</code>:</p>
<pre><code>// get the data back
var entry = performance.getEntriesByName("start");
// -&gt; {"name": "start", "entryType": "mark", "startTime": 1, "duration": 0}
</code></pre>
<p>Pretty simple right?  Again, see <a href="https://speedcurve.com/blog/user-timing-and-custom-metrics/">Steve&#8217;s article</a> for some great use cases.</p>
<p>So let&#8217;s imagine you&#8217;re sold on using UserTiming.  You start instrumenting you website, placing marks and measures throughout the life-cycle of your app.  Now what?</p>
<p>The data isn&#8217;t useful unless you&#8217;re looking at it.  On your own machine, you can query the PerformanceTimeline and see marks and measures in the <a href="http://nicj.net/usertiming-in-practice/">browser developer tools</a>. There are also <a href="http://soasta.com/mpulse">third</a> <a href="https://speedcurve.com">party</a> <a href="http://webpagetest.org">services</a> that give you a view of your UserTiming data.</p>
<p>What if you want to gather the data yourself?  What if you&#8217;re interested in trending different marks or measures in your own analytics tools?</p>
<p>The easy approach is to simply fetch all of the marks and measures via <code>performance.getEntriesByType()</code>, stringify the JSON, and XHR it back to your stats engine.</p>
<p>But how big is that data?</p>
<p>Let&#8217;s look at some example data &#8212; this was captured from a website I was browsing:</p>
<pre><code>{"duration":0,"entryType":"mark","name":"mark_perceived_load","startTime":1675.636999996641},
{"duration":0,"entryType":"mark","name":"mark_before_flex_bottom","startTime":1772.8529999985767},
{"duration":0,"entryType":"mark","name":"mark_after_flex_bottom","startTime":1986.944999996922},
{"duration":0,"entryType":"mark","name":"mark_js_load","startTime":2079.4459999997343},
{"duration":0,"entryType":"mark","name":"mark_before_deferred_js","startTime":2152.8769999968063},
{"duration":0,"entryType":"mark","name":"mark_after_deferred_js","startTime":2181.611999996676},
{"duration":0,"entryType":"mark","name":"mark_site_init","startTime":2289.4089999972493}]
</code></pre>
<p>That&#8217;s 657 bytes for just 7 marks.  What if you want to log dozens, hundreds, or even thousands of important events on your page?  What if you have a Single Page App, where the user can generate many events over the lifetime of their session?</p>
<p>Clearly, we can do better.  The signal : noise ratio of stringified JSON isn&#8217;t that good.  As a performance-conscientious developer, we should strive to minimize our visitor&#8217;s upstream bandwidth usage when sending our analytics packets.</p>
<p>Let&#8217;s see what we can do.</p>
<h2>The Goal</h2>
<p>Our goal is to reduce the size of an array of marks and measures down to a data structure that&#8217;s as small as possible so that we&#8217;re only left with a minimal payload that can be quickly beacon&#8217;d to a server for aggregate analysis.</p>
<p>For a similar domain-specific compression technique for <a href="http://nicj.net/resourcetiming-in-practice/">ResourceTiming</a> data, please see my post on <a href="http://nicj.net/compressing-resourcetiming/">Compressing ResourceTiming</a>.  The techniques we will discuss for UserTiming will build on some of the same things we can do for ResourceTiming data.</p>
<p>An additional goal is that we&#8217;re going to stick with techniques where the resulting compressed data doesn&#8217;t expand from <a href="https://en.wikipedia.org/wiki/Query_string#URL_encoding">URL encoding</a> if used in a query-string parameter.  This makes it easy to just tack on the data to an existing analytics or <a href="https://en.wikipedia.org/wiki/Real_user_monitoring">Real-User-Monitoring (RUM)</a> beacon.</p>
<h2>The Approach</h2>
<p>There are two main areas of our data-structure that we can compress.  Let&#8217;s take a single measure as an example:</p>
<pre><code>{  
    "name":      "measureName",
    "entryType": "measure",
    "startTime": 2289.4089999972493,
    "duration":  100.12314141 
}
</code></pre>
<p>What data is important here?  Each mark and measure has 4 attributes:</p>
<ol>
<li>Its name</li>
<li>Whether it&#8217;s a mark or a measure</li>
<li>Its start time</li>
<li>Its duration (for marks, this is 0)</li>
</ol>
<p>I&#8217;m going to suggest we can break these down into two main areas: The <em>object</em> and its <em>payload</em>.  The <em>object</em> is simply the mark or measure&#8217;s name.  The <em>payload</em> is its start time, and if its a measure, it&#8217;s duration.  A duration implies that the object is a measure, so we don&#8217;t need to track that attribute independently.</p>
<p>Essentially, we can break up our UserTiming data into a key-value pair.  Grouping by the mark or measure name let&#8217;s us play some interesting games, so the name will be the key.  The value (payload) will be the list of start times and durations for each mark or measure name.</p>
<p>First, we&#8217;ll compress the payload (all of the timestamps and durations).  Then, we can compress the list of objects.</p>
<p>So, let&#8217;s start out by compressing the timestamps!</p>
<h2>Compressing the Timestamps</h2>
<p>The first thing we want to compress for each mark or measure are its timestamps.</p>
<p>To begin with, <code>startTime</code> and <code>duration</code> are in millisecond resolution, with microseconds in the fraction.  Most people probably don&#8217;t need microsecond resolution, and it adds a ton of byte size to the payload.  A <code>startTime</code> of <code>2289.4089999972493</code> can probably be compressed down to just 2,289 milliseconds without sacrificing much accuracy.</p>
<p>So let&#8217;s say we have 3 marks to begin with:</p>
<pre><code>{"duration":0,"entryType":"mark","name":"mark1","startTime":100},
{"duration":0,"entryType":"mark","name":"mark1","startTime":150},
{"duration":0,"entryType":"mark","name":"mark1","startTime":500}
</code></pre>
<p>Grouping by mark name, we can reduce this structure to an array of start times for each mark:</p>
<pre><code>{ "mark1": [100, 150, 500] }
</code></pre>
<p>One of the truths of UserTiming is that when you fetch the entries via <code>performance.getEntries()</code>, they are in sorted order.</p>
<p>Let&#8217;s use this to our advantage, by offsetting each timestamp by the one in front of it.  For example, the <code>150</code> timestamp is only 50ms away from the <code>100</code> timestamp before it, so its value can be instead set to <code>50</code>.  <code>500</code> is 350ms away from <code>150</code>, so it gets set to <code>350</code>.  We end up with smaller integers this way, which will make compression easier later:</p>
<pre><code>{ "mark1": [100, 50, 350] }
</code></pre>
<p>How can we compress the numbers further?  Remember, one goal is to make the resulting data transmit easier on a URL (query string), so we mostly want to use the ASCII alpha-numeric set of characters.</p>
<p>One really easy way of reducing the number of bytes taken by a number in JavaScript is using <a href="https://en.wikipedia.org/wiki/Senary#Base_36_as_senary_compression">Base-36</a> encoding.  In other words, <code>0=0</code>, <code>10=a</code>, <code>35=z</code>.  Even better, JavaScript has this built-in to <code>Integer.toString(36)</code>:</p>
<pre><code>(35).toString(36)          == "z" (saves 1 character)
(99999999999).toString(36) == "19xtf1tr" (saves 3 characters)
</code></pre>
<p>Once we Base-36 encode all of our offset timestamps, we&#8217;re left with a smaller number of characters:</p>
<pre><code>{ "mark1": ["2s", "1e", "9q"] }
</code></pre>
<p>Now that we have these timestamps offsets in Base-36, we can combine (join) them into a single string so they&#8217;re easily transmitted.  We should avoid using the comma character (<code>,</code>), as it is one of the reserved characters of the URI spec (<a href="http://tools.ietf.org/html/rfc3986">RFC 3986</a>), so it will be escaped to <code>%2C</code>.</p>
<p>The list of non-URI-encoded characters is pretty small:</p>
<p><code>[0-9a-zA-Z]</code> <code>$</code> <code>-</code> <code>_</code> <code>.</code> <code>+</code> <code>!</code> <code>*</code> <code>'</code> <code>(</code> <code>)</code></p>
<p>The period (<code>.</code>) looks a lot like a comma, so let&#8217;s go with that.  Applying a simple <code>Array.join(".")</code>, we get:</p>
<pre><code>{ "mark1": "2s.1e.9q" }
</code></pre>
<p>So we&#8217;re really starting to reduce the byte size of these timestamps.  But wait, there&#8217;s more we can do!</p>
<p>Let&#8217;s say we have some timestamps that came in at a semi-regular interval:</p>
<pre><code>{"duration":0,"entryType":"mark","name":"mark1","startTime":100},
{"duration":0,"entryType":"mark","name":"mark1","startTime":200},
{"duration":0,"entryType":"mark","name":"mark1","startTime":300}
</code></pre>
<p>Compressed down, we get:</p>
<pre><code>{ "mark1": "2s.2s.2s" }
</code></pre>
<p>Why should we repeat ourselves?</p>
<p>Let&#8217;s use one of the other non-URI-encoded characters, the asterisk (<code>*</code>), to note when a timestamp offset repeats itself:</p>
<ul>
<li>A single <code>*</code> means it repeated twice</li>
<li><code>*[n]</code> means it repeated n times.</li>
</ul>
<p>So the above timestamps can be compressed further to:</p>
<pre><code>{ "mark1": "2s*3" }
</code></pre>
<p>Obviously, this compression depends on the application&#8217;s characteristics, but periodic marks can be seen in the wild.</p>
<h3>Durations</h3>
<p>What about measures?  Measures have the additional data component of a <code>duration</code>.  For marks these are always <code>0</code> (you&#8217;re just logging a point in time), but durations are another millisecond attribute.</p>
<p>We can adapt our previous string to include durations, if available.  We can even mix marks and measures of the same name and not get confused later.</p>
<p>Let&#8217;s use this data set as an example.  One mark and two measures (sharing the same name):</p>
<pre><code>{"duration":0,"entryType":"mark","name":"foo","startTime":100},
{"duration":100,"entryType":"measure","name":"foo","startTime":150},
{"duration":200,"entryType":"measure","name":"foo","startTime":500}
</code></pre>
<p>Instead of an array of Base36-encoded offset timestamps, we need to include a duration, if available.  Picking another non-URI-encoded character, the under-bar (<code>_</code>), we can easily &#8220;tack&#8221; this information on to the end of each <code>startTime</code>.</p>
<p>For example, with a <code>startTime</code> of <code>150</code> (<code>1e</code> in Base-36) and a duration of <code>100</code> (<code>2s</code> in Base-36), we get a simple string of <code>1e_2s</code>.</p>
<p>Combining the above marks and measures, we get:</p>
<pre><code>{ "foo": "2s.1e_2s.9q_5k" }
</code></pre>
<p>Later, when we&#8217;re decoding this, we haven&#8217;t lost track of the fact that there are both marks and measures intermixed here, since only measures have durations.</p>
<p>Going back to our original example:</p>
<pre><code>[{"duration":0,"entryType":"mark","name":"mark1","startTime":100},
{"duration":0,"entryType":"mark","name":"mark1","startTime":150},
{"duration":0,"entryType":"mark","name":"mark1","startTime":500}]
</code></pre>
<p>Let&#8217;s compare that JSON string to how we&#8217;ve compressed it (still in JSON form, which isn&#8217;t very URI-friendly):</p>
<pre><code>{"mark1":"2s.1e.9q"}
</code></pre>
<p>198 bytes originally versus 21 bytes with just the above techniques, or about 10% of the original size.</p>
<p>Not bad so far.</p>
<h2>Compressing the Array</h2>
<p>Most sites won&#8217;t have just a single mark or measure name that they want to transmit.  Most sites using UserTiming will have many different mark/measure names and values.</p>
<p>We&#8217;ve compressed the actual timestamps to a pretty small (URI-friendly) value, but what happens when we need to transmit an array of different marks/measures and their respective timestamps?</p>
<p>Let&#8217;s pretend there are 3 marks and 3 measures on the page, each with one timestamp.  After applying timestamp compression, we&#8217;re left with:</p>
<pre><code>{
    "mark1": "2s",
    "mark2": "5k",
    "mark3": "8c",
    "measure1": "2s_2s",
    "measure2": "5k_5k",
    "measure3": "8c_8c"
}
</code></pre>
<p>There are several ways we can compress this data to a format suitable for URL transmission.  Let&#8217;s explore.</p>
<h3>Using an Array</h3>
<p>Remember, JSON is not URI friendly, mostly due to curly braces (<code>{ }</code>), quotes (<code>"</code>) and colons (<code>:</code>) having to be escaped.</p>
<p>Even in a minified JSON form:</p>
<pre><code>{"mark1":"2s","mark2":"5k","mark3":"8c","measure1
":"2s_2s","measure2":"5k_5k","measure3":"8c_8c"}
(98 bytes)
</code></pre>
<p>This is what it looks like after URI encoding:</p>
<pre><code>%7B%22mark1%22%3A%222s%22%2C%22mark2%22%3A%225k%2
2%2C%22mark3%22%3A%228c%22%2C%22measure1%22%3A%22
2s_2s%22%2C%22measure2%22%3A%225k_5k%22%2C%22meas
ure3%22%3A%228c_8c%22%7D
(174 bytes)
</code></pre>
<p>Gah!  That&#8217;s almost 77% overhead.</p>
<p>Since we have a list of known keys (names) and values, we could instead change this object into an &#8220;array&#8221; where we&#8217;re not using <code>{ } " :</code> characters to delimit things.</p>
<p>Let&#8217;s use another URI-friendly character, the tilde (<code>~</code>), to separate each.  Here&#8217;s what the format could look like:</p>
<pre><code>[name1]~[timestamp1]~[name2]~[timestamp2]~[...]
</code></pre>
<p>Using our data:</p>
<pre><code>mark1~2s~mark2~5k~mark3~8c~measure1~2s_2s~measure
2~5k_5k~measure3~8c_8c~
(73 bytes)
</code></pre>
<p>Note that this depends on your names not including a tilde, or, you can pre-escape tildes in names to <code>%7E</code>.</p>
<h3>Using an Optimized Trie</h3>
<p>That&#8217;s one way of compressing the data.  In some cases, we can do better, especially if your names look similar.</p>
<p>One great technique we used in <a href="https://github.com/nicjansma/resourcetiming-compression.js">compressing ResourceTiming</a> data is an <a href="http://en.wikipedia.org/wiki/Trie">optimized Trie</a>.  Essentially, you can compress strings anytime one is a prefix of another.</p>
<p>In our example above, <code>mark1</code>, <code>mark2</code> and <code>mark3</code> are perfect candidates, since they all have a stem of <code>"mark"</code>.  In optimized Trie form, our above data would look something closer to:</p>
<pre><code>{
    "mark": {
        "1": "2s",
        "2": "5k",
        "3": "8c"
    },
    "measure": {
        "1": "2s_2s",
        "2": "5k_5k",
        "3": "8c_8c"
    }
}
</code></pre>
<p>Minified, this is 13% smaller than the original non-Trie data:</p>
<pre><code>{"mark":{"1":"2s","2":"5k","3":"8c"},"measure":{"
1":"2s_2s","2":"5k_5k","3":"8c_8c"}}
(86 bytes)
</code></pre>
<p>However, this is not as easily compressible into a tilde-separated array, since it&#8217;s no longer a flat data structure.</p>
<p>There&#8217;s actually a great way to compress this JSON data for URL-transmission, called <a href="https://github.com/Sage/jsurl">JSURL</a>.  Basically, the JSURL replaces all non-URI-friendly characters with a better URI-friendly representation.  Here&#8217;s what the above JSON looks like regular URI-encoded:</p>
<pre><code>%7B%22mark%22%3A%7B%221%22%3A%222s%22%2C%222%22%3
A%225k%22%2C%223%22%3A%228c%22%7D%2C%22measure%22
%3A%7B%22%0A1%22%3A%222s_2s%22%2C%222%22%3A%225k_
5k%22%2C%223%22%3A%228c_8c%22%7D%7D
(185 bytes)
</code></pre>
<p>Versus <a href="https://github.com/Sage/jsurl">JSURL</a> encoded:</p>
<pre><code>~(m~(ark~(1~'2s~2~'5k~3~'8c)~easure~(1~'2s_2s~2~'
5k_5k~3~'8c_8c)))
(67 bytes)
</code></pre>
<p>This JSURL encoding of an optimized Trie reduces the bytes size by 10% versus a tilde-separated array.</p>
<h3>Using a Map</h3>
<p>Finally, if you know what your mark / measure names will be ahead of time, you may not need to transmit the actual names at all.  If the set of your names is finite, and could maintain a map of name : index pairs, and only have to transmit the indexed value for each name.</p>
<p>Using the 3 marks and measures from before:</p>
<pre><code>{
    "mark1": "2s",
    "mark2": "5k",
    "mark3": "8c",
    "measure1": "2s_2s",
    "measure2": "5k_5k",
    "measure3": "8c_8c"
}
</code></pre>
<p>What if we simply mapped these names to numbers 0-5:</p>
<pre><code>{
    "mark1": 0,
    "mark2": 1,
    "mark3": 2,
    "measure1": 3,
    "measure2": 4,
    "measure3": 5
}
</code></pre>
<p>Since we no longer have to compress names via a Trie, we can go back to an optimized array.  And since the size of the index is relatively small (values 0-35 fit into a single character), we can save some room by not having a dedicated character (<code>~</code>) that separates each index and value (timestamps).</p>
<p>Taking the above example, we can have each name fit into a string in this format:</p>
<pre><code>[index1][timestamp1]~[index2][timestamp2]~[...]
</code></pre>
<p>Using our data:</p>
<pre><code>02s~15k~28c~32s_2s~45k_5k~58c_8c
(32 bytes)
</code></pre>
<p>This structure is less than half the size of the optimized Trie (JSURL encoded).</p>
<p>If you have over 36 mapped name : index pairs, we can still accommodate them in this structure.  Remember, at value 36 (the 37th value from 0), <code>(36).toString(36) == 10</code>, taking two characters.  We can&#8217;t just use an index of two characters, since our assumption above is that the index is only a single character.</p>
<p>One way of dealing with this is by adding a special encoding if the index is over a certain value.  We&#8217;ll <em>optimize</em> the structure to assume you&#8217;re only going to use 36 values, but, if you have over 36, we can accommodate that as well.  For example, let&#8217;s use one of the final non-URI-encoded characters we have left over, the dash (<code>-</code>):</p>
<p>If the first character of an item in the array is:</p>
<ul>
<li><code>0-z</code> (index values 0 &#8211; 35), that is the index value</li>
<li><code>-</code>, the next two characters are the index (plus 36)</li>
</ul>
<p>Thus, the value 0 is encoded as <code>0</code>, 35 is encoded as <code>z</code>, 36 is encoded as <code>-00</code>, and 1331 is encoded as <code>-zz</code>.  This gives us a total of 1331 mapped values we can use, all using a single or 3 characters.</p>
<p>So, given compressed values of:</p>
<pre><code>{
    "mark1": "2s",
    "mark2": "5k",
    "mark3": "8c"
}
</code></pre>
<p>And a mapping of:</p>
<pre><code>{
    "mark1": 36,
    "mark2": 37,
    "mark3": 1331
}
</code></pre>
<p>You could compress this as:</p>
<pre><code>-002s~-015k~-zz8c
</code></pre>
<p>We now have 3 different ways of compressing our array of marks and measures.</p>
<p>We can even swap between them, depending on which compresses the best each time we gather UserTiming data.</p>
<h2>Test Cases</h2>
<p>So how do these techniques apply to some real-world (and concocted) data?</p>
<p>I navigated around the Alexa Top 50 (by traffic) websites, to see who&#8217;s using UserTiming (not many).  I gathered any examples I could, and created some of my own test cases as well.  With this, I currently have a corpus of 20 real and fake UserTiming examples.</p>
<p>Let&#8217;s first compare <code>JSON.stringify()</code> of our UserTiming data versus the culmination of all of the techniques above:</p>
<pre><code>+------------------------------+
¦ Test    ¦ JSON ¦ UTC ¦ UTC % ¦
+---------+------+-----+-------¦
¦ 01.json ¦ 415  ¦ 66  ¦ 16%   ¦
+---------+------+-----+-------¦
¦ 02.json ¦ 196  ¦ 11  ¦ 6%    ¦
+---------+------+-----+-------¦
¦ 03.json ¦ 521  ¦ 18  ¦ 3%    ¦
+---------+------+-----+-------¦
¦ 04.json ¦ 217  ¦ 36  ¦ 17%   ¦
+---------+------+-----+-------¦
¦ 05.json ¦ 364  ¦ 66  ¦ 18%   ¦
+---------+------+-----+-------¦
¦ 06.json ¦ 334  ¦ 43  ¦ 13%   ¦
+---------+------+-----+-------¦
¦ 07.json ¦ 460  ¦ 43  ¦ 9%    ¦
+---------+------+-----+-------¦
¦ 08.json ¦ 91   ¦ 20  ¦ 22%   ¦
+---------+------+-----+-------¦
¦ 09.json ¦ 749  ¦ 63  ¦ 8%    ¦
+---------+------+-----+-------¦
¦ 10.json ¦ 103  ¦ 32  ¦ 31%   ¦
+---------+------+-----+-------¦
¦ 11.json ¦ 231  ¦ 20  ¦ 9%    ¦
+---------+------+-----+-------¦
¦ 12.json ¦ 232  ¦ 19  ¦ 8%    ¦
+---------+------+-----+-------¦
¦ 13.json ¦ 172  ¦ 34  ¦ 20%   ¦
+---------+------+-----+-------¦
¦ 14.json ¦ 658  ¦ 145 ¦ 22%   ¦
+---------+------+-----+-------¦
¦ 15.json ¦ 89   ¦ 48  ¦ 54%   ¦
+---------+------+-----+-------¦
¦ 16.json ¦ 415  ¦ 33  ¦ 8%    ¦
+---------+------+-----+-------¦
¦ 17.json ¦ 196  ¦ 18  ¦ 9%    ¦
+---------+------+-----+-------¦
¦ 18.json ¦ 196  ¦ 8   ¦ 4%    ¦
+---------+------+-----+-------¦
¦ 19.json ¦ 228  ¦ 50  ¦ 22%   ¦
+---------+------+-----+-------¦
¦ 20.json ¦ 651  ¦ 38  ¦ 6%    ¦
+---------+------+-----+-------¦
¦ Total   ¦ 6518 ¦ 811 ¦ 12%   ¦
+------------------------------+

Key:
* JSON      = JSON.stringify(UserTiming).length (bytes)
* UTC       = Applying UserTimingCompression (bytes)
* UTC %     = UTC bytes / JSON bytes
</code></pre>
<p>Pretty good, right?  On average, we shrink the data down to about 12% of its original size.</p>
<p>In addition, the resulting data is now URL-friendly.</p>
<h2>UserTiming-Compression.js</h2>
<p><code>usertiming-compression.js</code> (and its companion, <code>usertiming-decompression.js</code>) are open-source JavaScript modules (<code>UserTimingCompression</code> and <code>UserTimingDecompression</code>) that apply all of the techniques above.</p>
<p>They are available on Github at <a href="https://github.com/nicjansma/usertiming-compression.js">github.com/nicjansma/usertiming-compression.js</a>.</p>
<p>These scripts are meant to provide an easy, drop-in way of compressing your UserTiming data.  They compress UserTiming via one of the methods listed above, depending on which way compresses best.</p>
<p>If you have intimate knowledge of your UserTiming marks, measures and how they&#8217;re organized, you could probably construct an even more optimized data structure for capturing and transmitting your UserTiming data.  You could also trim the scripts to only use the compression technique that works best for you.</p>
<h2>Versus Gzip / Deflate</h2>
<p>Wait, why did we go through all of this mumbo-jumbo when there are already great ways of compression data?  Why not just gzip the stringified JSON?</p>
<p>That&#8217;s one approach.  One challenge is there isn&#8217;t native support for gzip in JavaScript.  Thankfully, you can use one of the excellent open-source libraries like <a href="https://github.com/nodeca/pako">pako</a>.</p>
<p>Let&#8217;s compare the UserTimingCompression techniques to gzipping the raw UserTiming JSON:</p>
<pre><code>+----------------------------------------------------+
¦ Test    ¦ JSON ¦ UTC ¦ UTC % ¦ JSON.gz ¦ JSON.gz % ¦
+---------+------+-----+-------+---------+-----------¦
¦ 01.json ¦ 415  ¦ 66  ¦ 16%   ¦ 114     ¦ 27%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 02.json ¦ 196  ¦ 11  ¦ 6%    ¦ 74      ¦ 38%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 03.json ¦ 521  ¦ 18  ¦ 3%    ¦ 79      ¦ 15%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 04.json ¦ 217  ¦ 36  ¦ 17%   ¦ 92      ¦ 42%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 05.json ¦ 364  ¦ 66  ¦ 18%   ¦ 102     ¦ 28%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 06.json ¦ 334  ¦ 43  ¦ 13%   ¦ 96      ¦ 29%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 07.json ¦ 460  ¦ 43  ¦ 9%    ¦ 158     ¦ 34%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 08.json ¦ 91   ¦ 20  ¦ 22%   ¦ 88      ¦ 97%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 09.json ¦ 749  ¦ 63  ¦ 8%    ¦ 195     ¦ 26%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 10.json ¦ 103  ¦ 32  ¦ 31%   ¦ 102     ¦ 99%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 11.json ¦ 231  ¦ 20  ¦ 9%    ¦ 120     ¦ 52%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 12.json ¦ 232  ¦ 19  ¦ 8%    ¦ 123     ¦ 53%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 13.json ¦ 172  ¦ 34  ¦ 20%   ¦ 112     ¦ 65%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 14.json ¦ 658  ¦ 145 ¦ 22%   ¦ 217     ¦ 33%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 15.json ¦ 89   ¦ 48  ¦ 54%   ¦ 91      ¦ 102%      ¦
+---------+------+-----+-------+---------+-----------¦
¦ 16.json ¦ 415  ¦ 33  ¦ 8%    ¦ 114     ¦ 27%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 17.json ¦ 196  ¦ 18  ¦ 9%    ¦ 81      ¦ 41%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 18.json ¦ 196  ¦ 8   ¦ 4%    ¦ 74      ¦ 38%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 19.json ¦ 228  ¦ 50  ¦ 22%   ¦ 103     ¦ 45%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ 20.json ¦ 651  ¦ 38  ¦ 6%    ¦ 115     ¦ 18%       ¦
+---------+------+-----+-------+---------+-----------¦
¦ Total   ¦ 6518 ¦ 811 ¦ 12%   ¦ 2250    ¦ 35%       ¦
+----------------------------------------------------+

Key:
* JSON      = JSON.stringify(UserTiming).length (bytes)
* UTC       = Applying UserTimingCompression (bytes)
* UTC %     = UTC bytes / JSON bytes
* JSON.gz   = gzip(JSON.stringify(UserTiming)).length
* JSON.gz % = JSON.gz bytes / JSON bytes
</code></pre>
<p>As you can see, gzip does a pretty good job of compressing raw JSON (stringified) &#8211; on average, reducing the size of to 35% of the original.  However, UserTimingCompression does a much better job, reducing to 12% of overall size.</p>
<p>What if instead of gzipping the UserTiming JSON, we gzip the minified timestamp map?  For example, instead of:</p>
<pre><code>[{"duration":0,"entryType":"mark","name":"mark1","startTime":100},
{"duration":0,"entryType":"mark","name":"mark1","startTime":150},
{"duration":0,"entryType":"mark","name":"mark1","startTime":500}]
</code></pre>
<p>What if we gzipped the output of compressing the timestamps?</p>
<pre><code>{"mark1":"2s.1e.9q"}
</code></pre>
<p>Here are the results:</p>
<pre><code>+-----------------------------------+
¦ Test    ¦ UTC ¦ UTC.gz ¦ UTC.gz % ¦
+---------+-----+--------+----------¦
¦ 01.json ¦ 66  ¦ 62     ¦ 94%      ¦
+---------+-----+--------+----------¦
¦ 02.json ¦ 11  ¦ 24     ¦ 218%     ¦
+---------+-----+--------+----------¦
¦ 03.json ¦ 18  ¦ 28     ¦ 156%     ¦
+---------+-----+--------+----------¦
¦ 04.json ¦ 36  ¦ 46     ¦ 128%     ¦
+---------+-----+--------+----------¦
¦ 05.json ¦ 66  ¦ 58     ¦ 88%      ¦
+---------+-----+--------+----------¦
¦ 06.json ¦ 43  ¦ 43     ¦ 100%     ¦
+---------+-----+--------+----------¦
¦ 07.json ¦ 43  ¦ 60     ¦ 140%     ¦
+---------+-----+--------+----------¦
¦ 08.json ¦ 20  ¦ 33     ¦ 165%     ¦
+---------+-----+--------+----------¦
¦ 09.json ¦ 63  ¦ 76     ¦ 121%     ¦
+---------+-----+--------+----------¦
¦ 10.json ¦ 32  ¦ 45     ¦ 141%     ¦
+---------+-----+--------+----------¦
¦ 11.json ¦ 20  ¦ 37     ¦ 185%     ¦
+---------+-----+--------+----------¦
¦ 12.json ¦ 19  ¦ 35     ¦ 184%     ¦
+---------+-----+--------+----------¦
¦ 13.json ¦ 34  ¦ 40     ¦ 118%     ¦
+---------+-----+--------+----------¦
¦ 14.json ¦ 145 ¦ 112    ¦ 77%      ¦
+---------+-----+--------+----------¦
¦ 15.json ¦ 48  ¦ 45     ¦ 94%      ¦
+---------+-----+--------+----------¦
¦ 16.json ¦ 33  ¦ 50     ¦ 152%     ¦
+---------+-----+--------+----------¦
¦ 17.json ¦ 18  ¦ 37     ¦ 206%     ¦
+---------+-----+--------+----------¦
¦ 18.json ¦ 8   ¦ 23     ¦ 288%     ¦
+---------+-----+--------+----------¦
¦ 19.json ¦ 50  ¦ 53     ¦ 106%     ¦
+---------+-----+--------+----------¦
¦ 20.json ¦ 38  ¦ 51     ¦ 134%     ¦
+---------+-----+--------+----------¦
¦ Total   ¦ 811 ¦ 958    ¦ 118%     ¦
+-----------------------------------+

Key:
* UTC     = Applying full UserTimingCompression (bytes)
* TS.gz   = gzip(UTC timestamp compression).length
* TS.gz % = TS.gz bytes / UTC bytes
</code></pre>
<p>Even with pre-applying the timestamp compression and gzipping the result, gzip doesn&#8217;t beat the full UserTimingCompression techniques.  Here, in general, gzip is 18% larger than UserTimingCompression.  There are a few cases where gzip is better, notably in test cases with a lot of repeating strings.</p>
<p>Additionally, applying gzip requires your app include a JavaScript gzip library, like pako &#8212; whose deflate code is currently around <a href="https://github.com/nodeca/pako/blob/master/dist/pako_deflate.min.js">26.3 KB minified</a>.  usertiming-compression.js is much smaller, at only <a href="https://github.com/nicjansma/usertiming-compression.js/tree/master/dist/usertiming-compression.js">3.9 KB</a> minified.</p>
<p>Finally, if you&#8217;re using gzip compression, you can&#8217;t just stick the gzip data into a Query String, as <a href="https://en.wikipedia.org/wiki/Query_string#URL_encoding">URL encoding</a> will increase its size tremendously.</p>
<p>If you&#8217;re already using gzip to compress data, it&#8217;s a decent choice, but applying some domain-specific knowledge about our data-structures give us better compression in most cases.</p>
<h2>Versus MessagePack</h2>
<p><a href="http://msgpack.org/">MessagePack</a> is another interesting choice for compressing data.  In fact, its motto is &#8220;<em>It&#8217;s like JSON. but fast and small.</em>&#8220;.  I like MessagePack and use it for other projects.  MessagePack is an efficient binary serialization format that takes JSON input and distills it down to a minimal form.  It works with <em>any</em> JSON data structure, and is very portable.</p>
<p>How does MessagePack compare to the UserTiming compression techniques?</p>
<p>MessagePack only compresses the original UserTiming JSON to 72% of its original size.  Great for a general compression library, but not nearly as good as UserTimingCompression can do.  Notably, this is because MessagePack is retaining the JSON strings (e.g. <code>startTime</code>, <code>duration</code>, etc) for each UserTiming object:</p>
<pre><code>+--------------------------------------------------------+
¦         ¦ JSON ¦ UTC ¦ UTC % ¦ JSON.pack ¦ JSON.pack % ¦
+---------+------+-----+-------+-----------+-------------¦
¦ Total   ¦ 6518 ¦ 811 ¦ 12%   ¦ 4718      ¦ 72%         ¦
+--------------------------------------------------------+

Key:
* UTC         = Applying UserTimingCompression (bytes)
* UTC %       = UTC bytes / JSON bytes
* JSON.pack   = MsgPack(JSON.stringify(UserTiming)).length
* JSON.pack % = TS.pack bytes / UTC bytes
</code></pre>
<p>What if we just MessagePack the compressed timestamps? (e.g. <code>{"mark1":"2s.1e.9q", ...}</code>)</p>
<pre><code>+---------------------------------------+
¦ Test    ¦ UTC ¦ TS.pack  ¦ TS.pack %  ¦
+---------+-----+----------+------------¦
¦ 01.json ¦ 66  ¦ 73       ¦ 111%       ¦
+---------+-----+----------+------------¦
¦ 02.json ¦ 11  ¦ 12       ¦ 109%       ¦
+---------+-----+----------+------------¦
¦ 03.json ¦ 18  ¦ 19       ¦ 106%       ¦
+---------+-----+----------+------------¦
¦ 04.json ¦ 36  ¦ 43       ¦ 119%       ¦
+---------+-----+----------+------------¦
¦ 05.json ¦ 66  ¦ 76       ¦ 115%       ¦
+---------+-----+----------+------------¦
¦ 06.json ¦ 43  ¦ 44       ¦ 102%       ¦
+---------+-----+----------+------------¦
¦ 07.json ¦ 43  ¦ 43       ¦ 100%       ¦
+---------+-----+----------+------------¦
¦ 08.json ¦ 20  ¦ 21       ¦ 105%       ¦
+---------+-----+----------+------------¦
¦ 09.json ¦ 63  ¦ 63       ¦ 100%       ¦
+---------+-----+----------+------------¦
¦ 10.json ¦ 32  ¦ 33       ¦ 103%       ¦
+---------+-----+----------+------------¦
¦ 11.json ¦ 20  ¦ 21       ¦ 105%       ¦
+---------+-----+----------+------------¦
¦ 12.json ¦ 19  ¦ 20       ¦ 105%       ¦
+---------+-----+----------+------------¦
¦ 13.json ¦ 34  ¦ 33       ¦ 97%        ¦
+---------+-----+----------+------------¦
¦ 14.json ¦ 145 ¦ 171      ¦ 118%       ¦
+---------+-----+----------+------------¦
¦ 15.json ¦ 48  ¦ 31       ¦ 65%        ¦
+---------+-----+----------+------------¦
¦ 16.json ¦ 33  ¦ 40       ¦ 121%       ¦
+---------+-----+----------+------------¦
¦ 17.json ¦ 18  ¦ 21       ¦ 117%       ¦
+---------+-----+----------+------------¦
¦ 18.json ¦ 8   ¦ 11       ¦ 138%       ¦
+---------+-----+----------+------------¦
¦ 19.json ¦ 50  ¦ 52       ¦ 104%       ¦
+---------+-----+----------+------------¦
¦ 20.json ¦ 38  ¦ 40       ¦ 105%       ¦
+---------+-----+----------+------------¦
¦ Total   ¦ 811 ¦ 867      ¦ 107%       ¦
+---------------------------------------+

Key:
* UTC       = Applying full UserTimingCompression (bytes)
* TS.pack   = MsgPack(UTC timestamp compression).length
* TS.pack % = TS.pack bytes / UTC bytes
</code></pre>
<p>For our 20 test cases, MessagePack is about 7% larger than the UserTiming compression techniques.</p>
<p>Like using a JavaScript module for gzip, the most popular MessagePack JavaScript modules are pretty hefty, at <a href="https://github.com/msgpack/msgpack-javascript/blob/master/msgpack.js">29.2 KB</a>, <a href="https://github.com/kawanet/msgpack-lite/blob/master/dist/msgpack.min.js">36.9 KB</a>, and <a href="https://github.com/mcollina/msgpack5/blob/master/dist/msgpack5.min.js">104 KB</a>.  Compare this to only <a href="https://github.com/nicjansma/usertiming-compression.js/tree/master/dist/usertiming-compression.js">3.9 KB</a> minified for usertiming-compression.js.</p>
<p>Basically, if you have good domain-specific knowledge of your data-structures, you can often compress better than a general-case minimizer like gzip or MessagePack.</p>
<h2>Conclusion</h2>
<p>It&#8217;s fun taking a data-structure you want to work with and compressing it down as much as possible.</p>
<p>UserTiming is a great (and under-utilized) browser API that I hope to see get adopted more.  If you&#8217;re already using UserTiming, you might have already solved the issue of how to capture, transmit and store these datapoints.  If not, I hope these techniques and tools will help you on your way towards using the API.</p>
<p>Do you have ideas for how to compress this data down even further?  Let me know!</p>
<p><a href="https://github.com/nicjansma/usertiming-compression.js">usertiming-compression.js</a> (and usertiming-decompression.js) are available on <a href="https://github.com/nicjansma/usertiming-compression.js">Github</a>.</p>
<h3>Resources</h3>
<ul>
<li><a href="http://www.w3.org/TR/user-timing/">UserTiming W3C Specification</a></li>
<li><a href="http://www.w3.org/TR/performance-timeline/">PerformanceTimeline W3C Specification</a></li>
<li><a href="http://nicj.net/usertiming-in-practice/">UserTiming in Practice</a></li>
<li><a href="https://speedcurve.com/blog/user-timing-and-custom-metrics/">Steve Souders&#8217; UserTiming and Custom Metrics</a></li>
<li><a href="http://nicj.net/resourcetiming-in-practice/">ResourceTiming in Practice</a></li>
<li><a href="http://nicj.net/compressing-resourcetiming/">Compressing ResourceTiming</a></li>
</ul><p>The post <a href="https://nicj.net/compressing-usertiming/" target="_blank">Compressing UserTiming</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/compressing-usertiming/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Forensic Tools for In-Depth Performance Investigations</title>
		<link>https://nicj.net/forensic-tools-for-in-depth-performance-investigations/</link>
					<comments>https://nicj.net/forensic-tools-for-in-depth-performance-investigations/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 15 Oct 2015 19:19:13 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1851</guid>

					<description><![CDATA[<p>Another talk Philip Tellis and I gave at Velocity New York 2015 was about the forensic tools we use for investigating performance issues.  Check it out on Slideshare: In this talk, we cover a variety of tools such as WebPagetest, tcpdump, Wireshark, Cloudshark, browser developer tools, Chrome tracing, netlog, Fiddler, RUM, TamperMonkey, NodeJS, virtualization, Event Tracing for [&#8230;]</p>
<p>The post <a href="https://nicj.net/forensic-tools-for-in-depth-performance-investigations/" target="_blank">Forensic Tools for In-Depth Performance Investigations</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Another talk Philip Tellis and I gave at Velocity New York 2015 was about the forensic tools we use for investigating performance issues.  Check it out on <a href="http://www.slideshare.net/nicjansma/forensic-tools-for-indepth-performance-investigations">Slideshare</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/forensic-tools-for-indepth-performance-investigations"><img loading="lazy" class="aligncenter size-large wp-image-1962" src="https://o.nicj.net/wp-content/uploads/2015/10/Forensic-Tools-for-In-Depth-Performance-Investigations-600x340.png" alt="forensic-tools-for-in-depth-performance-investigations" width="600" height="340" /></a></p>
<p>In this talk, we cover a variety of tools such as WebPagetest, tcpdump, Wireshark, Cloudshark, browser developer tools, Chrome tracing, netlog, Fiddler, RUM, TamperMonkey, NodeJS, virtualization, Event Tracing for Windows (ETW), xperf and more while diving into real issues we&#8217;ve had to investigate in the past.</p>
<p>The talk is also available on <a href="https://www.youtube.com/watch?v=jgGbdoN4l2k">YouTube</a>.</p><p>The post <a href="https://nicj.net/forensic-tools-for-in-depth-performance-investigations/" target="_blank">Forensic Tools for In-Depth Performance Investigations</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/forensic-tools-for-in-depth-performance-investigations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Measuring the Performance of Single Page Applications</title>
		<link>https://nicj.net/measuring-the-performance-of-single-page-applications/</link>
					<comments>https://nicj.net/measuring-the-performance-of-single-page-applications/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 15 Oct 2015 19:17:10 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1844</guid>

					<description><![CDATA[<p>Philip Tellis and I recently gave this talk at Velocity New York 2015.  Check out the slides on Slideshare: In the talk, we discuss the three main challenges of measuring the performance of SPAs, and how we&#8217;ve been able to build SPA performance monitoring into Boomerang. The talk is also available on YouTube.</p>
<p>The post <a href="https://nicj.net/measuring-the-performance-of-single-page-applications/" target="_blank">Measuring the Performance of Single Page Applications</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Philip Tellis and I recently gave this talk at <a href="http://velocityconf.com/devops-web-performance-ny-2015">Velocity New York 2015</a>.  Check out the slides on <a href="http://www.slideshare.net/nicjansma/measuring-the-performance-of-single-page-applications">Slideshare</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/measuring-the-performance-of-single-page-applications"><img loading="lazy" class="aligncenter size-large wp-image-1964" src="https://o.nicj.net/wp-content/uploads/2015/10/Measuring-the-Performance-of-Single-Page-Applications-600x336.png" alt="measuring-the-performance-of-single-page-applications" width="600" height="336" /></a></p>
<p>In the talk, we discuss the three main challenges of measuring the performance of SPAs, and how we&#8217;ve been able to build SPA performance monitoring into <a href="https://github.com/SOASTA/boomerang">Boomerang</a>.</p>
<p>The talk is also available on <a href="https://www.youtube.com/watch?v=CYEYtQPofhQ">YouTube</a>.</p><p>The post <a href="https://nicj.net/measuring-the-performance-of-single-page-applications/" target="_blank">Measuring the Performance of Single Page Applications</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/measuring-the-performance-of-single-page-applications/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>UserTiming in Practice</title>
		<link>https://nicj.net/usertiming-in-practice/</link>
					<comments>https://nicj.net/usertiming-in-practice/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Sat, 30 May 2015 00:44:01 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1790</guid>

					<description><![CDATA[<p>Last updated: May 2021 Table Of Contents Introduction How was it done before? 2.1. What&#8217;s Wrong With This? Marks and Measures 3.1. How to Use 3.2. Example Usage 3.3. Standard Mark Names 3.4. UserTiming Level 3 3.5. Arbitrary Timestamps 3.6. Arbitrary Metadata Benefits Developer Tools Use Cases Compressing Availability Using NavigationTiming Data Conclusion Updates Introduction [&#8230;]</p>
<p>The post <a href="https://nicj.net/usertiming-in-practice/" target="_blank">UserTiming in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Last updated: May 2021</p>
<h2>Table Of Contents</h2>
<ol>
<li><a href="#usertiming-introduction">Introduction</a></li>
<li><a href="#usertiming-how-was-it-done-before">How was it done before?</a><br />
2.1. <a href="#usertiming-whats-wrong-with-this">What&#8217;s Wrong With This?</a></li>
<li><a href="#usertiming-marks-and-measures">Marks and Measures</a><br />
3.1. <a href="#usertiming-how-to-use">How to Use</a><br />
3.2. <a href="#usertiming-example-usage">Example Usage</a><br />
3.3. <a href="#usertiming-standard-mark-names">Standard Mark Names</a><br />
3.4. <a href="#usertiming-usertiming3">UserTiming Level 3</a><br />
3.5. <a href="#usertiming-arbitrary-timestamps">Arbitrary Timestamps</a><br />
3.6. <a href="#usertiming-arbitrary-metadata">Arbitrary Metadata</a></li>
<li><a href="#usertiming-benefits">Benefits</a></li>
<li><a href="#usertiming-developer-tools">Developer Tools</a></li>
<li><a href="#usertiming-use-cases">Use Cases</a></li>
<li><a href="#usertiming-compressing">Compressing</a></li>
<li><a href="#usertiming-availability">Availability</a></li>
<li><a href="#usertiming-diy-open-source-commercial">Using NavigationTiming Data</a></li>
<li><a href="#usertiming-conclusion">Conclusion</a></li>
<li><a href="#usertiming-updates">Updates</a></li>
</ol>
<p><a name="usertiming-introduction"></a></p>
<h2>Introduction</h2>
<p><a href="http://www.w3.org/TR/user-timing/">UserTiming</a> is a specification developed by the <a href="http://www.w3.org/2010/webperf">W3C Web Performance working group</a>, with the goal of giving the developer a standardized interface to log timestamps (&quot;marks&quot;) and durations (&quot;measures&quot;).</p>
<p>UserTiming utilizes the <a href="http://nicj.net/resourcetiming-in-practice/">PerformanceTimeline</a> that we saw in <a href="http://nicj.net/resourcetiming-in-practice/">ResourceTiming</a>, but all of the UserTiming events are put there by the <strong>you</strong> the developer (or the third-party scripts you&#8217;ve included in the page).</p>
<p><a href="https://www.w3.org/TR/user-timing-1/">UserTiming Level 1</a> and <a href="https://www.w3.org/TR/user-timing-2/">Level 2</a> are both  a <strong>Recommendation</strong>, which means that browser vendors are encouraged to implement it.  <a href="https://www.w3.org/TR/user-timing-3/">Level 3</a> adds additional features and is in development.</p>
<p>As of May 2021, 96.6% of the world-wide browser market-share support UserTiming.</p>
<p><a name="usertiming-how-was-it-done-before"></a></p>
<h2>How was it done before?</h2>
<p>Prior to UserTiming, developers have been keeping track of performance metrics, such as logging timestamps and event durations by using simple JavaScript:</p>
<pre><code>var start = new Date().getTime();
// do stuff
var now = new Date().getTime();
var duration = now - start;</code></pre>
<p><a name="usertiming-whats-wrong-with-this"></a></p>
<h2>What&#8217;s wrong with this?</h2>
<p>Well, nothing really, but&#8230; we can do better.</p>
<p>First, as <a href="http://nicj.net/navigationtiming-in-practice/">discussed previously</a>, <code>Date().getTime()</code> is not reliable and <code>DOMHighResTimestamp</code>s should be used instead (e.g. <code>performance.now()</code>).</p>
<p>Second, by logging your performance metrics into the standard interface of UserTiming, browser developer tools and third-party analytics services will be able to read and understand your performance metrics.</p>
<p><a name="usertiming-marks-and-measures"></a></p>
<h2>Marks and Measures</h2>
<p>Developers generally use two core ideas to profile their code.  First, they keep track of <strong>timestamps</strong> for when events happen.  They may log these timestamps (e.g. via <code>Date().getTime()</code>) into JavaScript variables to be used later.</p>
<p>Second, developers often keep track of <strong>durations</strong> of events.  This is often done by taking the difference of two timestamps.</p>
<p>Timestamps and durations correspond to &quot;marks&quot; and &quot;measures&quot; in UserTiming terms.  A mark is a timestamp, in <a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code></a> format.  A measure is a duration, the difference between two marks, also measured in milliseconds.</p>
<p><a name="usertiming-how-to-use"></a></p>
<h2>How to use</h2>
<p>Creating a mark or measure is done via the <code>window.performance</code> interface:</p>
<pre><code>partial interface Performance {
    void mark(DOMString markName);

    void clearMarks(optional  DOMString markName);

    void measure(DOMString measureName, optional DOMString startMark,
        optional DOMString endMark);

    void clearMeasures(optional DOMString measureName);
};

interface PerformanceEntry {
  readonly attribute DOMString name;
  readonly attribute DOMString entryType;
  readonly attribute DOMHighResTimeStamp startTime;
  readonly attribute DOMHighResTimeStamp duration;
};

interface PerformanceMark : PerformanceEntry { };

interface PerformanceMeasure : PerformanceEntry { };</code></pre>
<p>A mark (<code>PerformanceMark</code>) is an example of a <code>PerformanceEntry</code>, with no additional attributes:</p>
<ul>
<li><code>name</code> is the mark&#8217;s name</li>
<li><code>entryType</code> is <code>&quot;mark&quot;</code></li>
<li><code>startTime</code> is the time the mark was created</li>
<li><code>duration</code> is always <code>0</code></li>
</ul>
<p>A measure (<code>PerformanceMeasure</code>) is also an example of a <code>PerformanceEntry</code>, with no additional attributes:</p>
<ul>
<li><code>name</code> is the measure&#8217;s name</li>
<li><code>entryType</code> is <code>&quot;measure&quot;</code></li>
<li><code>startTime</code> is the <code>startTime</code> of the start mark</li>
<li><code>duration</code> is the difference between the <code>startTime</code> of the start and end mark</li>
</ul>
<p><a name="usertiming-example-usage"></a></p>
<h2>Example Usage</h2>
<p>Let&#8217;s start by logging a couple marks (timestamps):</p>
<pre><code>// mark
performance.mark(&quot;start&quot;); 
// -&gt; {&quot;name&quot;: &quot;start&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 1, &quot;duration&quot;: 0}

performance.mark(&quot;end&quot;); 
// -&gt; {&quot;name&quot;: &quot;end&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 2, &quot;duration&quot;: 0}

performance.mark(&quot;another&quot;); 
// -&gt; {&quot;name&quot;: &quot;another&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 3, &quot;duration&quot;: 0}
performance.mark(&quot;another&quot;); 
// -&gt; {&quot;name&quot;: &quot;another&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 4, &quot;duration&quot;: 0}
performance.mark(&quot;another&quot;); 
// -&gt; {&quot;name&quot;: &quot;another&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 5, &quot;duration&quot;: 0}</code></pre>
<p>Later, you may want to compare two marks (<code>start</code> vs. <code>end</code>) to create a measure (called <code>diff</code>), such as:</p>
<pre><code>performance.measure(&quot;diff&quot;, &quot;start&quot;, &quot;end&quot;);
// -&gt; {&quot;name&quot;: &quot;diff&quot;, &quot;entryType&quot;: &quot;measure&quot;, &quot;startTime&quot;: 1, &quot;duration&quot;: 1}</code></pre>
<p>Note that <code>measure()</code> always calculates the difference by taking the <em>latest</em> timestamp that was seen for a mark.  So if you did a measure against the <code>another</code> marks in the example above, it will take the timestamp of the third call to <code>mark(&quot;another&quot;)</code>:</p>
<pre><code>performance.measure(&quot;diffOfAnother&quot;, &quot;start&quot;, &quot;another&quot;);
// -&gt; {&quot;name&quot;: &quot;diffOfAnother&quot;, &quot;entryType&quot;: &quot;measure&quot;, &quot;startTime&quot;: 1, &quot;duration&quot;: 4}</code></pre>
<p>There are many ways to create a measure:</p>
<ul>
<li>If you call <code>measure(name)</code>, the <code>startTime</code> is assumed to be <a href="http://nicj.net/navigationtiming-in-practice/"><code>window.performance.timing.navigationStart</code></a> and the <code>endTime</code> is assumed to be now.</li>
<li>If you call <code>measure(name, startMarkName)</code>, the <code>startTime</code> is assumed to be <code>startTime</code> of the given mark&#8217;s name and the <code>endTime</code> is assumed to be now.</li>
<li>If you call <code>measure(name, startMarkName, endMarkName)</code>, the <code>startTime</code> is assumed to be <code>startTime</code> of the given start mark&#8217;s name and the <code>endTime</code> is assumed to be the <code>startTime</code> of the given end mark&#8217;s name.</li>
</ul>
<p>Some examples of using <code>measure()</code>:</p>
<pre><code>// log the beginning of our task (assuming now is &#039;1&#039;)
performance.mark(&quot;start&quot;);
// -&gt; {&quot;name&quot;: &quot;start&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 1, &quot;duration&quot;: 0}

// do work (assuming now is &#039;2&#039;)
performance.mark(&quot;start2&quot;);
// -&gt; {&quot;name&quot;: &quot;start2&quot;, &quot;entryType&quot;: &quot;mark&quot;, &quot;startTime&quot;: 2, &quot;duration&quot;: 0}

// measure from navigationStart to now (assuming now is &#039;3&#039;)
performance.measure(&quot;time to get to this point&quot;);
// -&gt; {&quot;name&quot;: &quot;time to get to this point&quot;, &quot;entryType&quot;: &quot;measure&quot;, &quot;startTime&quot;: 0, &quot;duration&quot;: 3}

// measure from &quot;now&quot; to the &quot;start&quot; mark (assuming now is &#039;4&#039;)
performance.measure(&quot;time to do stuff&quot;, &quot;start&quot;);
// -&gt; {&quot;name&quot;: &quot;time to do stuff&quot;, &quot;entryType&quot;: &quot;measure&quot;, &quot;startTime&quot;: 1, &quot;duration&quot;: 3}

// measure from &quot;start2&quot; to the &quot;start&quot; mark
performance.measure(&quot;time from start to start2&quot;, &quot;start&quot;, &quot;start2&quot;);
// -&gt; {&quot;name&quot;: &quot;time from start to start2&quot;, &quot;entryType&quot;: &quot;measure&quot;, &quot;startTime&quot;: 1, &quot;duration&quot;: 1}</code></pre>
<p>Once a mark or measure has been created, you can query for all marks, all measures, or specific marks/measures via the <a href="http://www.w3.org/TR/performance-timeline/">PerformanceTimeline</a>.  Here&#8217;s a review of the PerformanceTimeline methods:</p>
<pre><code>window.performance.getEntries();
window.performance.getEntriesByType(type);
window.performance.getEntriesByName(name, type);</code></pre>
<ul>
<li><code>getEntries()</code>: Gets all entries in the timeline</li>
<li><code>getEntriesByType(type)</code>: Gets all entries of the specified type (eg <code>resource</code>, <code>mark</code>, <code>measure</code>)</li>
<li><code>getEntriesByName(name, type)</code>: Gets all entries with the specified name (eg URL or mark name). <code>type</code> is optional, and will filter the list to that type.</li>
</ul>
<p>Here&#8217;s an example of using the PerformanceTimeline to fetch a mark:</p>
<pre><code>// performance.getEntriesByType(&quot;mark&quot;);
[
    {
        &quot;duration&quot;:0,
        &quot;startTime&quot;:1
        &quot;entryType&quot;:&quot;mark&quot;,
        &quot;name&quot;:&quot;start&quot;
    },
    {
        &quot;duration&quot;:0,
        &quot;startTime&quot;:2,
        &quot;entryType&quot;:&quot;mark&quot;,
        &quot;name&quot;:&quot;start2&quot;
    },
    ...
]

// performance.getEntriesByName(&quot;time from start to start2&quot;, &quot;measure&quot;);
[
    {
        &quot;duration&quot;:1,
        &quot;startTime&quot;:1,
        &quot;entryType&quot;:&quot;measure&quot;,
        &quot;name&quot;:&quot;time from start to start2&quot;
    }
]</code></pre>
<p>You also have the ability to clear (remove) marks and measures from the buffer:</p>
<pre><code class="language-js">// clears all marks
performance.clearMarks();

// clears the named marks
performance.clearMarks(&quot;my-mark&quot;);

// clears all measures
performance.clearMeasures();

// clears the named measures
performance.clearMeasures(&quot;my-measure&quot;);</code></pre>
<p>You can also skip the buffer and listen for marks or measures via a <code>PerformanceObserver</code>:</p>
<pre><code class="language-js">if (typeof window.PerformanceObserver === &quot;function&quot;) {
  var userTimings = [];

  var observer = new PerformanceObserver(function(entries) {
    Array.prototype.push.apply(userTimings, entries.getEntries());
  });

  observer.observe({entryTypes: [&#039;mark&#039;, &#039;measure&#039;]});
}</code></pre>
<p><a name="usertiming-standard-mark-names"></a></p>
<h2>Standard Mark Names</h2>
<p>There are a couple of mark names that were at one point suggested by the <a href="http://www.w3.org/TR/user-timing/">W3C specification</a> to have special meanings:</p>
<ul>
<li><code>mark_fully_loaded</code>: The time when the page is considered fully loaded as marked by the developer in their application</li>
<li><code>mark_fully_visible</code>: The time when the page is considered completely visible to an end-user as marked by the developer in their application</li>
<li><code>mark_above_the_fold</code>: The time when all of the content in the visible viewport has been presented to the end-user as marked by the developer in their application</li>
<li><code>mark_time_to_user_action</code>: The time of the first user interaction with the page during or after a navigation, such as scroll or click, as marked by the developer in their application</li>
</ul>
<p>By using these standardized names, other third-party tools could have theoretically picked up on your meanings and treated them specially (for example, by overlaying them on your waterfall).</p>
<p>These names were removed from the <a href="https://www.w3.org/TR/user-timing-2/">Level 2</a> of the spec.  You can still use those names if you choose, but I&#8217;m not aware of any tools that treat them specially.</p>
<p>Obviously, you can use these mark names (or anything else) for anything you want, and don&#8217;t have to stick by the recommended meanings.</p>
<p><a name="usertiming-usertiming3"></a></p>
<h2>UserTiming3</h2>
<p><a href="https://www.w3.org/TR/user-timing-3/">Level 3</a> of the specification is still under development, but has some additional features that may be useful:</p>
<ul>
<li>Ability to execute marks and measures across arbitrary timestamps</li>
<li>Support for reporting arbitrary metadata along with marks and measures</li>
</ul>
<p>Not all browsers support Level 3.  As of May 2021, the only browser that does is Chrome.</p>
<p><a name="usertiming-arbitrary-timestamps"></a></p>
<h3>Arbitrary Timestamps</h3>
<p>With UserTiming Level 3, you can now specify a <code>startTime</code> (for marks), and a <code>start</code> and/or <code>end</code> time and/or <code>duration</code> for measures.</p>
<p>For marks, this gives you finer control over the exact timestamp, instead of taking &quot;now&quot; as the &quot;start&quot; timestamp.  For example, you could save a time associated with an event (via <code>performance.now()</code>), and you may not be sure you want to log that event as a mark until later.  Later, if you create a mark for it, you can give the timestamp you had stored away:</p>
<pre><code class="language-js">// do something -- not sure you want to mark yet?
var weShouldMark = false;
var startTime = performance.now();
doSomeWork();

// do other things
weShouldMark = doOtherThings();

// decide you wanted to mark that start
if (weShouldMark) {
  performance.mark(&quot;work-started&quot;, {
    startTime: startTime
  });
}</code></pre>
<p>For measures, you can now specify an arbitrary <code>start</code>, <code>end</code> or <code>duration</code>:</p>
<pre><code class="language-js">// specifying a start and end
performance.measure(&quot;my-custom-measure&quot;, {
  start: startTime,
  end: performance.now()
});

// specifying a duration (need to specify start or end as well)
performance.measure(&quot;my-custom-measure&quot;, {
  start: startTime,
  duration: 100 // ms
});</code></pre>
<p><a name="usertiming-arbitrary-metadata"></a></p>
<h3>Arbitrary Metadata / <code>detail</code></h3>
<p>Both marks and measures now allow you to specify a <code>detail</code> option, which is an object that will be stored alongside the mark/measure for later retrieval.  For example, if you have any metadata you want saved as part of the mark/measure, you can store it and get it later:</p>
<pre><code class="language-js">performance.mark(&quot;my-mark&quot;, {
  detail: {
    page: &quot;this-page&quot;,
    component: &quot;that-component&quot;
  },
});

performance.getEntriesByName(&quot;my-mark&quot;)[0];
// {
//   name: &quot;my-mark&quot;,
//   startTime: 12345,
//   duration: 0,
//   detail: { page: &quot;this-page&quot;, component: &quot;that-component&quot;}
// }</code></pre>
<p><a name="usertiming-benefits"></a></p>
<h2>Benefits</h2>
<p>So why would you use UserTiming over just <code>Date().getTime()</code> or <code>performance.now()</code>?</p>
<p>First, it uses the <code>PerformanceTimeline</code>, so <code>marks</code> and <code>measures</code> are in the PerformanceTimeline along with other events</p>
<p>Second, it uses <code>DOMHighResTimestamp</code> instead of <code>Date</code> so the timestamps have sub-millisecond resolution, and are <a href="http://nicj.net/navigationtiming-in-practice/">monotonically non-decreasing</a> (so aren&#8217;t affected by the client&#8217;s clock).</p>
<p><a name="usertiming-developer-tools"></a></p>
<h2>Developer Tools</h2>
<p>UserTiming marks and measures are currently available in the Chrome, Internet Explorer Developer Tools.</p>
<p>For Chrome, they are in <em>Performance</em> traces under <em>Timings</em>:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2021/05/usertiming-chrome-dev-tools.png" alt="UserTiming in Chrome Dev Tools" /></p>
<p>For IE, they are called <em>User marks</em> and are shown as upside-down red triangles below:</p>
<p><a href="https://msdn.microsoft.com/en-us/library/dn743687%28v=vs.85%29.aspx#ui_responsiveness_changes"><img src="https://o.nicj.net/wp-content/uploads/2015/05/f12-user-mark.png" alt="UserTiming in IE F12 Dev Tools" /></a></p>
<p>They are not yet shown in <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1186580">Firefox</a> or Safari.</p>
<p><a name="usertiming-use-cases"></a></p>
<h2>Use Cases</h2>
<p>How could you use UserTiming?  Here are some ideas:</p>
<ul>
<li>Any place that you&#8217;re already logging timestamps or calculating durations could be switched to UserTiming</li>
<li>Easy way to add profiling events to your application</li>
<li>Note important scenario durations in your Performance Timeline</li>
<li>Measure important durations for analytics</li>
</ul>
<p><a name="usertiming-compressing"></a></p>
<h2>Compressing</h2>
<p>If you&#8217;re adding UserTiming instrumentation to your page, you probably also want to consume it.  One way is to grab everything, package it up, and send it back to your own server for analysis.</p>
<p>In my <a href="http://nicj.net/compressing-usertiming/">UserTiming Compression article</a>, I go over a couple ways of how to do this.  Versus just sending the UserTiming JSON, <a href="https://github.com/nicjansma/usertiming-compression.js">usertiming-compression.js</a> can reduce the byte size down to just 10-15% of the original.</p>
<p><a name="usertiming-availability"></a></p>
<h2>Availability</h2>
<p>UserTiming is available in most modern browsers.  According to <a href="http://caniuse.com/#feat=user-timing">caniuse.com</a> 96.6% of world-wide browser market share supports ResourceTiming, as of May 2021.  This includes Internet Explorer 10+, Firefox 38+, Chrome 25+, Opera 15+, Safari 11+ and Android Browser 4.4+.</p>
<p><a href="http://caniuse.com/#feat=user-timing"><img src="https://o.nicj.net/wp-content/uploads/2021/05/caniuse-usertiming-2021.png" alt="CanIUse - UserTiming" /></a></p>
<p>If you want to use UserTiming for everything, there are polyfills available that work 100% reliably in all browsers.</p>
<p>I have one such polyfill, <a href="http://nicj.net/usertiming-js/">UserTiming.js</a>, available on <a href="https://github.com/nicjansma/usertiming.js">Github</a>.</p>
<p><a name="usertiming-diy-open-source-commercial"></a></p>
<h2>DIY / Open Source / Commercial</h2>
<p>If you want to use UserTiming, you could easily compress and beacon the data to your back-end for processing.</p>
<p><a href="http://webpagetest.org"><strong>WebPageTest</strong></a> sends UserTiming to Google Analytics, Boomerang and Akamai mPulse:</p>
<p><a href="http://webpagetest.org"><img src="https://o.nicj.net/wp-content/uploads/2015/06/user-timing-screen-annotate.png" alt="WebPageTest UserTiming" /></a></p>
<p><a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp"><strong>Akamai mPulse</strong></a> collects UserTiming information for any Custom Timers you specify (<em>I work at Akamai, on mPulse and Boomerang</em>):</p>
<p><a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp"><img src="https://o.nicj.net/wp-content/uploads/2015/05/soasta-mpulse-resourcetiming.png" alt="Akamai mPulse" /></a></p>
<p><a name="usertiming-conclusion"></a></p>
<h2>Conclusion</h2>
<p>UserTiming is a great interface to log your performance metrics into a standardized interface.  As more services and browsers support UserTiming, you will be able to see your data in more and more places.</p>
<p>That wraps up our talk about <a href="http://nicj.net/measuring-the-performance-of-your-web-apps/">how to monitor and measure the performance of your web apps</a>.  Hope you enjoyed it.  </p>
<p>Other articles in this series:</p>
<ul>
<li><a href="http://nicj.net/navigationtiming-in-practice/">NavigationTiming in Practice</a></li>
<li><a href="http://nicj.net/resourcetiming-in-practice/">ResourceTiming in Practice</a></li>
</ul>
<p>More resources:</p>
<ul>
<li><a href="http://www.w3.org/TR/user-timing/">UserTiming W3C specification</a></li>
<li><a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code> W3C specification</a></li>
<li><a href="http://www.w3.org/TR/performance-timeline/#performanceentry">PerformanceTimeline W3C specification</a></li>
<li><a href="http://nicj.net/usertiming-js/">UserTiming.js polyfill</a></li>
</ul>
<p><a name="usertiming-updates"></a></p>
<h2>Updates</h2>
<ul>
<li>2015-12-01: Added <em>Compressing</em> section</li>
<li>2021-05:
<ul>
<li>Updated caniuse.com market share</li>
<li>Added example usage via <code>PerformanceObserver</code></li>
<li>Added details about Level 3 usage (arbitrary timestamps and details)</li>
<li>Added a Table of Contents</li>
<li>Updated Standard Mark Names section about deprecation</li>
</ul>
</li>
</ul><p>The post <a href="https://nicj.net/usertiming-in-practice/" target="_blank">UserTiming in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/usertiming-in-practice/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>ResourceTiming in Practice</title>
		<link>https://nicj.net/resourcetiming-in-practice/</link>
					<comments>https://nicj.net/resourcetiming-in-practice/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 29 May 2015 00:33:20 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1728</guid>

					<description><![CDATA[<p>Last updated: May 2021 Table Of Contents Introduction How was it done before? How to use 3.1 Interlude: PerformanceTimeline 3.2 ResourceTiming 3.3 Initiator Types 3.4 What Resources are Included 3.5 Crawling IFRAMEs 3.6 Cached Resources 3.7 304 Not Modified 3.8 The ResourceTiming Buffer 3.9 Timing-Allow-Origin 3.10 Blocking Time 3.11 Content Sizes 3.12 Service Workers 3.13 [&#8230;]</p>
<p>The post <a href="https://nicj.net/resourcetiming-in-practice/" target="_blank">ResourceTiming in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Last updated: <a href="#resourcetiming-in-practice-updates">May 2021</a></p>
<h2>Table Of Contents</h2>
<ol>
<li><a href="#introduction">Introduction</a></li>
<li><a href="#how-was-it-done-before">How was it done before?</a></li>
<li><a href="#how-to-use">How to use</a><br />
3.1 <a href="#interlude-performanceTimeline">Interlude: PerformanceTimeline</a><br />
3.2 <a href="#back-to-resourcetiming">ResourceTiming</a><br />
3.3 <a href="#initiator-types">Initiator Types</a><br />
3.4 <a href="#what-resources-are-included">What Resources are Included</a><br />
3.5 <a href="#crawling-iframes">Crawling IFRAMEs</a><br />
3.6 <a href="#cached-resources">Cached Resources</a><br />
3.7 <a href="#304-not-modified">304 Not Modified</a><br />
3.8 <a href="#the-resourcetiming-buffer">The ResourceTiming Buffer</a><br />
3.9 <a href="#timing-allow-origin">Timing-Allow-Origin</a><br />
3.10 <a href="#blocking-time">Blocking Time</a><br />
3.11 <a href="#content-sizes">Content Sizes</a><br />
3.12 <a href="#serviceworker">Service Workers</a><br />
3.13 <a href="#compressing-resourcetiming-data">Compressing ResourceTiming Data</a><br />
3.14 <a href="#performanceobserver">PerformanceObserver</a></li>
<li><a href="#use-cases">Use Cases</a><br />
4.1 <a href="#diy-and-open-source">DIY and Open-Source</a><br />
4.2 <a href="#commercial-solutions">Commercial Solutions</a></li>
<li><a href="#availability">Availability</a></li>
<li><a href="#tips">Tips</a></li>
<li><a href="#browser-bugs">Browser Bugs</a></li>
<li><a href="#conclusion">Conclusion</a></li>
<li><a href="#resourcetiming-in-practice-updates">Updates</a></li>
</ol>
<p><a name="introduction"></a></p>
<h2>1. Introduction</h2>
<p><a href="http://www.w3.org/TR/resource-timing/">ResourceTiming</a> is a specification developed by the <a href="http://www.w3.org/2010/webperf">W3C Web Performance working group</a>, with the goal of exposing accurate performance metrics about all of the resources downloaded during the page load experience, such as images, CSS and JavaScript.</p>
<p>ResourceTiming builds on top of the concepts of <a href="http://nicj.net/navigationtiming-in-practice/">NavigationTiming</a> and provides many of the same measurements, such as the timings of each resource&#8217;s DNS, TCP, request and response phases, along with the final &quot;loaded&quot; timestamp.</p>
<p>ResourceTiming takes its inspiration from resource Waterfalls.  If you&#8217;ve ever looked at the Networking tab in Internet Explorer, Chrome or Firefox developer tools, you&#8217;ve seen a Waterfall before.  A Waterfall shows all of the resources fetched from the network in a timeline, so you can quickly visualize any issues.  Here&#8217;s an example from the Chrome Developer Tools:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2015/05/resource-timing-inspiration.png" alt="ResourceTiming inspiration" /></p>
<p><a href="https://www.w3.org/TR/resource-timing-1/">ResourceTiming (Level 1)</a> is a <strong>Candidate Recommendation</strong>, which means it has been shipped in major browsers.  <a href="https://www.w3.org/TR/resource-timing-2/">ResourceTiming (Level 2)</a> is a <strong>Working Draft</strong> and adds additional features like content sizes and new attributes.  It is still a work-in-progress, but many browsers already support it.</p>
<p>As of May 2021, <a href="https://caniuse.com/#feat=resource-timing">96.7% of the world-wide browser market-share</a> supports ResourceTiming.</p>
<p><a name="how-was-it-done-before"></a></p>
<h2>How was it done before?</h2>
<p>Prior to ResourceTiming, you could measure the time it took to download resources on your page by hooking into the associated element&#8217;s <code>onload</code> event, such as for Images.</p>
<p>Take this example code:</p>
<pre><code class="language-javascript">var start = new Date().getTime();
var image1 = new Image();

image1.onload = function() {
    var now = new Date().getTime();
    var latency = now - start;
    alert(&quot;End to end resource fetch: &quot; + latency);
};
image1.src = &#039;http://foo.com/image.png&#039;;</code></pre>
<p>With the code above, the image is inserted into the DOM when the script runs, at which point it sets the <code>start</code> variable to the current time.  The image&#8217;s <code>onload</code> event calculates how long it took for the resource to be fetched.</p>
<p>While this is one method for measuring the download time of an image, it&#8217;s not very practical.</p>
<p>First of all, it only measures the end-to-end download time, plus any overhead required for the browser to fire the <code>onload</code> callback.  For images, this could also include the time it takes to parse and render the image.  You cannot get a breakdown of DNS, TCP, SSL, request or response times with this method.</p>
<p>Another issue is with the use of <code>Date.getTime()</code>, which has some major drawbacks.  See our discussion on <a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code></a> in the <a href="http://nicj.net/navigationtiming-in-practice/">NavigationTiming</a> discussion for more details.</p>
<p>Most importantly, to use this method you have to construct your entire web app dynamically, at runtime. Dynamically adding all of the elements that would trigger resource fetches in <code>&lt;script&gt;</code> tags is not practical, nor performant.  You would have to insert <em>all</em> <code>&lt;img&gt;</code>, <code>&lt;link rel=&quot;stylesheet&quot;&gt;</code>, and <code>&lt;script&gt;</code> tags to instrument everything.  Doing this via JavaScript is not performant, and the browser cannot <a href="http://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster/">pre-fetch resources</a> that would have otherwise been in the HTML.</p>
<p>Finally, it&#8217;s impossible to measure all resources that are fetched by the browser using this method.  For example, it&#8217;s not possible to hook into stylesheets or fonts defined via <code>@import</code> or <code>@font-face</code> statements.</p>
<p>ResourceTiming addresses all of these problems.</p>
<p><a name="how-to-use"></a></p>
<h2>How to use</h2>
<p>ResourceTiming data is available via several methods on the <code>window.performance</code> interface:</p>
<pre><code>window.performance.getEntries();
window.performance.getEntriesByType(type);
window.performance.getEntriesByName(name, type);</code></pre>
<p>Each of these functions returns a list of <code>PerformanceEntry</code>s.  <code>getEntries()</code> will return a list of all entries in the PerformanceTimeline (see below), while if you use  <code>getEntriesByType(&quot;resource&quot;)</code> or <code>getEntriesByName(&quot;foo&quot;, &quot;resource&quot;)</code>, you can limit your query to just entries of the type <code>PerformanceResourceTiming</code>, which inherits from <code>PerformanceEntry</code>.</p>
<p>That may sound confusing, but when you look at the array of ResourceTiming objects, they&#8217;ll simply have a combination of the attributes below.  Here&#8217;s the <a href="http://www.w3.org/TR/WebIDL/">WebIDL</a> (definition) of a <a href="http://www.w3.org/TR/navigation-timing/#sec-navigation-info-interface"><code>PerformanceEntry</code></a>:</p>
<pre><code>interface PerformanceEntry {
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
};</code></pre>
<p>Each <code>PerformanceResourceTiming</code> is a <code>PerformanceEntry</code>, so has the above attributes, as well as the attributes below:</p>
<pre><code>[Exposed=(Window,Worker)]
interface PerformanceResourceTiming : PerformanceEntry {
    readonly attribute DOMString           initiatorType;
    readonly attribute DOMString           nextHopProtocol;
    readonly attribute DOMHighResTimeStamp workerStart;
    readonly attribute DOMHighResTimeStamp redirectStart;
    readonly attribute DOMHighResTimeStamp redirectEnd;
    readonly attribute DOMHighResTimeStamp fetchStart;
    readonly attribute DOMHighResTimeStamp domainLookupStart;
    readonly attribute DOMHighResTimeStamp domainLookupEnd;
    readonly attribute DOMHighResTimeStamp connectStart;
    readonly attribute DOMHighResTimeStamp connectEnd;
    readonly attribute DOMHighResTimeStamp secureConnectionStart;
    readonly attribute DOMHighResTimeStamp requestStart;
    readonly attribute DOMHighResTimeStamp responseStart;
    readonly attribute DOMHighResTimeStamp responseEnd;
    readonly attribute unsigned long long  transferSize;
    readonly attribute unsigned long long  encodedBodySize;
    readonly attribute unsigned long long  decodedBodySize;
    serializer = {inherit, attribute};
};</code></pre>
<p><a name="interlude-performanceTimeline"></a></p>
<h2>Interlude: PerformanceTimeline</h2>
<p>The <a href="http://www.w3.org/TR/performance-timeline/"><code>PerformanceTimeline</code></a> is a critical part of ResourceTiming, and one interface that you can use to fetch ResourceTiming data, as well as other performance information, such as <a href="http://www.w3.org/TR/user-timing/">UserTiming</a> data.  See also the section on the <a href="#performanceobserver">PerformanceObserver</a> for another way of consuming ResourceTiming data.</p>
<p>The methods <code>getEntries()</code>, <code>getEntriesByType()</code> and <code>getEntriesByName()</code> that you saw above are the primary interfaces of the PerformanceTimeline.  The idea is to expose all browser performance information via a standard interface.</p>
<p>All browsers that support ResourceTiming (or UserTiming) will also support the PerformanceTimeline.</p>
<p>Here are the primary methods:</p>
<ul>
<li><code>getEntries()</code>: Gets all entries in the timeline</li>
<li><code>getEntriesByType(type)</code>: Gets all entries of the specified type (eg <code>resource</code>, <code>mark</code>, <code>measure</code>)</li>
<li><code>getEntriesByName(name, type)</code>: Gets all entries with the specified name (eg URL or mark name). <code>type</code> is optional, and will filter the list to that type.</li>
</ul>
<p>We&#8217;ll use the PerformanceTimeline to fetch ResourceTiming data (and <a href="https://nicj.net/usertiming-in-practice/">UserTiming</a> data).</p>
<p><a name="back-to-resourcetiming"></a></p>
<h2>Back to ResourceTiming</h2>
<p>ResourceTiming takes its inspiration from the <a href="http://nicj.net/navigationtiming-in-practice/">NavigationTiming timeline</a>.</p>
<p>Here are the phases a single resource would go through during the fetch process:</p>
<p><img src="https://o.nicj.net/wp-content/uploads/2015/05/resource-timing.png" alt="ResourceTiming timeline" /></p>
<p>To fetch all of the resources on a page, you simply call one of the PerformanceTimeline methods:</p>
<pre><code>var resources = window.performance.getEntriesByType(&quot;resource&quot;);

/* eg:
[
    {
        name: &quot;https://www.foo.com/foo.png&quot;,

        entryType: &quot;resource&quot;,

        startTime: 566.357000003336,
        duration: 4.275999992387369,

        initiatorType: &quot;img&quot;,
        nextHopProtocol: &quot;h2&quot;,

        workerStart: 300.0,
        redirectEnd: 0,
        redirectStart: 0,
        fetchStart: 566.357000003336,
        domainLookupStart: 566.357000003336,
        domainLookupEnd: 566.357000003336,
        connectStart: 566.357000003336,
        secureConnectionStart: 0,
        connectEnd: 566.357000003336,
        requestStart: 568.4959999925923,
        responseStart: 569.4220000004862,
        responseEnd: 570.6329999957234,

        transferSize: 1000,
        encodedBodySize: 1000,
        decodedBodySize: 1000,
    }, ...
]
*/</code></pre>
<p>Please note that all of the timestamps are <a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code></a>s, so they are relative to <code>window.performance.timing.navigationStart</code> or <code>window.performance.timeOrigin</code>.  Thus a value of <code>500</code> means 500 milliseconds after the page load started.</p>
<p>Here is a description of all of the ResourceTiming attributes:</p>
<ul>
<li><code>name</code> is the fully-resolved URL of the attribute (relative URLs in your HTML will be expanded to include the full protocol, domain name and path)</li>
<li><code>entryType</code> will always be <code>&quot;resource&quot;</code> for ResourceTiming entries</li>
<li><code>startTime</code> is the time the resource started being fetched (e.g. offset from the <code>performance.timeOrigin</code>)</li>
<li><code>duration</code> is the overall time required to fetch the resource</li>
<li><code>initiatorType</code> is the <code>localName</code> of the element that initiated the fetch of the resource (see <a href="#initiator-types">details below</a>)</li>
<li><code>nextHopProtocol</code>: <a href="https://tools.ietf.org/html/rfc7301#section-6">ALPN Protocol ID</a> such as <code>http/0.9</code> <code>http/1.0</code> <code>http/1.1</code> <code>h2</code> <code>hq</code> <code>spdy/3</code> (ResourceTiming Level 2)</li>
<li><code>workerStart</code> is the time immediately before the active Service Worker received the <code>fetch</code> event, if a ServiceWorker is installed</li>
<li><code>redirectStart</code> and <code>redirectEnd</code> encompass the time it took to fetch any previous resources that redirected to the final one listed.  If either timestamp is <code>0</code>, there were no redirects, <em>or</em> one of the redirects wasn&#8217;t from the same origin as this resource.</li>
<li><code>fetchStart</code> is the time this specific resource started being fetched, not including redirects</li>
<li><code>domainLookupStart</code> and <code>domainLookupEnd</code> are the timestamps for DNS lookups</li>
<li><code>connectStart</code> and <code>connectEnd</code> are timestamps for the TCP connection</li>
<li><code>secureConnectionStart</code> is the start timestamp of the SSL handshake, if any.  If the connection was over HTTP, or if the browser doesn&#8217;t support this timestamp (eg. Internet Explorer), it will be <code>0</code>.</li>
<li><code>requestStart</code> is the timestamp that the browser started to request the resource from the remote server</li>
<li><code>responseStart</code> and <code>responseEnd</code> are the timestamps for the start of the response and when it finished downloading</li>
<li><code>transferSize</code>: Bytes transferred for the HTTP response header and content body (ResourceTiming Level 2)</li>
<li><code>decodedBodySize</code>: Size of the body after removing any applied content-codings (ResourceTiming Level 2)</li>
<li><code>encodedBodySize</code>: Size of the body after prior to removing any applied content-codings (ResourceTiming Level 2)</li>
</ul>
<p><code>duration</code> includes the time it took to fetch all redirected resources (if any) as well as the final resource.  To track the overall time it took to fetch just the final resource, you may want to use <code>(responseEnd</code> &#8211; <code>fetchStart)</code>.</p>
<p>ResourceTiming does not (<a href="https://github.com/w3c/resource-timing/issues/90">yet</a>) include attributes that expose the HTTP status code of the resource (for privacy concerns).</p>
<p><a name="initiator-types"></a></p>
<h2>Initiator Types</h2>
<p><code>initiatorType</code> is the <code>localName</code> of the element that fetched the resource &#8212; in other words, the name of the associated HTML element.</p>
<p>The most common values seen for this attribute are:</p>
<ul>
<li><code>img</code></li>
<li><code>link</code></li>
<li><code>script</code></li>
<li><code>css</code>: <code>url()</code>, <code>@import</code></li>
<li><code>xmlhttprequest</code></li>
<li><code>iframe</code> (known as <code>subdocument</code> in some versions of IE)</li>
<li><code>body</code></li>
<li><code>input</code></li>
<li><code>frame</code></li>
<li><code>object</code></li>
<li><code>image</code></li>
<li><code>beacon</code></li>
<li><code>fetch</code></li>
<li><code>video</code></li>
<li><code>audio</code></li>
<li><code>source</code></li>
<li><code>track</code></li>
<li><code>embed</code></li>
<li><code>eventsource</code></li>
<li><code>navigation</code></li>
<li><code>other</code></li>
<li><code>use</code></li>
</ul>
<p>It&#8217;s important to note the <code>initiatorType</code> is not a &quot;Content Type&quot;.  It is the element that triggered the fetch, not the type of content fetched.</p>
<p>As an example, some of the above values can be confusing at first glance.  For example, A <code>.css</code> file may have an <code>initiatorType</code> of <code>&quot;link&quot;</code> or <code>&quot;css&quot;</code> because it can either be fetched via a <code>&lt;link&gt;</code> tag or via an <code>@import</code> in a CSS file.  While an <code>initiatorType</code> of <code>&quot;css&quot;</code> might actually be a <code>foo.jpg</code> image, because CSS fetched an image.</p>
<p>The <code>iframe</code> initiator type is for <code>&lt;IFRAME&gt;</code>s on the page, and the <code>duration</code> will be how long it took to load that frame&#8217;s HTML (e.g. how long it took for <code>responseEnd</code> of the HTML).  It will <em>not</em> include the time it took for the <code>&lt;IFRAME&gt;</code> itself to fire its <code>onload</code> event, so resources fetched <em>within</em> the <code>&lt;IFRAME&gt;</code> will not be represented in an <code>iframe</code>&#8216;s <code>duration</code>.  (<a href="http://dev.nicj.net/resourcetiming/iframe.html">Example test case</a>)</p>
<p>Here&#8217;s a list of common HTML elements and JavaScript APIs and what <code>initiatorType</code> they should map to:</p>
<ul>
<li><code>&lt;img src=&quot;...&quot;&gt;</code>: <code>img</code></li>
<li><code>&lt;img srcset=&quot;...&quot;&gt;</code>: <code>img</code></li>
<li><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;...&quot;&gt;</code>: <code>link</code></li>
<li><code>&lt;link rel=&quot;prefetch&quot; href=&quot;...&quot;&gt;</code>: <code>link</code></li>
<li><code>&lt;link rel=&quot;preload&quot; href=&quot;...&quot;&gt;</code>: <code>link</code></li>
<li><code>&lt;link rel=&quot;prerender&quot; href=&quot;...&quot;&gt;</code>: <code>link</code></li>
<li><code>&lt;link rel=&quot;manfiest&quot; href=&quot;...&quot;&gt;</code>: <code>link</code></li>
<li><code>&lt;script src=&quot;...&quot;&gt;</code>: <code>script</code></li>
<li>CSS <code>@font-face { src: url(...) }</code>: <code>css</code></li>
<li>CSS <code>background: url(...)</code>: <code>css</code></li>
<li>CSS <code>@import url(...)</code>: <code>css</code></li>
<li>CSS <code>cursor: url(...)</code>: <code>css</code></li>
<li>CSS <code>list-style-image: url(...)</code>: <code>css</code></li>
<li><code>&lt;body background=&#039;&#039;&gt;</code>: <code>body</code></li>
<li><code>&lt;input src=&#039;&#039;&gt;</code>: <code>input</code></li>
<li><code>XMLHttpRequest.open(...)</code>: <code>xmlhttprequest</code></li>
<li><code>&lt;iframe src=&quot;...&quot;&gt;</code>: <code>iframe</code></li>
<li><code>&lt;frame src=&quot;...&quot;&gt;</code>: <code>frame</code></li>
<li><code>&lt;object&gt;</code>: <code>object</code></li>
<li><code>&lt;svg&gt;&lt;image xlink:href=&quot;...&quot;&gt;</code>: <code>image</code></li>
<li><code>&lt;svg&gt;&lt;use&gt;</code>: <code>use</code></li>
<li><code>navigator.sendBeacon(...)</code>: <code>beacon</code></li>
<li><code>fetch(...)</code>: <code>fetch</code></li>
<li><code>&lt;video src=&quot;...&quot;&gt;</code>: <code>video</code></li>
<li><code>&lt;video poster=&quot;...&quot;&gt;</code>: <code>video</code></li>
<li><code>&lt;video&gt;&lt;source src=&quot;...&quot;&gt;&lt;/video&gt;</code>: <code>source</code></li>
<li><code>&lt;audio src=&quot;...&quot;&gt;</code>: <code>audio</code></li>
<li><code>&lt;audio&gt;&lt;source src=&quot;...&quot;&gt;&lt;/audio&gt;</code>: <code>source</code></li>
<li><code>&lt;picture&gt;&lt;source srcset=&quot;...&quot;&gt;&lt;/picture&gt;</code>: <code>source</code></li>
<li><code>&lt;picture&gt;&lt;img src=&quot;...&quot;&gt;&lt;/picture&gt;</code>: <code>img</code></li>
<li><code>&lt;picture&gt;&lt;img srcsec=&quot;...&quot;&gt;&lt;/picture&gt;</code>: <code>img</code></li>
<li><code>&lt;track src=&quot;...&quot;&gt;</code>: <code>track</code></li>
<li><code>&lt;embed src=&quot;...&quot;&gt;</code>: <code>embed</code></li>
<li><code>favicon.ico</code>: <code>link</code></li>
<li>EventSource: <code>eventsource</code></li>
</ul>
<p>Not all browsers correctly report the <code>initiatorType</code> for the above resources.  <a href="http://w3c-test.org/resource-timing/resource_initiator_types.html">See this web-platform-tests test case for details</a>.</p>
<p><a name="what-resources-are-included"></a></p>
<h2>What Resources are Included</h2>
<p>All of the resources that your browser fetches to construct the page should be listed in the ResourceTiming data.  This includes, but is not limited to images, scripts, css, fonts, videos, IFRAMEs and XHRs.</p>
<p>Some browsers (eg. Internet Explorer) may include other non-fetched resources, such as <code>about:blank</code> and <code>javascript:</code> URLs in the ResourceTiming data.  This is likely a bug and may be fixed in upcoming versions, but you may want to filter out non-<code>http:</code> and <code>https:</code> protocols.</p>
<p>Additionally, some browser extensions may trigger downloads and thus you may see some of those downloads in your ResourceTiming data as well.</p>
<p>Not all resources will be fetched successfully.  There might have been a networking error, due to a DNS, TCP or SSL/TLS negotiation failure.  Or, the server might return a 4xx or 5xx response.  How this information is surfaced in ResourceTiming depends on the browser:</p>
<ul>
<li>DNS failure (cross-origin)
<ul>
<li>Chrome &lt;= 78: No ResourceTiming entry</li>
<li>Chrome &gt;= 79: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Internet Explorer: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Edge: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Firefox: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>duration</code> is <code>0</code> in some cases.  <code>responseEnd</code> is non-zero.</li>
<li>Safari: No ResourceTiming entry</li>
</ul>
</li>
<li>TCP failure (cross-origin)
<ul>
<li>Chrome &lt;= 78: No ResourceTiming entry</li>
<li>Chrome &gt;= 79: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Internet Explorer: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Edge: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Firefox: <code>domainLookupStart</code> through <code>responseStart</code> and <code>duration</code> are <code>0</code>.  <code>responseEnd</code> is non-zero.</li>
<li>Safari: No ResourceTiming entry</li>
</ul>
</li>
<li>SSL failure (cross-origin)
<ul>
<li>Chrome &lt;= 78: No ResourceTiming entry</li>
<li>Chrome &gt;= 79: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Internet Explorer: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Edge: <code>domainLookupStart</code> through <code>responseStart</code> are <code>0</code>.  <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Firefox: <code>domainLookupStart</code> through <code>responseStart</code> and <code>duration</code> are <code>0</code>.  <code>responseEnd</code> is non-zero.</li>
<li>Safari: No ResourceTiming entry</li>
</ul>
</li>
<li>4xx/5xx response (same-origin)
<ul>
<li>Chrome &lt;= 78: No ResourceTiming entry</li>
<li>Chrome &gt;= 79: All timestamps are non-zero.</li>
<li>Internet Explorer: All timestamps are non-zero.</li>
<li>Edge: All timestamps are non-zero.</li>
<li>Firefox: All timestamps are non-zero.</li>
<li>Safari &lt;= 12: No ResourceTiming entry.</li>
<li>Safari &gt;= 13: All timestamps are non-zero.</li>
</ul>
</li>
<li>4xx/5xx response (cross-origin)
<ul>
<li>Chrome &lt;= 78: No ResourceTiming entry</li>
<li>Chrome &gt;= 79: All timestamps are non-zero.</li>
<li>Internet Explorer: <code>startTime</code>, <code>fetchStart</code>, <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Edge: <code>startTime</code>, <code>fetchStart</code>, <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Firefox: <code>startTime</code>, <code>fetchStart</code>, <code>responseEnd</code> and <code>duration</code> are non-zero.</li>
<li>Safari &lt;= 12: No ResourceTiming entry.</li>
<li>Safari &gt;= 13: All timestamps are non-zero.</li>
</ul>
</li>
</ul>
<p>The working group is attempting to get these behaviors more consistent across browsers. You can read <a href="https://github.com/w3c/resource-timing/issues/12">this post</a> for further details as well as <a href="https://github.com/w3c/resource-timing/issues/12#issuecomment-840316432">inconsistencies</a> found.  See the <a href="#browser-bugs">browser bugs</a> section for relevant bugs.</p>
<p>Note that the <strong>root page (your HTML) is not included in ResourceTiming</strong>.  You can get all of that data from NavigationTiming.</p>
<p>An additional set of resources that may also be missing from ResourceTiming are resources <a href="https://www.w3.org/TR/resource-timing-2/#processing-model">fetched by cross-origin stylesheets fetched with <code>no-cors</code> policy</a>.</p>
<p>There are a few additional reasons why a resource might not be in the ResourceTiming data.  See the <a href="#crawling-iframes">Crawling IFRAMEs</a> and <a href="#timing-allow-origin">Timing-Allow-Origin</a> sections for more details, or read the <a href="https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/">ResourceTiming Visibility</a> post for an in-depth look.</p>
<p><a name="crawling-iframes"></a></p>
<h2>Crawling IFRAMEs</h2>
<p>There are two important caveats when working with ResourceTiming data:</p>
<ol>
<li>Each <code>&lt;IFRAME&gt;</code> on the page will only report on its <em>own</em> resources, so you must look at every frame&#8217;s <code>performance.getEntriesByType(&quot;resource&quot;)</code></li>
<li>You cannot access <code>frame.performance.getEntriesByType()</code> in a cross-origin frame</li>
</ol>
<p>If you want to capture <em>all</em> of the resources that were fetched for a page load, you need to crawl all of the frames on the page (and sub-frames, etc), and join their entries to the main window&#8217;s.</p>
<p><a href="https://gist.github.com/nicjansma/10dc57bae1f6cf139630c6b055d23f98">This gist</a> shows a naive way of crawling all frames. For a version that deals with all of the complexities of the crawl, such as adjusting resources in each frame to the correct <code>startTime</code>, you should check out <a href="https://github.com/akamai/boomerang/blob/master/plugins/restiming.js">Boomerang’s restiming.js plugin</a>.</p>
<p>However, even if you attempt to crawl all frames on the page, many pages include third-party scripts, libraries, ads, and other content that loads <em>within</em> cross-origin frames.  We have <em>no</em> way of accessing the ResourceTiming data from these frames.  Over 30% of resources in the Alexa Top 1000 are completely invisible to ResourceTiming because they&#8217;re loaded in a cross-origin frame.</p>
<p>Please see my in-depth post on <a href="https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/">ResourceTiming Visibility</a> for details on how this might affect you, and suggested workarounds.</p>
<p><a name="cached-resources"></a></p>
<h2>Cached Resources</h2>
<p>Cached resources will show up in ResourceTiming right along side resources that were fetched from the network.</p>
<p>For browsers that do not support ResourceTiming Level 2 with the <a href="#content-sizes">content size</a> attributes, there&#8217;s no direct indicator for the resource that it was served from the cache.  In practice, resources with a very short <code>duration</code> (say under 30 milliseconds) are likely to have been served from the browser&#8217;s cache.  They might take a few milliseconds due to disk latencies.</p>
<p>Browsers that support ResourceTiming Level 2 expose the <a href="#content-sizes">content size</a> attributes, which gives us a lot more information about the cache state of each resource.  We can look at <code>transferSize</code> to determine cache hits.</p>
<p>Here is example code to determine cache hit status:</p>
<pre><code class="language-javascript">function isCacheHit() {
  // if we transferred bytes, it must not be a cache hit
  // (will return false for 304 Not Modified)
  if (transferSize &gt; 0) return false;

  // if the body size is non-zero, it must mean this is a
  // ResourceTiming2 browser, this was same-origin or TAO,
  // and transferSize was 0, so it was in the cache
  if (decodedBodySize &gt; 0) return true;

  // fall back to duration checking (non-RT2 or cross-origin)
  return duration &lt; 30;
}</code></pre>
<p>This algorithm isn&#8217;t perfect, but probably covers 99% of cases.</p>
<p>Note that conditional validations that return a <code>304 Not Modifed</code> would be considered a cache miss with the above algorithm.</p>
<p><a name="304-not-modified"></a></p>
<h2>304 Not Modified</h2>
<p>Conditionally fetched resources (with an <code>If-Modified-Since</code> or <code>Etag</code> header) might return a <code>304 Not Modified</code> response.</p>
<p>In this case, the <code>tranferSize</code> might be small because it just reflects the <code>304 Not Modified</code> response and no content body.  <code>transferSize</code> might be less than the <code>encodedBodySize</code> in this case.</p>
<p><code>encodedBodySize</code> and <code>decodedBodySize</code> should be the body size of the previously-cached resource.</p>
<p>(there is a Chrome 65 <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=834083">browser bug</a> that might result in the <code>encodedBodySize</code> and <code>decodedBodySize</code> being 0)</p>
<p>Here is example code to detect 304s:</p>
<pre><code class="language-javascript">function is304() {
  if (encodedBodySize &gt; 0 &amp;&amp;
      tranferSize &gt; 0 &amp;&amp;
      tranferSize &lt; encodedBodySize) {
    return true;
  }

  // unknown
  return null;
}</code></pre>
<p><a name="the-resourcetiming-buffer"></a></p>
<h2>The ResourceTiming Buffer</h2>
<p>There is a ResourceTiming buffer (per document / IFRAME) that stops filling after its limit is reached.  By default, all modern browsers (except Internet Explorer / Edge) currently set this limit to 150 entries (per frame).  Internet Explorer 10+ and Edge default to 500 entries (per frame).  Some browsers may have also updated their default to <a href="https://github.com/w3c/resource-timing/pull/155">250 entries per frame</a>.</p>
<p>The reasoning behind limiting the number of entries is to ensure that, for the vast majority of websites that are not consuming ResourceTiming entries, the browser&#8217;s memory isn&#8217;t consumed indefinitely holding on to a lot of this information.  In addition, for sites that periodically fetch new resources (such as XHR polling), we would&#8217;t want the ResourceTiming buffer to grow unbound.</p>
<p>Thus, if you will be consuming ResourceTiming data, you need to have awareness of the buffer.  If your site only downloads a handful of resources for each page load (&lt; 100), and does nothing afterwards, you probably won&#8217;t hit the limit.</p>
<p>However, if your site downloads over a hundred resources, or you want to be able to monitor for resources fetched on an ongoing basis, you can do one of three things.</p>
<p>First, you can listen for the <a href="http://www.w3.org/TR/resource-timing/#attributes-1"><code>onresourcetimingbufferfull</code></a> event which gets fired on the document when the buffer is full.  You can then use <a href="http://www.w3.org/TR/resource-timing/#methods"><code>setResourceTimingBufferSize(n)</code></a> or <a href="http://www.w3.org/TR/resource-timing/#methods"><code>clearResourceTimings()</code></a> to resize or clear the buffer.</p>
<p>As an example, to keep the buffer size at 150 yet continue tracking resources after the first 150 resources were added, you could do something like this;</p>
<pre><code>if (&quot;performance&quot; in window) {
  function onBufferFull() {
    var latestEntries = performance.getEntriesByType(&quot;resource&quot;);
    performance.clearResourceTimings();

    // analyze or beacon latestEntries, etc
  }

  performance.onresourcetimingbufferfull = performance.onwebkitresourcetimingbufferfull = onBufferFull;
}</code></pre>
<p>Note <code>onresourcetimingbufferfull</code> is not currently supported in Internet Explorer (10, 11 or Edge).</p>
<p>If your site is on the verge of 150 resources, and you don&#8217;t want to manage the buffer, you could also just safely increase the buffer size to something reasonable in your HTML header:</p>
<pre><code>&lt;html&gt;&lt;head&gt;
&lt;script&gt;
if (&quot;performance&quot; in window 
    &amp;&amp; window.performance 
    &amp;&amp; window.performance.setResourceTimingBufferSize) {
    performance.setResourceTimingBufferSize(300);
}
&lt;/script&gt;
...
&lt;/head&gt;...</code></pre>
<p>(you should do this for any <code>&lt;iframe&gt;</code> that might load more than 150 resources too)</p>
<p><strong>Don&#8217;t just <code>setResourceTimingBufferSize(99999999)</code></strong> as this could grow your visitors&#8217;s browser&#8217;s memory unnecessarily.</p>
<p>Finally, you can also use a <a href="#performanceobserver">PerformanceObserver</a> to manage your own &quot;buffer&quot; of ResourceTiming data.</p>
<p>Note: Some browsers are <a href="https://github.com/w3c/resource-timing/pull/155">starting up update</a> the default limit of 150 resources to 250 resources instead.</p>
<p><a name="timing-allow-origin"></a></p>
<h2>Timing-Allow-Origin</h2>
<p>A <strong>cross-origin</strong> resource is any resource that doesn&#8217;t originate from the same domain as the page.  For example, if your visitor is on <code>http://foo.com/</code> and you&#8217;ve fetched resources from <code>http://cdn.foo.com</code> or <code>http://mycdn.com</code>, those resources will both be considered cross-origin.</p>
<p>By default, cross-origin resources only expose timestamps for the following attributes:</p>
<ul>
<li><code>startTime</code> (will equal <code>fetchStart</code>)</li>
<li><code>fetchStart</code></li>
<li><code>responseEnd</code></li>
<li><code>duration</code></li>
</ul>
<p>This is to protect your privacy (so an attacker can&#8217;t load random URLs to see where you&#8217;ve been).</p>
<p>This means that all of the following attributes will be <code>0</code> for cross-origin resources:</p>
<ul>
<li><code>redirectStart</code></li>
<li><code>redirectEnd</code></li>
<li><code>domainLookupStart</code></li>
<li><code>domainLookupEnd</code></li>
<li><code>connectStart</code></li>
<li><code>connectEnd</code></li>
<li><code>secureConnectionStart</code></li>
<li><code>requestStart</code></li>
<li><code>responseStart</code></li>
</ul>
<p>In addition, all size information will be <code>0</code> for cross-origin resources:</p>
<ul>
<li><code>transferSize</code></li>
<li><code>encodedBodySize</code></li>
<li><code>decodedBodySize</code></li>
</ul>
<p>In addition, cross-origin resources that redirected will have a <code>startTime</code> that <em>only</em> reflects the final resource &#8212; <code>startTime</code> will equal <code>fetchStart</code> instead of <code>redirectStart</code>.  This means the time of any redirect(s) will be hidden from ResourceTiming.</p>
<p>Luckily, if you control the domains you&#8217;re fetching other resources from, you can overwrite this default precaution by sending a <a href="http://www.w3.org/TR/resource-timing/#timing-allow-origin"><code>Timing-Allow-Origin</code></a> HTTP response header:</p>
<pre><code>Timing-Allow-Origin = &quot;Timing-Allow-Origin&quot; &quot;:&quot; origin-list-or-null | &quot;*&quot;</code></pre>
<p>In practice, most people that send the <code>Timing-Allow-Origin</code> HTTP header just send a wildcard origin:</p>
<pre><code>Timing-Allow-Origin: *</code></pre>
<p>So if you&#8217;re serving any of your content from another domain name, i.e. from a CDN, it is <em>strongly recommended</em> that you set the <code>Timing-Allow-Origin</code> header for those responses.</p>
<p>Thankfully, third-party libraries for widgets, ads, analytics, etc are starting to set the header on their content. Only about <a href="https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/#cross-origin-resources">13%</a> currently do, but this is growing (according to the <a href="http://httparchive.org/">HTTP Archive</a>).  Notably, Google, Facebook, Disqus, and mPulse send this header for their scripts.</p>
<p><a name="blocking-time"></a></p>
<h2>Blocking Time</h2>
<p>Browsers will only open a limited number of connections to each unique origin (protocol/server name/port) when downloading resources.</p>
<p>If there are more resources than the # of connections, the later resources will be &quot;blocking&quot;, waiting for their turn to download.</p>
<p>Blocking time is generally seen as &quot;missing periods&quot; (non-zero durations) that occur between <code>connectEnd</code> and <code>requestStart</code> (when waiting on a Keep-Alive TCP connection to reuse), or between <code>fetchStart</code> and <code>domainLookupStart</code> (when waiting on things like the browser&#8217;s cache).</p>
<p>The <code>duration</code> attribute includes Blocking time.  So in general, you may not want to use <code>duration</code> if you&#8217;re only interested in actual network timings.</p>
<p>Unfortunately, <code>duration</code>, <code>startTime</code> and <code>responseEnd</code> are the only attributes you get with cross-origin resources, so you can&#8217;t easily subtract out Blocking time from cross-origin resources.</p>
<p>To calculate Blocking time, you would do something like this:</p>
<pre><code>var blockingTime = 0;
if (res.connectEnd &amp;&amp; res.connectEnd === res.fetchStart) {
    blockingTime = res.requestStart - res.connectEnd;
} else if (res.domainLookupStart) {
    blockingTime = res.domainLookupStart - res.fetchStart;
}</code></pre>
<p><a name="content-sizes"></a></p>
<h2>Content Sizes</h2>
<p>Beginning with ResourceTiming 2, content sizes are included for all same-origin or <code>Timing-Allow-Origin</code> resources:</p>
<ul>
<li><code>transferSize</code>: Bytes transferred for HTTP response header and content body</li>
<li><code>decodedBodySize</code>: Size of the body after removing any applied content-codings</li>
<li><code>encodedBodySize</code>: Size of the body after prior to removing any applied content-codings</li>
</ul>
<p>Some notes:</p>
<ul>
<li>If <code>transferSize</code> is <code>0</code>, <em>and</em> timestamps like <code>responseStart</code> are filled in, the resource was served from the cache</li>
<li>If <code>transferSize</code> is <code>0</code>, <em>but</em> timestamps like <code>responseStart</code> are <em>also</em> <code>0</code>, the resource was cross-origin, so you should look at the <a href="#cached-resources">cached state algorithm</a> to determine its cache state</li>
<li><code>transferSize</code> might be less than <code>encodedBodySize</code> in cases where a conditional validation occurred (e.g. <code>304 Not Modified</code>).  In this case, <code>transferSize</code> would be the size of the <code>304</code> headers, while <code>encodedBodySize</code> would be the size of the cached response body from the previous request.</li>
<li>If <code>encodedBodySize</code> and <code>decodedBodySize</code> are non-<code>0</code> and <em>differ</em>, the content was compressed (e.g. gzip or Brotli)</li>
<li><code>encodedBodySize</code> might be <code>0</code> in some cases (e.g. <code>HTTP 204 (No Content)</code> or 3XX responses)</li>
<li>See the <a href="#serviceworker">ServiceWorker</a> section for details when a ServiceWorker is involved</li>
</ul>
<p><a name="serviceworker"></a></p>
<h2>ServiceWorkers</h2>
<p>If you are using <a href="http://www.w3.org/TR/service-workers/">ServiceWorkers</a> in your app, you can get information about the time the ServiceWorker activated (<code>fetch</code> was fired) for each resource via the <a href="http://www.w3.org/TR/resource-timing/#widl-PerformanceResourceTiming-workerStart"><code>workerStart</code> attribute</a>.</p>
<p>The difference between <code>workerStart</code> and <code>fetchStart</code> is the processing time of the ServiceWorker:</p>
<pre><code>var workerProcessingTime = 0;
if (res.workerStart &amp;&amp; res.fetchStart) {
    workerProcessingTime = res.fetchStart - res.workerStart;
}</code></pre>
<p>When a ServiceWorker is active for a resource, the size attributes of <code>transferSize</code>, <code>encodedBodySize</code> and <code>decodedBodySize</code> are under-specified, inconsistent between browsers, and will often be exactly <code>0</code> even when bytes are transferred.  There is an open <a href="https://github.com/w3c/navigation-timing/issues/124">NavigationTiming issue</a> tracking this.  In addition, the timing attributes may be under-specified in some cases, with a <a href="https://github.com/w3c/resource-timing/issues/119">separate issue</a> tracking that.</p>
<p><a name="compressing-resourcetiming-data"></a></p>
<h2>Compressing ResourceTiming Data</h2>
<p>The <a href="http://httparchive.org">HTTP Archive</a> tells us there are about 100 HTTP resources on average, per page, with an average URL length of 85 bytes.</p>
<p>On average, each resource is ~ 500 bytes when <code>JSON.stringify()</code>&#8216;d.</p>
<p>That means you could expect around 45 KB of ResourceTiming data per page load on the &quot;average&quot; site.</p>
<p>If you&#8217;re considering beaconing ResourceTiming data back to your own servers for analysis, you may want to consider compressing it first.</p>
<p>There&#8217;s a couple things you can do to compress the data, and I&#8217;ve <a href="http://nicj.net/compressing-resourcetiming/">written about</a> these methods already.  I&#8217;ve shared an <a href="https://github.com/nicjansma/resourcetiming-compression.js">open-source script</a> that can compress ResourceTiming data that looks like this:</p>
<pre><code>{
    &quot;responseEnd&quot;:323.1100000002698,
    &quot;responseStart&quot;:300.5000000000000,
    &quot;requestStart&quot;:252.68599999981234,
    &quot;secureConnectionStart&quot;:0,
    &quot;connectEnd&quot;:0,
    &quot;connectStart&quot;:0,
    &quot;domainLookupEnd&quot;:0,
    &quot;domainLookupStart&quot;:0,
    &quot;fetchStart&quot;:252.68599999981234,
    &quot;redirectEnd&quot;:0,
    &quot;redirectStart&quot;:0,
    &quot;duration&quot;:71.42400000045745,
    &quot;startTime&quot;:252.68599999981234,
    &quot;entryType&quot;:&quot;resource&quot;,
    &quot;initiatorType&quot;:&quot;script&quot;,
    &quot;name&quot;:&quot;http://foo.com/js/foo.js&quot;
}</code></pre>
<p>To something much smaller, like this (which contains 3 resources):</p>
<pre><code>{
    &quot;http://&quot;: {
        &quot;foo.com/&quot;: {
            &quot;js/foo.js&quot;: &quot;370,1z,1c&quot;,
            &quot;css/foo.css&quot;: &quot;48c,5k,14&quot;
        },
        &quot;moo.com/moo.gif&quot;: &quot;312,34,56&quot;
    }
}</code></pre>
<p>Overall, we can compresses ResourceTiming data down to about 15% of its original size.</p>
<p>Example code to do this compression is available on <a href="https://github.com/nicjansma/resourcetiming-compression.js">github</a>.</p>
<p>See also the discussion on payload sizes in my <a href="https://nicj.net/beaconing-in-practice/#beaconing-payload">Beaconing In Practice</a> article.</p>
<p><a name="performanceobserver"></a></p>
<h2>PerformanceObserver</h2>
<p>Instead of using <code>performance.getEntriesByType(&quot;resource&quot;)</code> to fetch all of the current resources from the ResourceTiming buffer, you could instead use a <a href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver">PerformanceObserver</a> to get notified about all fetches.</p>
<p>Example usage:</p>
<pre><code>if (typeof window.PerformanceObserver === &quot;function&quot;) {
  var resourceTimings = [];

  var observer = new PerformanceObserver(function(entries) {
    Array.prototype.push.apply(resourceTimings, entries.getEntries());
  });

  observer.observe({entryTypes: [&#039;resource&#039;]});
}</code></pre>
<p>The benefits of using a PerformanceObserver are:</p>
<ul>
<li>You have stricter control over buffering old entries (if you want to buffer at all)</li>
<li>If there are multiple scripts or libraries on the page trying to manage the ResourceTiming buffer (by setting its size or clearing it), the PerformanceObserver won&#8217;t be affected by it.</li>
</ul>
<p>However, there are two major challenges with using a PerformanceObserver for ResourceTiming:</p>
<ul>
<li>You&#8217;ll need to register the PerformanceObserver before everything else on the page, ideally via an inline-<code>&lt;script&gt;</code> tag in your page&#8217;s <code>&lt;head&gt;</code>.  Otherwise, requests that fire before the PerformanceObserver initializes won&#8217;t be delivered to the callback.  There is <a href="https://github.com/w3c/performance-timeline/pull/76">some work</a> being done to add a <code>buffered: true</code> option, but it is not yet implemented in browsers.  In the meantime, you could call <code>observer.observe()</code> and then immediately call <code>performance.getEntriesByType(&quot;resource&quot;)</code> to get the current buffer.</li>
<li>Since each frame on the page maintains its own buffer of ResoruceTiming entries, and its own PerformanceObserver list, you will need to register a PerformanceObserver in all frames on the page, including child frames, grandchild frames, etc.  This results in a race condition, where you need to either monitor the page for all <code>&lt;iframe&gt;</code>s being created an immediately hook a PerformanceObserver in their window, or, you&#8217;ll have to crawl all of the frames later and get <code>performance.getEntriesByType(&quot;resource&quot;)</code> anyways.  There are some <a href="https://github.com/w3c/performance-timeline/issues/86">thoughts</a> about adding a <code>bubbles: true</code> flag to make this easier.</li>
</ul>
<p><a name="use-cases"></a></p>
<h2>Use Cases</h2>
<p>Now that ResourceTiming data is available in the browser in an accurate and reliable manner, there are a lot of things you can do with the information.  Here are some ideas:</p>
<ul>
<li>Send all ResourceTimings to your backend analytics</li>
<li>Raise an analytics event if any resource takes over X seconds to download (and trend this data)</li>
<li>Watch specific resources (eg third-party ads or analytics) and complain if they are slow</li>
<li>Monitor the overall health of your DNS infrastructure by beaconing DNS resolve time per-domain</li>
<li>Look for production resource errors (eg 4xx/5xx) in browsers that add errors to the buffer it (IE/Firefox)</li>
<li>Use ResourceTiming to determine your site&#8217;s &quot;visual complete&quot; metric by looking at timings of all above-the-fold images</li>
</ul>
<p>The possibilities are nearly endless.  Please leave a comment with how you&#8217;re using ResourceTiming data.</p>
<p><a name="diy-and-open-source"></a></p>
<h2>DIY and Open-Source</h2>
<p>Here are several interesting DIY / open-source solutions that utilize ResourceTiming data:</p>
<p><strong>Andy Davies&#8217; Waterfall.js</strong> shows a waterfall of any page&#8217;s resources via a bookmarklet:  <a href="https://github.com/andydavies/waterfall">github.com/andydavies/waterfall</a></p>
<p><a href="https://github.com/andydavies/waterfall"><img src="https://o.nicj.net/wp-content/uploads/2015/05/andydavies-waterfall.png" alt="Andy Davies&#039; Waterfall.js" /></a></p>
<p><strong>Mark Zeman&#8217;s Heatmap bookmarklet / Chrome extension</strong> gives a heatmap of when images loaded on your page: <a href="http://github.com/zeman/perfmap">github.com/zeman/perfmap</a></p>
<p><a href="http://github.com/zeman/perfmap"><img src="https://o.nicj.net/wp-content/uploads/2015/05/zeeman-perfmap.png" alt="Mark Zeman&#039;s Heatmap bookmarklet and extension" /></a></p>
<p><strong>Nurun&#8217;s Performance Bookmarklet</strong> breaks down your resources and creates a waterfall and some interesting charts: <a href="https://github.com/nurun/performance-bookmarklet">github.com/nurun/performance-bookmarklet</a></p>
<p><a href="https://github.com/nurun/performance-bookmarklet"><img src="https://o.nicj.net/wp-content/uploads/2015/05/nurun.png" alt="Nurun&#039;s Performance Bookmarklet" /></a></p>
<p><strong>Boomerang</strong> (which I work on) also captures ResourceTiming data and beacons it back to your backend analytics server: <a href="https://github.com/lognormal/boomerang">github.com/lognormal/boomerang</a></p>
<p><a name="commercial-solutions"></a></p>
<h2>Commercial Solutions</h2>
<p>If you don&#8217;t want to build or manage a DIY / Open-Source solution to gather ResourceTiming data, there are many great commercial services available.</p>
<p><em>Disclaimer: I work at Akamai, on mPulse and Boomerang</em></p>
<p><a href="http://akamai.com"><strong>Akamai mPulse</strong></a> captures 100% of your site&#8217;s traffic and gives you Waterfalls for each visit:</p>
<p><a href="http://akamai.com"><img src="https://o.nicj.net/wp-content/uploads/2015/05/soasta-mpulse-resourcetiming.png" alt="Akamai mPulse Resource Timing" /></a></p>
<p><a href="http://newrelic.com"><strong>New Relic Browser</strong></a>:</p>
<p><a href="http://newrelic.com"><img src="https://o.nicj.net/wp-content/uploads/2015/05/new-relic-browser-pro.png" alt="New Relic Browser" /></a></p>
<p><a href="http://appdynamics.com/"><strong>App Dynamics Web EUEM</strong></a>:</p>
<p><a href="http://appdynamics.com/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/appdynamics-webeuem.png" alt="App Dynamics Web EUEM" /></a></p>
<p><a href="https://www.dynatrace.com/platform/offerings/user-experience-management/"><strong>Dynatrace UEM</strong></a></p>
<p><a href="https://www.dynatrace.com/platform/offerings/user-experience-management/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/UserActionPurePath-Waterfall-600x448.png" alt="Dynatrace UEM" /></a></p>
<p><a name="availability"></a></p>
<h2>Availability</h2>
<p>ResourceTiming is available in most modern browsers.  According to <a href="http://caniuse.com/#feat=resource-timing">caniuse.com</a>, 96.7% of world-wide browser market share supports ResourceTiming (as of May 2021).  This includes Internet Explorer 10+, Edge, Firefox 36+, Chrome 25+, Opera 15+, Safari 11 and Android Browser 4.4+.</p>
<p><a href="http://caniuse.com/#feat=resource-timing"><img src="https://o.nicj.net/wp-content/uploads/2021/05/caniuse-restiming-2021.png" alt="CanIUse - ResourceTiming - April 2018" /></a></p>
<p>There are no polyfills available for ResourceTiming, as the data is just simply not available if the browser doesn&#8217;t expose it.</p>
<p><a name="tips"></a></p>
<h2>Tips</h2>
<p>Here are some additional (and re-iterated) tips for using ResourceTiming data:</p>
<ul>
<li>For many sites, most of your content will not be same-origin, so ensure all of your CDNs and third-party libraries send the <code>Timing-Allow-Origin</code> HTTP response header.</li>
<li>Each <code>IFRAME</code> will have its own ResourceTiming data, and those resources won&#8217;t be included in the parent FRAME/document.  You&#8217;ll need to traverse the document frames to get all resources.  See <a href="https://github.com/nicjansma/resourcetiming-compression.js">github.com/nicjansma/resourcetiming-compression.js</a>.</li>
<li><a href="https://nicj.net/resourcetiming-visibility-third-party-scripts-ads-and-page-weight/">Resources loaded from cross-origin frames will not be visible</a></li>
<li>ResourceTiming data <em>does not</em> ((yet)[<a href="https://github.com/w3c/resource-timing/issues/90">https://github.com/w3c/resource-timing/issues/90</a>]) include the HTTP response code for privacy concerns.</li>
<li>If you&#8217;re going to be managing the ResourceTiming buffer, make sure no other scripts are managing it as well (eg third-party analytics scripts).  Otherwise, you may have two listeners for <code>onresourcetimingbufferfull</code> stomping on each other.</li>
<li>The <code>duration</code> attribute includes Blocking time (when a resource is blocked behind other resources on the same socket).</li>
<li><code>about:blank</code> and <code>javascript:</code> URLs may be in  the ResourceTiming data for some browsers, and you may want to filter them out.</li>
<li>Browser extensions may show up in ResourceTiming data, if they initiate downloads.  We&#8217;ve seen Skype and other extensions show up.</li>
</ul>
<p><a name="browser-bugs"></a></p>
<h2>ResourceTiming Browser Bugs</h2>
<p>Browsers aren&#8217;t perfect, and unfortunately there some outstanding browser bugs around ResourceTiming data.  Here are some of the known ones (some of which may have been fixed the time you read this):</p>
<ul>
<li><a href="http://w3c-test.org/resource-timing/resource_initiator_types.html">All browsers have issues with the <code>initiatorType</code> attribute</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=711060">Chrome: <code>sendBeacon()</code> does not add entries to ResourceTiming</a> (fixed)</li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=805069">Chrome: <code>fetch()</code> resources sometimes don&#8217;t get added to the ResourceTiming timeline</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=834083">Chrome: ResourceTiming encodedBodySize and decodedBodySize are 0 for 304 Not Modified responses</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=824155">Chrome: ResourceTiming is affected by page activity</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=404501">Chrome: secureConnectionStart is 0 but it should be a timestamp</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=449224">Chrome: secureConnectionStart does not report start of HTTPS handshake</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=545936">Chrome: ResourceTiming does not work wih Preconnect</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=649571">Chrome: PerformanceResourceTiming.initiatorType === &quot;&quot; when used with the fetch API</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=760059">Chrome: ResourceTiming (DNS) inaccurate when using HTTP/2</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=460879">Chrome: Missing PerformanceResourceTiming entries for Requests that don&#8217;t receive a Response</a></li>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=933858">Chrome: Zero-byte IFRAMEs do not create PerformanceResourceTiming entries</a></li>
<li><a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/16739941/">Edge: Incorrect behavior populating ResourceTimings performance buffer</a></li>
<li><a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12947899/">Edge: pageshow is firing before image is visible on the page and its ResourceTiming data is ready</a></li>
<li><a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/20329563/">Edge: Edge exposing TAO-restricted timings when port differs</a></li>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=193902">Safari: Should surface entries for failed requests (DNS, TCP, SSL, 4xx, 5xx)</a></li>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=194858">Safari: Zero-byte IFRAMEs do not create PerformanceResourceTiming entries</a></li>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=225733">Safari: Resource Timing: secureConnectionStart == 0 when a connection is re-used</a></li>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=225737">Safari: Resource Timing: Duration is 0 in many cases</a></li>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1523275">Firefox: ResourceTiming duration should be non-0 for failed DNS, TCP, SSL</a></li>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1523291">Firefox: ResourceTiming secureConnectionStart is 0 for some HTTPS connections</a></li>
</ul>
<p>Sidebar &#8211; it&#8217;s great that browser vendors are tracking these issues publicly.</p>
<p><a name="conclusion"></a></p>
<h2>Conclusion</h2>
<p>ResourceTiming exposes accurate performance metrics for all of the resources fetched on your page.  You can use this data for a variety of scenarios, from investigating the performance of your third-party libraries to taking specific actions when resources aren&#8217;t performing according to your performance goals.</p>
<p>Next up: Using UserTiming data to expose custom metrics for your JavaScript apps in a standardized way.</p>
<p>Other articles in this series:</p>
<ul>
<li><a href="http://nicj.net/navigationtiming-in-practice/">NavigationTiming in Practice</a></li>
<li><a href="http://nicj.net/usertiming-in-practice/">UserTiming in Practice</a></li>
</ul>
<p>More resources:</p>
<ul>
<li><a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code> W3C specification</a></li>
<li><a href="http://www.w3.org/TR/resource-timing/">ResourceTiming W3C specification</a></li>
<li><a href="http://www.w3.org/TR/performance-timeline/">PerformanceTimeline W3C specification</a></li>
<li><a href="http://nicj.net/compressing-resourcetiming/">Compressing ResourceTiming</a></li>
<li><a href="http://nicj.net/compressing-usertiming/">Compressing UserTiming</a></li>
</ul>
<p><a name="resourcetiming-in-practice-updates"></a></p>
<h2>Updates</h2>
<ul>
<li>2016-01-03: Updated Firefox&#8217;s 404 and DNS behavior via Aaron Peters</li>
<li>2018-04:
<ul>
<li>Updated the <code>PerformanceResourceTiming</code> interface for ResourceTiming (Level 2) and descriptions of ResourceTiming 2 attributes</li>
<li>Updated an incorrect statement about <code>initiatorType=&#039;iframe&#039;</code>.  Previously this document stated that the <code>duration</code>    would include the time it took for the <code>&lt;IFRAME&gt;</code> to download static embedded resources.  This is not correct.  <code>duration</code> only includes the time it takes to download the <code>&lt;IFRAME&gt;</code> HTML bytes (through <code>responseEnd</code> of the HTML, so it does not include the <code>onload</code> duration of the <code>&lt;IFRAME&gt;</code>).</li>
<li>Updated list of attributes that are <code>0</code> for cross-origin resources (to include size attributes)</li>
<li>Added a note with examples on how to <a href="#crawling-iframes">crawl frames</a></li>
<li>Added a note on ResourceTiming Visibility and how cross-origin frames affect ResourceTiming</li>
<li>Removed some notes about older versions of Chrome</li>
<li>Added section on <a href="#content-sizes">Content Sizes</a></li>
<li>Updated the <a href="#cached-resources">Cached Resources</a> section to add an algorithm for ResourceTiming2 data</li>
<li>Updated <code>initiatorType</code> list as well as added a map of common elements to what <code>initiatorType</code> they would be</li>
<li>Added a note about ResourceTiming missing resources fetched by cross-origin stylesheets fetched with no-cors policy</li>
<li>Added note about <code>startTime</code> missing when there are redirects with no Timing-Allow-Origin</li>
<li>Added a section for <a href="#304-not-modified"><code>304 Not Modified</code> responses</a></li>
<li>Added a section on <a href="#performanceobserver"><code>PerformanceObserver</code></a></li>
<li>Updated the <a href="#serviceworker">ServiceWorker</a> section</li>
<li>Added a section on <a href="#browser-bugs">Browser Bugs</a></li>
<li>Updated caniuse.com market share</li>
</ul>
</li>
<li>2018-06:
<ul>
<li>Updated note that IE/Edge have a default buffer size of 500 entries</li>
<li>Added note about change to increase recommended buffer size of 150 to 250</li>
</ul>
</li>
<li>2019-01
<ul>
<li>Added a Table of Contents</li>
<li>Updated <a href="#what-resources-are-included">What Resources are Included</a> based on recent <a href="https://github.com/w3c/resource-timing/issues/12#issuecomment-458135884">research</a> and inclusion of Safari.</li>
</ul>
</li>
<li>2021-05
<ul>
<li>Updated <a href="#content-sizes">Content Sizes</a> and <a href="#serviceworker">ServiceWorker</a> sections for how the former is affected by the later</li>
<li>Updated caniuse.com market share</li>
<li>Added some additional known browser bugs</li>
</ul>
</li>
</ul><p>The post <a href="https://nicj.net/resourcetiming-in-practice/" target="_blank">ResourceTiming in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/resourcetiming-in-practice/feed/</wfw:commentRss>
			<slash:comments>26</slash:comments>
		
		
			</item>
		<item>
		<title>NavigationTiming in Practice</title>
		<link>https://nicj.net/navigationtiming-in-practice/</link>
					<comments>https://nicj.net/navigationtiming-in-practice/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 27 May 2015 23:29:39 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1680</guid>

					<description><![CDATA[<p>Last updated: May 2021 Table Of Contents Introduction How was it done before? 2.1. What&#8217;s Wrong With This? Interlude: DOMHighResTimestamp 3.1. Why Not the Date Object? Accessing NavigationTiming Data 4.1. NavigationTiming Timeline 4.2. Example Data 4.3. How to Use 4.4. NavigationTiming2 4.5. Service Workers Using NavigationTiming Data 5.1 DIY 5.2 Open-Source 5.3 Commercial Solutions Availability [&#8230;]</p>
<p>The post <a href="https://nicj.net/navigationtiming-in-practice/" target="_blank">NavigationTiming in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Last updated: <a href="#navtiming-updates">May 2021</a></p>
<h2>Table Of Contents</h2>
<ol>
<li><a href="#navtiming-introduction">Introduction</a></li>
<li><a href="#navtiming-how-was-it-done-before">How was it done before?</a><br />
2.1. <a href="#navtiming-whats-wrong-with-this">What&#8217;s Wrong With This?</a></li>
<li><a href="#navtiming-interlude-domhighrestimestamp">Interlude: DOMHighResTimestamp</a><br />
3.1. <a href="#navtiming-why-not-date-object">Why Not the Date Object?</a></li>
<li><a href="#navtiming-accessing-navigationtiming-data">Accessing NavigationTiming Data</a><br />
4.1. <a href="#navtiming-timeline">NavigationTiming Timeline</a><br />
4.2. <a href="#navtiming-example-data">Example Data</a><br />
4.3. <a href="#navtiming-how-to-use">How to Use</a><br />
4.4. <a href="#navtiming-navigationtiming2">NavigationTiming2</a><br />
4.5. <a href="#navtiming-service-workers">Service Workers</a></li>
<li><a href="#navtiming-using-navigationtiming-data">Using NavigationTiming Data</a><br />
5.1 <a href="#navtiming-diy">DIY</a><br />
5.2 <a href="#navtiming-open-source">Open-Source</a><br />
5.3 <a href="#navtiming-commercial-solutions">Commercial Solutions</a></li>
<li><a href="#navtiming-availability">Availability</a></li>
<li><a href="#navtiming-tips">Tips</a></li>
<li><a href="#navtiming-browser-bugs">Browser Bugs</a></li>
<li><a href="#navtiming-conclusion">Conclusion</a></li>
<li><a href="#navtiming-updates">Updates</a></li>
</ol>
<p><a name="navtiming-introduction"></a></p>
<h2>Introduction</h2>
<p><a href="http://www.w3.org/TR/navigation-timing/">NavigationTiming</a> is a specification developed by the <a href="http://www.w3.org/2010/webperf">W3C Web Performance working group</a>, with the goal of exposing accurate performance metrics that describe your visitor&#8217;s page load experience (via JavaScript).</p>
<p><a href="https://www.w3.org/TR/navigation-timing/">NavigationTiming (Level 1)</a> is currently a <strong>Recommendation</strong>, which means that browser vendors are encouraged to implement it, and it has been shipped in all major browsers.</p>
<p><a href="https://www.w3.org/TR/navigation-timing-2/">NavigationTiming (Level 2)</a> is a <strong>Working Draft</strong> and adds additional features like content sizes and other new data.  It is still a work-in-progress, but many browsers already support it.</p>
<p>As of May 2021, <a href="https://caniuse.com/#feat=nav-timing">97.9% of the world-wide browser market-share</a> supports NavigationTiming (Level 1).</p>
<p>Let&#8217;s take a deep-dive into NavigationTiming!</p>
<p><a name="navtiming-how-was-it-done-before"></a></p>
<h2>How it was done before?</h2>
<p>NavigationTiming exposes performance metrics to JavaScript that were never available in older browsers, such as your page&#8217;s network timings and breakdown.  Prior to NavigationTiming, you could not measure your page&#8217;s DNS, TCP, request or response times because all of those phases occurred before your application (JavaScript) started up, and the browser did not expose them.</p>
<p>Before NavigationTiming was available, you could still estimate some performance metrics, such as how long it took for your page&#8217;s static resources to download.  To do this, you can hook into the browser&#8217;s <code>onload</code> event, which is fired once all of the static resources on your page (such as JavaScript, CSS, IMGs and IFRAMES) have been downloaded.</p>
<p>Here&#8217;s sample (though not very accurate) code:</p>
<pre><code>&lt;html&gt;&lt;head&gt;&lt;script&gt;
var start = new Date().getTime();

function onLoad {
  var pageLoadTime = (new Date().getTime()) - start;
}

body.addEventListener(&#039;load&#039;, onLoad, false);
&lt;/script&gt;&lt;/head&gt;&lt;/html&gt;</code></pre>
<p><a name="navtiming-whats-wrong-with-this"></a></p>
<h2>What&#8217;s wrong with this?</h2>
<p>First, it only measures the time from when the JavaScript runs to when the last static resource is downloaded.</p>
<p>If that&#8217;s all you&#8217;re interested in measuring, that&#8217;s fine, but there&#8217;s a large part of the user&#8217;s experience that you&#8217;ll be blind to.</p>
<p>Let&#8217;s review the main phases that the browser goes through when fetching your HTML:</p>
<ol>
<li>DNS resolve: Look up the domain name to find what IP address to connect to</li>
<li>TCP connect: Connect to your server on port 80 (HTTP) or 443 (HTTPS) via TCP</li>
<li>Request: Send a HTTP request, with headers and cookies</li>
<li>Response: Wait for the server to start sending the content (back-end time)</li>
</ol>
<p>It&#8217;s only after Phase 4 (Response) is complete that your HTML is parsed and your JavaScript can run.</p>
<p>Phase 1-4 timings will vary depending on the network.  One visitor might fetch your content in 100 ms while it might take another user, on a slower connection, 5,000 ms before they see your content.  That delay translates into a painful user-experience.</p>
<p>Thus if you&#8217;re only monitoring your application from JavaScript in the <code>&lt;HEAD&gt;</code> to the <code>onload</code> (as in the snippet above), you are blind to a large part of the overall experience.</p>
<p>So the primitive approach above has several downsides:</p>
<ul>
<li>It only measures the time from when the JavaScript runs to when the last static resource is downloaded</li>
<li>It misses the initial DNS lookup, TCP connection and HTTP request phases</li>
<li><code>Date().getTime()</code> is not reliable</li>
</ul>
<p><a name="navtiming-interlude-domhighrestimestamp"></a></p>
<h2>Interlude &#8211; DOMHighResTimeStamp</h2>
<p>What about #3?  Why is <code>Date.getTime()</code> (or <code>Date.now()</code> or <code>+(new Date)</code>) not reliable?</p>
<p>Let&#8217;s talk about another modern browser feature, <a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code></a>, aka <code>performance.now()</code>.</p>
<p><code>DOMHighResTimeStamp</code> is a new data type for performance interfaces.  In JavaScript, it&#8217;s typed as a regular <code>number</code> primitive, but anything that exposes a <code>DOMHighResTimeStamp</code> is following several conventions.</p>
<p>Notably, <code>DOMHighResTimeStamp</code> is a <strong>monotonically non-decreasing timestamp with an epoch of <code>performance.timeOrigin</code> and sub-millisecond resolution</strong>.  It is used by several W3C webperf performance specs, and can always be queried via <code>window.performance.now()</code>;</p>
<p><a name="navtiming-why-not-date-object"></a></p>
<h2>Why not just use the <code>Date</code> object?</h2>
<p><code>DOMHighResTimeStamp</code> helps solve three shortcomings of <code>Date</code>.  Let&#8217;s break its definition down:</p>
<ul>
<li><strong>monotonically non-decreasing</strong> means that every time you fetch a <code>DOMHighResTimeStamp</code>, its&#8217; value will always be at least the same as when you accessed it last.  It will never decrease.</li>
<li><strong>timestamp with an epoch of <code>performance.timeOrigin</code></strong> means it&#8217;s value is a timestamp, whose basis (start) is <code>window.performance.timeOrigin</code>.  Thus a <code>DOMHighResTimeStamp</code> of <code>10</code> means it&#8217;s 10 milliseconds after time time given by <code>performance.timeOrigin</code></li>
<li><strong>sub-millisecond resolution</strong> means the value has the resolution of at least a millisecond.  In practice, <code>DOMHighResTimeStamp</code>s will be a <code>number</code> with the milliseconds as whole-numbers and fractions of a millisecond represented after the decimal.  For example, <code>1.5</code> means 1500 microseconds, while <code>100.123</code> means 100 milliseconds and 123 microseconds.</li>
</ul>
<p>Each of these points addresses a shortcoming of the <code>Date</code> object.  First and foremost, <em>monotonically non-decreasing</em> fixes a subtle issue with the Date object that you may not know exists.  The problem is that <code>Date</code> simply exposes the value of your end-user&#8217;s clock, according to the operating system.  While the majority of the time this is OK, the system clock can be influenced by outside events, even in the middle of when your app is running.</p>
<p>For example, when the user changes their clock, or an atomic clock service adjusts it, or daylight-savings kicks in, the system clock may jump forward, or even go backwards!</p>
<p>So imagine you&#8217;re performance-profiling your application by keeping track of the start and end timestamps of some event via the <code>Date</code> object.  You track the start time&#8230; and then your end-users atomic clock kicks in and adjusts the time forward an hour&#8230; and now, from JavaScript <code>Date</code>&#8216;s point of view, it seems like your application just took an hour to do a simple task.  </p>
<p>This can even lead to problems when doing statistical analysis of your performance data.  Imagine if your monitoring tool is taking the <em>mean</em> value of operational times and one of your users&#8217; clocks jumped forward 10 years.  That outlier, while &quot;true&quot; from the point of view of <code>Date</code>, will skew the rest of your data significantly.</p>
<p><code>DOMHighResTimeStamp</code> addresses this issue by guaranteeing it is <em>monotonically non-decreasing</em>.  Every time you access <code>performance.now()</code>, you are guaranteed it will be at least equal to, if not greater than, the last time you accessed it.</p>
<p>You should&#8217;t mix <code>Date</code> timestamps (which are <a href="http://en.wikipedia.org/wiki/Unix_time">Unix epoch</a> based, so you get sample times like <code>1430700428519</code>) with <code>DOMHighResTimeStamp</code>s.  If the user&#8217;s clock changes, and you mix both <code>Date</code> and <code>DOMHighResTimeStamp</code>s, the former could be wildly different from the later.</p>
<p>To help enforce this, <code>DOMHighResTimeStamp</code> is not Unix epoch based.  Instead, its epoch is <code>window.performance.timeOrigin</code> (more details of which are below).  Since it has sub-millisecond resolution, this means that the values that you get from it are the number of milliseconds since the page load started.  As a benefit, this makes them easier to read than <code>Date</code> timestamps, since they&#8217;re relatively small and you don&#8217;t need to do <code>(now - startTime)</code> math to know when something started running.</p>
<p><code>DOMHighResTimeStamp</code> is available in <a href="http://caniuse.com/#feat=high-resolution-time">most modern browsers</a>, including Internet Explorer 10+, Edge, Firefox 15+, Chrome 20+, Safari 8+ and Android 4.4+.  If you want to be able to always get timestamps via <code>window.performance.now()</code>, you can use a <a href="https://gist.github.com/paulirish/5438650">polyfill</a>.  Note these polyfills will be millisecond-resolution timestamps with a epoch of &quot;something&quot; in unsupported browsers, since <em>monotonically non-decreasing</em> can&#8217;t be guaranteed and <em>sub-millisecond</em> isn&#8217;t available unless the browser supports it.</p>
<p>As a summary:</p>
<table>
<thead>
<tr>
<th></th>
<th>Date</th>
<th><a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code></a></th>
</tr>
</thead>
<tbody>
<tr>
<td>Accessed via</td>
<td><code>Date().getTime()</code></td>
<td><code>performance.now()</code></td>
</tr>
<tr>
<td>Resolution</td>
<td>millisecond</td>
<td>sub-millisecond</td>
</tr>
<tr>
<td>Start</td>
<td>Unix epoch</td>
<td><code>performance.timeOrigin</code></td>
</tr>
<tr>
<td>Monotonically Non-decreasing</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Affected by user&#8217;s clock</td>
<td>Yes</td>
<td>No</td>
</tr>
<tr>
<td>Example</td>
<td><code>1420147524606</code></td>
<td><code>3392.275999998674</code></td>
</tr>
</tbody>
</table>
<p><a name="navtiming-accessing-navigationtiming-data"></a></p>
<h2>Accessing NavigationTiming Data</h2>
<p>So, how do you access NavigationTiming data?</p>
<p>The simplest (and now deprecated) method is that all of the performance metrics from NavigationTiming are available underneath the <code>window.performance</code> DOM object.  See the <a href="#navigationtiming2">NavigationTiming2</a> section for a more modern way of accessing this data.</p>
<p>NavigationTiming&#8217;s metrics are primarily available underneath <code>window.performance.navigation</code> and <code>window.performance.timing</code>.  The former provides performance characteristics (such as the type of navigation, or the number of redirects taken to get to the current page) while the latter exposes performance metrics (timestamps).</p>
<p>Here&#8217;s the <a href="http://www.w3.org/TR/WebIDL/">WebIDL</a> (definition) of the Level 1 interfaces (see the <a href="#navtiming-navigationtiming2">NavigationTiming2</a> section below for details on accessing the new data)</p>
<p><a href="http://www.w3.org/TR/navigation-timing/#sec-navigation-info-interface"><code>window.performance.navigation</code></a>:</p>
<pre><code>interface PerformanceNavigation {
  const unsigned short TYPE_NAVIGATE = 0;
  const unsigned short TYPE_RELOAD = 1;
  const unsigned short TYPE_BACK_FORWARD = 2;
  const unsigned short TYPE_RESERVED = 255;
  readonly attribute unsigned short type;
  readonly attribute unsigned short redirectCount;
};</code></pre>
<p><a href="http://www.w3.org/TR/navigation-timing/#sec-navigation-timing-interface"><code>window.performance.timing</code></a>:</p>
<pre><code>interface PerformanceTiming {
    readonly attribute unsigned long long navigationStart;
    readonly attribute unsigned long long unloadEventStart;
    readonly attribute unsigned long long unloadEventEnd;
    readonly attribute unsigned long long redirectStart;
    readonly attribute unsigned long long redirectEnd;
    readonly attribute unsigned long long fetchStart;
    readonly attribute unsigned long long domainLookupStart;
    readonly attribute unsigned long long domainLookupEnd;
    readonly attribute unsigned long long connectStart;
    readonly attribute unsigned long long connectEnd;
    readonly attribute unsigned long long secureConnectionStart;
    readonly attribute unsigned long long requestStart;
    readonly attribute unsigned long long responseStart;
    readonly attribute unsigned long long responseEnd;
    readonly attribute unsigned long long domLoading;
    readonly attribute unsigned long long domInteractive;
    readonly attribute unsigned long long domContentLoadedEventStart;
    readonly attribute unsigned long long domContentLoadedEventEnd;
    readonly attribute unsigned long long domComplete;
    readonly attribute unsigned long long loadEventStart;
    readonly attribute unsigned long long loadEventEnd;
};</code></pre>
<p><a name="navtiming-timeline"></a></p>
<h2>The NavigationTiming Timeline</h2>
<p>Each of the timestamps above corresponds with events in the timeline below:</p>
<p><a href="http://www.w3.org/TR/navigation-timing/"><img src="https://o.nicj.net/wp-content/uploads/2021/05/timestamp-diagram.svg" alt="NavigationTiming timeline" /></a></p>
<p>Note that each of the timestamps are <a href="http://en.wikipedia.org/wiki/Unix_time">Unix epoch</a>-based, instead of being <code>performance.timeOrigin</code>-based like <code>DOMHighResTimeStamp</code>s.  This has been addressed in <a href="http://www.w3.org/TR/navigation-timing-2/">NavigationTiming2</a>.</p>
<p>The entire process starts at <code>timing.navigationStart</code> (which should be the same as <code>performance.timeOrigin</code>).  This is when your end-user started the navigation.  They might have clicked on a link, or hit reload in your browser.  The <code>navigation.type</code> property tells you what type of page-load it was: a regular navigation (link- or bookmark- click) (<code>TYPE_NAVIGATE = 0</code>), a reload (<code>TYPE_RELOAD = 1</code>), or a back-forward navigation (<code>TYPE_BACK_FORWARD = 2</code>).  Each of these types of navigations will have different performance characteristics.</p>
<p>Around this time, the browser will also start to unload the previous page.  If the previous page is the same origin (domain) as the current page, the timestamps of that document&#8217;s <code>onunload</code> event (start and end) will be filled in as <code>timing.unloadEventStart</code> and <code>timing.unloadEventEnd</code>.  If the previous page was on another origin (or there was no previous page), these timestamps will be <code>0</code>.</p>
<p>Next, in some cases, your site may go through one or more HTTP redirects before it reaches the final destination.  <code>navigation.redirectCount</code> gives you an important insight into how many hops it took for your visitor to reach your page.  <a href="http://en.wikipedia.org/wiki/HTTP_301">301</a> and <a href="http://en.wikipedia.org/wiki/HTTP_302">302</a> redirects each take time, so for performance reasons you should reduce the number of redirects to reach your content to 0 or 1.  Unfortunately, due to security concerns, you do not have access to the actual URLs that redirected to this page, and it is entirely possibly that a third-party site (not under your control) initiated the redirect.  The difference between <code>timing.redirectStart</code> and <code>timing.redirectEnd</code> encompasses all of the redirects.  If these values are <code>0</code>, it means that either there were no redirects, or <a href="https://github.com/w3c/navigation-timing/issues/73#issuecomment-311952046">at least one of the redirects was from a different origin</a>.</p>
<p><code>fetchStart</code> is the next timestamp, and indicates the timestamp for the start of the fetch of the current page.  If there were no redirects when loading the current page, this value should equal <code>navigationStart</code>.  Otherwise, it should equal <code>redirectEnd</code>.</p>
<p>Next, the browser goes through the networking phases required to fetch HTML over HTTP.  First the domain is resolved (<code>domainLookupStart</code> and <code>domainLookupEnd</code>), then a TCP connection is initiated (<code>connectStart</code> and <code>connectEnd</code>).  Once connected, a HTTP request (with headers and cookies) is sent (<code>requestStart</code>).  Once data starts coming back from the server, <code>responseStart</code> is filled, and is ended when the last byte from the server is read at <code>responseEnd</code>.</p>
<p>Note that the only phase without an <code>end</code> timestamp is <code>requestEnd</code>, as the browser does not have insight into when the server received the response.</p>
<p>Any of the above phases (DNS, TCP, request or response) might not take any time, such as when DNS was already resolved, a TCP connection is re-used or when content is served from disk.  In this case, the timestamps should not be <code>0</code>, but should reflect the timestamp that the phase started and ended, even if the duration is <code>0</code>.  For example, if <code>fetchStart</code> is at <code>1000</code> and a TCP connection is reused, <code>domainLookupStart</code>, <code>domainLookupEnd</code>, <code>connectStart</code> and <code>connectEnd</code> should all be <code>1000</code> as well.</p>
<p><code>secureConnectionStart</code> is an optional timestamp that is only filled in if it the page was loaded over a secure connection.  In that case, it represents the time that the SSL/TLS handshake started.</p>
<p>After <code>responseStart</code>, there are several timestamps that represent phases of the DOM&#8217;s lifecycle.  These are <code>domLoading</code>, <code>domInteractive</code>, <code>domContentLoadedEventStart</code>, <code>domContentLoadedEventEnd</code> and <code>domComplete</code>. </p>
<p><code>domLoading</code>, <code>domInteractive</code> and <code>domComplete</code> correspond to when the Document&#8217;s <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState"><code>readyState</code></a> are set to the corresponding <code>loading</code>, <code>interactive</code> and <code>complete</code> states.  </p>
<p><code>domContentLoadedEventStart</code> and <code>domContentLoadedEventEnd</code> correspond to when the <a href="https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded"><code>DOMContentLoaded</code></a> event fires on the <code>document</code> and when it has completed running.</p>
<p>Finally, once the body&#8217;s <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload"><code>onload</code></a> event fires, <code>loadEventStart</code> is filled in.  Once all of the <code>onload</code> handlers are complete, <code>loadEventEnd</code> is filled in.  <strong>Note</strong> this means if you&#8217;re querying <code>window.performance.timing</code> from within the <code>onload</code> event, <code>loadEventEnd</code> will be <code>0</code>.  You could work around this by querying the timestamps from a <code>setTimeout(..., 10)</code> fired from within the <code>onload</code> event, as in the code example below.</p>
<p><strong>Note</strong>: There is a bug in some browsers where they are reporting <code>0</code> for some timestamps.  This is a bug, as all same-origin timestamps should be filled in, but if you&#8217;re consuming this data, you may have to adjust for this.</p>
<p>Browser vendors are also free to ad their own additional timestamps to <code>window.performance.timing</code>.   Here is the only currently known vendor-prefixed timestamp available:</p>
<ul>
<li><a href="https://msdn.microsoft.com/en-us/library/ff974719%28v=vs.85%29.aspx"><code>msFirstPaint</code></a> &#8211; Internet Explorer 9+ only, this event corresponds to when the first paint occurred within the document.  It makes no guarantee about <em>what</em> content was painted &#8212; in fact, the paint could be just the &quot;white out&quot; prior to other content being displayed.  Do not rely on this event to determine when the user started seeing actual content.</li>
</ul>
<p><a name="navtiming-example-data"></a></p>
<h2>Example data</h2>
<p>Here&#8217;s sample data from a page load:</p>
<pre><code>// window.performance.navigation
redirectCount: 0
type: 0

// window.performance.timing
navigationStart: 1432762408327,
unloadEventEnd: 0,
unloadEventStart: 0,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 1432762408648,
connectEnd: 1432762408886,
secureConnectionStart: 1432762408777,
connectStart: 1432762408688,
domainLookupStart: 1432762408660,
domainLookupEnd: 1432762408688,
requestStart: 1432762408886,
responseStart: 1432762409141,
responseEnd: 1432762409229,
domComplete: 1432762411136,
domLoading: 1432762409147,
domInteractive: 1432762410129,
domInteractive: 1432762410129,
domContentLoadedEventStart: 1432762410164,
domContentLoadedEventEnd: 1432762410263,
loadEventEnd: 1432762411140,
loadEventStart: 1432762411136</code></pre>
<p><a name="navtiming-how-to-use"></a></p>
<h2>How to Use</h2>
<p>All of the metrics exposed on the <code>window.performance</code> interface are available to your application via JavaScript.  Here&#8217;s example code for gathering durations of the different phases of the main page load experience:</p>
<pre><code>function onLoad() {
  if (&#039;performance&#039; in window &amp;&amp; &#039;timing&#039; in window.performance) {
    // gather after all other onload handlers have fired
    setTimeout(function() {
      var t = window.performance.timing;
      var ntData = {
        redirect: t.redirectEnd - t.redirectStart,
        dns: t.domainLookupEnd - t.domainLookupStart,
        connect: t.connectEnd - t.connectStart,
        ssl: t.secureConnectionStart ? (t.connectEnd - secureConnectionStart) : 0,
        request: t.responseStart - t.requestStart,
        response: t.responseEnd - t.responseStart,
        dom: t.loadEventStart - t.responseEnd,
        total: t.loadEventEnd - t.navigationStart
      };
    }, 0);
  }
}</code></pre>
<p><a name="navtiming-navigationtiming2"></a></p>
<h2>NavigationTiming2</h2>
<p>Currently a Working Draft, <a href="http://www.w3.org/TR/navigation-timing-2/">NavigationTiming (Level 2)</a> builds on top of NavigationTiming:</p>
<ul>
<li>Now based on Resource Timing Level 2</li>
<li>Support for the <a href="http://www.w3.org/TR/performance-timeline/">Performance Timeline</a> and via a <code>PerformanceObserver</code></li>
<li>Support for <a href="http://www.w3.org/TR/hr-time/">High Resolution Time</a></li>
<li>Adds the next hop protocol</li>
<li>Adds transfer and content sizes</li>
<li>Adds ServerTiming</li>
<li>Add ServiceWorker information</li>
</ul>
<p>The Level 1 interface, <code>window.performance.timing</code>, will not been changed for Level 2.  Level 2 features are <em>not</em> being added to that interface, primarily because the timestamps under <code>window.performance.timing</code> are <strong>not</strong> <code>DOMHighResTimeStamp</code> timestamps (such as <code>100.123</code>), but Unix-epoch timestamps (e.g. <code>1420147524606</code>). </p>
<p>Instead, there&#8217;s a new <code>navigation</code> type available from the PerformanceTimeline that contains all of the Level 2 data.</p>
<p>Here&#8217;s an example of how to get the new NavigationTiming data:</p>
<pre><code class="language-javascript">if (&#039;performance&#039; in window &amp;&amp;
    window.performance &amp;&amp;
    typeof window.performance.getEntriesByType === &#039;function&#039;) {
    var ntData = window.performance.getEntriesByType(&quot;navigation&quot;)[0];
}</code></pre>
<p>Example data:</p>
<pre><code> {
    &quot;name&quot;: &quot;https://website.com/&quot;,
    &quot;entryType&quot;: &quot;navigation&quot;,
    &quot;startTime&quot;: 0,
    &quot;duration&quot;: 1568.5999999986961,
    &quot;initiatorType&quot;: &quot;navigation&quot;,
    &quot;nextHopProtocol&quot;: &quot;h2&quot;,
    &quot;workerStart&quot;: 0,
    &quot;redirectStart&quot;: 0,
    &quot;redirectEnd&quot;: 0,
    &quot;fetchStart&quot;: 3.600000054575503,
    &quot;domainLookupStart&quot;: 3.600000054575503,
    &quot;domainLookupEnd&quot;: 3.600000054575503,
    &quot;connectStart&quot;: 3.600000054575503,
    &quot;connectEnd&quot;: 3.600000054575503,
    &quot;secureConnectionStart&quot;: 0,
    &quot;requestStart&quot;: 9.700000053271651,
    &quot;responseStart&quot;: 188.50000004749745,
    &quot;responseEnd&quot;: 194.2999999737367,
    &quot;transferSize&quot;: 7534,
    &quot;encodedBodySize&quot;: 7287,
    &quot;decodedBodySize&quot;: 32989,
    &quot;serverTiming&quot;: [],
    &quot;unloadEventStart&quot;: 194.90000000223517,
    &quot;unloadEventEnd&quot;: 195.10000001173466,
    &quot;domInteractive&quot;: 423.9999999990687,
    &quot;domContentLoadedEventStart&quot;: 423.9999999990687,
    &quot;domContentLoadedEventEnd&quot;: 520.9000000031665,
    &quot;domComplete&quot;: 1562.900000018999,
    &quot;loadEventStart&quot;: 1562.900000018999,
    &quot;loadEventEnd&quot;: 1568.5999999986961,
    &quot;type&quot;: &quot;navigate&quot;,
    &quot;redirectCount&quot;: 0
}</code></pre>
<p>As you can see, all of the fields from NavigationTiming Level 1 are there (except <code>domLoading</code> which was <a href="https://w3c.github.io/navigation-timing/#the-performancetiming-interface">removed</a>), but they&#8217;re all <code>DOMHighResTimeStamp</code> timestamps now.</p>
<p>In addition, there are new Level 2 fields:</p>
<ul>
<li><code>nextHopProtocol</code>: <a href="https://tools.ietf.org/html/rfc7301#section-6">ALPN Protocol ID</a> such as <code>http/0.9</code> <code>http/1.0</code> <code>http/1.1</code> <code>h2</code> <code>hq</code> <code>spdy/3</code> (ResourceTiming Level 2)</li>
<li><code>workerStart</code> is the time immediately before the active Service Worker received the <code>fetch</code> event, if a ServiceWorker is installed</li>
<li><code>transferSize</code>: Bytes transferred for the HTTP response header and content body</li>
<li><code>decodedBodySize</code>: Size of the body after removing any applied content-codings</li>
<li><code>encodedBodySize</code>: Size of the body after prior to removing any applied content-codings</li>
<li><code>serverTiming</code>: ServerTiming data</li>
</ul>
<p><a name="navtiming-service-workers"></a></p>
<h3>Service Workers</h3>
<p>While NavigationTiming2 added a timestamp for <code>workerStart</code>, if you have a Service Worker active for your domain, there are some caveats to be aware of:</p>
<ul>
<li><code>workerStart</code> is not <a href="https://github.com/w3c/navigation-timing/pull/131">yet</a> part of the timeline diagram above, and it&#8217;s exact meaning is <a href="https://github.com/w3c/navigation-timing/pull/131">still under discussion</a></li>
<li>The size attributes (<code>transferSize</code> <code>decodedBodySize</code> <code>encodedBodySize</code>) <a href="https://github.com/w3c/navigation-timing/issues/124">are under-specified</a>, and may be <code>0</code> if the root HTML was delivered by the Service Worker</li>
<li>The <code>workerStart</code> timestamp may not be reliable if there were any <a href="https://github.com/w3c/navigation-timing/pull/131">redirects during the navigation</a></li>
</ul>
<p><a name="navtiming-using-navigationtiming-data"></a></p>
<h2>Using NavigationTiming Data</h2>
<p>With access to all of this performance data, you are free to do with it whatever you want.  You could analyze it on the client, notifying you when there are problems.  You could send 100% of the data to your back-end analytics server for later analysis.  Or, you could hook the data into a DIY or commercial RUM solution that does this for you automatically.</p>
<p>Let&#8217;s explore all of these options:</p>
<p><a name="navtiming-diy"></a></p>
<h2>DIY</h2>
<p>There are many DIY / Open Source solutions out there that gather and analyze data exposed by NavigationTiming.</p>
<p>Here are some DIY ideas for what you can do with NavigationTiming:</p>
<ul>
<li>Gather the <code>performance.timing</code> metrics on your own and alert you if they are over a certain threshold (warning: this could be noisy)</li>
<li>Gather the <code>performance.timing</code> metrics on your own and XHR every page-load&#8217;s metrics to your backend for analysis</li>
<li>Watch for any pages that resulted in one or more redirects via <code>performance.navigation.redirectCount</code></li>
<li>Determine what percent of users go back-and-forth on your site via <code>performance.navigation.type</code></li>
<li>Accurately monitor your app&#8217;s bootstrap time that runs in the body&#8217;s <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload"><code>onload</code></a> event via <code>(loadEventEnd - loadEventStart)</code></li>
<li>Monitor the performance of your DNS servers</li>
<li>Measure DOM event timestamps without adding event listeners</li>
</ul>
<p><a name="navtiming-open-source"></a></p>
<h2>Open-Source</h2>
<p>There are some great projects out there that consume NavigationTiming information.</p>
<p><a href="https://github.com/lognormal/boomerang">Boomerang</a>, an open-source library developed by Philip Tellis, had a method for tracking performance metrics before NavigationTiming was supported in modern browsers.  Today, it incorporates NavigationTiming data if available.  It does all of the hard work of gathering various performance metrics, and lets you beacon (send) the data to a server of your choosing.  (I am a contributor to the project).</p>
<p>To compliment Boomerang, there are a couple open-source servers that receive Boomerang data, such as <a href="http://cruft.io/posts/introducing-boomcatch/"><strong>Boomcatch</strong></a> and <a href="https://github.com/andreas-marschke/boomerang-express"><strong>BoomerangExpress</strong></a>.  In both cases, you&#8217;ll still be left to analyze the data on your own:</p>
<p><a href="https://github.com/andreas-marschke/boomerang-express"><img src="https://o.nicj.net/wp-content/uploads/2015/05/boomerangexpress.png" alt="BoomerangExpress" /></a></p>
<p>To view NavigationTiming data for any site you visit, you can use this <a href="http://kaaes.github.io/timing/"><strong>kaaes bookmarklet</strong></a>:</p>
<p><a href="http://kaaes.github.io/timing/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/kaaes-bookmarklet.png" alt="kaaes bookmarklet" /></a></p>
<p><a href="http://www.sitespeed.io/"><strong>SiteSpeed.io</strong></a> helps you track your site&#8217;s performance metrics and scores (such as PageSpeed and YSlow):</p>
<p><a href="http://www.sitespeed.io/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/sitespeedio.png" alt="SiteSpeed.io" /></a></p>
<p>Finally, if you&#8217;re already using <a href="https://github.com/piwik/piwik/"><strong>Piwik</strong></a>, there&#8217;s a plugin that gathers NavigationTiming data from your visitors:</p>
<p><code>&quot;generation time&quot; = responseEnd - requestStart</code></p>
<p><a href="https://github.com/piwik/piwik/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/piwik.png" alt="Piwik" /></a></p>
<p><a name="navtiming-commercial-solutions"></a></p>
<h2>Commercial Solutions</h2>
<p>If you don&#8217;t want to build or manage a DIY / Open-Source solution to gather RUM metrics, there are many great commercial services available.</p>
<p><em>Disclaimer: I work at Akamai, on mPulse and Boomerang</em></p>
<p><a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp"><strong>Akamai mPulse</strong></a>, which gathers 100% of your visitor&#8217;s performance data:</p>
<p><a href="https://www.akamai.com/us/en/products/performance/mpulse-real-user-monitoring.jsp"><img src="https://o.nicj.net/wp-content/uploads/2015/05/soasta-mpulse.png" alt="Akamai mPulse" /></a></p>
<p><a href="http://www.google.com/analytics/"><strong>Google Analytics Site Speed</strong></a>:</p>
<p><a href="http://www.google.com/analytics/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/google-analytics.png" alt="Google Analytics Site Speed" /></a></p>
<p><a href="http://newrelic.com/browser-monitoring"><strong>New Relic Browser</strong></a>:</p>
<p><a href="http://newrelic.com/browser-monitoring"><img src="https://o.nicj.net/wp-content/uploads/2015/05/new-relic.jpg" alt="New Relic Browser" /></a></p>
<p><a href="http://www.neustar.biz/"><strong>NeuStar WPM</strong></a>:</p>
<p><a href="http://www.neustar.biz/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/neustar-wpm.jpg" alt="NeuStar WPM" /></a></p>
<p><a href="http://speedcurve.com/"><strong>SpeedCurve</strong></a>:</p>
<p><a href="http://speedcurve.com/"><img src="https://o.nicj.net/wp-content/uploads/2015/05/speedcurve.png" alt="SpeedCurve" /></a></p>
<p>There may be others as well &#8212; please leave a comment if you have experience using another service.</p>
<p><a name="navtiming-availability"></a></p>
<h2>Availability</h2>
<p>NavigationTiming is available in all modern browsers.  According to <a href="http://caniuse.com/#feat=nav-timing">caniuse.com</a> 97.9% of world-wide browser market share supports NavigationTiming, as of May 2021.  This includes Internet Explore 9+, Edge, Firefox 7+, Chrome 6+, Opera 15+, Android Browser 4+, Mac Safari 8+ and iOS Safari 9+.</p>
<p><a href="http://caniuse.com/#feat=nav-timing"><img src="https://o.nicj.net/wp-content/uploads/2021/05/caniuse-navtiming-2021.png" alt="CanIUse NavigationTiming" /></a></p>
<p><a name="navtiming-tips"></a></p>
<h2>Tips</h2>
<p>Some final tips to re-iterate if you want to use NavigationTiming data:</p>
<ul>
<li>Use <code>fetchStart</code> instead of <code>navigationStart</code>, unless you&#8217;re interested in redirects, browser tab initialization time, etc.</li>
<li><code>loadEventEnd</code> will be <code>0</code> until <em>after</em> the body&#8217;s <code>onload</code> event has finished (so you can&#8217;t measure it in the <code>load</code> event itself).</li>
<li>We don&#8217;t have an accurate way to measure the &quot;request time&quot;, as <code>requestEnd</code> is invisible to us (the server sees it).</li>
<li><code>secureConnectionStart</code> isn&#8217;t available in Internet Explorer, and will be <code>0</code> in other browsers unless on a HTTPS link.</li>
<li>If your site is the home-page for a user, you may see some <code>0</code> timestamps.  Timestamps up through the <code>responseEnd</code> event may be <code>0</code> duration because some browsers speculatively pre-fetch home pages (and don&#8217;t report the correct timings).</li>
<li>If you&#8217;re going to be <a href="https://nicj.net/beaconing-in-practice/#beaconing-reliability-avoiding-abandons">beaconing data to your back-end for analysis</a>, if possible, send the data immediately after the body&#8217;s <code>onload</code> event versus waiting for <code>onbeforeunload</code>.  <code>onbeforeunload</code> isn&#8217;t 100% reliable, and may not fire in some browsers (such as iOS Safari).</li>
<li>Single-Page Apps: You&#8217;ll need a different solution for &quot;soft&quot; or &quot;in-page&quot; navigations (<a href="https://soasta.github.io/boomerang/BOOMR.plugins.SPA.html">Boomerang</a> has SPA support).</li>
</ul>
<p><a name="navtiming-browser-bugs"></a></p>
<h2>Browser Bugs</h2>
<p>NavigationTiming data may not be perfect, and in some cases, incorrect due to browser bugs.  Make sure to validate your data before you use it.</p>
<p>We&#8217;ve seen the following problems in the wild:</p>
<ul>
<li><em>Safari 8/9</em>: <code>requestStart</code> and <code>responseStart</code> might be <em>less</em> than <code>navigationStart</code> and <code>fetchStart</code></li>
<li><em>Safari 8/9</em> and <em>Chrome</em> (as recent as 56): <code>requestStart</code> and <code>responseStart</code> might be <em>less</em> than <code>fetchStart</code>, <code>connect*</code> and <code>domainLookup*</code></li>
<li><em>Chrome</em> (as recent as 56): <code>requestStart</code> is equal to <code>navigationStart</code> but <em>less</em> than <code>fetchStart</code>, <code>connect*</code> and <code>domainLookup*</code></li>
<li><em>Firefox</em>: Reporting <code>0</code> for timestamps that should always be filled in, such as <code>domainLookup*</code>, <code>connect*</code> and <code>requestStart</code>.</li>
<li><em>Chrome</em>: Some timestamps are double what they should be (e.g. if &quot;now&quot; is <code>1524102861420</code>, we see timestamps around <code>3048205722840</code>, year 2066)</li>
<li><em>Chrome</em>: When the page has redirects, the <code>responseStart</code> is less than <code>redirectEnd</code> and <code>fetchStart</code></li>
<li><em>Firefox</em>: The NavigationTiming of the iframe (window.frames[0].performance.timing) does not include redirect counts or redirect times, and many other timestamps are <code>0</code></li>
</ul>
<p>If you&#8217;re analyzing NavigationTiming data, you should ensure that all timestamps increment according to the timeline.  If not, you should probably question all of the timestamps and discard.</p>
<p>Some known bug reports:</p>
<ul>
<li><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=127644">Chrome: performance.timing.requestStart before performance.timing.navigationStart</a></li>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=168055">Safari: requestStart, responseStart before navigationStart</a></li>
<li><a href="https://bugs.webkit.org/show_bug.cgi?id=168057">Safari: navigationStart and DOM timings in Unix epoch, other timings 0-based for Back-Forward navigations</a></li>
</ul>
<p><a name="navtiming-conclusion"></a></p>
<h2>Conclusion</h2>
<p>NavigationTiming exposes valuable and accurate performance metrics in modern browsers.  If you&#8217;re interested in <a href="http://nicj.net/measuring-the-performance-of-your-web-apps/">measuring and monitoring the performance of your web app</a>, NavigationTiming data is the first place you should look.</p>
<p>Next up: Interested in capturing the same network timings for all of the sub-resources on your page, such as images, JavaScript, and CSS?  <a href="http://nicj.net/resourcetiming-in-practice/">ResourceTiming</a> is what you want.</p>
<p>Other articles in this series:</p>
<ul>
<li><a href="http://nicj.net/resourcetiming-in-practice/">ResourceTiming in Practice</a></li>
<li><a href="http://nicj.net/usertiming-in-practice/">UserTiming in Practice</a></li>
</ul>
<p>More resources:</p>
<ul>
<li><a href="http://www.w3.org/TR/navigation-timing/">NavigationTiming W3C specification</a></li>
<li><a href="http://www.w3.org/TR/navigation-timing-2/">NavigationTiming2 W3C specification</a></li>
<li><a href="http://www.w3.org/TR/hr-time/"><code>DOMHighResTimeStamp</code> W3C specification</a></li>
</ul>
<p><a name="navtiming-updates"></a></p>
<h2>Updates</h2>
<ul>
<li>2018-04:
<ul>
<li>Updated caniuse.com market share</li>
<li>Updated NavigationTiming2 information, usage, fields</li>
<li>Added more browser bugs that we&#8217;ve found</li>
</ul>
</li>
<li>2021-05:
<ul>
<li>Updated caniuse.com market share</li>
<li>Added a Service Workers section</li>
<li>Replaced usage of <code>performance.timing.navigationStart</code> as a time origin with <code>performance.timeOrigin</code></li>
<li>Minor grammar updates</li>
<li>Added a Table of Contents</li>
</ul>
</li>
</ul><p>The post <a href="https://nicj.net/navigationtiming-in-practice/" target="_blank">NavigationTiming in Practice</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/navigationtiming-in-practice/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
		<item>
		<title>Measuring the Performance of Your Web Apps</title>
		<link>https://nicj.net/measuring-the-performance-of-your-web-apps/</link>
					<comments>https://nicj.net/measuring-the-performance-of-your-web-apps/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Mon, 25 May 2015 13:54:36 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1621</guid>

					<description><![CDATA[<p>You know that performance matters, right? Just a few seconds slower and your site could be turning away thousands (or millions) of visitors. Don&#8217;t take my word for it: there are plenty of case studies, articles, findings, presentations, charts and more showing just how important it is to make your site load quickly. Google is [&#8230;]</p>
<p>The post <a href="https://nicj.net/measuring-the-performance-of-your-web-apps/" target="_blank">Measuring the Performance of Your Web Apps</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>You know that performance matters, right?</p>
<p>Just a few seconds slower and your site could be turning away thousands (or millions) of visitors.  Don&#8217;t take my word for it: there are plenty of <a href="http://www.webperformancetoday.com/2011/11/23/case-study-slow-page-load-mobile-business-metrics/">case</a> <a href="http://www.prnewswire.com/news-releases/customers-are-won-or-lost-in-one-second-finds-new-aberdeen-report-65399152.html">studies</a>, <a href="http://www.soasta.com/the-performance-beacon/synthetic-real-user-measurement-monitoring-rum/">articles</a>, <a href="http://www.webperformancetoday.com/2015/04/15/new-findings-state-union-ecommerce-page-speed-web-performance-spring-2015/">findings</a>, <a href="http://www.slideshare.net/cliffcrocker/velocity-ny-how-to-measure-revenue-in-milliseconds">presentations</a>, <a href="https://blog.kissmetrics.com/loading-time/?wide=1">charts</a> and <a href="http://radar.oreilly.com/2009/07/velocity-making-your-site-fast.html">more</a> showing just how important it is to make your site load quickly.  Google is even starting to <a href="http://searchengineland.com/google-testing-red-slow-label-search-results-slower-sites-215483">shame-label</a> slow sites.  You don&#8217;t want to be that guy.</p>
<p>So how do you monitor and measure the performance of your web apps?</p>
<p>The performance of any system can be measured from several different points of view.  Let&#8217;s take a brief look at three of the most common performance viewpoints for a web app: from the eyes of the <strong>developer</strong>, the <strong>server</strong> and the <strong>end-user</strong>.</p>
<p><em>This is the beginning of a series of articles that will expand upon the content given during my talk &quot;<a href="http://www.slideshare.net/nicjansma/make-itfastcodemash2015">Make it Fast: Using Modern Brower APIs to Monitor and Improve the Performance of your Web Applications</a>&quot; at <a href="http://www.codemash.org/">CodeMash 2015</a>.</em></p>
<h2>Developer</h2>
<p>The developer&#8217;s machine is the first line of defense in ensuring your web application is performing as intended. While developing your app, you are probably building, testing and addressing performance issues as you see them.</p>
<p>In addition to simply using your app, there are many tools you can use to measure how it&#8217;s performing.  Some of my favorites are:</p>
<ul>
<li>
<strong>Profiling</strong>, by using timestamps, <a href="http://xdebug.org/">XDebug</a>, <a href="http://xhprof.io/">XHProf</a>, and <a href="https://www.jetbrains.com/profiler/">JetBrains dotTrace</a>
</li>
<li>
<strong>Browser developer tools</strong>, in IE, Chrome, Firefox, Opera and Safari</li>
<li>
<strong>Network monitoring</strong>, such as <a href="http://www.telerik.com/fiddler">Fiddler</a>, <a href="https://www.wireshark.org/">WireShark</a> and <a href="http://www.tcpdump.org/">tcpdump</a>
</li>
</ul>
<p>While ensuring everything is performing well on your development machine (which probably has tons of RAM, CPU and a quick connection to your servers) is a good first step, you also need to make sure your app is playing well with other services on your network, such as your web server, database, etc.</p>
<h2>Server</h2>
<p>Monitoring the server(s) that run your infrastructure (such as web, database, and other back-end services) is critical for a performance monitoring strategy. Many resources and tools have been developed to help engineers monitor what their servers are doing. Performance monitoring at the server level is critical for reliability (ensuring your core services are running) and scalability (ensuring your infrastructure is performing at the level you want).</p>
<p>From each of your servers&#8217; points of view, there are several components that you can monitor to have visibility into how your infrastructure is performing.  Some common monitoring and measuring tools are:</p>
<ul>
<li>
<strong>HTTP logs</strong>, such as <a href="http://httpd.apache.org/docs/1.3/logs.html">apache</a>, <a href="http://nginx.com/resources/admin-guide/logging-and-monitoring/">nginx</a>, <a href="http://www.haproxy.org/">haproxy</a>
</li>
<li>
<strong>Server monitoring</strong>, such as <a href="http://en.wikipedia.org/wiki/Top_(software)">top</a>, <a href="http://en.wikipedia.org/wiki/Iostat">iostat</a>, <a href="http://en.wikipedia.org/wiki/Vmstat">vmstat</a>, <a href="http://www.cacti.net/">cacti</a>, <a href="http://en.wikipedia.org/wiki/Multi_Router_Traffic_Grapher">mrtg</a>, <a href="https://www.nagios.org/">nagios</a>, <a href="http://newrelic.com/">New Relic APM</a>
</li>
<li>
<strong>Load testing</strong>, such as <a href="http://en.wikipedia.org/wiki/ApacheBench">ab</a>, <a href="http://jmeter.apache.org/">jmeter</a>, <a href="http://www.soasta.com/products/cloudtest/">SOASTA CloudTest</a>, <a href="http://blazemeter.com/">BlazeMeter</a>, <a href="http://www.hp.com/go/loadrunner">HP Loadrunner</a>
</li>
<li>
<strong>Ops monitoring</strong>, such as <a href="http://aws.amazon.com/cloudwatch/">Amazon CloudWatch</a>, <a href="https://www.datadoghq.com/">Datadog</a>, <a href="http://copperegg.com/">CopperEgg</a>
</li>
</ul>
<p>By putting these tools together, you can get a pretty good sense of how your overall infrastructure is performing.</p>
<h2>End-user</h2>
<p>So you&#8217;ve developed your app, deployed it to production, and have been monitoring your infrastructure closely to ensure all of your servers are performing smoothly.</p>
<p>Everything should be golden, right? Your end-users are having a fantastical experience and every one of them just <em>loves</em> visiting your site.</p>
<p>&#8230; clearly, that&#8217;s probably not the case. The majority of your end-users don&#8217;t surf the web on $3,000 development machines, using the latest cutting-edge browser on a low-latency link from your datacenter. A lot of your users are probably on a low-end tablet, on a cell network, 2,000 miles away from your datacenter.</p>
<p>The experience you&#8217;ve curated while developing your web app on your high-end development machine will probably be the best experience possible. All of your visitors will likely experience something worse, from <em>not-a-noticeable-difference</em> down to <em>can&#8217;t-stand-how-slow-it-is-and-will-never-come-back</em>.</p>
<p>Measuring performance from the server and the developer&#8217;s perspective is not the full story. In the end, <strong>the only thing that really matters is what your visitor sees</strong>, and the experience they have.</p>
<p>Just a few years ago, the web development community didn&#8217;t have a lot of tools available to monitor the performance from their end-users&#8217; perspectives. Sure, you could capture simple JavaScript timestamps within your code:</p>
<pre><code class="js">var startTime = Date.now();
// do stuff
var elaspedTime = Date.now() - startTime;
</code></pre>
<p>You could spread this code throughout your app and listen for browser events such as <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload">onload</a>, but simple timestamps don&#8217;t give a lot of visibility into the performance of your end-users.</p>
<p>In addition, since this style of timestamp/profiling is just JavaScript, you have zero visibility into the browser&#8217;s networking performance and what happened before the browser parsed your HTML and JavaScript.</p>
<h2>W3C Webperf Working Group</h2>
<p>To solve these issues, in 2010 the <a href="http://www.w3.org/">W3C</a> (a standards body in charge of developing web standards such as HTML5, CSS, etc.) formed a new working group with the mission of giving developers the ability to assess and understand the performance characteristics of their web apps.</p>
<p>The <a href="http://www.w3.org/2010/webperf/">W3C webperf working group</a> is an organization whose members include Microsoft, Google, Mozilla, Opera, Facebook, Netflix, SOASTA and more. The working group collaboratively develops standards with the following goals:</p>
<ul>
<li>Expose information that was not previously available</li>
<li>
<p>Give developers the tools they need to make their applications more efficient</p>
</li>
<li>Little to no overhead</li>
<li>Easy to understand APIs</li>
</ul>
<p>Since it&#8217;s inception, the working group has published a number of standards, many of which are available in modern browsers today. Some of these standards are:</p><p>The post <a href="https://nicj.net/measuring-the-performance-of-your-web-apps/" target="_blank">Measuring the Performance of Your Web Apps</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/measuring-the-performance-of-your-web-apps/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>JavaScript Module Patterns</title>
		<link>https://nicj.net/javascript-module-patterns/</link>
					<comments>https://nicj.net/javascript-module-patterns/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 18 Mar 2015 02:22:10 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1609</guid>

					<description><![CDATA[<p>Presented at the Lansing JavaScript Meetup: Slides are available on Slideshare or Github.</p>
<p>The post <a href="https://nicj.net/javascript-module-patterns/" target="_blank">JavaScript Module Patterns</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Presented at the <a href="http://www.meetup.com/Lansing-Javascript-Meetup/events/212175702/">Lansing JavaScript Meetup</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/javascript-modulepatterns"><img loading="lazy" class=" size-large wp-image-1615 alignnone" src="https://o.nicj.net/wp-content/uploads/2015/03/javascript-module-patterns1-600x323.png" alt="javascript-module-patterns" width="600" height="323" /></a></p>
<p>Slides are available on <a href="http://www.slideshare.net/nicjansma/javascript-modulepatterns">Slideshare</a> or <a href="https://github.com/nicjansma/talks">Github</a>.</p><p>The post <a href="https://nicj.net/javascript-module-patterns/" target="_blank">JavaScript Module Patterns</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/javascript-module-patterns/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Make It Fast &#8211; CodeMash 2015</title>
		<link>https://nicj.net/make-it-fast-codemash-2015/</link>
					<comments>https://nicj.net/make-it-fast-codemash-2015/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 08 Jan 2015 19:10:01 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1599</guid>

					<description><![CDATA[<p>Presented at CodeMash 2015:</p>
<p>The post <a href="https://nicj.net/make-it-fast-codemash-2015/" target="_blank">Make It Fast - CodeMash 2015</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Presented at <a href="http://codemash.org">CodeMash 2015</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/make-itfastcodemash2015"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2015/01/make-it-fast-codemash-2015-e1420819129408-600x244.png" alt="Make It Fast - CodeMash 2015" width="600" height="244" class="alignleft size-large wp-image-1600" /></a></p><p>The post <a href="https://nicj.net/make-it-fast-codemash-2015/" target="_blank">Make It Fast - CodeMash 2015</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/make-it-fast-codemash-2015/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Compressing ResourceTiming</title>
		<link>https://nicj.net/compressing-resourcetiming/</link>
					<comments>https://nicj.net/compressing-resourcetiming/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 07 Nov 2014 19:37:37 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1570</guid>

					<description><![CDATA[<p>At SOASTA, we&#8217;re building tools and services to help our customers understand and improve the performance of their websites. Our mPulse product utilizes Real User Monitoring to capture data about page-load performance. For browser-side data collection, mPulse uses Boomerang, which beacons every single page-load experience back to our real time analytics engine. Boomerang utilizes NavigationTiming [&#8230;]</p>
<p>The post <a href="https://nicj.net/compressing-resourcetiming/" target="_blank">Compressing ResourceTiming</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>At <a href="http://soasta.com/">SOASTA</a>, we&#8217;re building tools and services to help our customers understand and improve the performance of their websites. Our <a href="https://www.soasta.com/performance-monitoring/">mPulse</a> product utilizes <a href="http://en.wikipedia.org/wiki/Real_user_monitoring">Real User Monitoring</a> to capture data about page-load performance.</p>
<p>For browser-side data collection, mPulse uses <a href="http://www.lognormal.com/boomerang/doc/">Boomerang</a>, which beacons every single page-load experience back to our real time analytics engine. Boomerang utilizes <a href="http://www.w3.org/TR/navigation-timing/">NavigationTiming</a> when possible to relay accurate performance metrics about the page load, such as the timings of DNS, TCP, SSL and the HTTP response.</p>
<p><a href="http://www.w3.org/TR/resource-timing">ResourceTiming</a> is another important feature in modern browsers that gives JavaScript access to performance metrics about the page&#8217;s components fetched from the network, such as CSS, JavaScript and images. mPulse will soon be releasing a new feature that lets our customers view the complete waterfall of every visitor&#8217;s session, which can be a tremendous help in debugging performance issues.</p>
<p>The challenge with ResourceTiming is that it offers a <em>lot</em> of data if you want to beacon it all back to a server. For each resource, there&#8217;s <a href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming">data on</a>:</p>
<ul>
<li>URL</li>
<li>Initiating element (eg IMG)</li>
<li>Start time</li>
<li>Duration</li>
<li>Plus 11 other timestamps</li>
</ul>
<p>Here&#8217;s an example of <code>performance.getEntriesByType('resource')</code> of a single resource:</p>
<pre><code>{"responseEnd":2436.426999978721,"responseStart":2435.966999968514,
"requestStart":2435.7460000319406,"secureConnectionStart":0,
"connectEnd":2434.203000040725,"connectStart":2434.203000040725,
"domainLookupEnd":2434.203000040725,"domainLookupStart":2434.203000040725,
"fetchStart":2434.203000040725,"redirectEnd":0,"redirectStart":0,
"initiatorType":"internal","duration":2.2239999379962683,
"startTime":2434.203000040725,"entryType":"resource","name":"http://nicj.net/"}
</code></pre>
<p><code>JSON.stringify()</code>&#8216;d, that&#8217;s 469 bytes for this one resource.  Multiple that by each resource on your page, and you can quickly see that gathering and beaconing all of this data back to a server will take a lot of bandwidth and storage if you&#8217;re tracking this for every single visitor to your site. The <a href="http://httparchive.org/">HTTP Archive</a> tells us that the <a href="http://httparchive.org/trends.php">average page</a> is composed of 99 HTTP resources, with an average URL length of 85 bytes.</p>
<p>So for a rough estimate you could expect around 45 KB of ResourceTiming data per page load.</p>
<h2>The Goal</h2>
<p>We wanted to find a way to compress this data before we JSON serialize it and beacon it back to our server.</p>
<p><a href="http://bluesmoon.info/">Philip Tellis</a>, the author of Boomerang, and I have come up with several compression techniques that can reduce the above data to about 15% of it&#8217;s original size.</p>
<h2>Techniques</h2>
<p>Let&#8217;s start out with a single resouce, as you get back from <code>window.performance.getEntriesByType("resource")</code>:</p>
<pre><code>{  
  "responseEnd":323.1100000002698,
  "responseStart":300.5000000000000,
  "requestStart":252.68599999981234,
  "secureConnectionStart":0,
  "connectEnd":0,
  "connectStart":0,
  "domainLookupEnd":0,
  "domainLookupStart":0,
  "fetchStart":252.68599999981234,
  "redirectEnd":0,
  "redirectStart":0,
  "duration":71.42400000045745,
  "startTime":252.68599999981234,
  "entryType":"resource",
  "initiatorType":"script",
  "name":"http://foo.com/js/foo.js"
}
</code></pre>
<h3>Step 1: Drop some attributes</h3>
<p>We don&#8217;t need:</p>
<ul>
<li><a href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"><code>entryType</code></a> will always be <code>resource</code></li>
<li><a href="http://www.w3.org/TR/resource-timing/#performanceresourcetiming"><code>duration</code></a> can always be calculated as <code>responseEnd - startTime</code>.</li>
<li><a href="http://www.w3.org/TR/resource-timing/#dom-performanceresourcetiming-fetchstart"><code>fetchStart</code></a> will always be <code>startTime</code> (with no redirects) or <code>redirectEnd</code> (with redirects)</li>
</ul>
<pre><code>{  
  "responseEnd":323.1100000002698,
  "responseStart":300.5000000000000,
  "requestStart":252.68599999981234,
  "secureConnectionStart":0,
  "connectEnd":0,
  "connectStart":0,
  "domainLookupEnd":0,
  "domainLookupStart":0,
  "redirectEnd":0,
  "redirectStart":0,
  "startTime":252.68599999981234,
  "initiatorType":"script",
  "name":"http://foo.com/js/foo.js"
}
</code></pre>
<h3>Step 2: Change into a fixed-size array</h3>
<p>Since we know all of the attributes ahead of time, we can change the object into a fixed-sized array.  We&#8217;ll create a new object where each key is the URL, and its value is a fixed-sized array.  We&#8217;ll take care of duplicate URLs later:</p>
<pre><code>{ "name": [initiatorType, startTime, redirectStart, redirectEnd,
   domainLookupStart, domainLookupEnd, connectStart, secureConnectionStart, 
   connectEnd, requestStart, responseStart, responseEnd] }
</code></pre>
<p>With our data:</p>
<pre><code>{ "http://foo.com/foo.js": ["script", 252.68599999981234, 0, 0
   0, 0, 0, 0, 
   0, 252.68599999981234, 300.5000000000000, 323.1100000002698] }
</code></pre>
<h3>Step 3: Drop microsecond timings</h3>
<p>For our purposes, we don&#8217;t need sub-milliscond accuracy, so we can round all timings to the nearest millisecond:</p>
<pre><code>{ "http://foo.com/foo.js": ["script", 252, 0, 0, 0, 0, 0, 0, 0, 252, 300, 323] }
</code></pre>
<h3>Step 4: Trie</h3>
<p>We can now use an <a href="http://en.wikipedia.org/wiki/Trie">optimized Trie</a> to compress the URLs.  A Trie is an optimized tree structure where associative array keys are compressed.</p>
<p>Mark Holland and Mike McCall discussed this technique at <a href="http://velocityconf.com/velocity2014/public/schedule/detail/35174">Velocity</a> this year.</p>
<p>Here&#8217;s an example with multiple resources:</p>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": ["script", 252, 0, 0, 0, 0, 0, 0, 0, 252, 300, 323]
            "css/foo.css": ["css", 300, 0, 0, 0, 0, 0, 0, 0, 305, 340, 500]
        },
        "other.com/other.css": [...]
    }
}
</code></pre>
<h3>Step 5: Offset from <code>startTime</code></h3>
<p>If we offset all of the timestamps from <code>startTime</code> (which they should always be larger than), they may use fewer characters:</p>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": ["script", 252, 0, 0, 0, 0, 0, 0, 0, 0, 48, 71],
            "css/foo.css": ["script", 300, 0, 0, 0, 0, 0, 5, 40, 200]
        },
        "other.com/other.css": [...]
    }
}
</code></pre>
<h3>Step 6: Reverse the timestamps and drop any trailing 0s</h3>
<p>The only two required timestamps in ResourceTiming are <code>startTime</code> and <code>responseEnd</code>.  Other timestamps may be zero due to being a <a href="http://www.w3.org/TR/resource-timing/#cross-origin-resources">Cross-Origin resource</a>, or a timestamp that was &#8220;zero&#8221; because it didn&#8217;t take any time offset from <code>startTime</code>, such as <code>domainLookupStart</code> if DNS was already resolved.</p>
<p>If we re-order the timestamps so that, after <code>startTime</code>, we put them in reverse order, we&#8217;re more likely to have the &#8220;zero&#8221; timestamps at the end of the array.</p>
<pre><code>{ "name": [initiatorType, startTime, responseEnd, responseStart,
   requestStart, connectEnd, secureConnectionStart, connectStart,
   domainLookupEnd, domainLookupStart, redirectEnd, redirectStart] }
</code></pre>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": ["script", 252, 71, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0]
            "css/foo.css": ["script", 300, 200, 40, 5, 0, 0, 0, 0, 0, 0, 0, 0]
        }
    }
}
</code></pre>
<p>Once we have all of the zero timestamps towards the end of the array, we can drop any repeating trailing zeros.  When reading later, missing array values can be interpreted as zero.</p>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": ["script", 252, 71, 48]
            "css/foo.css": ["css", 300, 200, 40]
        }
    }
}
</code></pre>
<h3>Step 7: Convert <code>initiatorType</code> into a lookup</h3>
<p>Using a numeric lookup instead of a string will save some bytes for <code>initiatorType</code>:</p>
<pre><code>var INITIATOR_TYPES = {
    "other": 0,
    "img": 1,
    "link": 2,
    "script": 3,
    "css": 4,
    "xmlhttprequest": 5
};
{
    "http://": {
        "foo.com/": {
            "js/foo.js": [3, 252, 71, 48]
            "css/foo.css": [4, 300, 200, 40]
        }
    }
}
</code></pre>
<h3>Step 8: Use Base36 for numbers</h3>
<p><a href="http://en.wikipedia.org/wiki/Base_36">Base 36</a> is convenient because it can result in smaller byte-size than Base-10 and has built-in browser support in JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString"><code>toString(36)</code></a>:</p>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": [3, "70", "1z", "1c"]
            "css/foo.css": [4, "8c", "5k", "14"]
        }
    }
}
</code></pre>
<h3>Step 9: Compact the array into a string</h3>
<p>A JSON string representation of an array (separated by commas) saves a few bytes during serialization.  We&#8217;ll designate the first byte as the <code>initiatorType</code>:</p>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": "370,1z,1c",
            "css/foo.css": "48c,5k,14"
        }
    }
}
</code></pre>
<h3>Step 10: Multiple hits</h3>
<p>Finally, if there are multiple hits to the same resource, the keys (URLs) in the Trie will conflict with each other.</p>
<p>Let&#8217;s fix this by concatenating multiple hits to the same URL via a special character such as pipe <code>|</code> (see <code>foo.js</code> below):</p>
<pre><code>{
    "http://": {
        "foo.com/": {
            "js/foo.js": "370,1z,1c|390,1,2",
            "css/foo.css": "48c,5k,14"
        }
    }
}
</code></pre>
<h2>Step 11: Gzip or MsgPack</h2>
<p>Applying gzip compression or <a href="http://msgpack.org/">MsgPack</a> can give additional savings during transport and storage.</p>
<h2>Results</h2>
<p>Overall, the above techniques compress raw <code>JSON.stringify(performance.getEntriesByType('resource'))</code> to about 15% of its original size.</p>
<p>Taking a few sample pages:</p>
<ul>
<li>Search engine home page
<ul>
<li>Raw: 1,000 bytes</li>
<li>Compressed: 172 bytes</li>
</ul>
</li>
<li>Questions and answers page:
<ul>
<li>Raw: 5,453 bytes</li>
<li>Compressed: 789 bytes</li>
</ul>
</li>
<li>News home page
<ul>
<li>Raw: 32,480 bytes</li>
<li>Compressed: 4,949 bytes</li>
</ul>
</li>
</ul>
<h2>How-To</h2>
<p>These compression techniques have been added to the latest version of <a href="http://www.lognormal.com/boomerang/doc/">Boomerang</a>.</p>
<p>I&#8217;ve also released a small library that does the compression as well as de-compression of the optimized result: <a href="https://github.com/nicjansma/resourcetiming-compression.js">resourcetiming-compression.js</a>.</p>
<p><i>This article also appears on <a href="http://www.soasta.com/soastas-technical-blog/compressing-resourcetiming/">soasta.com</a></i>.</p><p>The post <a href="https://nicj.net/compressing-resourcetiming/" target="_blank">Compressing ResourceTiming</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/compressing-resourcetiming/feed/</wfw:commentRss>
			<slash:comments>9</slash:comments>
		
		
			</item>
		<item>
		<title>Sails.js Intro</title>
		<link>https://nicj.net/sails-js-intro/</link>
					<comments>https://nicj.net/sails-js-intro/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 08 Aug 2014 12:52:27 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1551</guid>

					<description><![CDATA[<p>Last night at GrNodeDev I gave a small presentation on Sails.js, an awesome Node.js web framework built on top of Express. Slides are available on Slideshare and Github:</p>
<p>The post <a href="https://nicj.net/sails-js-intro/" target="_blank">Sails.js Intro</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Last night at <a href="http://meetup.com/GrNodeDev">GrNodeDev</a> I gave a small presentation on <a href="http://sailsjs.org/">Sails.js</a>, an awesome Node.js web framework built on top of Express.</p>
<p>Slides are available on <a href="http://www.slideshare.net/nicjansma/sailsjs-intro">Slideshare</a> and <a href="http://github.com/nicjansma/talks">Github</a>:</p>
<p><a href="https://www.slideshare.net/nicjansma/sailsjs-intro"><img loading="lazy" class="alignleft size-large wp-image-1559" alt="Sails.js Intro Slides" src="https://o.nicj.net/wp-content/uploads/2014/08/sails1-600x464.png" width="600" height="464" /></a></p><p>The post <a href="https://nicj.net/sails-js-intro/" target="_blank">Sails.js Intro</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/sails-js-intro/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spark Core Water Sensor</title>
		<link>https://nicj.net/spark-core-water-sensor/</link>
					<comments>https://nicj.net/spark-core-water-sensor/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 20 Jun 2014 19:00:52 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1535</guid>

					<description><![CDATA[<p>I&#8217;ve been playing around with a Spark Core, which is a small, cheap ($39) Wifi-enabled Arduino-compatible device.  As a software guy, I don&#8217;t do much with hardware, but the Spark Core makes it really easy to get going. Anyways, I was able to hook up a Grove Water Sensor to it, and have it mounted [&#8230;]</p>
<p>The post <a href="https://nicj.net/spark-core-water-sensor/" target="_blank">Spark Core Water Sensor</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="https://github.com/nicjansma/spark-core-water-sensor/"><img loading="lazy" class="alignright size-medium wp-image-1540" alt="Spark Core Water Sensor" src="https://o.nicj.net/wp-content/uploads/2014/06/core-300x225.jpg" width="300" height="225" /></a></p>
<p>I&#8217;ve been playing around with a <a href="http://spark.io">Spark Core</a>, which is a small, cheap ($39) Wifi-enabled Arduino-compatible device.  As a software guy, I don&#8217;t do much with hardware, but the Spark Core makes it really easy to get going.</p>
<p>Anyways, I was able to hook up a <a href="http://www.seeedstudio.com/wiki/Grove_-_Water_Sensor">Grove Water Sensor</a> to it, and have it mounted near my sump pump.  If the pump would ever go out, the device will send me a text message (via <a href="http://twilio.com">Twilio</a>) to alert me.</p>
<p>The whole setup is <a href="http://www.hackster.io/nicjansma/spark-water-sensor">pretty simple</a>, and I&#8217;ve put all the relevant firmware and software up on <a href="https://github.com/nicjansma/spark-core-water-sensor/">Github</a> in case anyone else is interested in doing something similar.  Total hardware cost was $42, and just $1/month for Twilio.</p><p>The post <a href="https://nicj.net/spark-core-water-sensor/" target="_blank">Spark Core Water Sensor</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/spark-core-water-sensor/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Appcelerator Titanium Intro (2014)</title>
		<link>https://nicj.net/appcelerator-titanium-intro-2014/</link>
					<comments>https://nicj.net/appcelerator-titanium-intro-2014/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 06 Jun 2014 14:36:37 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1530</guid>

					<description><![CDATA[<p>I was part of a panel during last night&#8217;s GrDevNight discussing cross-platform mobile development.  Afterwards, I gave a short presentation on Appcelerator:</p>
<p>The post <a href="https://nicj.net/appcelerator-titanium-intro-2014/" target="_blank">Appcelerator Titanium Intro (2014)</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I was part of a panel during last night&#8217;s <a href="http://grdevnight.org/">GrDevNight</a> discussing cross-platform mobile development.  Afterwards, I gave a short <a href="http://www.slideshare.net/nicjansma/appcelerator-titanium-intro-2014">presentation on Appcelerator</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/appcelerator-titanium-intro-2014"><img loading="lazy" class="aligncenter size-large wp-image-1531" alt="Appcelerator Titanium Intro" src="https://o.nicj.net/wp-content/uploads/2014/06/deck-600x460.png" width="600" height="460" /></a></p><p>The post <a href="https://nicj.net/appcelerator-titanium-intro-2014/" target="_blank">Appcelerator Titanium Intro (2014)</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/appcelerator-titanium-intro-2014/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>The Happy Path: Migration Strategies for Node.js</title>
		<link>https://nicj.net/the-happy-path-migration-strategies-for-node-js/</link>
					<comments>https://nicj.net/the-happy-path-migration-strategies-for-node-js/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 06 May 2014 00:55:44 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1517</guid>

					<description><![CDATA[<p>Today Brian Anderson, Jason Sich and I gave a presentation at GLSEC 2014 titled The Happy Path: Migration Strategies for Node.js. It is available on Slideshare: The presentation and code examples are also available on Github.</p>
<p>The post <a href="https://nicj.net/the-happy-path-migration-strategies-for-node-js/" target="_blank">The Happy Path: Migration Strategies for Node.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Today <a href="https://github.com/brianmatic">Brian Anderson</a>, <a href="https://github.com/brianmatic">Jason Sich</a> and I gave a presentation at <a href="http://glsec.softwaregr.org">GLSEC 2014</a> titled <em>The Happy Path: Migration Strategies for Node.js. </em></p>
<p>It is available on Slideshare:</p>
<p><a href="http://www.slideshare.net/nicjansma/the-happy-path-migration-strategies-for-nodejs"><img loading="lazy" class="aligncenter size-large wp-image-1518" alt="The Happy Path: Migration Strageies for Node.js" src="https://o.nicj.net/wp-content/uploads/2014/05/the-happy-path-migration-strategies-for-nodejs-600x462.png" width="600" height="462" /></a></p>
<p>The presentation and code examples are also available on <a href="https://github.com/nicjansma/talks">Github</a>.</p><p>The post <a href="https://nicj.net/the-happy-path-migration-strategies-for-node-js/" target="_blank">The Happy Path: Migration Strategies for Node.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/the-happy-path-migration-strategies-for-node-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>adblock-detector.js</title>
		<link>https://nicj.net/adblock-detector-js/</link>
					<comments>https://nicj.net/adblock-detector-js/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Sat, 22 Mar 2014 18:15:16 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1478</guid>

					<description><![CDATA[<p>I run advertising on several of my websites, mostly through Google AdSense. My sites are free communities that don&#8217;t otherwise sell products, so advertising is the main way I cover operational expenses. AdSense has been a great partner over the years and the ads they serve aren&#8217;t too obtrusive. However, I realize that many people [&#8230;]</p>
<p>The post <a href="https://nicj.net/adblock-detector-js/" target="_blank">adblock-detector.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I run advertising on several of my websites, mostly through <a href="http://www.google.com/adsense">Google AdSense</a>. My sites are free communities that don&#8217;t otherwise sell products, so advertising is the main way I cover operational expenses. AdSense has been a great partner over the years and the ads they serve aren&#8217;t too obtrusive.</p>
<p>However, I realize that many people see all advertising as annoying, and some run ad-blockers in their browser to filter out ads. <a href="https://adblockplus.org/">AdBlock Plus</a> and others are becoming more popular every year.</p>
<p>Since advertising is such an important part of my business, I wanted to try to quantify what percentage of ads were being hidden by my visitor&#8217;s ad-blockers. I did a bit of testing to determine how to detect if my ads were being blocked, then ran an experiment on two of my sites. The first site, with a travel focus, saw approximately 9.4% of ads being blocked by visitors. The second site, with a gaming focus, had over 26% of ads blocked.  The industry average is around 23%.</p>
<p>While the ad-block rates are fairly high, I&#8217;m honestly not upset or surprised by the results. Generally, people that have an ad-blocker installed won&#8217;t be the kind of audience that is likely to click on an ad. In fact, I often run an ad-blocker myself. However, knowing which visitors have blocked the ads gives me an important metric to track in my analytics. It also offers me the opportunity to give one last plea to the visitor by subtly asking them to support the site via donations if they visit often.</p>
<p>What I don&#8217;t want to do is annoy any visitors that are using ad-blockers with my plea, but I do think there&#8217;s an opportunity, if you&#8217;re respectful with your request, to gently suggest to the visitor an alternate method of supporting the site. Below are screenshots of what <a href="http://sarna.net">sarna.net</a> looks like if you visit with an ad-blocker installed.</p>
<p><a href="http://sarna.net"><img loading="lazy" class="aligncenter size-large wp-image-1479" alt="sarna-ad-blocker-full" src="https://o.nicj.net/wp-content/uploads/2014/03/sarna-ad-blocker-full-600x348.png" width="600" height="348" /></a></p>
<p>Zoomed in, you can see I provide the visitor alternate means of supporting the site, as well as a way to disable the message for 100 days if they find it annoying:</p>
<p><a href="http://sarna.net"><img loading="lazy" class="aligncenter size-large wp-image-1480" alt="sarna-ad-blocker-zoomed" src="https://o.nicj.net/wp-content/uploads/2014/03/sarna-ad-blocker-zoomed-600x69.png" width="600" height="69" /></a></p>
<p>Since this prompt is text-only and a muted color, I feel that it is an unobtrusive, respectful way of reaching out to the visitor.  So far, I haven&#8217;t had any complaints about the new prompt &#8212; and I&#8217;ve had a few donations as well.  A very small percentage click on the &#8220;<em>hide this message&#8230;&#8221;</em> link.</p>
<p>The logic to detect ad-blocking is fairly straightforward, though there are a few caveats when detecting cross-browser.  Other sites might find it useful, so I&#8217;ve packaged it up into a new module called <a href="https://github.com/nicjansma/adblock-detector.js">adblock-detector.js</a>.  I&#8217;ve only tested it in a limited environment (IE, Chrome and Firefox with AdBlock Plus), so I&#8217;m looking for help from others that can test other browsers, browser versions, ad-blockers and ad publishers.</p>
<p>You can use adblock-detector.js to collect metrics on your ad-block rate, or to appeal to your visitors as I&#8217;m doing.  I provide examples for both in the repository.</p>
<p><strong>Please</strong> use the knowledge gained for good (eg. analytics, subtle prompts), not evil (eg. more ads).</p>
<p>If you want a fully-baked solution, I would also recommend <a href="http://pagefair.com/">PageFair</a>, which can help you track your ad-block rate, and more.</p>
<p>adblock-detector.js is free, open-source, and available on <a href="https://github.com/nicjansma/adblock-detector.js">Github</a></p><p>The post <a href="https://nicj.net/adblock-detector-js/" target="_blank">adblock-detector.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/adblock-detector-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Using Phing for Fun and Profit</title>
		<link>https://nicj.net/using-phing-for-fun-and-profit/</link>
					<comments>https://nicj.net/using-phing-for-fun-and-profit/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 12 Feb 2014 17:57:40 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1465</guid>

					<description><![CDATA[<p>I gave a small presentation on using Phing as a PHP build system for GrPhpDev on 2014-02-11. It&#8217;s available on SlideShare: The presentation and examples are also available on Github.</p>
<p>The post <a href="https://nicj.net/using-phing-for-fun-and-profit/" target="_blank">Using Phing for Fun and Profit</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I gave a small presentation on using <a href="http://phing.info/">Phing</a> as a PHP build system for <a href="http://www.meetup.com/GrPhpDev/">GrPhpDev</a> on 2014-02-11. It&#8217;s available on <a href="http://www.slideshare.net/nicjansma/using-phingforfunandprofit">SlideShare</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/using-phingforfunandprofit"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2014/02/slide-1-638.jpg" alt="Using Phing for Fun and Profit" width="638" height="493" class="aligncenter size-full wp-image-1466" srcset="https://o.nicj.net/wp-content/uploads/2014/02/slide-1-638.jpg 638w, https://o.nicj.net/wp-content/uploads/2014/02/slide-1-638-400x309.jpg 400w" sizes="(max-width: 638px) 100vw, 638px" /></a></p>
<p>The presentation and examples are also available on <a href="https://github.com/nicjansma/talks">Github</a>.</p><p>The post <a href="https://nicj.net/using-phing-for-fun-and-profit/" target="_blank">Using Phing for Fun and Profit</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/using-phing-for-fun-and-profit/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ChecksumVerifier &#8211; A Windows Command-Line Tool to Verify the Integrity of your Files</title>
		<link>https://nicj.net/checksumverifier-a-windows-command-line-tool-to-verify-the-integrity-of-your-files/</link>
					<comments>https://nicj.net/checksumverifier-a-windows-command-line-tool-to-verify-the-integrity-of-your-files/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 04 Feb 2014 17:10:19 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1445</guid>

					<description><![CDATA[<p>Several years ago I wrote a small tool called ChecksumVerifier. It maintains a database of files and their checksums, and helps you verify that the files have not changed. I use it on my external hard drive backups to validate that the files are not being corrupted due to bitrot or other disk corruption.  At [&#8230;]</p>
<p>The post <a href="https://nicj.net/checksumverifier-a-windows-command-line-tool-to-verify-the-integrity-of-your-files/" target="_blank">ChecksumVerifier - A Windows Command-Line Tool to Verify the Integrity of your Files</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Several years ago I wrote a small tool called <a href="https://github.com/nicjansma/checksum-verifier">ChecksumVerifier</a>.  It maintains a database of files and their checksums, and helps you verify that the files have not changed.  I use it on my external hard drive backups to validate that the files are not being corrupted due to <a href="http://en.wikipedia.org/wiki/Bit_rot">bitrot</a> or other disk corruption.  At the time I created it, there were several other simple and commercial Windows apps that would do the same thing, but nothing was free and command-line based.  I&#8217;ve been meaning to clean it up so I could open-source it, which I finally had the time to do last weekend.</p>
<p>A <a href="http://en.wikipedia.org/wiki/Checksum">checksum</a> is a small sequence of 20-200 characters (depending on the specific algorithm used) that is calculated by reading the input file and applying a mathematical algorithm to its contents. Even a file as large as 1TB will only have a small 20-200 character checksum, so checksums are an efficient way of saving the file&#8217;s state without saving it&#8217;s entire contents. ChecksumVerifier uses the MD5, SHA-1, SHA-256 and SHA-512 algorithms, which are generally <a href="http://en.wikipedia.org/wiki/Collision_resistant">collision resistant</a> enough for validating the integrity of file contents.</p>
<p>One example usage of ChecksumVerifier is to verify the integrity of external hard drive backups. After saving files to an external disk, you can run <code>ChecksumVerifier -update</code> to calculate the checksums of all of the files on the external disk. At a later date, if you want to validate that the files on the disk have not been added, removed or changed, you can run <code>ChecksumVerifier -verify</code> and it will re-calculate all of the disks&#8217; checksums and compare them to the original database to see if any files have been changed in any way.</p>
<p>ChecksumVerifier is pretty flexible and has several command line options:<br />
<code></p>
<pre>Usage: ChecksumVerifier.exe [-update | -verify] -db [xml file] [options]

actions:
     -update:                Update checksum database
     -verify:                Verify checksum database

required:
     -db [xml file]          XML database file

options:
     -match [match]          Files to match (glob pattern such as * or *.jpg or ??.foo) (default: *)
     -exclude [match]        Files to exclude (glob pattern such as * or *.jpg or ??.foo) (default: empty)
     -basePath [path]        Base path for matching (default: current directory)
     -r, -recurse            Recurse (directories only, default: off)

path storage options:
     -relativePath           Relative path (default)
     -fullPath               Full path
     -fullPathNodrive        Full path - no drive letter

checksum options:
     -md5                    MD5 (default)
     -sha1                   SHA-1
     -sha256                 SHA-2 256 bits
     -sha512                 SHA-2 512 bits

-verify options:
     -ignoreMissing          Ignore missing files (default: off)
     -showNew                Show new files (default: off)
     -ignoreChecksum         Don't calculate checksum (default: off)

-update options:
     -removeMissing          Remove missing files (default: off)
     -ignoreNew              Don't add new files (default: off)
     -pretend                Show what would happen - don't write out XML (default: off)
</pre>
<p></code></p>
<p><a href="https://github.com/nicjansma/checksum-verifier">ChecksumVerifier</a> is free, open-source and available on <a href="https://github.com/nicjansma/checksum-verifier">github</a>.</p><p>The post <a href="https://nicj.net/checksumverifier-a-windows-command-line-tool-to-verify-the-integrity-of-your-files/" target="_blank">ChecksumVerifier - A Windows Command-Line Tool to Verify the Integrity of your Files</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/checksumverifier-a-windows-command-line-tool-to-verify-the-integrity-of-your-files/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Minifig Collector v11.0</title>
		<link>https://nicj.net/minifig-collector-v11-0/</link>
					<comments>https://nicj.net/minifig-collector-v11-0/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 08 Oct 2013 13:10:07 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1402</guid>

					<description><![CDATA[<p>My original Minifig Collector app (which was the first Android app I ever created), which has seen over 150,000 installs, just got a major facelift and some new features version 11.0.  It now has a more modern-looking UI, can import/export your figures to Brickset, and let&#8217;s you finger-swipe back and forth. Check it out! Screenshots:</p>
<p>The post <a href="https://nicj.net/minifig-collector-v11-0/" target="_blank">Minifig Collector v11.0</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>My original <a href="http://minifigcollector.com/">Minifig Collector</a> app (which was the first Android app I ever created), which has seen over 150,000 installs, just got a major facelift and some new features version 11.0.  It now has a more modern-looking UI, can import/export your figures to <a href="http://brickset.com">Brickset</a>, and let&#8217;s you finger-swipe back and forth. Check it out!</p>
<p><b>Screenshots:</b></p>
<div style="text-align: center;"><a href="http://minifigcollector.com/"><img loading="lazy" class="size-medium wp-image-1403" alt="main" src="https://o.nicj.net/wp-content/uploads/2013/10/main-168x300.png" width="168" height="300" /></a> <a href="http://minifigcollector.com/"><img loading="lazy" class="size-medium wp-image-1404" alt="list" src="https://o.nicj.net/wp-content/uploads/2013/10/list-168x300.png" width="168" height="300" /></a></div>
<div style="clear: both;"></div>
<div style="text-align: center;"><a href="http://minifigcollector.com/"><img loading="lazy" class="size-medium wp-image-1405" alt="browse" src="https://o.nicj.net/wp-content/uploads/2013/10/browse-168x300.png" width="168" height="300" /></a> <a href="http://minifigcollector.com/"><img loading="lazy" class="size-medium wp-image-1406" alt="brickset" src="https://o.nicj.net/wp-content/uploads/2013/10/brickset-168x300.png" width="168" height="300" /></a></div><p>The post <a href="https://nicj.net/minifig-collector-v11-0/" target="_blank">Minifig Collector v11.0</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/minifig-collector-v11-0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Unofficial LEGO® Minifigure Catalog v2.0</title>
		<link>https://nicj.net/unofficial-lego-minifigure-catalog/</link>
					<comments>https://nicj.net/unofficial-lego-minifigure-catalog/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Fri, 13 Sep 2013 16:18:59 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1382</guid>

					<description><![CDATA[<p>Over the past few weeks I&#8217;ve been working on a new version 2.0 of the Unofficial LEGO® Minifigure Catalog app. We&#8217;ve just released the version 2.0 to the Apple iTunes and Google Play App stores. Version 2.0 introduces tablet support along with a complete visual facelift. In addition, there are several performance improvements that make [&#8230;]</p>
<p>The post <a href="https://nicj.net/unofficial-lego-minifigure-catalog/" target="_blank">Unofficial LEGO® Minifigure Catalog v2.0</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Over the past few weeks I&#8217;ve been working on a new version 2.0 of the <a href="http://www.minifigure.org/application/">Unofficial LEGO® Minifigure Catalog app</a>.  We&#8217;ve just released the version 2.0 to the <a href="http://itunes.apple.com/us/app/the-unofficial-lego-minifigure/id486578707">Apple iTunes</a> and <a href="https://play.google.com/store/apps/details?id=org.minifigure.theUnofficialLEGOminifigureCatalog">Google Play App stores</a>.</p>
<p>Version 2.0 introduces <b>tablet</b> support along with a complete visual facelift.  In addition, there are several performance improvements that make the app much faster when browsing, and I&#8217;ve added the ability to browse minifigures, sets and heads by name (in addition to by year and theme).</p>
<p><a href="http://www.minifigure.org/application/"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2013/09/all-33-600x665.png" alt="all-3" width="600" height="665" class="aligncenter size-large wp-image-1398" /></a></p>
<p>Check it out!</p>
<p>Note: LEGO® is a trademark of the LEGO Group of companies which does not sponsor, authorize or endorse this app.</p><p>The post <a href="https://nicj.net/unofficial-lego-minifigure-catalog/" target="_blank">Unofficial LEGO® Minifigure Catalog v2.0</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/unofficial-lego-minifigure-catalog/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>SaltThePass mobile app now available on iTunes, Google Play and Amazon</title>
		<link>https://nicj.net/saltthepass-mobile-app-now-available-on-itunes-google-play-and-amazon-appstore/</link>
					<comments>https://nicj.net/saltthepass-mobile-app-now-available-on-itunes-google-play-and-amazon-appstore/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 31 Jul 2013 23:23:08 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1366</guid>

					<description><![CDATA[<p>A few months ago I released SaltThePass.com, which is a password generator that will help you generate unique, secure passwords for all of the websites you visit based on a single Master Password that you remember. I&#8217;ve been working on a mobile / offline iOS and Android app that gives you all of the features of the saltthepass.com [&#8230;]</p>
<p>The post <a href="https://nicj.net/saltthepass-mobile-app-now-available-on-itunes-google-play-and-amazon-appstore/" target="_blank">SaltThePass mobile app now available on iTunes, Google Play and Amazon</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>A few months ago I released <a href="https://saltthepass.com">SaltThePass.com</a>, which is a password generator that will help you generate unique, secure passwords for all of the websites you visit based on a single <a href="#help-master-password">Master Password</a> that you remember.</p>
<p>I&#8217;ve been working on a mobile / offline iOS and Android app that gives you all of the features of the saltthepass.com website.  The apps are now in the <a href="https://itunes.apple.com/us/app/saltthepass/id677698963">Apple iTunes App Store</a>, <a href="https://play.google.com/store/apps/details?id=com.saltthepass.app">Google Play App Store</a> and the <a href="http://www.amazon.com/Nicholas-Jansma-SaltThePass/dp/B00E3W7YWS/">Amazon Appstore</a>.</p>
<p>Let me know if you use them!<br />
<a href="https://saltthepass.com/#mobile"><img loading="lazy" class="aligncenter size-large wp-image-1375" alt="iPad, iPhone and Android apps available" src="https://o.nicj.net/wp-content/uploads/2013/07/ipad-iphone-android1-600x618.png" width="600" height="618" /></a></p><p>The post <a href="https://nicj.net/saltthepass-mobile-app-now-available-on-itunes-google-play-and-amazon-appstore/" target="_blank">SaltThePass mobile app now available on iTunes, Google Play and Amazon</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/saltthepass-mobile-app-now-available-on-itunes-google-play-and-amazon-appstore/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to deal with a WordPress wp-comments-post.php SPAM attack</title>
		<link>https://nicj.net/how-to-deal-with-a-wordpress-wp-comments-post-php-spam-attack/</link>
					<comments>https://nicj.net/how-to-deal-with-a-wordpress-wp-comments-post-php-spam-attack/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 09 May 2013 15:14:58 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1328</guid>

					<description><![CDATA[<p>This morning I woke up to several website monitoring alarms going off.  My websites were becoming intermittently unavailable due to extremely high server load (&#62;190).  It appears nicj.net had been under a WordPress comment-SPAM attack from thousands of IP addresses overnight.  After a few hours of investigation, configuration changes and cleanup, I think I&#8217;ve resolved [&#8230;]</p>
<p>The post <a href="https://nicj.net/how-to-deal-with-a-wordpress-wp-comments-post-php-spam-attack/" target="_blank">How to deal with a WordPress wp-comments-post.php SPAM attack</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>This morning I woke up to several website monitoring alarms going off.  My websites were becoming intermittently unavailable due to extremely high server load (&gt;190).  It appears nicj.net had been under a WordPress comment-SPAM attack from thousands of IP addresses overnight.  After a few hours of investigation, configuration changes and cleanup, I think I&#8217;ve resolved the issue.  I&#8217;m still under attack, but the changes I&#8217;ve made have removed all of the comment SPAM and have reduced the server load back to normal.</p>
<p>Below is a chronicle of how I investigated the problem, how I cleaned up the SPAM, and how I&#8217;m preventing it from happening again.</p>
<h2>Investigation</h2>
<p>The first thing I do when website monitoring alarms are going off (I use <a href="http://pingdom.com">Pingdom</a> and <a href="http://www.cacti.net/">Cacti</a>) is to log into the server and check its load.  Load is an indicator of how busy your server is.  Anything greater than the number of CPUs on your server is cause for alarm.  My load is usually around 2.0 &#8212; when I logged in, it was 196:</p>
<p style="padding-left: 30px;"><code>[nicjansma@server3 ~]$ uptime<br />
06:09:48 up 104 days, 11:25,  1 user,  load average: <strong>196.32</strong>, 167.75, 156.40</code></p>
<p>Next, I checked <code>top</code> and found that mysqld was likely the cause of the high load because it was using 200-1000% of the CPU:</p>
<p style="padding-left: 30px;"><code>top - 06:16:45 up 104 days, 11:32, 2 users, load average: 97.69, 162.31, 161.74<br />
Tasks: 597 total, 1 running, 596 sleeping, 0 stopped, 0 zombie<br />
Cpu(s): 3.8%us, 19.1%sy, 0.0%ni, 10.7%id, 66.2%wa, 0.0%hi, 0.1%si, 0.0%st<br />
Mem: 12186928k total, 12069408k used, 117520k free, 5868k buffers<br />
Swap: 4194296k total, 2691868k used, 1502428k free, 3894808k cached<br />
</code></p>
<p style="padding-left: 30px;"><code>PID   USER  PR NI VIRT RES  SHR  S <strong>%CPU</strong>  %MEM TIME+ COMMAND<br />
24846 mysql 20 0 26.6g 6.0g 2.6g S <strong>260.6</strong> 51.8 18285:17 <strong>mysqld</strong></code></p>
<p>Using <code>SHOW PROCESSLIST</code> in MySQL (via <a href="http://www.phpmyadmin.net/home_page/index.php">phpMyAdmin</a>), I saw about 100 processes working on the <code>wp_comments</code> table in the nicj.net WordPress database.</p>
<p>I was already starting to guess that I was under some sort of WordPress comment SPAM attack, so I checked out my Apache access_log and found nearly 800,000 POSTS to wp-comments-post.php since yesterday.  They all look a bit like this:</p>
<p style="padding-left: 30px;"><code>[nicjansma@server3 ~]$ grep POST access_log<br />
36.248.44.7 - - [09/May/2013:06:07:29 -0700] "POST /wp-comments-post.php HTTP/1.1" 302 20 "http://nicj.net/2009/04/01/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)"</code></p>
<p>What&#8217;s worse, the SPAMs were coming from over 3,000 unique IP addresses.  Essentially, it was a distributed denial of service (DDoS) attack:</p>
<p style="padding-left: 30px;"><code>[nicjansma@server3 ~]$ grep POST access_log | awk '{print $1}' | sort | uniq -c | wc -l<br />
3105</code></p>
<p>NicJ.net was getting hundreds of thousands of POSTS to wp-comments-post.php, which was causing Apache and MySQL to do a whole lot of work checking them against Akismet for SPAM and saving in the WordPress database.  I logged into the WordPress Admin interface, which verified the problem as well:</p>
<p style="padding-left: 30px;"><em>There are <strong>809,345 comments </strong>in your spam queue right now.</em></p>
<p>Yikes!</p>
<h2>Stopping the Attack</h2>
<p>First things first, if you&#8217;re under an attack like this, the quickest thing you can do to stop the attack is by disabling comments on your WordPress site.  There are a few ways of doing this.</p>
<p>One way is to go into <em>Settings</em> &gt; <em>Discussion &gt; </em>and un-check <em>Allow people to post comments on new articles.</em></p>
<p>The second way is to rename <code>wp-comments-post.php</code>, which is what spammers use directly to add comments to your blog.  I renamed my file <code>wp-comments-post.php.bak</code> temporarily, so I could change it back later.  In addition, I created a 0-byte placeholder file called <code>wp-comments-post.php</code> so the POSTS will look to the spammers like they succeeded, but the 0-byte file takes up less server resources than a 404 page:</p>
<p style="padding-left: 30px;"><code>[nicjansma@server3 ~]$ mv wp-comments-post.php wp-comments-post.php.bak &amp;&amp; touch wp-comments-post.php</code></p>
<p>Either of these methods should stop the SPAM attack immediately.  5 minutes after I did this, my server load was back down to ~2.0.</p>
<p>Now that the spammers are essentially POSTing data to your blank <code>wp-comments-post.php</code> file, new comments shouldn&#8217;t be appearing in your blog.  While this will reduce the overhead of the SPAM attack, they are still consuming your bandwidth and web server connections with their POSTs.  To stop the spammers from even sending a single packet to your webserver, you can create a small script that automatically drops packets from IPs that are posting several times to wp-comments-post.php.  This is easily done via a simple script like my <a href="http://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/">Autoban Website Spammers via the Apache Access log</a> post.  Change <code>THRESHOLD</code> to something small like 10, and <code>SEARCHTERM</code> to <code>wp-comments-post.php</code> and you will be automatically dropping packets from IPs that try to post more than 10 comments a day.</p>
<h2>Cleaning up the Mess</h2>
<p>At this point, I still had 800,000+ SPAMs in my WordPress moderation queue.  I feel bad for Akismet, they actually classified them all!</p>
<p>I tried removing the SPAM comments by going to <em>Comments</em> &gt; <em>Spam &gt; Empty Spam</em>, but I think it was too much for Apache to handle and it crashed.  Time to remove them from MySQL instead!</p>
<p>Via phpMyAdmin, I found that not only were there 800,000+ SPAMs in the database, the <code>wp_comments</code> table was over 3.6 GB and the <code>wp_commentmeta</code> was at 8.1 GB!</p>
<p>Here&#8217;s how to clean out the <code>wp_comments</code> table from any comments marked as SPAM:</p>
<p style="padding-left: 30px;"><code>DELETE FROM wp_comments WHERE comment_approved = 'spam';</code></p>
<p style="padding-left: 30px;"><code>OPTIMIZE TABLE wp_comments</code></p>
<p>In addition to the <code>wp_comments</code> table, the <code>wp_commentmeta</code> table has metadata about all of the comments. You can safely remove any comment metadata for comments that are no longer there:</p>
<p style="padding-left: 30px;"><code>DELETE FROM wp_commentmeta WHERE comment_id NOT IN (SELECT comment_id FROM wp_comments)</code></p>
<p style="padding-left: 30px;"><code>OPTIMIZE TABLE wp_commentmeta</code></p>
<p>For me, this removed 800,000+ rows of <code>wp_comments</code> (bringing it down from 3.6 GB to just 207 KB) and 2,395,512 rows of <code>wp_commentmeta</code> (bringing it down from 8.1 GB to just 136 KB).</p>
<h2>Preventing Future Attacks</h2>
<p>There are a few preventative measures you can take to stop SPAM attacks like these.</p>
<p><b>NOTE:</b> Remember to rename your <code>wp-comments-post.php.bak</code> (or turn Comments back on) after you&#8217;re happy with the prevention techniques you&#8217;re using.</p>
<ol>
<li>Disable Comments on your blog entirely (<em>Settings</em> &gt; <em>Discussion</em> &gt; <em>Allow people to post comments on new articles.</em>) (probably not desirable for most people)</li>
<li>Turn off Comments for older posts (spammers seem to target older posts that rank higher in search results). Here&#8217;s a way to <a href="http://perishablepress.com/wordpress-tip-disable-comments-in-old-posts-via-php/">disable comments automatically after 30 days</a>.</li>
<li>Rename <code>wp-comments-post.php</code> to something else, such as <code><strong>my</strong>-comments-post.php</code>. Comment spammers often just assume your code is at the <code>wp-comments-post.php</code> URL and won&#8217;t check your site&#8217;s HTML to verify this is the case. If you rename <code>wp-comments-post.php</code> and change all occurrences of that URL in your theme, your site should continue to work while the spammers hit a bogus URL. You can follow this <a href="http://codex.wordpress.org/User:Yami_McMoots/Renaming_Scripts">renaming guide</a> for more details.</li>
<li>Enable a Captcha for your comments so automated bots are less likely to be able to SPAM your blog. I&#8217;ve had great success with <a href="http://areyouahuman.com">Are You A Human</a>.</li>
<li>The <a href="http://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/">Autoban Website Spammers via the Apache Access log</a> post describes my method for automatically dropping packets from <em>bad citizen</em> IP addresses.</li>
</ol>
<p>After all of these changes, my server load is back to normal and I&#8217;m not getting any new SPAM comments.  The DDoS is still hitting my server, but their IP addresses are slowly getting packets dropped via my script every 10 minutes.</p>
<p>Hopefully these steps can help others out there.  Good luck! Fighting spammers is a never-ending battle!</p><p>The post <a href="https://nicj.net/how-to-deal-with-a-wordpress-wp-comments-post-php-spam-attack/" target="_blank">How to deal with a WordPress wp-comments-post.php SPAM attack</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/how-to-deal-with-a-wordpress-wp-comments-post-php-spam-attack/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
		<item>
		<title>2012 Minifigures Available</title>
		<link>https://nicj.net/2012-minifigures-available/</link>
					<comments>https://nicj.net/2012-minifigures-available/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 18 Apr 2013 02:02:51 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1321</guid>

					<description><![CDATA[<p>Thanks to Christoph&#8216;s hard work taking photos of all 529 minifigures released in 2012, the 2012 minifigs are now available for purchase in the Unofficial Minifigure Catalog app. To purchase the update, first update the database to the latest version (Settings &#62; Database) and then go to Settings &#62; Collections and look for the purchase button.</p>
<p>The post <a href="https://nicj.net/2012-minifigures-available/" target="_blank">2012 Minifigures Available</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Thanks to <a href="http://minifigure.org">Christoph</a>&#8216;s hard work taking photos of all <strong>529</strong> minifigures released in 2012, the 2012 minifigs are now available for purchase in the <a href="http://www.minifigure.org/application/">Unofficial Minifigure Catalog</a> app.</p>
<p>To purchase the update, first update the database to the latest version (<em>Settings</em> &gt; <em>Database</em>) and then go to <em>Settings</em> &gt; <em>Collections</em> and look for the purchase button.</p>
<p><a href="http://minifigure.org/application/"><img loading="lazy" class="aligncenter size-full wp-image-1322" title="Minifigs 2012" src="https://o.nicj.net/wp-content/uploads/2013/04/minifigs-2012.png" alt="" width="270" height="480" srcset="https://o.nicj.net/wp-content/uploads/2013/04/minifigs-2012.png 270w, https://o.nicj.net/wp-content/uploads/2013/04/minifigs-2012-225x400.png 225w" sizes="(max-width: 270px) 100vw, 270px" /></a></p><p>The post <a href="https://nicj.net/2012-minifigures-available/" target="_blank">2012 Minifigures Available</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/2012-minifigures-available/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>UserTiming.js</title>
		<link>https://nicj.net/usertiming-js/</link>
					<comments>https://nicj.net/usertiming-js/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 16 Apr 2013 03:05:56 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1314</guid>

					<description><![CDATA[<p>UserTiming is one of the W3C specs that I helped design while working at Microsoft through the W3C WebPerf working group.  It helps developers measure the performance of their web applications by giving them access to high precision timestamps. It also provides a standardized API that analytics scripts and developer tools can use to display [&#8230;]</p>
<p>The post <a href="https://nicj.net/usertiming-js/" target="_blank">UserTiming.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="http://www.w3.org/TR/user-timing/">UserTiming</a> is one of the W3C specs that I helped design while working at Microsoft through the W3C WebPerf working group.  It helps developers measure the performance of their web applications by giving them access to high precision timestamps. It also provides a standardized API that analytics scripts and developer tools can use to display performance metrics.</p>
<p>UserTiming is natively supported in IE 10 and prefixed in Chrome 25+.  I wanted to use the interface for a few of my projects so I created a small <a href="http://remysharp.com/2010/10/08/what-is-a-polyfill/">polyfill</a> to help patch other browsers that don&#8217;t support it natively. Luckily, a JavaScript version of UserTiming can be implemented and be 100% API functional &#8212; you just lose some precision and performance vs. native browser support.</p>
<p>So here it is: <a href="https://github.com/nicjansma/usertiming.js">UserTiming.js</a></p>
<p>README:</p>
<p style="padding-left: 30px;"><em>UserTiming.js is a polyfill that adds <a href="http://www.w3.org/TR/user-timing/">UserTiming</a> support to browsers that do not natively support it.</em></p>
<p style="padding-left: 30px;"><em>UserTiming is accessed via the <a href="http://www.w3.org/TR/performance-timeline/">PerformanceTimeline</a>, and requires <a href="http://www.w3.org/TR/hr-time/"><code>window.performance.now()</code></a> support, so UserTiming.js adds a limited version of these interfaces if the browser does not support them (which is likely the case if the browser does not natively support UserTiming).</em></p>
<p style="padding-left: 30px;"><em>As of 2013-04-15, UserTiming is natively supported by the following browsers:</em></p>
<ul style="padding-left: 30px;">
<li style="padding-left: 30px;"><em>IE 10+</em></li>
<li style="padding-left: 30px;"><em>Chrome 25+ (prefixed)</em></li>
</ul>
<p style="padding-left: 30px;"><em>UserTiming.js has been verified to add UserTiming support to the following browsers:</em></p>
<ul style="padding-left: 30px;">
<li style="padding-left: 30px;"><em>IE 6-9</em></li>
<li style="padding-left: 30px;"><em>Firefox 3.6+ (previous versions not tested)</em></li>
<li style="padding-left: 30px;"><em>Safari 4.0.5+ (previous versions not tested)</em></li>
<li style="padding-left: 30px;"><em>Opera 10.50+ (previous versions not tested)</em></li>
</ul>
<p style="padding-left: 30px;"><em>UserTiming.js will detect native implementations of UserTiming, <code>window.performance.now()</code> and the PerformanceTimeline and will not make any changes if those interfaces already exist.  When a prefixed version is found, it is copied over to the unprefixed name.</em></p>
<p>UserTiming.js can be found on <a href="https://github.com/nicjansma/usertiming.js">GitHub</a> and as the <a href="https://npmjs.org/package/usertiming">npm usertiming module</a>.</p><p>The post <a href="https://nicj.net/usertiming-js/" target="_blank">UserTiming.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/usertiming-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>breakup.js</title>
		<link>https://nicj.net/breakup-js/</link>
					<comments>https://nicj.net/breakup-js/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Mon, 15 Apr 2013 00:11:12 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1300</guid>

					<description><![CDATA[<p>It&#8217;s not you, it&#8217;s me. A few months ago I released a small JavaScript micro-framework: breakup.js Serially enumerating over a collection (such as using async.forEachSeries()in Node.js or jQuery.each() in the browser) can lead to performance and responsiveness issues if processing or looping through the collection takes too long. In some browsers, enumerating over a large [&#8230;]</p>
<p>The post <a href="https://nicj.net/breakup-js/" target="_blank">breakup.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><em>It&#8217;s not you, it&#8217;s me.</em></p>
<p>A few months ago I released a small JavaScript micro-framework: <a title="breakup.js" href="https://github.com/nicjansma/breakup.js">breakup.js</a></p>
<p>Serially enumerating over a collection (such as using <code>async.forEachSeries()</code>in Node.js or <code>jQuery.each()</code> in the browser) can lead to performance and responsiveness issues if processing or looping through the collection takes too long. In some browsers, enumerating over a large number of elements (or doing a lot of work on each element) may cause the browser to become unresponsive, and possibly prompt the user to stop running the script.</p>
<p>breakup.js helps solve this problem by breaking up the enumeration into time-based chunks, and yielding to the environment if a threshold of time has passed before continuing.  This will help avoid a Long Running Script dialog in browsers as they are given a chance to update their UI.  It is meant to be a simple, drop-in replacement for <code>async.forEachSeries()</code>.  It also provides <code>breakup.each()</code> as a replacement for <code>jQuery.each()</code> (though the developer may have to modify code-flow to deal with the asynchronous nature of breakup.js).</p>
<p>breakup.js does this by keeping track of how much time the enumeration has taken after processing each item.  If the enumeration time has passed a threshold (the default is 50ms, but this can be customized), the enumeration will yield before resuming.  Yielding can be done immediately in environments that support it (such as <code>process.nextTick()</code> in Node.js and <code>setImmediate()</code> in modern browsers), and will fallback to a <code>setTimeout(..., 4)</code> in older browsers.  This yield will allow the environment to do any UI and other processing work it wants to do.  In browsers, this will help reduce the chance of a Long Running Script dialog.</p>
<p>breakup.js is primarily meant to be used in a browser environment, as Node.js code is already asynchronously driven. You won&#8217;t see a Long Running Script dialog in Node.js. However, you&#8217;re welcome to use the breakup Node.js module if you want have more control over how much  time your enumerations take.  For example, if you have thousands of items to enumerate and you want to process them lazily, you could set the threshold to 100ms with a 10000ms wait time and specify the <code>forceYield</code> parameter, so other work is prioritized.</p>
<p>Check it out on <a href="https://github.com/nicjansma/breakup.js#each">Github</a> or via <a href="https://npmjs.org/package/breakup">npm</a>.</p><p>The post <a href="https://nicj.net/breakup-js/" target="_blank">breakup.js</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/breakup-js/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SaltThePass.com</title>
		<link>https://nicj.net/saltthepass-com/</link>
					<comments>https://nicj.net/saltthepass-com/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 27 Mar 2013 03:28:00 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1290</guid>

					<description><![CDATA[<p>As many geeks do, I have a collection of about 30-odd domain names that I&#8217;ve purchased over the past few years for awesome-at-the-time ideas that I just never found the time to work on. Last month, I resolved stop collecting these domains and instead make some visible progress on them, one at a time. SaltThePass [&#8230;]</p>
<p>The post <a href="https://nicj.net/saltthepass-com/" target="_blank">SaltThePass.com</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>As many geeks do, I have a collection of about 30-odd domain names that I&#8217;ve purchased over the past few years for <em>awesome-at-the-time</em> ideas that I just never found the time to work on.</p>
<p>Last month, I resolved stop collecting these domains and instead make some visible progress on them, one at a time.</p>
<p>SaltThePass is my first project.  Do you have an account on LinkedIn, Evernote, or Yahoo?  All of these sites had password breaches in the last year that compromised their user&#8217;s logins and passwords.  One big problem people face today is managing all of the passwords they use for all of the sites that they visit.  People often re-use the same password on many sites because it would be impossible to remember hundreds of different passwords.  Unfortunately, this means that if a single site is hacked and your password is revealed, the attacker may have access to your account on all of the other sites you visit.</p>
<p>To help solve this problem, I created <a href="https://saltthepass.com">SaltThePass.com</a>.  Salt The Pass is a password generator that will help you generate unique, secure passwords for all of the websites you visit based on a single Master Password that you remember.  You don&#8217;t need to install any additional software, and you can access your passwords from anywhere you have internet access.</p>
<p>Check it out at <a href="https://saltthepass.com">https://saltthepass.com</a> and let me know what you think!</p>
<p><a href="https://saltthepass.com"><img loading="lazy" class="aligncenter size-full wp-image-1293" title="SaltThePass.com" src="https://o.nicj.net/wp-content/uploads/2013/03/ss.png" alt="" width="600" height="366" srcset="https://o.nicj.net/wp-content/uploads/2013/03/ss.png 600w, https://o.nicj.net/wp-content/uploads/2013/03/ss-400x244.png 400w" sizes="(max-width: 600px) 100vw, 600px" /></a></p><p>The post <a href="https://nicj.net/saltthepass-com/" target="_blank">SaltThePass.com</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/saltthepass-com/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Using Modern Browser APIs to Improve the Performance of Your Web Applications</title>
		<link>https://nicj.net/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications/</link>
					<comments>https://nicj.net/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Tue, 26 Feb 2013 19:25:40 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1282</guid>

					<description><![CDATA[<p>Last night I gave a short presentation on Using Modern Browser APIs to Improve the Performance of Your Web Applications at GrWebDev. It&#8217;s available on SlideShare: Two other presentations I gave late last year are available here as well: Debugging IE Performance issues with Xperf, ETW and NavigationTiming Appcelerator Titanium Intro</p>
<p>The post <a href="https://nicj.net/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications/" target="_blank">Using Modern Browser APIs to Improve the Performance of Your Web Applications</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Last night I gave a short presentation on <i>Using Modern Browser APIs to Improve the Performance of Your Web Applications</i> at <a href="http://www.meetup.com/grwebdev/">GrWebDev</a>.</p>
<p>It&#8217;s available on <a href="http://www.slideshare.net/nicjansma/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications">SlideShare</a>:</p>
<p><a href="http://www.slideshare.net/nicjansma/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications"><img loading="lazy" src="https://o.nicj.net/wp-content/uploads/2013/02/slide-1-638.jpg" alt="Usinng Modern Browser APIs SlideShare deck" width="638" height="479" class="aligncenter size-full wp-image-1354" srcset="https://o.nicj.net/wp-content/uploads/2013/02/slide-1-638.jpg 638w, https://o.nicj.net/wp-content/uploads/2013/02/slide-1-638-400x300.jpg 400w" sizes="(max-width: 638px) 100vw, 638px" /></a></p>
<p>Two other presentations I gave late last year are available here as well:</p>
<ul>
<li><a href="http://www.slideshare.net/nicjansma/debugging-ie-performance-issues-with-xperf-etw-and-navigationtiming">Debugging IE Performance issues with Xperf, ETW and NavigationTiming</a></li>
<li><a href="http://www.slideshare.net/nicjansma/appcelerator-titanium-intro">Appcelerator Titanium Intro</a></li>
</ul><p>The post <a href="https://nicj.net/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications/" target="_blank">Using Modern Browser APIs to Improve the Performance of Your Web Applications</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/using-modern-browser-apis-to-improve-the-performance-of-your-web-applications/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Switch your HTPC back to Media Center after logging out of Remote Desktop</title>
		<link>https://nicj.net/switch-your-htpc-back-to-media-center-after-logging-out-of-remote-desktop/</link>
					<comments>https://nicj.net/switch-your-htpc-back-to-media-center-after-logging-out-of-remote-desktop/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 23 May 2012 17:45:24 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1263</guid>

					<description><![CDATA[<p>I have a Windows 7 Media Center PC hooked up to the TV in our living room.  It&#8217;s paired to a 4-stream Ceton CableCard adapter and is great for watching both TV and movies. Sometimes I need to Remote Desktop (RDP) into the machine to install updates or make other changes.  During this, and after logging out, [&#8230;]</p>
<p>The post <a href="https://nicj.net/switch-your-htpc-back-to-media-center-after-logging-out-of-remote-desktop/" target="_blank">Switch your HTPC back to Media Center after logging out of Remote Desktop</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I have a Windows 7 Media Center PC hooked up to the TV in our living room.  It&#8217;s paired to a 4-stream <a href="http://cetoncorp.com/">Ceton</a> CableCard adapter and is great for watching both TV and movies.</p>
<p>Sometimes I need to Remote Desktop (RDP) into the machine to install updates or make other changes.  During this, and after logging out, the Media Center PC is left at the login-screen on the TV.  So the next time I sit down to watch TV, I have to find the wireless keyboard and enter my password to log back in.</p>
<p>Since this can get annoying, I&#8217;ve created a small script on the desktop that automatically switches the console session (what&#8217;s shown on the TV) back to the primary user and re-starts Media Center.  This way, the next person that uses the TV doesn&#8217;t have to log back in.  When I&#8217;m done in the RDP session, I simply start the batch script and it logs me out of RDP and logs the TV back in.</p>
<p>Here&#8217;s the simple script:</p>
<pre style="padding-left: 30px;">call %windir%\system32\tscon.exe 1 /dest:console
start "Media Center" /max %windir%\ehome\ehshell.exe
exit /b 1</pre><p>The post <a href="https://nicj.net/switch-your-htpc-back-to-media-center-after-logging-out-of-remote-desktop/" target="_blank">Switch your HTPC back to Media Center after logging out of Remote Desktop</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/switch-your-htpc-back-to-media-center-after-logging-out-of-remote-desktop/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>PngOutBatch: Optimize your PNGs by running PngOut multiple times</title>
		<link>https://nicj.net/pngoutbatch-optimize-your-pngs-by-running-pngout-multiple-times/</link>
					<comments>https://nicj.net/pngoutbatch-optimize-your-pngs-by-running-pngout-multiple-times/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 16 May 2012 02:23:42 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1254</guid>

					<description><![CDATA[<p>PngOut is a command-line tool that can losslessly reduce the file size of your PNGs. In many cases, it can reduce the size of a PNG by 10-15%. I&#8217;ve even seen some cases where it was able to reduce the file size by over 50%. There are several other PNG compression utilties out there, such [&#8230;]</p>
<p>The post <a href="https://nicj.net/pngoutbatch-optimize-your-pngs-by-running-pngout-multiple-times/" target="_blank">PngOutBatch: Optimize your PNGs by running PngOut multiple times</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><a href="http://advsys.net/ken/utils.htm">PngOut</a> is a command-line tool that can losslessly reduce the file size of your PNGs. In many cases, it can reduce the size of a PNG by 10-15%. I&#8217;ve even seen some cases where it was able to reduce the file size by over 50%.</p>
<p>There are several other PNG compression utilties out there, such as <a href="http://pmt.sourceforge.net/pngcrush/">pngcrush</a> and <a href="http://advancemame.sourceforge.net/comp-readme.html">AdvanceCOMP</a>, but I&#8217;ve found PngOut to be the best optimizer <em>most of the time</em>.</p>
<p>There&#8217;s an excellent tutorial on <a title="PngOut" href="http://advsys.net/ken/util/pngout.htm">PngOut</a> for first-timers.  Running PngOut is pretty easy, simply run it once agaist your PNG:</p>
<pre style="padding-left: 30px;">PngOut.exe [image.png]</pre>
<p>However, to get the best optimization of your images, you can run PngOut multiple times with different block sizes (eg, <code>/b1024</code>) and randomized initial tables (<code>/r</code>).</p>
<p>There&#8217;s a commercial program, <a href="http://www.ardfry.com/pngoutwin/">PngOutWin</a> that can run through all of the block sizes using multiple CPU cores, but I wanted something free that I could run from the command line.</p>
<p>To aid in this, I created a simple DOS batch script that runs PngOut through 9 different block sizes (from 0 to 8192), with each block size run multiple times with random initial tables.</p>
<p>While the first iteration of PngOut does all of the heavy lifting, I&#8217;ve sometimes found that using the different block sizes can eek out a few extra bytes (sometimes 100-bytes or more than the initial pass).  You may not care about optimizing your PNG to the absolute last byte possible, but I try to run any new PNGs ready for production in my websites and mobile apps through this batch script before they&#8217;re committed to the wild.</p>
<p>Running PngOutBatch is as easy as running PngOut:</p>
<pre style="padding-left: 30px;">PngOutBatch.cmd [image.png] [number of iterations per block size - defaults to 5]</pre>
<p>PngOutBatch will show progress as it reduces the file size.  Here&#8217;s a sample compressing the PNG logo from <a href="http://www.libpng.org/pub/png/">libpng.org</a>:</p>
<pre style="padding-left: 30px;">Blocksize: 0
Iteration #1: Saved 2529 bytes
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
Blocksize: 128
Iteration #1: Saved 606 bytes
Iteration #2: Saved 10 bytes
Iteration #3: No savings
Iteration #4: Saved 2 bytes
Iteration #5: No savings
Blocksize: 192
Iteration #1: No savings
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
Blocksize: 256
Iteration #1: Saved 1 bytes
Iteration #2: No savings
Iteration #3: Saved 5 bytes
Iteration #4: Saved 11 bytes
Iteration #5: No savings
Blocksize: 512
Iteration #1: No savings
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
Blocksize: 1024
Iteration #1: No savings
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
Blocksize: 2048
Iteration #1: No savings
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
Blocksize: 4096
Iteration #1: No savings
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
Blocksize: 8192
Iteration #1: No savings
Iteration #2: No savings
Iteration #3: No savings
Iteration #4: No savings
Iteration #5: No savings
D:\temp\test.png: SUCCESS: 17260 bytes originally, 14096 bytes final: 3164 bytes saved</pre>
<p>The first block size (0) reduced the file by 2529 bytes, then the 128-byte block size further reduced it by 606, 10 then 2 bytes. The 192-byte block size didn&#8217;t help, but a 256-byte block size reduced the file size by 1, 5 then 11 more bytes.  Larger block sizes didn&#8217;t help, but at the end of the day we reduced the PNG by 3164 bytes (18%), and 635 bytes (25% more) than if we had only run it once.</p>
<p>The PngOutBatch.cmd script is hosted at <a href="https://gist.github.com/2706660">Gist.Github</a> if you want to use it or contribute changes.</p><p>The post <a href="https://nicj.net/pngoutbatch-optimize-your-pngs-by-running-pngout-multiple-times/" target="_blank">PngOutBatch: Optimize your PNGs by running PngOut multiple times</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/pngoutbatch-optimize-your-pngs-by-running-pngout-multiple-times/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>DIY Cloud Backup using Amazon EC2 and EBS</title>
		<link>https://nicj.net/diy-cloud-backup-using-amazon-ec2-and-ebs/</link>
					<comments>https://nicj.net/diy-cloud-backup-using-amazon-ec2-and-ebs/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Mon, 20 Feb 2012 20:18:44 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1183</guid>

					<description><![CDATA[<p>I&#8217;ve created a small set of scripts that allows you to use Amazon Web Services to backup files to your own personal &#8220;cloud&#8221;. It&#8217;s available at GitHub for you to download or fork. Features Uses rsync over ssh to securely backup your Windows machines to Amazon&#8217;s EC2 (Elastic Compute Cloud) cloud, with persistent storage provided [&#8230;]</p>
<p>The post <a href="https://nicj.net/diy-cloud-backup-using-amazon-ec2-and-ebs/" target="_blank">DIY Cloud Backup using Amazon EC2 and EBS</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve created a small set of scripts that allows you to use Amazon Web Services to backup files to your own personal &#8220;cloud&#8221;. It&#8217;s available at <a href="https://github.com/nicjansma/amazon-cloud-backup">GitHub</a> for you to download or fork.</p>
<h2>Features</h2>
<ul>
<li>Uses rsync over ssh to securely backup your Windows machines to Amazon&#8217;s EC2 (Elastic Compute Cloud) cloud, with persistent storage provided by Amazon EBS (Elastic Block Store)</li>
<li>Rsync efficiently mirrors your data to the cloud by only transmitting changed deltas, not entire files</li>
<li>An Amazon EC2 instance is used as a temporary server inside Amazon&#8217;s data center to backup your files, and it is only running while you are actively performing the rsync</li>
<li>An Amazon EBS volume holds your backup and is only attached during the rsync, though you could attach it to any other EC2 instance later for data retrieval, or snapshot it to S3 for point-in-time backup</li>
</ul>
<h2>Introduction</h2>
<p>There are several online backup services available, from <a href="http://mozy.com/">Mozy</a> to <a href="http://www.carbonite.com/en/">Carbonite</a> to <a href="http://dropbox.com">Dropbox</a>. They all provide various levels of backup services for little or no cost. They usually require you to run one of their apps on your machine, which backs up your files periodically to their &#8220;cloud&#8221; of storage.</p>
<p>While these services may suffice for the majority of people, you may wish to take a little more control of your backup process. For example, you are trusting their client app to do the right thing, and for your files to be stored securely in their data centers. They may also put limits on the rate they upload your backups, change their cost, or even go out of business.</p>
<p>On the other hand, one of the simplest tools to backup files is a program called <a href="http://en.wikipedia.org/wiki/Rsync">rsync</a>, which has been around for a long time. It efficiently transfers files over a network, and can be used to only transfer the parts of a file that have changed since the last sync. Rsync can be run on Linux or Windows machines through <a href="http://www.cygwin.com">Cygwin</a>. It can be run over SSH, so backups are performed with encryption. The problem is you need a Linux rsync server somewhere as the remote backup destination.</p>
<p>Instead of relying on one of the commercial backup services, I wanted to create a DIY backup &#8220;cloud&#8221; that I had complete control of. This script uses Amazon Web Services, a service from Amazon that offers on-demand compute instances (EC2) and storage volumes (EBS). It uses the amazingly simple, reliable and efficient rsync protocol to back up your documents quickly to Amazon&#8217;s data centers, only using an EC2 instance for the duration of the rsync. Your backups are stored on EBS volumes in Amazon&#8217;s data center, and you have complete control over them. By using this DIY method of backup, you get complete control of your backup experience. No upload rate-limiting, no client program constantly running on your computer. You can even do things like encrypt the volume you&#8217;re backing up to. <strong>NOTE: </strong>As of 2014-05-21, EBS volumes can be <a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html">encrypted automatically</a>.</p>
<p>The only service you&#8217;re paying for is Amazon EC2 and EBS, which is pretty cheap, and not likely to disappear any time soon. For example, my monthly EC2 costs for perfoming a weekly backup are less than a dollar, and EBS costs at this time are as cheap as $0.10/GB/mo.</p>
<p>These scripts are provided to give you a simple way to backup your files via rsync to Amazon&#8217;s infrastructure, and can be easily adapted to your needs.</p>
<h2>How It Works</h2>
<p>This script is a simple DOS batch script that can be run to launch an EC2 instance, perform the rsync, stop the instance, and check on the status of your instances.</p>
<p>After you&#8217;ve created your personal backup &#8220;cloud&#8221; (see <em>Amazon Cloud Setup</em>), and have the <em>Required Tools</em>, you simply run the <code>amazon-cloud-backup.cmd -start</code> to startup a new EC2 instance. Internally, this uses the Amazon API Developer Tools to start the instance via <code>ec2-run-instances</code>. There&#8217;s a custom bootscript for the instance, <code>amazon-cloud-backup.bootscript.sh</code> that works well with the Amazon Linux AMIs to enable <code>root</code> access to the machine over SSH (they initially only offer the user <code>ec2-user</code> SSH access). We need root access to perform the mount of the volume.</p>
<p>After the instance is started, the script attaches your personal EBS volume to the device. Its remote address is queried via<code>ec2-describe-instances</code> and SSH is used to mount the EBS volume to a backup point (eg, <code>/backup</code>). Once this is completed, your remote EC2 instance and EBS volume are ready for you to rsync.</p>
<p>To start the rsync, you simply need to run <code>amazon-cloud-backup.cmd -rsync [options]</code>. Rsync is started over SSH, and your files are backed up to the remote volume.</p>
<p>Once the backup is complete, you can stop the EC2 instance at any time by running <code>amazon-cloud-backup.cmd -stop</code>, or get the status of the instance by running <code>amazon-cloud-backup.cmd -status</code>. You can also check on the free space on the volume by running <code>amazon-cloud-backup.cmd -volumestatus</code>.</p>
<p>There are a couple things you will need to configure to set this all up. First you need to sign up for Amazon Web Services and generate the appropriate keys and certificates. Then you need a few helper programs on your machine, for example <code>rsync.exe</code> and <code>ssh.exe</code>. Finally, you need to set a few settings in <code>amazon-cloud-backup.cmd</code> so the backup is tailored to your keys and requirements.</p>
<h2>Amazon &#8220;Cloud&#8221; Setup</h2>
<p>To use this script, you need to have an Amazon Web Services account. You can sign up for one at <a href="https://aws.amazon.com/">https://aws.amazon.com/</a>. Once you have an Amazon Web Services account, you will also need to sign up for Amazon EC2.</p>
<p>Once you have access to EC2, you will need to do the following.</p>
<ol>
<li>Create a X.509 Certificate so we can enable API access to the Amazon Web Service API. You can get this in your <a href="https://aws-portal.amazon.com/gp/aws/securityCredentials">Security Credentials</a> page. Click on the <em>X.509 Certificates</em> tab, then <em>Create a new Certificate</em>. Download both the X.509 Private Key and Certificate files (pk-xyz.pem and cert-xyz.pem).<a href="https://aws-portal.amazon.com/gp/aws/securityCredentials"><img loading="lazy" class="alignnone size-full wp-image-1216" style="margin-left: 20px; margin-top: 10px;" title="access-credentials" alt="" src="https://o.nicj.net/wp-content/uploads/2012/02/access-credentials.png" width="600" height="439" srcset="https://o.nicj.net/wp-content/uploads/2012/02/access-credentials.png 600w, https://o.nicj.net/wp-content/uploads/2012/02/access-credentials-400x293.png 400w" sizes="(max-width: 600px) 100vw, 600px" /></a></li>
<li>Determine which Amazon Region you want to work out of. See their <a href="http://docs.amazonwebservices.com/general/latest/gr/rande.html">Reference</a> page for details. For example, I&#8217;m in the Pacific Northwest so I chose us-west-2 (Oregon) as the Region.<a href="https://o.nicj.net/wp-content/uploads/2012/02/ec2-regions.png"><img loading="lazy" class="alignnone size-full wp-image-1220" style="margin-left: 20px; margin-top: 10px;" title="ec2-regions" alt="" src="https://o.nicj.net/wp-content/uploads/2012/02/ec2-regions.png" width="502" height="256" srcset="https://o.nicj.net/wp-content/uploads/2012/02/ec2-regions.png 502w, https://o.nicj.net/wp-content/uploads/2012/02/ec2-regions-400x204.png 400w" sizes="(max-width: 502px) 100vw, 502px" /></a></li>
<li>Create an EC2 Key Pair so you can log into your EC2 instance via SSH. You can do this in the <a href="https://console.aws.amazon.com/ec2/">AWS Management Console</a>. Click on <em>Create a Key Pair</em>, name it (for example, &#8220;amazon-cloud-backup-rsync&#8221;) and download the .pem file.<a href="https://o.nicj.net/wp-content/uploads/2012/02/create-key-pair.png"><img loading="lazy" class="alignnone size-full wp-image-1217" style="margin-left: 20px; margin-top: 10px;" title="create-key-pair" alt="" src="https://o.nicj.net/wp-content/uploads/2012/02/create-key-pair.png" width="600" height="475" srcset="https://o.nicj.net/wp-content/uploads/2012/02/create-key-pair.png 600w, https://o.nicj.net/wp-content/uploads/2012/02/create-key-pair-400x317.png 400w" sizes="(max-width: 600px) 100vw, 600px" /></a></li>
<li>Create an EBS Volume in the <a href="https://console.aws.amazon.com/ec2/">AWS Management Console</a>. Click on <em>Volumes</em> and then <em>Create Volume</em>. You can create whatever size volume you want, though you should note that you will pay monthly charges for the volume size, not the size of your backed up files.<a href="https://o.nicj.net/wp-content/uploads/2012/02/create-volume.png"><img loading="lazy" class="alignnone size-full wp-image-1219" style="margin-left: 20px; margin-top: 10px;" title="create-volume" alt="" src="https://o.nicj.net/wp-content/uploads/2012/02/create-volume.png" width="600" height="478" srcset="https://o.nicj.net/wp-content/uploads/2012/02/create-volume.png 600w, https://o.nicj.net/wp-content/uploads/2012/02/create-volume-400x319.png 400w" sizes="(max-width: 600px) 100vw, 600px" /></a></li>
<li>Determine which EC2 AMI (Amazon Machine Image) you want to use. I&#8217;m using the <a href="http://aws.amazon.com/amazon-linux-ami/">Amazon Linux AMI: EBS Backed 32-bit</a> image. This is a Linux image provided and maintained by Amazon. You&#8217;ll need to pick the appropriate AMI ID for your region. If you do not use one of the Amazon-provided AMIs, you may need to modify <code>amazon-cloud-backup.bootscript.sh</code> for the backup to work.</li>
<li>Create a new EC2 Security Group that allows SSH access. In the <a href="https://console.aws.amazon.com/ec2/">AWS Management Console</a>, under EC2, open the <em>Security Groups</em> pane. Select <em>Create Security Group</em> and name it &#8220;ssh&#8221; or something similar. Once added, edit its <em>Inbound</em> rules to allow port 22 from all sources &#8220;0.0.0.0/0&#8221;. If you know what your remote IP address is ahead of time, you could limit the source to that IP.<a href="https://o.nicj.net/wp-content/uploads/2012/02/create-security-group.png"><img loading="lazy" class="alignnone size-full wp-image-1218" style="margin-left: 20px; margin-top: 10px;" title="create-security-group" alt="" src="https://o.nicj.net/wp-content/uploads/2012/02/create-security-group.png" width="600" height="477" srcset="https://o.nicj.net/wp-content/uploads/2012/02/create-security-group.png 600w, https://o.nicj.net/wp-content/uploads/2012/02/create-security-group-400x318.png 400w" sizes="(max-width: 600px) 100vw, 600px" /></a></li>
<li>Launch an EC2 instance with the &#8220;ssh&#8221; Security Group. After you launch the instance, you can use the <em>Attach Volume</em> button in the<em>Volumes</em> pane to attach your new volume as <code>/dev/sdb</code>.<a href="https://o.nicj.net/wp-content/uploads/2012/02/launch.png"><img loading="lazy" class="alignnone size-full wp-image-1221" style="margin-left: 20px; margin-top: 10px;" title="launch" alt="" src="https://o.nicj.net/wp-content/uploads/2012/02/launch.png" width="600" height="388" srcset="https://o.nicj.net/wp-content/uploads/2012/02/launch.png 600w, https://o.nicj.net/wp-content/uploads/2012/02/launch-400x259.png 400w" sizes="(max-width: 600px) 100vw, 600px" /></a></li>
<li>Log-in to your EC2 instance using ssh (see <em>Required Tools</em>below) and fdisk the volume and create a filesystem. For example:
<pre>ssh -i my-rsync-key.pem ec2-user@ec2-1-2-3-4.us-west-1.compute.amazonaws.com
[ec2-user] sudo fdisk /dev/sdb
...
[ec2-user] sudo mkfs.ext4 /dev/sdb1</pre>
</li>
<li>Your Amazon personal &#8220;Cloud&#8221; is now setup.</li>
</ol>
<p>Many of the choices you&#8217;ve made in this section will need to be set as configuration options in the <code>amazon-cloud-backup.cmd</code> script.</p>
<h2>Required Tools</h2>
<p>You will need a couple tools on your Windows machine to perform the rsync backup and query the Amazon Web Services API.</p>
<ol>
<li>First, you&#8217;ll need a few binaries (<code>rsync.exe</code>, <code>ssh.exe</code>) on your system to facilitate the ssh/rsync transfer. <a href="http://www.cygwin.com/">Cygwin</a> can be used to accomplish this. You can easily install Cygwin from <a href="http://www.cygwin.com/">http://www.cygwin.com/</a>. After installing, pluck a couple files from the <code>bin/</code>folder and put them into this directory. The binaries you need are:
<pre>rsync.exe
ssh.exe
sleep.exe</pre>
<p>You may also need a couple libraries to ensure those binaries run:</p>
<pre>cygcrypto-0.9.8.dll
cyggcc_s-1.dll
cygiconv-2.dll
cygintl-8.dll
cygpopt-0.dll
cygspp-0.dll
cygwin1.dll
cygz.dll</pre>
</li>
<li>You will need the Amazon API Developer Tools, downloaded from <a href="http://aws.amazon.com/developertools/">http://aws.amazon.com/developertools/</a>. Place them in a sub-directory called <code>amazon-tools\</code></li>
</ol>
<h2>Script Configuration</h2>
<p>Now you simply have to configure <code>amazon-cloud-backup.cmd</code>.</p>
<p>Most of the settings can be left at their defaults, but you will likely need to change the locations and name of your X.509 Certificate and EC2 Key Pair.</p>
<h2>Usage</h2>
<p>Once you&#8217;ve done the steps in <em>Amazon &#8220;Cloud&#8221; Setup</em>, <em>Required Tools</em> and <em>Script Configuration</em>, you just need to run the <code>amazon-cloud-backup.cmd</code> script.</p>
<p>These simple steps will launch your EC2 instance, perform the rsync, and then stop the instance.</p>
<pre>amazon-cloud-backup.cmd -launch
amazon-cloud-backup.cmd -rsync
amazon-cloud-backup.cmd -stop</pre>
<p>After <code>-stop</code>, your EC2 instance will stop and the EBS volume will be un-attached.</p>
<h2>Source</h2>
<p>The source code is available at <a href="https://github.com/nicjansma/amazon-cloud-backup">GitHub</a>. Feel free to send pull requests for improvements!</p><p>The post <a href="https://nicj.net/diy-cloud-backup-using-amazon-ec2-and-ebs/" target="_blank">DIY Cloud Backup using Amazon EC2 and EBS</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/diy-cloud-backup-using-amazon-ec2-and-ebs/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Windows command-line regular expression renaming tool: RenameRegex</title>
		<link>https://nicj.net/windows-command-line-regular-expression-renaming-tool-renameregex/</link>
					<comments>https://nicj.net/windows-command-line-regular-expression-renaming-tool-renameregex/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Mon, 30 Jan 2012 23:26:23 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1175</guid>

					<description><![CDATA[<p>Every once in a while, I need to rename a bunch of files.  Instead of hand-typing all of the new names, sometimes a nice regular expression would get the job done a lot faster.  While there are a couple Windows GUI regular expression file renamers, I enjoy doing as much as I can from the [&#8230;]</p>
<p>The post <a href="https://nicj.net/windows-command-line-regular-expression-renaming-tool-renameregex/" target="_blank">Windows command-line regular expression renaming tool: RenameRegex</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Every once in a while, I need to rename a bunch of files.  Instead of hand-typing all of the new names, sometimes a nice regular expression would get the job done a lot faster.  While there are a couple <a href="http://geekswithblogs.net/cicorias/archive/2006/07/10/84601.aspx">Windows GUI</a> <a href="http://www.bennadel.com/blog/460-Flex-Renamer-Most-Awesome-Bulk-File-Folder-Regular-Expression-Renamer-Ever.htm">regular expression</a> <a href="http://www.publicspace.net/windows/BetterFileRename/">file</a> <a href="http://cybernetnews.com/regex-bulk-file-renaming/">renamers</a>, I enjoy doing as much as I can from the command-line.</p>
<p>Since .NET exposes an easy to use library for regular expressions, I created a small C# command-line app that can rename files via any regular expression.</p>
<p><strong>Usage:</strong></p>
<pre style="padding-left: 30px;">RR.exe file-match search replace [/p]
  /p: pretend (show what will be renamed)</pre>
<p>You can use .NET regular expressions for the search and replacement strings, including substitutions (for example, &quot;$1&quot; is the 1st capture group in the search term).</p>
<p><strong>Examples:</strong></p>
<p>Simple rename without a regular expression:</p>
<pre style="padding-left: 30px;">RR.exe * .ext1 .ext2</pre>
<p>Renaming with a replacement of all &quot;-&quot; characters to &quot;_&quot;:</p>
<pre style="padding-left: 30px;">RR.exe * "-" "_"</pre>
<p>Remove all numbers from the file names:</p>
<pre style="padding-left: 30px;">RR.exe * "[0-9]+" ""</pre>
<p>Rename files in the pattern of &quot;123_xyz.txt&quot; to &quot;xyz_123.txt&quot;:</p>
<pre style="padding-left: 30px;">RR.exe *.txt "([0-9]+)_([a-z]+)" "$2_$1"</pre>
<p><strong>Download</strong></p>
<p>You can download RenameRegex (RR.exe) from <a href="https://github.com/nicjansma/rename-regex/releases">here</a>.  The full source of RenameRegex is also available at <a href="https://github.com/nicjansma/rename-regex">GitHub</a> if you want to fork or modify it. If you make changes, let me know!</p><p>The post <a href="https://nicj.net/windows-command-line-regular-expression-renaming-tool-renameregex/" target="_blank">Windows command-line regular expression renaming tool: RenameRegex</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/windows-command-line-regular-expression-renaming-tool-renameregex/feed/</wfw:commentRss>
			<slash:comments>34</slash:comments>
		
		
			</item>
		<item>
		<title>Auto-ban website spammers via the Apache access_log</title>
		<link>https://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/</link>
					<comments>https://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/#respond</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Wed, 25 Jan 2012 04:43:48 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1143</guid>

					<description><![CDATA[<p>During the past few months, several of my websites have been the target of some sort of SPAM attack.  After my getting alerted that my servers were under high load (from Cacti), I found that a small number of IP addresses were loading and re-loading or POSTing to the same pages over and over again.  [&#8230;]</p>
<p>The post <a href="https://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/" target="_blank">Auto-ban website spammers via the Apache access_log</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>During the past few months, several of my websites have been the target of some sort of SPAM attack.  After my getting alerted that my servers were under high load (from <a href="http://www.cacti.net/">Cacti</a>), I found that a small number of IP addresses were loading and re-loading or POSTing to the same pages over and over again.  In one of the attacks, they were simply reloading a page several times a second from multiple IP addresses.  In another attack, they were POSTing several megabytes of data to a form (which spent time validating the input), several times a second. I&#8217;m not sure of their motives &#8211; my guess is that they&#8217;re either trying to game search rankings (the POSTings) or someone with an improperly configured robot.</p>
<p>Since I didn&#8217;t have anything in-place to automatically drop requests from these rogue SPAMmers, the servers were coming under increasing load and causing real visitor&#8217;s page loads to slow down.</p>
<p>After looking at the server&#8217;s Apache&#8217;s access_log, I was able to narrow down the IPs causing the issue.  With their IP, I simply created a few <a href="http://www.netfilter.org/">iptables</a> rules to drop all packets from their IP addresses. Within a few seconds, the load on the server returned to normal.</p>
<p>I didn&#8217;t want to play catch-up the next time this happened, so I created a small script to automatically parse my server&#8217;s access_logs and auto-ban any IP address that appears to be doing inappropriate things.</p>
<p>The script is pretty simple.  It uses <code>tail</code> to look at the last <code>$LINESTOSEARCH</code> lines of the <code>access_log</code>, grabs all of the IPs via <code>awk</code>, <code>sorts</code> and counts them via <code>uniq</code>, then looks to see if any of these IPs had loaded more than <code>$THRESHOLD</code> pages.  If so, it does a quick query of <code>iptables</code> to see if the IP is already banned.  If not, it adds a single <code>INPUT</code> rule to <code>DROP</code> packets from that IP.</p>
<p>Here&#8217;s the code:</p>
<pre class="bash">
#!/bin/bash

#
# Config
#

# if more than the threshold, the IP will be banned
THRESHOLD=100

# search this many recent lines of the access log
LINESTOSEARCH=50000

# term to search for
SEARCHTERM=POST

# logfile to search
LOGFILE=/var/log/httpd/access_log

# email to alert upon banning
ALERTEMAIL=foo@foo.com

#
# Get the last n lines of the access_log, and search for the term.  Sort and count by IP, outputting the IP if it's
# larger than the threshold.
#
for ip in `tail -n $LINESTOSEARCH $LOGFILE | grep "$SEARCHTERM" | awk "{print \\$1}" | sort | uniq -c | sort -rn | head -20 | awk "{if (\\$1 &gt; $THRESHOLD) print \\$2}"`
do
    # Look in iptables to see if this IP is already banned
    if ! iptables -L INPUT -n | grep -q $ip
    then
        # Ban the IP
        iptables -A INPUT -s $ip -j DROP
        
        # Notify the alert email
        iptables -L -n | mail -s "Apache access_log banned '$SEARCHTERM': $ip" $ALERTEMAIL
    fi
done
</pre>
<p>You can put this in your crontab, so it runs every X minutes. The script will probably need root access to use <code>iptables</code>.</p>
<p>I have the script in <code>/etc/cron.10minutes</code> and a crontab entry to run all files in that directory every 10 minutes: <code>/etc/crontab</code>:<br />
<code>0,10,20,30,40,50 * * * * root run-parts /etc/cron.10minutes</code></p>
<p><strong>Warning:</strong> Ensure that the <code>$SEARCHTERM</code> you use will not match a wide set of pages that at web crawler (for example, Google) would see. In my case, I set <code>SEARCHTERM=POST</code>, because I know that Google will not be posting to my website as all of the forms are excluded from crawling via <a href="http://www.robotstxt.org/">robots.txt</a>.</p>
<p>The full code is also available at <a href="https://gist.github.com/1674688">Gist.GitHub</a> if you want to fork or modify it. It&#8217;s a rather simplistic, brute-force approach to banning rogue IPs, but it has worked for my needs. You could easily update the script to be a bit smarter. If you do, let me know!</p><p>The post <a href="https://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/" target="_blank">Auto-ban website spammers via the Apache access_log</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/auto-ban-website-spammers-via-the-apache-access_log/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Mounting VHDs in Windows 7 from a command-line script</title>
		<link>https://nicj.net/mounting-vhds-in-windows-7-from-a-command-line-script/</link>
					<comments>https://nicj.net/mounting-vhds-in-windows-7-from-a-command-line-script/#comments</comments>
		
		<dc:creator><![CDATA[Nic]]></dc:creator>
		<pubDate>Thu, 05 Jan 2012 02:41:47 +0000</pubDate>
				<category><![CDATA[Tech]]></category>
		<guid isPermaLink="false">http://nicj.net/?p=1135</guid>

					<description><![CDATA[<p>Windows 7 has native support for VHDs (virtual hard disks) built into the OS. VHDs are great for virtual machines, native VHD booting into recent Windows OSs, or even moving whole file systems around. While you can mount VHDs from the Windows 7 diskmgmt.msc GUI, or via vhdmount, if you need support for mounting or unmounting [&#8230;]</p>
<p>The post <a href="https://nicj.net/mounting-vhds-in-windows-7-from-a-command-line-script/" target="_blank">Mounting VHDs in Windows 7 from a command-line script</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Windows 7 has native support for <a href="http://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;frm=1&amp;source=web&amp;cd=4&amp;ved=0CEQQFjAD&amp;url=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FVHD_(file_format)&amp;ei=Mg0FT9agGOSniQKy84SvDg&amp;usg=AFQjCNHRI-1mPPZ82eAr8W56lWkU_vYHhA&amp;sig2=_up9pv9zNqPv_hn2NsNYSw">VHDs</a> (virtual hard disks) built into the OS. VHDs are great for virtual machines, <a href="http://www.google.com/url?sa=t&amp;rct=j&amp;q=&amp;esrc=s&amp;frm=1&amp;source=web&amp;cd=3&amp;ved=0CDYQFjAC&amp;url=http%3A%2F%2Fblogs.msdn.com%2Fb%2Fknom%2Farchive%2F2009%2F04%2F07%2Fwindows-7-vhd-boot-setup-guideline.aspx&amp;ei=RQ0FT5vzA6OSiQK4num3Dg&amp;usg=AFQjCNH0QWg7oNTepj_pAQL9CkRL1doVWQ&amp;sig2=JONe0gvBfOQIgj5SJk-jwA">native VHD booting into recent Windows OSs</a>, or even moving whole file systems around.</p>
<p>While you can mount VHDs from the Windows 7 <a href="http://www.sevenforums.com/tutorials/566-virtual-hard-disk-create-attach-vhd.html">diskmgmt.msc GUI</a>, or via <a href="http://technet.microsoft.com/en-us/library/cc708295(WS.10).aspx">vhdmount</a>, if you need support for mounting or unmounting VHDs from the command-line on a vanilla Windows 7 / Server 2008 install, you have to use diskpart.</p>
<p>diskpart&#8217;s mount commands are pretty simple:</p>
<pre style="padding-left: 30px;">C:\&gt; diskpart
DISKPART&gt; sel vdisk file="[location of vhd]"
DISKPART&gt; attach vdisk</pre>
<p>Unmounting is just as simple:</p>
<pre style="padding-left: 30px;">C:\&gt; diskpart
DISKPART&gt; sel vdisk file="[location of vhd]"
DISKPART&gt; detach vdisk</pre>
<p>These commands work fine on an ad-hoc basis, but I had the need to automate loading a VHD from a script.  Luckily, diskpart takes a single parameter, /s, which specifies a diskpart &#8220;script&#8221;.  The script is simply the command you would have typed in above:</p>
<pre style="padding-left: 30px;">C:\&gt; diskpart /s [diskpart script file]</pre>
<p>I&#8217;ve created two simple scripts, MountVHD.cmd and UnmountVHD.cmd that create a &#8220;diskpart script&#8221;, run it, then remove the temporary file.  This way, you can simply run MountVHD.cmd and point it to your VHD:</p>
<pre style="padding-left: 30px;">C:\&gt; MountVHD.cmd [location of vhd] [drive letter - optional]</pre>
<p>Or unmount the same VHD:</p>
<pre style="padding-left: 30px;">C:\&gt; UnMountVHD.cmd [location of vhd]</pre>
<p>These files are hosted at <a href="https://gist.github.com/9b82f28a77f306b0cfc0">Gist.Github</a> if you want to use them or contribute changes.</p><p>The post <a href="https://nicj.net/mounting-vhds-in-windows-7-from-a-command-line-script/" target="_blank">Mounting VHDs in Windows 7 from a command-line script</a> first appeared on <a href="https://nicj.net/" target="_blank">NicJ.net</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://nicj.net/mounting-vhds-in-windows-7-from-a-command-line-script/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/

Page Caching using redis 
Content Delivery Network via Amazon Web Services: CloudFront: o.nicj.net
Minified using redis

Served from: nicj.net @ 2024-01-28 13:00:15 by W3 Total Cache
-->