Skip to content

luk3thomas/librato-client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

56 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Librato Client

NPM Package Stats Build Status

Analyze your frontend UI with LibratoClient. You can easily count, measure or time any event and submit it to Librato for graphing and alerts.

Getting Started

The LibratoClient is a frontend, JavaScript library designed to report metrics from your UI to a server side collection agent. You'll need to configure your server side collection agent to send the actual metrics to Librato.

  1. Create a server route for collecting UI metrics
  2. Setup LibratoClient in your frontend application

Create the server side route

The LibratoClient sends a JSON payload with four keys: type, metric, source, and value. The payload will look something like this:

{ type: 'timing',
  metric: 'ui.window.onload',
  source: 'mac.chrome.46',
  value: 1342 }

First, create a route handler that extracts data from the payload, and submits it to Librato. Let's assume we're using Ruby on Rails and we're using the librato-rails collection agent.

We'll need to route an endpoint, /collect to a route handler that submits our UI metrics to Librato.

Rails.application.routes.draw do
  post '/collect' => 'collection#collect'
end

The UI client sends a rather generic payload. In our route handler we'll repackage the data and submit it to the appropriate Librato instrument.

class CollectionController < ApplicationController

  def collect
    type      = params.delete 'type'
    metric    = params.delete 'metric'
    value     = params.delete 'value'
    source    = params.delete 'source'
    options   = { source: source }
    increment_options = { by: value,
                          sporadic: true,
                          source: options[:source] }

    case type
    when 'increment' then Librato.increment metric, increment_options
    when 'timing'    then Librato.timing    metric, value, options
    when 'measure'   then Librato.measure   metric, value, options
    end
    head :ok
  end
end

Setup LibratoClient in your frontend application

Now that we created a route we can add LibratoClient to our UI.

librato = new LibratoClient({
  endpoint: '/collect',
  prefix: 'ui',
  source: 'platform.browser.version',
});

Once we create the client we can begin using it to submit metrics to our endpoint.

librato.measure('foo')

When window.onload invokes the timer it will POST the following payload to our /collect endpoint:

{ type: 'measure',
  metric: 'ui.foo',
  source: 'mac.chrome.46',
  value: 986 }

That's it!

Advanced Example

The LibratoClient works well in a component based archetectiure. For example let's assume we have a modal. In the modal we are able to send a message. There are a few things we want to measure with our new modal.

  1. Count when the modal opens
  2. Count when the modal closes
  3. Time how long it takes to submit a message
  4. Measure the length of the message
  5. Count if the user wasn't able to send a message
function MessageModal() {
  this.librato   = librato.metric('modal.message');  // Sets the base metrics to `modal.message`
  this.messenger = new Messenger()
}

MessageModal.prototype = {

  open: function() {
    // Open the modal ...

    // Count when the modal opens
    this.librato.increment('toggle', { source: 'open' });

    /* Would send this payload to /collect:
     *  { type: 'increment',
     *    metric: 'ui.modal.message.toggle',
     *    source: 'open',
     *    value: 1 }
     */
  },

  close: function() {
    // Close the modal ...

    // Count when the modal closes
    this.librato.increment('toggle', { source: 'close' });

    /* Would send this payload to /collect:
     *  { type: 'increment',
     *    metric: 'ui.modal.message.toggle',
     *    source: 'close',
     *    value: 1 }
     */
  },

  submit: function(message) {
    this.messenger.send(message)

      // Time how long it takes to submit a message
      .then(librato.timing('time'))
      /* If the request is successful our timer will send the following payload
       * to the server:
       *  { type: 'timing',
       *    metric: 'ui.modal.message.time',
       *    source: 'mac.chrome.46',
       *    value: 435 }
       */

      .catch(function() {

        // Count if the user wasn't able to send a message
        librato.increment('submit.error')
        /* If the request is unsuccessful our incrementer will send the
         * following payload to the server:
         *  { type: 'increment',
         *    metric: 'ui.modal.message.submit.error',
         *    source: 'mac.chrome.46',
         *    value: 1 } */
      })

      // Measure the length of the message
      .finally(function(){
        librato.measure('length', message.length);
        /* Under any condition we'll send the message length to the server.
         *  { type: 'measure',
         *    metric: 'ui.modal.message.length',
         *    source: 'mac.chrome.46',
         *    value: 14 }
         */
        this.close();
      });
  }
};

Instruments

The LibratoClient has three instruments increment, measure, and timing. All instruments are very flexible and are able to be used in several different ways.

Increment

You may use any of the following strategies for incrementing the metric foo.count.

librato.increment('foo.count');                  // metric=foo.count, value=1, source=
librato.increment('foo.count', 5);               // metric=foo.count, value=5, source=
librato.increment('foo.count', { value: 5 });    // metric=foo.count, value=5, source=

You may also set a specific source:

librato.increment('foo.count', { value: 5, source: 'bar' }); // metric=foo.count, value=5, source=bar

The increment is also curryable, so you can partially apply the metric name and send the counter later.

