summaryrefslogtreecommitdiff
path: root/jarmon.js
diff options
context:
space:
mode:
authorRichard Wall <richard@aziz>2010-08-22 14:32:08 +0100
committerRichard Wall <richard@aziz>2010-08-22 14:32:08 +0100
commit8e973280259e070c287503d5636fe7d2e61d2f98 (patch)
tree6abb59b9175e17ec58f301d99426c682cbae6d9a /jarmon.js
parenta8dda6336615028fc08e0833700cded8cd2544dc (diff)
move jarmon.js into a subfolder so that we can point yuidoc at only that file
Diffstat (limited to 'jarmon.js')
-rw-r--r--jarmon.js954
1 files changed, 0 insertions, 954 deletions
diff --git a/jarmon.js b/jarmon.js
deleted file mode 100644
index 44c4dad..0000000
--- a/jarmon.js
+++ /dev/null
@@ -1,954 +0,0 @@
-/**
- * Copyright (c) 2010 Richard Wall <richard (at) the-moon.net>
- * See LICENSE for details.
- *
- * Wrappers and convenience fuctions for working with the javascriptRRD, jQuery,
- * and Flot charting packages.
- *
- * Designed to work well with the RRD files generated by Collectd:
- * - http://collectd.org/
- *
- * Requirements:
- * - JavascriptRRD: http://javascriptrrd.sourceforge.net/
- * - jQuery: http://jquery.com/
- * - Flot: http://code.google.com/p/flot/
- * - MochiKit.Async: http://www.mochikit.com/
- *
- * @module jarmon
- */
-
-/**
- * A namespace for Jarmon
- *
- * @class jarmon
- * @static
- */
-if(typeof jarmon == 'undefined') {
- var jarmon = {};
-}
-
-jarmon.downloadBinary = function(url) {
- /**
- * Download a binary file asynchronously using the jQuery.ajax function
- *
- * @method downloadBinary
- * @param url {String} The url of the object to be downloaded
- * @return {Object} A deferred which will callback with an instance of javascriptrrd.BinaryFile
- */
-
- var d = new MochiKit.Async.Deferred();
-
- $.ajax({
- _deferredResult: d,
- url: url,
- dataType: 'text',
- cache: false,
- beforeSend: function(request) {
- try {
- request.overrideMimeType('text/plain; charset=x-user-defined');
- } catch(e) {
- // IE doesn't support overrideMimeType
- }
- },
- success: function(data) {
- try {
- this._deferredResult.callback(new BinaryFile(data));
- } catch(e) {
- this._deferredResult.errback(e);
- }
- },
- error: function(xhr, textStatus, errorThrown) {
- // Special case for IE which handles binary data slightly
- // differently.
- if(textStatus == 'parsererror') {
- if (typeof xhr.responseBody != 'undefined') {
- return this.success(xhr.responseBody);
- }
- }
- this._deferredResult.errback(new Error(xhr.status));
- }
- });
- return d;
-};
-
-
-jarmon.localTimeFormatter = function (v, axis) {
- /**
- * Copied from jquery.flot.js and modified to allow timezone
- * adjustment.
- *
- * @method localTimeFormatter
- * @param v {Number} The timestamp to be formatted
- * @param axis {Object} A hash containing information about the time axis
- * @return {String} The formatted datetime string
- **/
- // map of app. size of time units in milliseconds
- var timeUnitSize = {
- "second": 1000,
- "minute": 60 * 1000,
- "hour": 60 * 60 * 1000,
- "day": 24 * 60 * 60 * 1000,
- "month": 30 * 24 * 60 * 60 * 1000,
- "year": 365.2425 * 24 * 60 * 60 * 1000
- };
-
- // Offset the input timestamp by the user defined amount
- var d = new Date(v + axis.options.tzoffset);
-
- // first check global format
- if (axis.options.timeformat != null)
- return $.plot.formatDate(d, axis.options.timeformat, axis.options.monthNames);
-
- var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
- var span = axis.max - axis.min;
- var suffix = (axis.options.twelveHourClock) ? " %p" : "";
-
- if (t < timeUnitSize.minute)
- fmt = "%h:%M:%S" + suffix;
- else if (t < timeUnitSize.day) {
- if (span < 2 * timeUnitSize.day)
- fmt = "%h:%M" + suffix;
- else
- fmt = "%b %d %h:%M" + suffix;
- }
- else if (t < timeUnitSize.month)
- fmt = "%b %d";
- else if (t < timeUnitSize.year) {
- if (span < timeUnitSize.year)
- fmt = "%b";
- else
- fmt = "%b %y";
- }
- else
- fmt = "%y";
-
- return $.plot.formatDate(d, fmt, axis.options.monthNames);
-};
-
-
-/**
- * A wrapper around an instance of javascriptrrd.RRDFile which provides a
- * convenient way to query the RRDFile based on time range, RRD data source (DS)
- * and RRD consolidation function (CF).
- *
- * @class jarmon.RrdQuery
- * @constructor
- * @param rrd {Object} A javascriptrrd.RRDFile
- * @param unit {String} The unit symbol for this data series
- **/
-jarmon.RrdQuery = function(rrd, unit) {
- this.rrd = rrd;
- this.unit = unit;
-};
-
-jarmon.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) {
- /**
- * Generate a Flot compatible data object containing rows between start and
- * end time. The rows are taken from the first RRA whose data spans the
- * requested time range.
- *
- * @method getData
- * @param startTime {Number} start timestamp
- * @param endTime {Number} end timestamp
- * @param dsId {Variant} identifier of the RRD datasource (string or number)
- * @param cfName {String} The name of an RRD consolidation function (CF)
- * eg AVERAGE, MIN, MAX
- * @return {Object} A Flot compatible data series
- * eg label: '', data: [], unit: ''
- **/
- var startTimestamp = startTime/1000;
-
- var lastUpdated = this.rrd.getLastUpdate();
- var endTimestamp = lastUpdated;
- if(endTime) {
- endTimestamp = endTime/1000;
- // If end time stamp is beyond the range of this rrd then reset it
- if(lastUpdated < endTimestamp) {
- endTimestamp = lastUpdated;
- }
- }
-
- if(dsId == null) {
- dsId = 0;
- }
- var ds = this.rrd.getDS(dsId);
-
- if(cfName == null) {
- cfName = 'AVERAGE';
- }
-
- var rra, step, rraRowCount, firstUpdated;
-
- for(var i=0; i<this.rrd.getNrRRAs(); i++) {
- // Look through all RRAs looking for the most suitable
- // data resolution.
- rra = this.rrd.getRRA(i);
-
- // If this rra doesn't use the requested CF then move on to the next.
- if(rra.getCFName() != cfName) {
- continue;
- }
-
- step = rra.getStep();
- rraRowCount = rra.getNrRows();
- firstUpdated = lastUpdated - (rraRowCount - 1) * step;
- // We assume that the RRAs are listed in ascending order of time range,
- // therefore the first RRA which contains the range minimum should give
- // the highest resolution data for this range.
- if(firstUpdated <= startTimestamp) {
- break;
- }
- }
- // If we got to the end of the loop without ever defining step, it means
- // that the CF check never succeded.
- if(!step) {
- throw new Error('Unrecognised consolidation function: ' + cfName);
- }
-
- var startRow = rraRowCount - parseInt((lastUpdated - Math.max(startTimestamp, firstUpdated))/step) - 1;
- var endRow = rraRowCount - parseInt((lastUpdated - endTimestamp)/step) - 1;
-
- var flotData = [];
- var timestamp = firstUpdated + (startRow - 1) * step;
- var dsIndex = ds.getIdx();
- for (var i=startRow; i<=endRow; i++) {
- flotData.push([timestamp*1000.0, rra.getEl(i, dsIndex)]);
- timestamp += step;
- }
-
- // Now get the date of the earliest record in entire rrd file, ie that of
- // the last (longest range) rra.
- rra = this.rrd.getRRA(this.rrd.getNrRRAs()-1);
- firstUpdated = lastUpdated - (rra.getNrRows() -1) * rra.getStep();
-
- return {'label': ds.getName(), 'data': flotData, 'unit': this.unit,
- 'firstUpdated': firstUpdated*1000.0,
- 'lastUpdated': lastUpdated*1000.0};
-};
-
-/**
- * A wrapper around RrdQuery which provides asynchronous access to the data in a
- * remote RRD file.
- *
- * @class jarmon.RrdQueryRemote
- * @constructor
- * @param url {String} The url of a remote RRD file
- * @param unit {String} The unit suffix of this data eg 'bit/sec'
- * @param downloader {Function} A callable which returns a Deferred and calls
- * back with a javascriptrrd.BinaryFile when it has downloaded.
- **/
-jarmon.RrdQueryRemote = function(url, unit, downloader) {
- this.url = url;
- this.unit = unit;
- this.downloader = downloader;
- this.lastUpdate = 0;
- this._download = null;
-};
-
-jarmon.RrdQueryRemote.prototype.getData = function(startTime, endTime, dsId) {
- /**
- * Return a Flot compatible data series asynchronously.
- *
- * @method getData
- * @param startTime {Number} The start timestamp
- * @param endTime {Number} The end timestamp
- * @param dsId {Variant} identifier of the RRD datasource (string or number)
- * @return {Object} A Deferred which calls back with a flot data series.
- **/
- var endTimestamp = endTime/1000;
-
- // Download the rrd if there has never been a download or if the last
- // completed download had a lastUpdated timestamp less than the requested
- // end time.
- // Don't start another download if one is already in progress.
- if(!this._download || (this._download.fired > -1 && this.lastUpdate < endTimestamp )) {
- this._download = this.downloader(this.url)
- .addCallback(
- function(self, binary) {
- // Upon successful download convert the resulting binary
- // into an RRD file and pass it on to the next callback
- // in the chain.
- var rrd = new RRDFile(binary);
- self.lastUpdate = rrd.getLastUpdate();
- return rrd;
- }, this);
- }
-
- // Set up a deferred which will call getData on the local RrdQuery object
- // returning a flot compatible data object to the caller.
- var ret = new MochiKit.Async.Deferred().addCallback(
- function(self, startTime, endTime, dsId, rrd) {
- return new jarmon.RrdQuery(rrd, self.unit).getData(startTime, endTime, dsId);
- }, this, startTime, endTime, dsId);
-
- // Add a pair of callbacks to the current download which will callback the
- // result which we setup above.
- this._download.addBoth(
- function(ret, res) {
- if(res instanceof Error) {
- ret.errback(res);
- } else {
- ret.callback(res);
- }
- return res;
- }, ret);
-
- return ret;
-};
-
-/**
- * Wraps RrdQueryRemote to provide access to a different RRD DSs within a
- * single RrdDataSource.
- *
- * @class jarmon.RrdQueryDsProxy
- * @constructor
- * @param rrdQuery {Object} An RrdQueryRemote instance
- * @param dsId {Variant} identifier of the RRD datasource (string or number)
- **/
-jarmon.RrdQueryDsProxy = function(rrdQuery, dsId) {
- this.rrdQuery = rrdQuery;
- this.dsId = dsId;
- this.unit = rrdQuery.unit;
-};
-
-jarmon.RrdQueryDsProxy.prototype.getData = function(startTime, endTime) {
- /**
- * Call I{RrdQueryRemote.getData} with a particular dsId
- *
- * @method getData
- * @param startTime {Number} A unix timestamp marking the start time
- * @param endTime {Number} A unix timestamp marking the start time
- * @return {Object} A Deferred which calls back with a flot data series.
- **/
- return this.rrdQuery.getData(startTime, endTime, this.dsId);
-};
-
-
-/**
- * A class for creating a Flot chart from a series of RRD Queries
- *
- * @class jarmon.Chart
- * @constructor
- * @param template {Object} A jQuery containing a single element into which the
- * chart will be drawn
- * @param options {Object} Flot options which control how the chart should be
- * drawn.
- **/
-jarmon.Chart = function(template, options) {
- this.template = template;
- this.options = jQuery.extend(true, {yaxis: {}}, options);
-
- this.data = [];
-
- var self = this;
-
-
- // Listen for clicks on the legend items - onclick enable / disable the
- // corresponding data source.
- $('.graph-legend .legendItem', this.template[0]).live('click', function(e) {
- self.switchDataEnabled($(this).text());
- self.draw();
- });
-
-
- this.options['yaxis']['ticks'] = function(axis) {
- /*
- * Choose a suitable SI multiplier based on the min and max values from
- * the axis and then generate appropriate yaxis tick labels.
- *
- * @param axis: An I{Object} with min and max properties
- * @return: An array of ~5 tick labels
- */
- var siPrefixes = {
- 0: '',
- 1: 'K',
- 2: 'M',
- 3: 'G',
- 4: 'T'
- }
- var si = 0;
- while(true) {
- if( Math.pow(1000, si+1)*0.9 > axis.max ) {
- break;
- }
- si++;
- }
-
- var minVal = axis.min/Math.pow(1000, si);
- var maxVal = axis.max/Math.pow(1000, si);
-
- var stepSizes = [0.01, 0.05, 0.1, 0.25, 0.5, 1, 5, 10, 25, 50, 100, 250];
- var realStep = (maxVal - minVal)/5.0;
-
- var stepSize, decimalPlaces = 0;
- for(var i=0; i<stepSizes.length; i++) {
- stepSize = stepSizes[i]
- if( realStep < stepSize ) {
- if(stepSize < 10) {
- decimalPlaces = 2;
- }
- break;
- }
- }
-
- if(self.options.yaxis.tickDecimals != null) {
- decimalPlaces = self.options.yaxis.tickDecimals;
- }
-
- var tickMin = minVal - minVal % stepSize;
- var tickMax = maxVal - maxVal % stepSize + stepSize
-
- var ticks = [];
- for(var j=tickMin; j<=tickMax; j+=stepSize) {
- ticks.push([
- j*Math.pow(1000, si),
- j.toFixed(decimalPlaces)
- ]);
- }
-
- self.siPrefix = siPrefixes[si];
-
- return ticks;
- };
-};
-
-jarmon.Chart.prototype.addData = function(label, db, enabled) {
- /**
- * Add details of a remote RRD data source whose data will be added to this
- * chart.
- *
- * @method addData
- * @param label {String} The label for this data which will be shown in the
- * chart legend
- * @param db {String} The url of the remote RRD database
- * @param enabled {Boolean} true if you want this data plotted on the chart,
- * false if not.
- **/
- if(typeof enabled == 'undefined') {
- enabled = true;
- }
- this.data.push([label, db, enabled]);
-};
-
-jarmon.Chart.prototype.switchDataEnabled = function(label) {
- /**
- * Enable / Disable a single data source
- *
- * @method switchDataEnabled
- * @param label {String} The label of the data source to be enabled /
- * disabled.
- **/
- for(var i=0; i<this.data.length; i++) {
- if(this.data[i][0] == label) {
- this.data[i][2] = !this.data[i][2];
- }
- }
-};
-
-jarmon.Chart.prototype.setTimeRange = function(startTime, endTime) {
- /**
- * Alter the time range of this chart and redraw
- *
- * @method setTimeRange
- * @param startTime {Number} The start timestamp
- * @param endTime {Number} The end timestamp
- **/
- this.startTime = startTime;
- this.endTime = endTime;
- return this.draw();
-}
-
-jarmon.Chart.prototype.draw = function() {
- /**
- * Draw the chart
- * A 'chart_loading' event is triggered before the data is requested
- * A 'chart_loaded' event is triggered when the chart has been drawn
- *
- * @method draw
- * @return {Object} A Deferred which calls back with the chart data when
- * the chart has been rendered.
- **/
- this.template.addClass('loading');
-
- var result;
- var results = [];
- for(var i=0; i<this.data.length; i++) {
- if(this.data[i][2]) {
- result = this.data[i][1].getData(this.startTime, this.endTime);
- } else {
- // If the data source has been marked as disabled return a fake
- // empty dataset
- // 0 values so that it can contribute to a stacked chart.
- // 0 linewidth so that it doesn't cause a line in stacked chart
- result = new MochiKit.Async.Deferred();
- result.callback({
- data: [
- [this.startTime, 0],
- [this.endTime, 0]
- ],
- lines: {
- lineWidth: 0
- }
- });
- }
-
- results.push(result);
- }
-
- return MochiKit.Async.gatherResults(results)
- .addCallback(
- function(self, data) {
- var i, label, disabled = [];
- unit = '';
- for(i=0; i<data.length; i++) {
- label = self.data[i][0];
- if(label) {
- data[i].label = label;
- }
- if(typeof data[i].unit != 'undefined') {
- // Just use the last unit for now
- unit = data[i].unit;
- }
- if(!self.data[i][2]) {
- disabled.push(label);
- }
- }
-
- $.plot(self.template.find('.chart'), data, self.options);
-
- var yaxisUnitLabel = $('<div>').text(self.siPrefix + unit)
- .css({width: '100px',
- position: 'absolute',
- top: '80px',
- left: '-90px',
- 'text-align': 'right'});
- self.template.find('.chart').append(yaxisUnitLabel);
-
- // Manipulate and move the flot generated legend to an
- // alternative position.
- // The default legend is formatted as an HTML table, so we
- // grab the contents of the cells and turn them into
- // divs.
- // Actually, formatting the legend first as a one column
- // table is useful as it generates an optimum label element
- // width which we can copy to the new divs + a little extra
- // to accomodate the color box
- var legend = self.template.find('.graph-legend');
- legend.empty();
- self.template.find('.legendLabel')
- .each(function(i, el) {
- var orig = $(el);
- var label = orig.text();
- var newEl = $('<div />')
- .attr('class', 'legendItem')
- .attr('title', 'Data series switch - click to turn this data series on or off')
- .width(orig.width()+20)
- .text(label)
- .prepend(orig.prev().find('div div').clone().addClass('legendColorBox'))
- .appendTo(legend);
- // The legend label is clickable - to enable /
- // disable different data series. The disabled class
- // results in a label formatted with strike though
- if( $.inArray(label, disabled) > -1 ) {
- newEl.addClass('disabled');
- }
- })
- .remove();
- legend.append($('<div />').css('clear', 'both'));
- self.template.find('.legend').remove();
-
- yaxisUnitLabel.position(self.template.position());
- return data;
- }, this)
- .addErrback(
- function(self, failure) {
- self.template.text('error: ' + failure.message);
- }, this)
- .addBoth(
- function(self, res) {
- self.template.removeClass('loading');
- return res;
- }, this);
-};
-
-
-jarmon.Chart.fromRecipe = function(recipes, templateFactory, downloader) {
- /**
- * A static factory method to generate a list of I{Chart} from a list of
- * recipes and a list of available rrd files in collectd path format.
- *
- * @method fromRecipe
- * @param recipes {Array} A list of recipe objects.
- * @param templateFactory {Function} A callable which generates an html
- * template for a chart.
- * @param downloader {Function} A download function which returns a Deferred
- * @return {Array} A list of Chart objects
- **/
-
- var charts = [];
- var dataDict = {};
-
- var recipe, chartData, template, c, i, j, ds, label, rrd, unit, re, match;
-
- for(i=0; i<recipes.length; i++) {
- recipe = recipes[i];
- chartData = [];
-
- for(j=0; j<recipe['data'].length; j++) {
- rrd = recipe['data'][j][0];
- ds = recipe['data'][j][1];
- label = recipe['data'][j][2];
- unit = recipe['data'][j][3];
- if(typeof dataDict[rrd] == 'undefined') {
- dataDict[rrd] = new jarmon.RrdQueryRemote(rrd, unit, downloader);
- }
- chartData.push([label, new jarmon.RrdQueryDsProxy(dataDict[rrd], ds)]);
- }
- if(chartData.length > 0) {
- template = templateFactory();
- template.find('.title').text(recipe['title']);
- c = new jarmon.Chart(template, recipe['options']);
- for(j=0; j<chartData.length; j++) {
- c.addData.apply(c, chartData[j]);
- }
- charts.push(c);
- }
- }
- return charts;
-};
-
-
-// Options common to all the chart on this page
-jarmon.Chart.BASE_OPTIONS = {
- grid: {
- clickable: false,
- borderWidth: 1,
- borderColor: "#000",
- color: "#000",
- backgroundColor: "#fff",
- tickColor: "#eee"
- },
- legend: {
- position: 'nw',
- noColumns: 1
- },
- selection: {
- mode: 'x'
- },
- series: {
- points: { show: false },
- lines: {
- show: true,
- steps: false,
- shadowSize: 0,
- lineWidth: 1
- },
- shadowSize: 0
- },
- xaxis: {
- mode: "time",
- tickFormatter: jarmon.localTimeFormatter
- }
-};
-
-// Extra options to generate a stacked chart
-jarmon.Chart.STACKED_OPTIONS = {
- series: {
- stack: true,
- lines: {
- fill: 0.5
- }
- }
-};
-
-
-// A selection of useful time ranges
-jarmon.timeRangeShortcuts = [
- ['last hour', function(now) { return [now-60*60*1000*1, now]; }],
- ['last 3 hours', function(now) { return [now-60*60*1000*3, now]; }],
- ['last 6 hours', function(now) { return [now-60*60*1000*6, now]; }],
- ['last 12 hours', function(now) { return [now-60*60*1000*12, now]; }],
- ['last day', function(now) { return [now-60*60*1000*24, now]; }],
- ['last week', function(now) { return [now-60*60*1000*24*7, now]; }],
- ['last month', function(now) { return [now-60*60*1000*24*31, now]; }],
- ['last year', function(now) { return [now-60*60*1000*24*365, now]; }]
-];
-
-
-/**
- * Presents the user with a form and a timeline with which they can choose a
- * time range and co-ordinates the refreshing of a series of charts.
- *
- * @class jarmon.ChartCoordinator
- * @constructor
- * @param ui {Object} A one element jQuery containing an input form and
- * placeholders for the timeline and for the series of charts.
- **/
-jarmon.ChartCoordinator = function(ui) {
- var self = this;
- this.ui = ui;
- this.charts = [];
-
- // Style and configuration of the range timeline
- this.rangePreviewOptions = {
- grid: {
- borderWidth: 1
- },
- selection: {
- mode: 'x'
- },
- xaxis: {
- mode: 'time',
- tickFormatter: jarmon.localTimeFormatter
- },
- yaxis: {
- ticks: []
- }
- };
-
- var options = this.ui.find('select[name="from_standard"]');
- for(var i=0; i<jarmon.timeRangeShortcuts.length; i++) {
- options.append($('<option />').text(jarmon.timeRangeShortcuts[i][0]));
- }
-
- // Append a custom option for when the user selects an area of the graph
- options.append($('<option />').text('custom'));
- // Select the first shortcut by default
- options.val(jarmon.timeRangeShortcuts[0][0]);
-
- options.bind('change', function(e) {
- // No point in updating if the user chose custom.
- if($(this).val() != 'custom') {
- self.update();
- }
- });
-
- // Update the time ranges and redraw charts when the custom datetime inputs
- // are changed
- this.ui.find('[name="from_custom"]').bind('change',
- function(e) {
- self.ui.find('[name="from_standard"]').val('custom');
- var tzoffset = parseInt(self.ui.find('[name="tzoffset"]').val());
- self.setTimeRange(new Date(this.value + ' UTC').getTime() - tzoffset, null);
- self.update();
- }
- );
-
- this.ui.find('[name="to_custom"]').bind('change',
- function(e) {
- self.ui.find('[name="from_standard"]').val('custom');
- var tzoffset = parseInt(self.ui.find('[name="tzoffset"]').val());
- self.setTimeRange(null, new Date(this.value + ' UTC').getTime() - tzoffset);
- self.update();
- }
- );
-
- // Populate a list of tzoffset options if the element is present in the
- // template as a select list
- tzoffsetEl = this.ui.find('[name="tzoffset"]');
- if(tzoffsetEl.is('select')) {
- var label, val;
- for(var i=-12; i<=12; i++) {
- label = 'UTC';
- val = i;
- if(val >= 0) {
- label += ' + ';
- } else {
- label += ' - ';
- }
- val = Math.abs(val).toString();
- if(val.length == 1) {
- label += '0';
- }
- label += val + '00';
- tzoffsetEl.append($('<option />').attr('value', i*60*60*1000).text(label));
- }
-
- tzoffsetEl.bind('change', function(e) {
- self.update();
- });
- }
-
- // Default timezone offset based on localtime
- var tzoffset = -1 * new Date().getTimezoneOffset() * 60 * 1000;
- tzoffsetEl.val(tzoffset);
-
- // Update the time ranges and redraw charts when the form is submitted
- this.ui.find('[name="action"]').bind('click', function(e) {
- self.update();
- return false;
- });
-
- // When a selection is made on the range timeline, or any of my charts
- // redraw all the charts.
- this.ui.bind("plotselected", function(event, ranges) {
- self.ui.find('[name="from_standard"]').val('custom');
- self.setTimeRange(ranges.xaxis.from, ranges.xaxis.to);
- self.update();
- });
-};
-
-
-jarmon.ChartCoordinator.prototype.update = function() {
- /**
- * Grab the start and end time from the ui form, highlight the range on the
- * range timeline and set the time range of all the charts and redraw.
- *
- * @method update
- **/
-
- var selection = this.ui.find('[name="from_standard"]').val();
-
- var now = new Date().getTime();
- for(var i=0; i<jarmon.timeRangeShortcuts.length; i++) {
- if(jarmon.timeRangeShortcuts[i][0] == selection) {
- range = jarmon.timeRangeShortcuts[i][1](now);
- this.setTimeRange(range[0], range[1]);
- break;
- }
- }
-
- var startTime = parseInt(this.ui.find('[name="from"]').val());
- var endTime = parseInt(this.ui.find('[name="to"]').val());
- var tzoffset = parseInt(this.ui.find('[name="tzoffset"]').val());
-
- this.ui.find('[name="from_custom"]').val(
- new Date(startTime + tzoffset).toUTCString().split(' ').slice(1,5).join(' '));
- this.ui.find('[name="to_custom"]').val(
- new Date(endTime + tzoffset).toUTCString().split(' ').slice(1,5).join(' '));
-
- this.rangePreviewOptions.xaxis.tzoffset = tzoffset;
-
- var chartsLoading = [];
- for(var i=0; i<this.charts.length; i++){
- this.charts[i].options.xaxis.tzoffset = tzoffset;
- // Don't render charts which are not currently visible
- if(this.charts[i].template.is(':visible')) {
- chartsLoading.push(
- this.charts[i].setTimeRange(startTime, endTime));
- }
- }
- return MochiKit.Async.gatherResults(chartsLoading).addCallback(
- function(self, startTime, endTime, chartData) {
-
- var firstUpdate = new Date().getTime();
- var lastUpdate = 0;
-
- for(var i=0; i<chartData.length; i++) {
- for(var j=0; j<chartData[i].length; j++) {
- if(chartData[i][j].firstUpdated < firstUpdate) {
- firstUpdate = chartData[i][j].firstUpdated;
- }
- if(chartData[i][j].lastUpdated > lastUpdate) {
- lastUpdate = chartData[i][j].lastUpdated;
- }
- }
- }
-
- var ranges = {
- xaxis: {
- from: Math.max(startTime, firstUpdate),
- to: Math.min(endTime, lastUpdate)
- }
- };
-
- // Add a suitable extended head and tail to preview graph time axis
- var HOUR = 1000 * 60 * 60;
- var DAY = HOUR * 24;
- var WEEK = DAY * 7;
- var MONTH = DAY * 31;
- var YEAR = DAY * 365;
- var periods = [HOUR, HOUR*6, HOUR*12,
- DAY, DAY*3,
- WEEK, WEEK*2,
- MONTH, MONTH*3, MONTH*6, YEAR];
-
- var range = ranges.xaxis.to - ranges.xaxis.from;
- for(var i=0; i<periods.length; i++) {
- if(range <= periods[i]) {
- i++;
- break;
- }
- }
-
- // Dummy data for the range timeline
- var data = [
- [Math.max(ranges.xaxis.from - periods[i-1], firstUpdate), 1],
- [Math.min(ranges.xaxis.to + periods[i-1], lastUpdate), 1]];
-
- self.rangePreview = $.plot(self.ui.find('.range-preview'), [data],
- self.rangePreviewOptions);
-
- self.rangePreview.setSelection(ranges, true);
- }, this, startTime, endTime);
-};
-
-jarmon.ChartCoordinator.prototype.setTimeRange = function(from, to) {
- /**
- * Set the start and end time fields in the form and trigger an update
- *
- * @method setTimeRange
- * @param startTime {Number} The start timestamp
- * @param endTime {Number} The end timestamp
- **/
- if(from != null) {
- this.ui.find('[name="from"]').val(from);
- }
- if(to != null) {
- this.ui.find('[name="to"]').val(to);
- }
-};
-
-jarmon.ChartCoordinator.prototype.init = function() {
- /**
- * Reset all charts and the input form to the default time range - last hour
- *
- * @method init
- **/
- this.update();
-};
-
-/**
- * Limit the number of parallel async calls
- *
- * @class jarmon.Parallimiter
- * @constructor
- * @param limit {Number} The maximum number of in progress calls
- **/
-jarmon.Parallimiter = function(limit) {
- this.limit = limit || 1;
- this._callQueue = [];
- this._currentCallCount = 0;
-};
-
-jarmon.Parallimiter.prototype.addCallable = function(callable, args) {
- /**
- * Add a function to be called when the number of in progress calls drops
- * below the configured limit
- *
- * @method addCallable
- * @param callable {Function} A function which returns a Deferred.
- * @param args {Array} A list of arguments to pass to the callable
- * @return {Object} A Deferred which fires with the result of the callable
- * when it is called.
- **/
- var d = new MochiKit.Async.Deferred();
- this._callQueue.unshift([d, callable, args]);
- this._nextCall();
- return d;
-};
-
-jarmon.Parallimiter.prototype._nextCall = function() {
- if(this._callQueue.length > 0) {
- if(this._currentCallCount < this.limit) {
- this._currentCallCount++;
- var nextCall = this._callQueue.pop();
- nextCall[1].apply(null, nextCall[2]).addBoth(
- function(self, d, res) {
- d.callback(res);
- self._currentCallCount--;
- self._nextCall();
- }, this, nextCall[0]);
- }
- }
-};