summaryrefslogtreecommitdiff
path: root/jarmon
diff options
context:
space:
mode:
Diffstat (limited to 'jarmon')
-rw-r--r--jarmon/external.doc.js63
-rw-r--r--jarmon/jarmon.js1055
-rw-r--r--jarmon/jarmon.test.js137
3 files changed, 629 insertions, 626 deletions
diff --git a/jarmon/external.doc.js b/jarmon/external.doc.js
new file mode 100644
index 0000000..10d9ea7
--- /dev/null
+++ b/jarmon/external.doc.js
@@ -0,0 +1,63 @@
+/**
+ * jQuery
+ * @module jQuery
+ * @see http://api.jquery.com/
+ */
+/**
+ * @typedef module:jQuery.jQuery
+ * @see http://api.jquery.com/Types/#jQuery
+ */
+/**
+ * @typedef module:jQuery.Deferred
+ * @see http://api.jquery.com/category/deferred-object/
+ */
+/**
+ * @function module:jQuery.ajax
+ * @see https://api.jquery.com/jQuery.ajax/
+ */
+
+/**
+ * {@link http://jquerytools.github.io/ jQuery Tools} / tabs
+ * @module jQueryTools/tabs
+ * @see http://jquerytools.github.io/documentation/tabs/index.html
+ */
+
+/**
+ * {@link http://jquerytools.github.io/ jQuery Tools} / toolbox/history
+ * @module jQueryTools/toolbox/history
+ * @see http://jquerytools.github.io/documentation/toolbox/history.html
+ */
+
+/**
+ * {@link http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/index.html javascriptRRD}/binaryXHR
+ * @module javascriptRRD/binaryXHR
+ * @see http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/binaryXHR_js.html
+ */
+/**
+ * @typedef module:javascriptRRD/binaryXHR.BinaryFile
+ * @see http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/binaryXHR_js.html#BinaryFile
+ */
+
+/**
+ * {@link http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/index.html javascriptRRD}/rrdFile
+ * @module javascriptRRD/rrdFile
+ * @see http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/rrdFile_js.html
+ */
+/**
+ * @typedef module:javascriptRRD/rrdFile.RRDFile
+ * @see http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/rrdFile_js.html#RRDFile
+ */
+
+/**
+ * {@link http://www.flotcharts.org/ Flot}
+ * @module Flot
+ * @see https://github.com/flot/flot/blob/master/API.md
+ */
+/**
+ * @typedef module:Flot.data
+ * @see https://github.com/flot/flot/blob/master/API.md#data-format
+ */
+/**
+ * @typedef module:Flot.options
+ * @see https://github.com/flot/flot/blob/master/API.md#plot-options
+ */
diff --git a/jarmon/jarmon.js b/jarmon/jarmon.js
index e30a228..c7af4ed 100644
--- a/jarmon/jarmon.js
+++ b/jarmon/jarmon.js
@@ -9,233 +9,41 @@
* - http://collectd.org/
*
* Requirements:
- * - JavascriptRRD: http://javascriptrrd.sourceforge.net/
- * - jQuery: http://jquery.com/
- * - Flot: http://code.google.com/p/flot/
- *
- * @module jarmon
+ * - javascriptRRD/rrdFile 0.6/1.1: http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/rrdFile_js.html
+ * - javascriptRRD/binaryXHR 0.6/1.1: http://javascriptrrd.sourceforge.net/docs/javascriptrrd_v1.1.1/doc/lib/binaryXHR_js.html
+ * - jQuery 1.6.3: http://jquery.com/
+ * - Flot 1.7: http://www.flotcharts.org/
+ * - jQueryTools/tabs: http://jquerytools.github.io/documentation/tabs/index.html
+ * - jQueryTools/toolbox/history: http://jquerytools.github.io/documentation/toolbox/history.html
*/
/**
* A namespace for Jarmon
*
- * @class jarmon
- * @static
+ * @namespace jarmon
*/
if(typeof(jarmon) === 'undefined') {
var jarmon = {};
}
-// A VBScript and Javascript helper function to convert IE responseBody to a
-// byte string.
-// http://miskun.com/javascript/internet-explorer-and-binary-files-data-access/
-var IEBinaryToArray_ByteStr_Script =
- "<!-- IEBinaryToArray_ByteStr -->\r\n"+
- "<script type='text/vbscript'>\r\n"+
- "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
- " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
- "End Function\r\n"+
- "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+
- " Dim lastIndex\r\n"+
- " lastIndex = LenB(Binary)\r\n"+
- " if lastIndex mod 2 Then\r\n"+
- " IEBinaryToArray_ByteStr_Last = Chr( AscB( MidB( Binary, lastIndex, 1 ) ) )\r\n"+
- " Else\r\n"+
- " IEBinaryToArray_ByteStr_Last = "+'""'+"\r\n"+
- " End If\r\n"+
- "End Function\r\n"+
- "</script>\r\n";
-document.write(IEBinaryToArray_ByteStr_Script);
-
-jarmon.GetIEByteArray_ByteStr = function(IEByteArray) {
- if(typeof(jarmon.ByteMapping) === 'undefined') {
- jarmon.ByteMapping = {};
- for ( var i = 0; i < 256; i++ ) {
- for ( var j = 0; j < 256; j++ ) {
- jarmon.ByteMapping[ String.fromCharCode( i + j * 256 ) ] =
- String.fromCharCode(i) + String.fromCharCode(j);
- }
- }
- }
-
- var rawBytes = IEBinaryToArray_ByteStr(IEByteArray);
- var lastChr = IEBinaryToArray_ByteStr_Last(IEByteArray);
- return rawBytes.replace(/[\s\S]/g,
- function( match ) { return jarmon.ByteMapping[match]; }) + lastChr;
-};
+/**
+ * @callback jarmon.RrdQueryRemote.Downloader
+ * @param url {string} The url of the object to be downloaded
+ * @return {module:jQuery.Deferred} A Deferred which will callback with an
+ * instance of {@link module:javascriptRRD/binaryXHR.BinaryFile}
+ */
-/*
- * BinaryFile over XMLHttpRequest
- * Part of the javascriptRRD package
- * Copyright (c) 2009 Frank Wuerthwein, fkw@ucsd.edu
- * MIT License [http://www.opensource.org/licenses/mit-license.php]
+/**
+ * Download a binary file asynchronously using the {@link jQuery.ajax}
+ * function.
*
- * Original repository: http://javascriptrrd.sourceforge.net/
+ * @member
+ * @type jarmon.RrdQueryRemote.Downloader
*
- * Based on:
- * Binary Ajax 0.1.5
- * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com,
- * http://blog.nihilogic.dk/
- * MIT License [http://www.opensource.org/licenses/mit-license.php]
+ * @requires jQuery
+ * @requires javascriptRRD/binaryXHR
*/
-
-// ============================================================
-// Exception class
-jarmon.InvalidBinaryFile = function(msg) {
- this.message=msg;
- this.name="Invalid BinaryFile";
-};
-
-// pretty print
-jarmon.InvalidBinaryFile.prototype.toString = function() {
- return this.name + ': "' + this.message + '"';
-};
-
-// =====================================================================
-// BinaryFile class
-// Allows access to element inside a binary stream
-jarmon.BinaryFile = function(strData, iDataOffset, iDataLength) {
-
- var data = strData;
- var dataOffset = iDataOffset || 0;
- var dataLength = 0;
- // added
- var doubleMantExpHi=Math.pow(2,-28);
- var doubleMantExpLo=Math.pow(2,-52);
- var doubleMantExpFast=Math.pow(2,-20);
-
- if (typeof strData === "string") {
- dataLength = iDataLength || data.length;
- } else {
- throw new jarmon.InvalidBinaryFile(
- "Unsupported type " + (typeof strData));
- }
-
- this.getRawData = function() {
- return data;
- };
-
- this.getByteAt = function(iOffset) {
- return data.charCodeAt(iOffset + dataOffset) & 0xFF;
- };
-
- this.getLength = function() {
- return dataLength;
- };
-
- this.getSByteAt = function(iOffset) {
- var iByte = this.getByteAt(iOffset);
- if (iByte > 127)
- return iByte - 256;
- else
- return iByte;
- };
-
- this.getShortAt = function(iOffset) {
- var iShort = (
- this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset);
- if (iShort < 0) iShort += 65536;
- return iShort;
- };
- this.getSShortAt = function(iOffset) {
- var iUShort = this.getShortAt(iOffset);
- if (iUShort > 32767)
- return iUShort - 65536;
- else
- return iUShort;
- };
- this.getLongAt = function(iOffset) {
- var iByte1 = this.getByteAt(iOffset),
- iByte2 = this.getByteAt(iOffset + 1),
- iByte3 = this.getByteAt(iOffset + 2),
- iByte4 = this.getByteAt(iOffset + 3);
-
- var iLong = (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
- if (iLong < 0) iLong += 4294967296;
- return iLong;
- };
- this.getSLongAt = function(iOffset) {
- var iULong = this.getLongAt(iOffset);
- if (iULong > 2147483647)
- return iULong - 4294967296;
- else
- return iULong;
- };
- this.getStringAt = function(iOffset, iLength) {
- var aStr = [];
- for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {
- aStr[j] = String.fromCharCode(this.getByteAt(i));
- }
- return aStr.join("");
- };
-
- // Added
- this.getCStringAt = function(iOffset, iMaxLength) {
- var aStr = [];
- for (var i=iOffset,j=0;(i<iOffset+iMaxLength) &&
- (this.getByteAt(i)>0);i++,j++) {
- aStr[j] = String.fromCharCode(this.getByteAt(i));
- }
- return aStr.join("");
- };
-
- // Added
- this.getDoubleAt = function(iOffset) {
- var iByte1 = this.getByteAt(iOffset),
- iByte2 = this.getByteAt(iOffset + 1),
- iByte3 = this.getByteAt(iOffset + 2),
- iByte4 = this.getByteAt(iOffset + 3),
- iByte5 = this.getByteAt(iOffset + 4),
- iByte6 = this.getByteAt(iOffset + 5),
- iByte7 = this.getByteAt(iOffset + 6),
- iByte8 = this.getByteAt(iOffset + 7);
- var iSign=iByte8 >> 7;
- var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4);
- var iMantHi=((((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5) << 8) + iByte4;
- var iMantLo=((((iByte3) << 8) + iByte2) << 8) + iByte1;
-
- if (iExpRaw===0) return 0.0;
- if (iExpRaw===0x7ff) return undefined;
-
- var iExp=(iExpRaw & 0x7FF)-1023;
-
- var dDouble = ((iSign===1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMantLo*doubleMantExpLo + iMantHi*doubleMantExpHi);
- return dDouble;
- };
- // added
- // Extracts only 4 bytes out of 8, loosing in precision (20 bit mantissa)
- this.getFastDoubleAt = function(iOffset) {
- var iByte5 = this.getByteAt(iOffset + 4),
- iByte6 = this.getByteAt(iOffset + 5),
- iByte7 = this.getByteAt(iOffset + 6),
- iByte8 = this.getByteAt(iOffset + 7);
- var iSign=iByte8 >> 7;
- var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4);
- var iMant=((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5;
-
- if (iExpRaw===0) return 0.0;
- if (iExpRaw===0x7ff) return undefined;
-
- var iExp=(iExpRaw & 0x7FF)-1023;
-
- var dDouble = ((iSign===1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMant*doubleMantExpFast);
- return dDouble;
- };
-
- this.getCharAt = function(iOffset) {
- return String.fromCharCode(this.getByteAt(iOffset));
- };
-};
-
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 = jQuery.Deferred();
$.ajax({
url: url,
@@ -253,15 +61,11 @@ jarmon.downloadBinary = function(url) {
delete this._nativeXhr;
},
success: function(data, textStatus, jqXHR) {
- // In IE we return the responseBody
- if(typeof(this._nativeXhr.responseBody) !== 'undefined') {
- d.resolve(
- new jarmon.BinaryFile(
- jarmon.GetIEByteArray_ByteStr(
- this._nativeXhr.responseBody)));
- } else {
- d.resolve(new jarmon.BinaryFile(data));
+ var response = this._nativeXhr.responseBody // for IE
+ if (response==undefined) {
+ response=this._nativeXhr.responseText // for standards-compliant browsers
}
+ d.resolve(new BinaryFile(response));
},
error: function(xhr, textStatus, errorThrown) {
d.reject(new Error(textStatus + ':' + xhr.status));
@@ -271,16 +75,18 @@ jarmon.downloadBinary = function(url) {
};
+/**
+ * Copied from jquery.flot.js and modified to allow timezone
+ * adjustment.
+ *
+ * @method
+ * @requires Flot
+ *
+ * @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
+ **/
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,
@@ -329,44 +135,37 @@ jarmon.localTimeFormatter = function (v, axis) {
/**
- * 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).
+ * A wrapper around an instance of {@link module:javascriptRRD/rrdFile.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
- * @param transformer {Function} A callable which performs a
- * tranfsformation of the values returned from the RRD file.
+ * @param rrd {module:javascriptRRD/rrdFile.RRDFile}
+ * @param unit {string} The unit symbol for this data series
**/
-jarmon.RrdQuery = function(rrd, unit, transformer) {
+jarmon.RrdQuery = function(rrd, unit) {
this.rrd = rrd;
this.unit = unit;
- if(typeof(transformer) !== 'undefined') {
- this.transformer = transformer;
- } else {
- this.transformer = function(v) {return v;};
- }
};
+/**
+ * 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
+ * @param startTimeJs {number} start timestamp in microseconds
+ * @param endTimeJs {number} end timestamp in microseconds
+ * @param [dsId=0] {(string|number)} identifier of the RRD datasource
+ * @param [cfName="AVERAGE"] {string} The name of an RRD consolidation function
+ * (CF) eg AVERAGE, MIN, MAX
+ * @param {function} [transformer=function(v){return v}] A callable which
+ * performs a transformation of the values returned from the RRD file.
+ * @return {module:Flot.data} A Flot compatible data series
+ * eg `{label: '', data: [], unit: ''}`
+ **/
jarmon.RrdQuery.prototype.getData = function(startTimeJs, endTimeJs,
- 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 startTimeJs {Number} start timestamp in microseconds
- * @param endTimeJs {Number} end timestamp in microseconds
- * @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: ''
- **/
-
+ dsId, cfName, transformer) {
if (startTimeJs >= endTimeJs) {
throw RangeError(
['starttime must be less than endtime.',
@@ -392,6 +191,10 @@ jarmon.RrdQuery.prototype.getData = function(startTimeJs, endTimeJs,
cfName = 'AVERAGE';
}
+ if(typeof(transformer) === 'undefined') {
+ transformer = function(v) {return v;};
+ }
+
var rra, step, rraRowCount, lastRowTime, firstRowTime;
for(var i=0; i<this.rrd.getNrRRAs(); i++) {
@@ -450,7 +253,7 @@ jarmon.RrdQuery.prototype.getData = function(startTimeJs, endTimeJs,
var val;
var timestamp = startRowTime;
for(i=startRowIndex; i<endRowIndex; i++) {
- val = this.transformer(rra.getEl(i, dsIndex));
+ val = transformer(rra.getEl(i, dsIndex));
flotData.push([timestamp*1000.0, val]);
timestamp += step;
}
@@ -466,31 +269,29 @@ jarmon.RrdQuery.prototype.getData = function(startTimeJs, endTimeJs,
};
+/**
+ * Return a list of RRD Data Source names
+ *
+ * @method
+ * @return {Array.<string>} An array of DS names.
+ **/
jarmon.RrdQuery.prototype.getDSNames = function() {
- /**
- * Return a list of RRD Data Source names
- *
- * @method getDSNames
- * @return {Array} An array of DS names.
- **/
return this.rrd.getDSNames();
};
/**
- * A wrapper around RrdQuery which provides asynchronous access to the data in a
- * remote RRD file.
+ * A wrapper around {@link jarmon.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.
- * @param transformer {Function} A callable which performs a
- * tranfsformation of the values returned from the RRD file.
+ * @requires javascriptRRD/rrdFile
+ *
+ * @param url {string} The url of a remote RRD file
+ * @param unit {string} The unit suffix of this data eg 'bit/sec'
+ * @param [downloader=jarmon.downloadBinary] {jarmon.RrdQueryRemote.Downloader}
**/
-jarmon.RrdQueryRemote = function(url, unit, downloader, transformer) {
+jarmon.RrdQueryRemote = function(url, unit, downloader) {
this.url = url;
this.unit = unit;
if(typeof(downloader) == 'undefined') {
@@ -498,13 +299,22 @@ jarmon.RrdQueryRemote = function(url, unit, downloader, transformer) {
} else {
this.downloader = downloader;
}
- this.transformer = transformer;
this.lastUpdate = 0;
this._download = null;
};
-
+/**
+ * Asynchronously call a method on the underlying {@link jarmon.RrdQuery}. Think of it
+ * as an async .apply().
+ *
+ * @method
+ * @private
+ * @param methodName {string}
+ * @param args {Array}
+ * @return {module:jQuery.Deferred} A Deferred that calls .methodName(args...) on the
+ * underlying {@link jarmon.RrdQuery}.
+ */
jarmon.RrdQueryRemote.prototype._callRemote = function(methodName, args) {
// Download the rrd if there has never been a download and don't start
// another download if one is already in progress.
@@ -514,7 +324,7 @@ jarmon.RrdQueryRemote.prototype._callRemote = function(methodName, args) {
}
// Set up a deferred which will call getData on the local RrdQuery object
- // returning a flot compatible data object to the caller.
+ // returning a Flot compatible data object to the caller.
var ret = jQuery.Deferred();
// Add a pair of callbacks to the current download which will callback the
@@ -541,7 +351,7 @@ jarmon.RrdQueryRemote.prototype._callRemote = function(methodName, args) {
var rrd = new RRDFile(res);
self.lastUpdate = rrd.getLastUpdate();
- var rq = new jarmon.RrdQuery(rrd, self.unit, self.transformer);
+ var rq = new jarmon.RrdQuery(rrd, self.unit);
try {
ret.resolve(rq[methodName].apply(rq, args));
} catch(e) {
@@ -555,31 +365,37 @@ jarmon.RrdQueryRemote.prototype._callRemote = function(methodName, args) {
};
+/**
+ * Return a Flot compatible data series asynchronously.
+ *
+ * @method
+ * @param startTime {number} The start timestamp
+ * @param endTime {number} The end timestamp
+ * @param [dsId=0] {(string|number)} identifier of the RRD datasource (string or number)
+ * @param [cfName="AVERAGE"] {string} The name of an RRD consolidation function
+ * (CF) eg AVERAGE, MIN, MAX
+ * @param {function} [transformer=function(v){return v}] A callable which
+ * performs a tranfsformation of the values returned from the RRD file.
+ * @return {module:jQuery.Deferred} A Deferred which calls back with a Flot data
+ * series.
+ **/
jarmon.RrdQueryRemote.prototype.getData = function(startTime, endTime,
- dsId, cfName) {
- /**
- * 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.
- **/
+ dsId, cfName, transformer) {
if(this.lastUpdate < endTime/1000) {
this._download = null;
}
- return this._callRemote('getData', [startTime, endTime, dsId, cfName]);
+ return this._callRemote('getData', [startTime, endTime, dsId, cfName, transformer]);
};
+/**
+ * Return a list of RRD Data Source names
+ *
+ * @method
+ * @return {module:jquery.Deferred} A Deferred which calls back with an array of
+ * DS names.
+ **/
jarmon.RrdQueryRemote.prototype.getDSNames = function() {
- /**
- * Return a list of RRD Data Source names
- *
- * @method getDSNames
- * @return {Object} A Deferred which calls back with an array of DS names.
- **/
return this._callRemote('getDSNames');
};
@@ -588,39 +404,45 @@ jarmon.RrdQueryRemote.prototype.getDSNames = function() {
* 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)
+ * @param rrdQuery {jarmon.RrdQueryRemote} An RrdQueryRemote instance
+ * @param dsId {(string|number)} identifier of the RRD datasource (string or number)
**/
-jarmon.RrdQueryDsProxy = function(rrdQuery, dsId) {
+jarmon.RrdQueryDsProxy = function(rrdQuery, dsId, transformer) {
this.rrdQuery = rrdQuery;
this.dsId = dsId;
this.unit = rrdQuery.unit;
+ this.transformer = transformer;
};
+/**
+ * Call I{@link RrdQueryRemote.getData} with a particular dsId
+ *
+ * @method
+ * @param startTime {number} A unix timestamp marking the start time
+ * @param endTime {number} A unix timestamp marking the start time
+ * @return {module:jQuery.Deferred} A Deferred which calls back with a Flot data
+ * series.
+ **/
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);
+ return this.rrdQuery.getData(startTime, endTime, this.dsId, undefined, this.transformer);
};
/**
* 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.
+ * @requires jQuery
+ * @requires Flot
+ *
+ * @param template {module:jQuery.jQuery} A jQuery containing a single element
+ * into which the chart will be drawn
+ * @param recipe {Object}
+ * @param recipe.title {string}
+ * @param recipe.data {Array}
+ * @param recipe.options {module:Flot.options} Flot options which control how
+ * the chart should be drawn.
**/
jarmon.Chart = function(template, recipe, downloader) {
this.template = template;
@@ -643,6 +465,11 @@ jarmon.Chart = function(template, recipe, downloader) {
self.draw();
});
+ /* @todo The user should be able to override this with `ticks`, `tickSize`,
+ * or `minTickSize`.
+ * @todo Look in to implementing this as a tickFormatter, and letting Flot
+ * take care of scaling.
+ */
this.options.yaxis.ticks = function(axis) {
/*
* Choose a suitable SI multiplier based on the min and max values from
@@ -660,7 +487,7 @@ jarmon.Chart = function(template, recipe, downloader) {
};
var si = 0;
while(true) {
- if( Math.pow(1000, si+1)*0.9 > axis.max ) {
+ if( Math.pow(1000, si+1)*0.9 > (axis.max - axis.min) ) {
break;
}
si++;
@@ -670,7 +497,7 @@ jarmon.Chart = function(template, recipe, downloader) {
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];
+ 1, 2, 5, 10, 20, 25, 50, 100, 250];
var realStep = (maxVal - minVal)/5.0;
var stepSize, decimalPlaces = 0;
@@ -705,6 +532,9 @@ jarmon.Chart = function(template, recipe, downloader) {
};
};
+/**
+ * @method
+ */
jarmon.Chart.prototype.setup = function() {
this.template.find('.title').text(this.recipe.title);
this.data = [];
@@ -724,38 +554,38 @@ jarmon.Chart.prototype.setup = function() {
if(typeof(dataDict[rrd]) === 'undefined') {
dataDict[rrd] = new jarmon.RrdQueryRemote(
- rrd, unit, this.downloader, transformer);
+ rrd, unit, this.downloader);
}
- this.addData(label, new jarmon.RrdQueryDsProxy(dataDict[rrd], ds));
+ this.addData(label, new jarmon.RrdQueryDsProxy(dataDict[rrd], ds, transformer));
}
};
+/**
+ * Add details of a remote RRD data source whose data will be added to this
+ * chart.
+ *
+ * @method
+ * @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=true] {boolean} true if you want this data plotted on the chart,
+ * false if not.
+ **/
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]);
};
+/**
+ * Enable / Disable a single data source
+ *
+ * @method
+ * @param label {string} The label of the data source to be enabled /
+ * disabled.
+ **/
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];
@@ -763,29 +593,31 @@ jarmon.Chart.prototype.switchDataEnabled = function(label) {
}
};
+/**
+ * Alter the time range of this chart and redraw
+ *
+ * @method
+ * @param startTime {number} The start timestamp
+ * @param endTime {number} The end timestamp
+ * @return {Object} The same thing as .draw() returns
+ **/
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();
};
+/**
+ * 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
+ * @return {module:jQuery.Deferred} A Deferred which calls back with the chart data
+ * when the chart has been rendered.
+ **/
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.
- **/
var self = this;
this.template.addClass('loading');
@@ -839,6 +671,7 @@ jarmon.Chart.prototype.draw = function() {
self.template.find('.chart').empty().show(),
data, self.options);
+ // @todo Make this styleable
var yaxisUnitLabel = $('<div>')
.text(self.siPrefix + unit)
.css({'width': '100px',
@@ -848,7 +681,7 @@ jarmon.Chart.prototype.draw = function() {
'text-align': 'right'});
self.template.find('.chart').append(yaxisUnitLabel);
- // Manipulate and move the flot generated legend to an
+ // 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
@@ -857,6 +690,10 @@ jarmon.Chart.prototype.draw = function() {
// 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
+ //
+ // @todo This seems super hacky
+ // @todo Look in to implementing this using Flot
+ // `.legend.container`.
var legend = self.template.find('.graph-legend').show();
legend.empty();
self.template.find('.legendLabel').each(
@@ -908,8 +745,12 @@ jarmon.Chart.prototype.draw = function() {
/**
* Generate a form through which to choose a data source from a remote RRD file
*
- * @class jarmon.RrdChooser
+ * @todo Create an example that uses this.
+ *
* @constructor
+ * @requires jQuery
+ *
+ * @param $tpl {module:jQuery.jQuery} jQuery object for the DOM node to attach to.
**/
jarmon.RrdChooser = function($tpl) {
this.$tpl = $tpl;
@@ -921,6 +762,9 @@ jarmon.RrdChooser = function($tpl) {
};
};
+/**
+ * @method
+ */
jarmon.RrdChooser.prototype.drawRrdUrlForm = function() {
var self = this;
this.$tpl.empty();
@@ -980,6 +824,9 @@ jarmon.RrdChooser.prototype.drawRrdUrlForm = function() {
).appendTo(this.$tpl);
};
+/**
+ * @method
+ */
jarmon.RrdChooser.prototype.drawDsLabelForm = function() {
var self = this;
this.$tpl.empty();
@@ -1024,6 +871,9 @@ jarmon.RrdChooser.prototype.drawDsLabelForm = function() {
};
+/**
+ * @method
+ */
jarmon.RrdChooser.prototype.drawDsSummary = function() {
var self = this;
this.$tpl.empty();
@@ -1046,6 +896,15 @@ jarmon.RrdChooser.prototype.drawDsSummary = function() {
};
+/**
+ * UI element to toggle datasources in a graph.
+ *
+ * @constructor
+ * @requires jQuery
+ *
+ * @param $tpl {module:jQuery.jQuery}
+ * @param chart {jarmon.Chart}
+ */
jarmon.ChartEditor = function($tpl, chart) {
this.$tpl = $tpl;
this.chart = chart;
@@ -1103,6 +962,10 @@ jarmon.ChartEditor = function($tpl, chart) {
);
};
+
+/**
+ * @method
+ */
jarmon.ChartEditor.prototype.draw = function() {
var self = this;
this.$tpl.empty();
@@ -1166,6 +1029,12 @@ jarmon.ChartEditor.prototype.draw = function() {
};
+/**
+ * @method
+ * @private
+ * @param $row {module:jQuery.jQuery}
+ * @return {module:jQuery.jQuery}
+ */
jarmon.ChartEditor.prototype._extractRowValues = function($row) {
return $row.find('input[type=text]').map(
function(i, el) {
@@ -1175,6 +1044,11 @@ jarmon.ChartEditor.prototype._extractRowValues = function($row) {
};
+/**
+ * @method
+ * @private
+ * @param record {Array}
+ */
jarmon.ChartEditor.prototype._addDatasourceRow = function(record) {
$('<tr/>').append(
$('<td/>').append(
@@ -1200,9 +1074,33 @@ jarmon.ChartEditor.prototype._addDatasourceRow = function(record) {
};
+/**
+ *
+ * The `recipe` is an array of `["Tab Name", [placeholder-name-list]]`
+ * pairs. For example:
+ *
+ * recipe = [
+ * ["System", ["cpu", "memory", "load"]],
+ * ["Network", ["interface"]]
+ * ];
+ *
+ * @constructor
+ * @requires jQuery
+ * @requires jQueryTools/tabs
+ * @requires jQueryTools/toolbox/history
+ *
+ * @param $tpl {module:jQuery.jQuery}
+ * @param recipe {Array.<Array.<string, Array.<string>>>} An array of
+ * `["Tab Name", [placeholder-name-list]]` pairs.
+ */
jarmon.TabbedInterface = function($tpl, recipe) {
this.$tpl = $tpl;
this.recipe = recipe;
+ /**
+ * An array of `["placeholder-name", placeholder]` pairs.
+ * @member
+ * @type {Array.<Array.<string, module:jQuery.jQuery>>}
+ */
this.placeholders = [];
this.$tabBar = $('<ul/>', {'class': 'css-tabs'}).appendTo($tpl);
@@ -1223,6 +1121,11 @@ jarmon.TabbedInterface = function($tpl, recipe) {
this.setup();
};
+/**
+ * @method
+ * @param tabName {string}
+ * @return {module:jQuery.jQuery} A <div> that is the contents of the tab panel.
+ */
jarmon.TabbedInterface.prototype.newTab = function(tabName) {
// Add a tab
$('<li/>').append(
@@ -1235,6 +1138,9 @@ jarmon.TabbedInterface.prototype.newTab = function(tabName) {
return $placeholder;
};
+/**
+ * @method
+ */
jarmon.TabbedInterface.prototype.setup = function() {
// Destroy then re-initialise the jquerytools tabs plugin
var api = this.$tabBar.data("tabs");
@@ -1244,15 +1150,30 @@ jarmon.TabbedInterface.prototype.setup = function() {
this.$tabBar.tabs(this.$tabPanels.children('div'), {history: true});
};
+/**
+ * Setup chart date range controls and all charts
+ *
+ * @todo Figure out how to allow `$chartTemplate` to be an HTML5 <template>
+ * element.
+ *
+ * @method
+ * @requires jQuery
+ *
+ * @param $chartTemplate {module:jQuery.jQuery} See {@link jarmon.Chart}
+ * @param chartRecipes {Object.<string, recipe>} A map of
+ * placeholder-names (see {@link jarmon.TabbedInterface})
+ * to chart recipes (see {@link jarmon.Chart})
+ * @param $tabTemplate {module:jQuery.jQuery} See {@link jarmon.TabbedInterface}
+ * @param tabRecipes {Array} See {@link jarmon.TabbedInterface}
+ * @param $controlPanelTemplate {module:jQuery.jQuery}
+ * @return {Array.<Array.<jarmon.Chart>, jarmon.TabbedInterface, jarmon.ChartCoordinator>}
+ **/
jarmon.buildTabbedChartUi = function ($chartTemplate, chartRecipes,
$tabTemplate, tabRecipes,
$controlPanelTemplate) {
- /**
- * Setup chart date range controls and all charts
- **/
var p = new jarmon.Parallimiter(2);
function serialDownloader(url) {
- return p.addCallable(jarmon.downloadBinary, [url]);
+ return p.addCallable(function(){ return jarmon.downloadBinary(url); });
}
var ti = new jarmon.TabbedInterface($tabTemplate, tabRecipes);
@@ -1297,7 +1218,7 @@ jarmon.buildTabbedChartUi = function ($chartTemplate, chartRecipes,
function(e) {
var cc = e.data.cc;
// XXX: Hack to give the tab just enough time to become visible
- // so that flot can calculate chart dimensions.
+ // so that Flot can calculate chart dimensions.
window.clearTimeout(cc.t);
cc.t = window.setTimeout(
function() {
@@ -1307,13 +1228,23 @@ jarmon.buildTabbedChartUi = function ($chartTemplate, chartRecipes,
);
// Initialise all the charts
- cc.init();
+ cc.update();
return [charts, ti, cc];
};
-// Options common to all the chart on this page
+/**
+ * Options common to all the chart on this page
+ *
+ * This is not used by Jarmon internally; it is here for convenience to use as
+ * part of chart recipes passed to {@linkcode jarmon.Chart new jarmon.Chart} (or
+ * {@linkcode jarmon.buildTabbedChartUi}).
+ *
+ * @static
+ * @default
+ * @type module:Flot.options
+ */
jarmon.Chart.BASE_OPTIONS = {
grid: {
clickable: false,
@@ -1346,7 +1277,17 @@ jarmon.Chart.BASE_OPTIONS = {
}
};
-// Extra options to generate a stacked chart
+/**
+ * Extra options to generate a stacked chart
+ *
+ * This is not used by Jarmon internally; it is here for convenience to use as
+ * part of chart recipes passed to {@linkcode jarmon.Chart new jarmon.Chart} (or
+ * {@linkcode jarmon.buildTabbedChartUi}).
+ *
+ * @static
+ * @default
+ * @type module:Flot.options
+ */
jarmon.Chart.STACKED_OPTIONS = {
series: {
stack: true,
@@ -1357,7 +1298,19 @@ jarmon.Chart.STACKED_OPTIONS = {
};
-// A selection of useful time ranges
+/**
+ * A selection of useful time ranges
+ *
+ * For the most part, this is an array of `["human description", function]`
+ * pairs. However, if a third member of a tuple is `===true`, then that tuple
+ * is used as the default option. If no tuple is identified as the default this
+ * way, then the default is the first tuple.
+ *
+ * @todo This should probably be a member of jarmon.ChartCoordinator.
+ *
+ * @member
+ * @default
+ */
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]; }],
@@ -1374,10 +1327,39 @@ jarmon.timeRangeShortcuts = [
* 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
+ * The `ui` parameter should be a jQuery for a DOM node containing elements
+ * matching these selectors:
+ *
+ * - `select[name="shortcuts"]` : An empty `<select>` to be populated with
+ * {@linkcode jarmon.timeRangeShortcuts}
+ * - `[name="from"]` ; An input to select the start datetime
+ * - `[name="to"]` : An input to select the end datetime
+ * - `[name="tzoffset"]` : An input to select the timezone
+ * - `[name="action"]` : A button to update the charts
+ * - `.range-preview` : Placeholder for a Flot chart
+ *
+ * The `[name="to"]` and `[name="from"]` inputs should behave somewhat like
+ * WHATWG HTML (revision 2016-12-12) `type=datetime-local` inputs with `step=1`.
+ * Because `type=datetime-local` is not currently in a W3C spec, and not all
+ * common browsers implement it, and polyfills vary in quality; Jarmon avoids
+ * interacting with it too deeply, placing only a few requirements placed on the
+ * implementation:
+ *
+ * - The value MUST be settable via `el.value = string` where `string` is of the
+ * format `%Y-%m-%dT%H:%M:%S`.
+ * - The value MUST be gettable via `Date.parse(el.value+'Z')`.
+ * - The user interface SHOULD prohibit the user from selecting a value that is
+ * before than the `min` attribute, which is in the format
+ * `%Y-%m-%dT%H:%M:%S`.
+ * - The user interface SHOULD prohibit the user from selecting a value that is
+ * after than the `max` attribute, which is in the format `%Y-%m-%dT%H:%M:%S`.
+ *
* @constructor
- * @param ui {Object} A one element jQuery containing an input form and
- * placeholders for the timeline and for the series of charts.
+ * @requires jQuery
+ * @requires Flot
+ *
+ * @param ui {module:jQuery.jQuery} A one element jQuery containing an input
+ * form and placeholders for the timeline and for the series of charts.
**/
jarmon.ChartCoordinator = function(ui, charts) {
var self = this;
@@ -1401,93 +1383,98 @@ jarmon.ChartCoordinator = function(ui, charts) {
}
};
- 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]));
- }
+ this.inputs = {
+ shortcuts: this.ui.find('select[name="shortcuts"]'),
+ from: this.ui.find('[name="from"]'),
+ to: this.ui.find('[name="to"]'),
+ tzoffset: this.ui.find('[name="tzoffset"]'),
+ action: this.ui.find('[name="action"]')
+ };
+
+ // DOM manipulation ////////////////////////////////////////////////////////
+
+ // (This is put in a closure to avoid leaking variables and making it hard
+ // for me to reason about the code.)
+ (function(options, tzoffsetEl) {
+ // Set up shortcuts
+ var option;
+ for(var i=0; i<jarmon.timeRangeShortcuts.length; i++) {
+ option = $('<option />').text(jarmon.timeRangeShortcuts[i][0])
+ if (jarmon.timeRangeShortcuts[i][2] === true) {
+ option[0].setAttribute('selected', 'selected');
+ }
+ options.append(option);
+ }
+ // Append a custom option for when the user selects an area of the graph
+ options.append($('<option />').text('custom'));
+
+ // Populate a list of tzoffset options if the element is present in the
+ // template as a select list
+ if (tzoffsetEl.is('select')) {
+ var label, val;
+ for(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));
+ }
+ }
- // 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]);
+ // Default timezone offset based on localtime
+ var tzoffset = -1 * (new Date()).getTimezoneOffset() * 60 * 1000;
+ tzoffsetEl.val(tzoffset);
+ })(this.inputs.shortcuts, this.inputs.tzoffset);
- options.bind('change', function(e) {
+ // Event bindings //////////////////////////////////////////////////////////
+
+ this.inputs.shortcuts.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',
+ this.inputs.from.bind('change',
function(e) {
- self.ui.find('[name="from_standard"]').val('custom');
- var tzoffset = parseInt(self.ui.find('[name="tzoffset"]').val(), 10);
- self.setTimeRange(
- new Date(this.value + ' UTC').getTime() - tzoffset, null);
+ self.inputs.shortcuts.val('custom');
self.update();
}
);
- this.ui.find('[name="to_custom"]').bind('change',
+ this.inputs.to.bind('change',
function(e) {
- self.ui.find('[name="from_standard"]').val('custom');
- var tzoffset = parseInt(self.ui.find('[name="tzoffset"]').val(), 10);
- self.setTimeRange(
- null, new Date(this.value + ' UTC').getTime() - tzoffset);
+ self.inputs.shortcuts.val('custom');
self.update();
}
);
- // Populate a list of tzoffset options if the element is present in the
- // template as a select list
- var tzoffsetEl = this.ui.find('[name="tzoffset"]');
- if(tzoffsetEl.is('select')) {
- var label, val;
- for(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);
+ this.inputs.tzoffset.bind('change', function(e) {
+ self.update();
+ });
- // Update the time ranges and redraw charts when the form is submitted
- this.ui.find('[name="action"]').bind('click', function(e) {
+ this.inputs.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.
- $(document).bind(
- 'plotselected',
- {self: this},
+ $(document).bind('plotselected', {},
function(e, ranges) {
- var self = e.data.self;
var eventSourceIsMine = false;
// plotselected event may be from my range selector chart or
- if( self.ui.has(e.target) ) {
+ if (self.ui.has(e.target) ) {
eventSourceIsMine = true;
} else {
// ...it may come from one of the charts under my supervision
@@ -1500,126 +1487,40 @@ jarmon.ChartCoordinator = function(ui, charts) {
}
if(eventSourceIsMine) {
- // Update the prepared time range select box to value "custom"
- self.ui.find('[name="from_standard"]').val('custom');
-
- // Update all my charts
self.setTimeRange(ranges.xaxis.from, ranges.xaxis.to);
- self.update();
}
}
);
-
- // Add dhtml calendars to the date input fields
- this.ui.find(".timerange_control img")
- .dateinput({
- 'format': 'dd mmm yyyy 00:00:00',
- 'max': +1,
- 'css': {'input': 'jquerytools_date'}})
- .bind('onBeforeShow', function(e) {
- var classes = $(this).attr('class').split(' ');
- var currentDate, input_selector;
- for(var i=0; i<=classes.length; i++) {
- input_selector = '[name="' + classes[i] + '"]';
- // Look for a neighboring input element whose name matches the
- // class name of this calendar
- // Parse the value as a date if the returned date.getTime
- // returns NaN we know it's an invalid date
- // XXX: is there a better way to check for valid date?
- currentDate = new Date($(this).siblings(input_selector).val());
- if(! isNaN(currentDate.getTime()) ) {
- $(this).data(
- 'dateinput')._initial_val = currentDate.getTime();
- $(this).data('dateinput').setValue(currentDate);
- break;
- }
- }
- })
- .bind(
- 'onHide',
- {self: this},
- function(e) {
- var self = e.data.self;
- // Called after a calendar date has been chosen by the user.
- // Use the sibling selector that we generated above
- // before opening the calendar
- var oldStamp = $(this).data('dateinput')._initial_val;
- var newDate = $(this).data('dateinput').getValue();
- // Only update the form field if the date has changed.
- if(oldStamp !== newDate.getTime()) {
- // Update the prepared time range select box to
- // value "custom"
- self.ui.find('[name="from_standard"]').val('custom');
- var from = null;
- var to = null;
- if($(this).hasClass('from_custom')){
- from = newDate.getTime();
- } else {
- to = newDate.getTime();
- }
- self.setTimeRange(from, to);
- self.update();
- }
- });
-
- // Avoid overlaps between the calendars
- // XXX: This is a bit of hack, what if there's more than one set of calendar
- // controls on a page?
- this.ui.find(".timerange_control img.from_custom").bind(
- 'onBeforeShow',
- {self: this},
- function(e) {
- var self = e.data.self;
- var otherVal = new Date(
- self.ui.find('.timerange_control [name="to_custom"]').val());
-
- $(this).data('dateinput').setMax(otherVal);
- }
- );
- this.ui.find(".timerange_control img.to_custom").bind(
- 'onBeforeShow',
- {self: this},
- function(e) {
- var self = e.data.self;
- var otherVal = new Date(
- self.ui.find('.timerange_control [name="from_custom"]').val());
-
- $(this).data('dateinput').setMin(otherVal);
- }
- );
-
};
-
+/**
+ * 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
+ * @return {module:jQuery.Deferred}
+ **/
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 self = this;
- var selection = this.ui.find('[name="from_standard"]').val();
+ var shortcut = this.inputs.shortcuts.val();
- var now = new Date().getTime();
+ var now = new Date();
for(var i=0; i<jarmon.timeRangeShortcuts.length; i++) {
- if(jarmon.timeRangeShortcuts[i][0] === selection) {
- var range = jarmon.timeRangeShortcuts[i][1](now);
- this.setTimeRange(range[0], range[1]);
+ if(jarmon.timeRangeShortcuts[i][0] === shortcut) {
+ var range = jarmon.timeRangeShortcuts[i][1](now.getTime());
+ this.inputs.from.val(this.unix2datetimelocal(range[0]))
+ this.inputs.to.val(this.unix2datetimelocal(range[1]));
break;
}
}
- var startTime = parseInt(this.ui.find('[name="from"]').val(), 10);
- var endTime = parseInt(this.ui.find('[name="to"]').val(), 10);
- var tzoffset = parseInt(this.ui.find('[name="tzoffset"]').val(), 10);
+ var startTime = this.datetimelocal2unix(this.inputs.from.val());
+ var endTime = this.datetimelocal2unix(this.inputs.to.val());
+ var tzoffset = parseInt(this.inputs.tzoffset.val(), 10);
- 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.inputs.from[0].setAttribute('max', this.inputs.to.val());
+ this.inputs.to[0].setAttribute( 'min', this.inputs.from.val());
+ this.inputs.to[0].setAttribute( 'max', now.toISOString().slice(0, -1));
this.rangePreviewOptions.xaxis.tzoffset = tzoffset;
@@ -1688,37 +1589,73 @@ jarmon.ChartCoordinator.prototype.update = function() {
});
};
+/**
+ * Convert a UNIX millisecond timestamp into a [datetime-local string][]; using
+ * the `[name="tzoffoffset"]` input for the timezone.
+ *
+ * [datetime-local string]: https://html.spec.whatwg.org/multipage/infrastructure.html#concept-datetime-local
+ *
+ * @method
+ * @private
+ * @param unix {number}
+ * @return {string}
+ */
+jarmon.ChartCoordinator.prototype.unix2datetimelocal = function(unix) {
+ var tz = parseInt(this.inputs.tzoffset.val(), 10);
+ return (new Date(unix + tz)).toISOString().slice(0, -1);
+}
+
+/**
+ * Convert a [datetime-local string][] into a UNIX millisecond timestamp; using
+ * the `[name="tzoffoffset"]` input for the timezone.
+ *
+ * [datetime-local string]: https://html.spec.whatwg.org/multipage/infrastructure.html#concept-datetime-local
+ *
+ * @method
+ * @private
+ * @param datetimelocal {string}
+ * @return {number}
+ */
+jarmon.ChartCoordinator.prototype.datetimelocal2unix = function(datetimelocal) {
+ var tz = parseInt(this.inputs.tzoffset.val(), 10);
+ return Date.parse(datetimelocal+'Z') - tz;
+};
+
+/**
+ * Set the start and end time fields in the form and trigger an update
+ *
+ * @method
+ * @param [startTime=] {number} The start timestamp
+ * @param [endTime=] {number} The end timestamp
+ **/
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(typeof(from) !== 'undefined' && from !== null) {
- this.ui.find('[name="from"]').val(from);
+ this.inputs.from.val(this.unix2datetimelocal(from))
}
if(typeof(to) !== 'undefined' && to !== null) {
- this.ui.find('[name="to"]').val(to);
+ this.inputs.to.val(this.unix2datetimelocal(to));
}
+ this.inputs.shortcuts.val('custom');
+ this.update();
};
+/**
+ * An alias for {@linkcode jarmon.ChartCoordinator#update .update()}.
+ *
+ * @method
+ * @deprecated Use {@linkcode jarmon.ChartCoordinator#update .update()} instead.
+ **/
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
+ * @requires jQuery
+ *
+ * @param limit {number} The maximum number of in progress calls
**/
jarmon.Parallimiter = function(limit) {
this.limit = limit || 1;
@@ -1726,23 +1663,29 @@ jarmon.Parallimiter = function(limit) {
this._currentCallCount = 0;
};
+/**
+ * Add a function to be called when the number of in progress calls drops
+ * below the configured limit
+ *
+ * @todo Remove the `args` argument in favor of passing in closures.
+ *
+ * @method
+ * @param callable {function} A function which returns a Deferred.
+ * @param args {Array} A list of arguments to pass to the callable
+ * @return {module:jQuery.jQuery} A Deferred which fires with the result of the callable
+ * when it is called.
+ **/
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 jQuery.Deferred();
this._callQueue.unshift([d, callable, args]);
this._nextCall();
return d;
};
+/**
+ * @method
+ * @private
+ */
jarmon.Parallimiter.prototype._nextCall = function() {
var self = this;
if(this._callQueue.length > 0) {
diff --git a/jarmon/jarmon.test.js b/jarmon/jarmon.test.js
index 5f5989d..0bccf9c 100644
--- a/jarmon/jarmon.test.js
+++ b/jarmon/jarmon.test.js
@@ -8,11 +8,11 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
Y.Test.Runner.add(new Y.Test.Case({
name: "jarmon.downloadBinary",
+ /**
+ * When url cannot be found, the deferred should errback with status
+ * 404.
+ **/
test_urlNotFound: function () {
- /**
- * When url cannot be found, the deferred should errback with status
- * 404.
- **/
var self = this;
var d = new jarmon.downloadBinary('non-existent-file.html');
d.always(
@@ -26,17 +26,17 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.wait();
},
+ /**
+ * When url is found, the deferred should callback with an instance
+ * of javascriptrrd.BinaryFile
+ **/
test_urlFound: function () {
- /**
- * When url is found, the deferred should callback with an instance
- * of javascriptrrd.BinaryFile
- **/
var self = this;
var d = new jarmon.downloadBinary('testfile.bin');
d.always(
function(ret) {
self.resume(function() {
- Y.Assert.isInstanceOf(jarmon.BinaryFile, ret);
+ Y.Assert.isInstanceOf(BinaryFile, ret);
Y.Assert.areEqual(
String.fromCharCode(0), ret.getCharAt(0));
});
@@ -69,11 +69,11 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
});
},
+ /**
+ * The generated rrd file should have a lastupdate date of
+ * 1980-01-01 00:50:01
+ **/
test_getLastUpdate: function () {
- /**
- * The generated rrd file should have a lastupdate date of
- * 1980-01-01 00:50:01
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -85,12 +85,12 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.wait();
},
+ /**
+ * The generated rrd file should have a single DS whose name is
+ * 'speed'. A RangeError is thrown if the requested index or dsName
+ * doesnt exist.
+ **/
test_getDSIndex: function () {
- /**
- * The generated rrd file should have a single DS whose name is
- * 'speed'. A RangeError is thrown if the requested index or dsName
- * doesnt exist.
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -110,10 +110,10 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.wait();
},
+ /**
+ * The generated rrd file should have a single RRA
+ **/
test_getNrRRAs: function () {
- /**
- * The generated rrd file should have a single RRA
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -124,13 +124,13 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.wait();
},
+ /**
+ * The generated rrd file should have a single RRA using AVERAGE
+ * consolidation, step=10, rows=6 and values 0-5
+ * rra.getEl throws a RangeError if asked for row which doesn't
+ * exist.
+ **/
test_getRRA: function () {
- /**
- * The generated rrd file should have a single RRA using AVERAGE
- * consolidation, step=10, rows=6 and values 0-5
- * rra.getEl throws a RangeError if asked for row which doesn't
- * exist.
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -168,10 +168,10 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
});
},
+ /**
+ * The starttime must be less than the endtime
+ **/
test_getDataTimeRangeOverlapError: function () {
- /**
- * The starttime must be less than the endtime
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -191,11 +191,11 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
},
+ /**
+ * Error is raised if the rrd file doesn't contain an RRA with the
+ * requested consolidation function (CF)
+ **/
test_getDataUnknownCfError: function () {
- /**
- * Error is raised if the rrd file doesn't contain an RRA with the
- * requested consolidation function (CF)
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -214,13 +214,13 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
},
+ /**
+ * The generated rrd file should have values 0-9 at 300s intervals
+ * starting at 1980-01-01 00:00:00
+ * Result should include a data points with times > starttime and
+ * <= endTime
+ **/
test_getData: function () {
- /**
- * The generated rrd file should have values 0-9 at 300s intervals
- * starting at 1980-01-01 00:00:00
- * Result should include a data points with times > starttime and
- * <= endTime
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -266,11 +266,11 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.wait();
},
+ /**
+ * If the requested time range is outside the range of the RRD file
+ * we should not get any values back
+ **/
test_getDataUnknownValues: function () {
- /**
- * If the requested time range is outside the range of the RRD file
- * we should not get any values back
- **/
var self = this;
this.d.done(
function(rrd) {
@@ -287,20 +287,17 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
return v * 10;
},
+ /**
+ * RrdQuery can be passed a transformer function which may
+ * manipulate the values from the RRDFile
+ **/
test_transformerFunction: function () {
- /**
- * RrdQuery can be passed a transformer function which may
- * manipulate the values from the RRDFile
- **/
var self = this;
this.d.done(
function(rrd) {
self.resume(function() {
- var rq = new jarmon.RrdQuery(rrd, '', self._x10transformer);
- var data = rq.getData(RRD_STARTTIME, RRD_ENDTIME);
- // Make sure that the transformer is the
- // function we asked for
- Y.Assert.areEqual(self._x10transformer, rq.transformer);
+ var rq = new jarmon.RrdQuery(rrd, '');
+ var data = rq.getData(RRD_STARTTIME, RRD_ENDTIME, undefined, undefined, self._x10transformer);
// Real data goes 0,1,2,3...
// should be transformed to 0,10,20...
Y.Assert.areEqual(0, data.data[0][1]);
@@ -321,10 +318,10 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.rq = new jarmon.RrdQueryRemote('build/test.rrd', '');
},
+ /**
+ * The starttime must be less than the endtime
+ **/
test_getDataTimeRangeOverlapError: function () {
- /**
- * The starttime must be less than the endtime
- **/
var self = this;
this.rq.getData(1, 0).fail(
function(res) {
@@ -336,11 +333,11 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
},
+ /**
+ * Error is raised if the rrd file doesn't contain an RRA with the
+ * requested consolidation function (CF)
+ **/
test_getDataUnknownCfError: function () {
- /**
- * Error is raised if the rrd file doesn't contain an RRA with the
- * requested consolidation function (CF)
- **/
var self = this;
this.rq.getData(RRD_STARTTIME, RRD_ENDTIME, 0, 'FOO').always(
function(res) {
@@ -352,13 +349,13 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
},
+ /**
+ * The generated rrd file should have values 0-9 at 300s intervals
+ * starting at 1980-01-01 00:00:00
+ * Result should include a data points with times > starttime and
+ * <= endTime
+ **/
test_getData: function () {
- /**
- * The generated rrd file should have values 0-9 at 300s intervals
- * starting at 1980-01-01 00:00:00
- * Result should include a data points with times > starttime and
- * <= endTime
- **/
var self = this;
this.rq.getData(RRD_STARTTIME + (RRD_STEP+1) * 1000,
RRD_ENDTIME - (RRD_STEP-1) * 1000).always(
@@ -399,11 +396,11 @@ YUI({ logInclude: { TestRunner: true } }).use('console', 'test', function(Y) {
this.wait();
},
+ /**
+ * If the requested time range is outside the range of the RRD file
+ * we should not get any values back
+ **/
test_getDataUnknownValues: function () {
- /**
- * If the requested time range is outside the range of the RRD file
- * we should not get any values back
- **/
var self = this;
this.rq.getData(RRD_ENDTIME, RRD_ENDTIME+1000).always(
function(data) {