Analyze your frontend UI with LibratoClient. You can easily count, measure or time any event and submit it to Librato for graphing and alerts.
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.
- Create a server route for collecting UI metrics
- Setup LibratoClient in your frontend application
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
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!
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.
- Count when the modal opens
- Count when the modal closes
- Time how long it takes to submit a message
- Measure the length of the message
- 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();
});
}
};
The LibratoClient has three instruments increment
, measure
, and timing
.
All instruments are very flexible and are able to be used in several different
ways.
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
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=
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
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