foo = librato.metric('foo')

foo.increment();                                         // metric=foo,       value=1, source=
foo.increment(5);                                        // metric=foo,       value=5, source=

When you curry the increment you can also apply a base metric name. It is helpful to use a base metric if you are instrumenting several different metrics.

foo = librato.metric('foo')

foo.increment('count');                                  // metric=foo.count, value=1, source=
foo.increment('count', 5);                               // metric=foo.count, value=5, source=
foo.increment('count', { value: 5 });                    // metric=foo.count, value=5, source=
foo.increment('count', { value: 5, source: 'baz' });     // metric=foo.count, value=5, source=baz

If you simply want to increment by one you can pass the increment instrument to a callback. The increment count is collected when the callback is invoked.

successCount = librato.metric('foo.success.count')
errorCount   = librato.metric('foo.error.count')

SomePromise().then(doSomething)
             .then(doSomethingElse)
             .then(successCount)    // Increment on success
             .catch(errorCount)     // Increment error on failure

Measure

The measure instrument is similar to the increment instrument except it expects a value in the second parameter.

librato.measure('foo', 5);                                    // metric=foo, value=5, source=
librato.measure('foo', { value: 5 });                         // metric=foo, value=5, source=
librato.measure('foo', { value: 5, source: 'bar' });          // metric=foo, value=5, source=bar

librato.metric('foo').measure(5);                             // metric=foo, value=5, source=
librato.metric('foo').measure({ value: 5 });                  // metric=foo, value=5, source=
librato.metric('foo').measure({ value: 5, source: 'bar' });   // metric=foo, value=5, source=bar

The measure method is also curryable. You can all the measure method with a metric name, and then you may call it a second time with the value. The measure instrument will not send data to the endpoint until it has both a metric and a value.

foo = librato.measure('foo')

foo.measure(9)                   // metric=foo, value=9, source=
foo.measure(8)                   // metric=foo, value=8, source=

Timing

The timing instrument collects a timing measure. You may partially evaluate a timing measure and send automatically calculate the time.

window.onload = librato.timing('window.onload')                 // metric=window.onload, value=1432, source=
window.onload = librato.source.('foo').timing('window.onload')  // metric=window.onload, value=1432, source=foo

In the case of a metric like window.onload want to measure the time from first byte until the page loads. One way to do this is to laod the librato client near the top of the page and attach a timing instrument to the callback. Obviously that isn't good for performance. We want to load all the scripts near the bottom of the document.

The timing instrument has the ability to backdate the start time.

In the <head> of the document

  <script>
    window._start = new Date();
  </script>
</head>

Then later on after we've initialized the library we can attach the listener.

window.onload = librato.timing('window.onload', { start: window._start })    // metric=window.onload, value=1432, source=

You may partially evaluate a timing measure and send send the time explicitly. The next time the timer is invoked it will calculate the time difference in milliseconds and send it to the endpoint.

Invoking with a promise:

done = librato.metric('foo').timing('time');

getAsync().then(done);                               // metric=foo.time,  value=231

Invoking as a callback:

// or as a callback
done = librato.metric('foo').timing('time');

getAsync(function(results) {
  doSomething(results);
  done();                                            // metric=foo.time,  value=231
});

You may also time blocks of code. The first parameter is a function done. When you invoke done the timing instrument will send metrics to /collect.

librato.timing('foo', function(done){
  doSomethingAsync(function(result){
    somethingElse(result);
    done();                                          // metric=foo, value=1432, source=
  });
});

You may explicitely send a timing measure directly.

librato.timing('foo.timing', 2314);                           // metric=foo, value=2314, source=
librato.timing('foo.timing', { value: 2314 });                // metric=foo, value=2314, source=
librato.timing('foo.timing', { value: 2314, source: 'bar' }); // metric=foo, value=2314, source=bar

librato.metric('foo').timing(2314);                           // metric=foo, value=2314, source=
librato.metric('foo').timing({ value: 2314 });                // metric=foo, value=2314, source=
librato.metric('foo').timing({ value: 2314, source: 'bar' }); // metric=foo, value=2314, source=bar

Configuring and forking the client

The librato client can update and change its configuration at any time. A new client is returned each time the settings are modified. Invoking metric or source does not mutate the original settings of your librato client instance.

You can think of the metric method as a base metric. Sometimes it is helpful to categorize metric names. For example, we might name our AWS EC2 metrics with a base prefix of AWS.EC2.

AWS.EC2.CPUCreditBalance
AWS.EC2.CPUCreditUsage
AWS.EC2.CPUUtilization
librato.metric('foo').increment('bar');   // metric=foo.bar, value=1
librato.metric('foo').increment();        // metric=foo,     value=1

You'll have to save the returned client if you want to use it with any of the new settings.

tracker = librato.metric('foo')
tracker.increment('bar')                  // metric=foo.bar, value=1
librato.increment('bar')                  // metric=bar,     value=1
tracker === librato                       // false