It has been a very busy period here at New Relic Synthetics. We have decided to take our current script runtime, the component that executes your monitors, and make it even more compatible with the latest Web technologies. Our goal is to ensure that we can provide you with results that are as close as possible to what a real customer experience would be when loading your website.

Current runtime overview

Our runtime is based on Node.js, and we pre-instantiate selenium-webdriver (i.e., the $browser variable), launching the browser that your script is going to control. The browser of choice is PhantomJS 1.9.x.

The only alteration to PhantomJS we did was to inject a Chrome-like User-Agent: this was done to ensure we don’t suffer server-side user-agent filtering. PhantomJS gave us a good WebKit implementation to work with, and lots of speed, in a browser that doesn’t require lots of resources.

The traffic generated by PhantomJS is then piped through a transparent Traffic Analysis Proxy. With that, we produce all the timings and sizing data that, ultimately, are what New Relic Synthetics is all about.

Here’s an overview of the PhantomJS-based runtime:

New Relic Synthetics PhantomJS-based runtime diagram

Unfortunately, over time it became evident that Synthetics’ success was exposing limitations in PhantomJS: a relatively old WebKit Engine, limited support for the latest Web standards and technologies, and lack of support for the Flash Player and SNI.

To ensure Synthetics customers get the most realistic service possible, we realized that we needed to replace PhantomJS with a production browser. (Note that I have been an active member of the development team for PhantomJS from the start: This decision wasn’t taken lightly.)

A new Chrome-based runtime

Yes, we are about to roll out a new runtime based on the Google Chrome browser. It comes with a more streamlined architecture (fewer moving parts) and leverages all the modern Web technologies that Chrome implements.

The most striking difference is that we will be able to work without the Traffic Analysis Proxy. This means even more accurate timing and sizing data, and no additional overhead. We will get our data directly from the Chrome network stack.

This is how it will look:

New Relic Synthetics Chrome-based runtime diagram

Because we will be consuming the data directly from inside Chrome, you can expect us to add new features and interesting new information over time to Synthetics. One example I can share right now is the ability to measure paint events in the browser. We plan to make good use of those.

Keeping up with Selenium

Our current runtime is based on the Node.js implementation of Selenium Webdriver. Selenium is a large and complex project; keeping up with the latest version of browser support and technology should be much easier using a WebDriver implementation with active (and paid) developers behind it.

Chrome uses ChromeDriver, which is developed and maintained by smart people at Google (and not just Google). Using that can free us to focus on making Synthetics even better.

The perks of using a modern browser

Using the latest stable version of Chrome will help bring lots of perks for our product. Here is a short list of new features, focused on the features you asked us:

  • Designed to ensure 100% compatibility with the most-used JS libraries (some customers reported PhantomJS having issues with Angular)
  • SNI support
  • Flash support
  • WebSockets support
  • The latest stable Chrome Blink Engine (supporting the latest HTML5 technologies)

Why Chrome?

Google Chrome is, today, the most-used browser worldwide.

Our aim is to make Synthetics as close as possible to a robot imitating your user—so, we have just listened to statistics. Plus, Chrome had all the right tools easily available to us to “hook it in.”

Extra APIs

Our runtime comes with a set of extra APIs that allow us to alter the traffic generated by the browser. You can now use

$browser.getHeaders() => keyValuesMap
$browser.addHeaders(keyValuesMap) => Promise
$browser.addHeader(key, value) => Promise
$browser.deleteHeaders(keysArray) => Promise
$browser.deleteHeader(key) => Promise
$browser.addHostnamesToBlacklist(hostnamesArray) => Promise
$browser.addHostnameToBlacklist(hostname) => Promise
$browser.deleteHostnamesFromBlacklist(hostnamesArray) => Promise //< NEW
$browser.deleteHostnameFromBlacklist(hostname) => Promise         //< NEW
$browser.addHostnamesToWhitelist(hostnamesArray) => Promise       //< NEW
$browser.addHostnameToWhitelist(hostname) => Promise             //< NEW
$browser.deleteHostnamesFromWhitelist(hostnamesArray) => Promise //< NEW
$browser.deleteHostnameFromWhitelist(hostname) => Promise         //< NEW

Some of those were already available, but we normalized the API to be Promise-based (like selenium-webdriver) and we added the ability to whitelist hostnames.

Node modules

One of the nicest things about our runtime is that we can preinstall some of the most-requested or most-famous NPM modules, so that you can use them in your scripts. The list of currently available modules includes:

{
...
"dependencies": {
"chai-webdriver": "0.9.3",
"chai": "1.9.1",
"crypto-js": "3.1.2-5",
"lodash": "2.4.1",
"mocha": "1.21.4",
"moment": "2.8.3",
"node-uuid": "1.4.1",
"nodeunit": "0.9.0",
"qunitjs": "1.15.0",
"should": "4.0.4",
"underscore": "1.7.0",
"validator": "3.18.1",
"faker": "2.0.1",
"consoleplusplus": "1.3.0",
"xml2js": "0.4.4",
"urllib": "2.3.0",
"urllib-sync": "1.0.1",
"q": "1.1.2",
"request": "2.53.0"
}
...
}

Sync vs. Async and tricky customer scripts

As seen above, Synthetics runs customer scripts in a “wrapped up” node runtime. To keep things simple and manageable, we do have a “post user script cleanup” in place. This means that the $browser variable that you get as well as the $http are both “cleaned up” at the end of your script.

For example, if your script looks something like:

$http.get("http://google.com", function() {
$browser.get("http://google.com");
});

you are submitting an http GET request, registering a callback for it and … that’s it! The script is over as far as we are concerned.

At that point, node has the “knowledge” (in the internal event-loop) that a callback is registered, and that’s why it doesn’t quit until the GET callback has been fulfilled. But from the perspective of our cleanup step, it’s time for us to begin cleanup:

1) shutdown the webdriver instance

2) gather logs

3) produce the final measurements

As you can imagine, the cleanup will always be faster than an HTTP GET: regardless of how fast google.com can reply, HTTP can’t match the speed of local JavaScript!

So by the time your callback is executed, the $browser variable has been destroyed; we have shut down the webdriver instance, and you are now unable to do what you want to do. How do we solve this?

After a deep investigation into NodeJS, we came up with a plan: Synchronous HTTP. This lets a script perform a “preparatory” step using a synchronous, blocking HTTP request, and then continue the execution with the “business logic.” And that’s why you can see urllib-sync in the list of allowed NPM modules above.

Yes, we know Node-purists may not like it. But we believe that useful software is more important than a software that follows dogmas.

Roll-out plan

The new Chrome-based runtime is available now for all new browser-based monitors. We are not switching existing monitors in the interest of consistency and stability. Existing customers who want to adopt it in their script will have to create a new one.

We would very much love to hear about your experience with the new runtime browser. Feel free to reach out via email to blog@newrelic.com or tweet us @NewRelic.

 

Background image courtesy of Shutterstock.com.

Ivan De Marino is a senior software engineer for New Relic. He's based in the United Kingdom. View posts by .

Interested in writing for New Relic Blog? Send us a pitch!