Improving Shipscope Performance with WebSockets

blog_header_codeship.png

In the post David McGaffin talks about how they improved Shipscope, a Google Chrome plugin that allows users to get Codeship build notifications with WebSockets.

Introduction

This article is part of

In this guest blog post David McGaffin, senior engineer at Codeship, talks about how they improved Shipscope, a Google Chrome extension that allows users to get Codeship build notifications; Codeship is a hosted Continuous Integration and Delivery service that speeds up software development teams by automating testing and deploying of their web applications.

David McGaffin is a senior engineer at Codeship. He likes coming up with new project ideas… and finishing some of them. He also likes learning new songs on his ukulele… and finishing some of those too. He’s from North Carolina but can’t fake a good Southern accent. You can follow him at @davidmcgaffin

Nearly a year ago, we released Shipscope, a Chrome extension for monitoring your projects on Codeship. We’ve received some really good feedback from our users, and I want to share some of that insight in this post.

In order to provide frequent status updates, Shipscope requested project status from our API server every 10 seconds. Multiply this by a rapidly growing userbase, and it really amounted to something.

There were two consequences of Shipscope’s frequent polling:

  • As Shipscope became more popular, the load on our API server grew. This was not a critical problem, but if we can reduce the number of requests to our API server while providing timely updates to users, then we should really try to do that.
  • Shipscope stayed pretty busy in the user’s browser, frequently requesting updates which most often would report no change to any build status. This is just wasteful use of CPU.

In our web-based UI, we have been using Pusher for a while to provide build progress updates to the browser via WebSockets. Using Pusher, our web client never needs to poll the server for build progress updates. The server sends a message on the socket when progress occurs in the build. The client then requests build info from the server and updates the UI. This provides immediate updates without any unnecessary polling.

For security reasons, we don’t send full build information across the WebSocket. We don’t want to send customer build info over a connection that could be unencrypted. Pusher allows payloads to be encrypted between the server and Pusher and then from Pusher to the client over the WebSocket.

However, that transfer between the HTTP port and the WebSocket port is not encrypted. Rather than trying to encrypt the build info before we send it, we choose to limit the data that we send over the WebSocket. So, the client is notified of an update, and the full update info is fetched securely over an HTTPS connection.

This approach has worked very well for us with our web client, so we decided we should give it a try with Shipscope. The first step is to get a Pusher app key, which is easy enough once you signup at Pusher’s website.

After installing Pusher’s JavaScript client, Shipscope initializes a Pusher instance like so:

1pusher = new Pusher(PUSHER_APP_KEY, {
2    authEndpoint: 'https://codeship.com/pusher/auth',
3    auth: {
4      params: {
5        api_key: options.api_key
6      }
7    },
8    encrypted: true
9})

The authEndpoint and auth properties allows us to verify that Shipscope can only subscribe to updates for which the api_key authorizes it. encrypted ensures that messages to and from Pusher are not in plain text.

Once we have a Pusher instance, we only need to subscribe to a channel. Each project has its own channel for publishing updates. So, if we are monitoring a project with id, 9876, then we’ll subscribe like this:

var channel = pusher.subscribe("private-project-9876")

and finally,

channel.bind(UPDATE_EVENT, onUpdate.bind(projectInfo))

will call onUpdate() with projectInfo bound to this every time a build starts or finishes in our project.

That saves a lot of effort for both Shipscope running in your browser and our API server, which now only gets requests when something interesting has happened.

One more thing that we get from Pusher is offline notification. If you somehow find yourself away from a wifi connection, the Pusher client fires a state_change event to let Shipscope know. We use this currently to change the Shipscope icon to red to let you know that Shipscope is offline.

Setting this up is as simple as:

1pusher.connection.bind('state_change', function() {
2  if (pusher.connection.state == 'connected') {
3    chrome.browserAction.setIcon({path: 'img/shipscope_icon_19.png'})
4    getShipscopeSummary()
5  } else {
6    chrome.browserAction.setIcon({path: 'img/shipscope_icon_19_error.png'})
7    chrome.browserAction.setBadgeText({text: ''})
8  }
9})

WebSockets have made a big difference to both our server and the Shipscope Chrome extension. Pusher made WebSockets easy. You can install Shipscope from the Chrome Web Store.