diff options
-rw-r--r-- | README | 31 | ||||
-rw-r--r-- | index.html | 68 | ||||
-rw-r--r-- | jrrd.js | 149 |
3 files changed, 227 insertions, 21 deletions
@@ -0,0 +1,31 @@ +jrrd 0.0.1 + + +What is this? +============= +Wrappers and convenience fuctions for working with the javascriptRRD, Flot and +Collectd. + + +Debian / Ubuntu Quick Start +=========================== +To get this demo working, you will need to serve this page from a +local webserver and serve the folder that contains your RRD files. + +This demo is designed to work with the RRD files generated by +<a href="http://collectd.org/">Collectd</a>. + +# Install and configure collectd (enable the rrd plugin) +$ aptitude install collectd + +# Create a project folder +$ mkdir -p ~/Projects/jrrd +$ cd ~/Projects/jrrd + +# Link to the collectd rrd folder +$ ln -f /var/lib/collectd/rrd/localhost data + +# Start a local webserver +$ aptitude install twisted +$ twistd -n web --port 8080 --path . + @@ -7,6 +7,8 @@ <style type="text/css"> body { font-family: sans; + width: 800px; + margin: 20px auto 0 auto; } form div { @@ -14,10 +16,10 @@ } h2 { - width: 750px; - padding: 0 0 0 50px; + padding: 0 0 0 55px; margin: 20px auto 5px auto; font-size: 14px; + text-align: left; } .loading { @@ -27,14 +29,12 @@ } .range-preview { - width: 800px; height:50px; margin: 0 auto 0 auto; position: relative; } .chart { - width:800px; height:200px; margin: 0 auto 0 auto; } @@ -60,19 +60,29 @@ padding: 0; border: none; } + + .notice { + border: 1px solid Green; + background: #FFDDFF; + margin-bottom: 20px; + padding: 5px; + } </style> - <script type="text/javascript" src="assets/javascript/jquery-1.4.2.min.js"></script> - <script type="text/javascript" src="assets/javascript/flot/excanvas.min.js"></script> - <script type="text/javascript" src="assets/javascript/flot/jquery.flot.js"></script> - <script type="text/javascript" src="assets/javascript/flot/jquery.flot.stack.js"></script> - <script type="text/javascript" src="assets/javascript/flot/jquery.flot.selection.js"></script> - <script type="text/javascript" src="assets/javascript/javascriptrrd/binaryXHR.js"></script> - <script type="text/javascript" src="assets/javascript/javascriptrrd/rrdFile.js"></script> - <script type="text/javascript" src="assets/javascript/MochiKit/Base.js"></script> - <script type="text/javascript" src="assets/javascript/MochiKit/Async.js"></script> + + <script type="text/javascript" src="http://svn.mochikit.com/mochikit/trunk/MochiKit/Base.js"></script> + <script type="text/javascript" src="http://svn.mochikit.com/mochikit/trunk/MochiKit/Async.js"></script> + <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> + <script type="text/javascript" src="http://flot.googlecode.com/svn/trunk/excanvas.min.js"></script> + <script type="text/javascript" src="http://flot.googlecode.com/svn/trunk/jquery.flot.js"></script> + <script type="text/javascript" src="http://flot.googlecode.com/svn/trunk/jquery.flot.stack.js"></script> + <script type="text/javascript" src="http://flot.googlecode.com/svn/trunk/jquery.flot.selection.js"></script> + + <script type="text/javascript" src="http://javascriptrrd.cvs.sourceforge.net/viewvc/*checkout*/javascriptrrd/v0/src/lib/binaryXHR.js?revision=1.5&content-type=text%2Fplain"></script> + <script type="text/javascript" src="http://javascriptrrd.cvs.sourceforge.net/viewvc/*checkout*/javascriptrrd/v0/src/lib/rrdFile.js?revision=1.8&content-type=text%2Fplain"></script> + <script type="text/javascript" src="jrrd.js"></script> <script type="text/javascript"> - + // Options common to all the chart on this page var baseOptions = { grid: { clickable: false, @@ -100,6 +110,7 @@ } }; + // Extra options to generate a stacked chart var stacked = { series: { stack: true, @@ -109,6 +120,7 @@ } }; + // Recipes for the charts on this page var recipes = [ { title: 'CPU Usage', @@ -156,7 +168,7 @@ }, { - title: 'Wlan0 Throughput (B/sec)', + title: 'Wlan0 Throughput', data: [ ['data/interface/if_octets-wlan0.rrd', 'tx', 'Transmit', 'b/sec'], ['data/interface/if_octets-wlan0.rrd', 'rx', 'Receive', 'b/sec'] @@ -174,11 +186,13 @@ chartTemplate.clone().appendTo('.charts'), recipe); }); + // Update all charts when a selection is made on one of them $('.charts').bind("plotselected", function(event, ranges) { cc.setTimeRange(new Date(ranges.xaxis.from), new Date(ranges.xaxis.to)); }); + // Show a loading icon when a chart is being redrawn $('.chart-container').live('chart_loading', function(e) { $(this).find('.title').addClass('loading'); }); @@ -187,12 +201,36 @@ $(this).find('.title').removeClass('loading'); }); + // Initialise all the charts cc.reset(); }); </script> </head> <body> + <div class="notice"> + <p>To get this demo working, you will need to serve this page from a + local webserver and serve the folder that contains your RRD files.</p> + <p>This demo is designed to work with the RRD files generated by + <a href="http://collectd.org/">Collectd</a>.</p> + <h3>Debian / Ubuntu Quick Start</h3> + <pre> + # Install and configure collectd (enable the rrd plugin) + $ aptitude install collectd + + # Create a project folder + $ mkdir -p ~/Projects/jrrd + $ cd ~/Projects/jrrd + + # Link to the collectd rrd folder + $ ln -f /var/lib/collectd/rrd/localhost data + + # Start a local webserver + $ aptitude install twisted + $ twistd -n web --port 8080 --path . + </pre> + </div> + <form method="GET" class="chartRangeControl"> <div> <label>Start: <input type="text" name="startTime" /></label> @@ -4,15 +4,27 @@ * Wrappers and convenience fuctions for working with the javascriptRRD, jQuery, * and flot charting packages. * - * javascriptRRD - http://javascriptrrd.sourceforge.net/ - * jQuery - http://jquery.com/ - * flot - http://code.google.com/p/flot/ + * 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/ */ if(typeof jrrd == 'undefined') { var jrrd = {}; } +/** + * Download a binary file asynchronously using the jQuery.ajax function + * + * @param url: The url of the object to be downloaded + * @return: A I{MochiKit.Async.Deferred} which will callback with an instance of + * I{javascriptrrd.BinaryFile} + **/ jrrd.downloadBinary = function(url) { var d = new MochiKit.Async.Deferred(); @@ -68,6 +80,20 @@ jrrd.RrdQuery = function(rrd, unit) { }; jrrd.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. + * + * @param startTime: The I{Date} start time + * @param endTime: The I{Date} end time + * @param dsId: An index I{Number} or key I{String} identifying the RRD + * datasource (DS). + * @param cfName: The name I{String} of an RRD consolidation function (CF) + * eg AVERAGE, MIN, MAX + * @return: A Flot compatible data series I{Object} + * eg {label:'', data:[], unit: ''} + **/ var startTimestamp = startTime.getTime()/1000; var lastUpdated = this.rrd.getLastUpdate(); @@ -130,7 +156,13 @@ jrrd.RrdQuery.prototype.getData = function(startTime, endTime, dsId, cfName) { return {label: ds.getName(), data: flotData, unit: this.unit}; }; - +/** + * A wrapper around RrdQuery which provides asynchronous access to the data in a + * remote RRD file. + * + * @param url: The url I{String} of a remote RRD file + * @param unit: The unit suffix I{String} of this data eg 'bit/sec' + **/ jrrd.RrdQueryRemote = function(url, unit) { this.url = url; this.unit = unit; @@ -139,6 +171,14 @@ jrrd.RrdQueryRemote = function(url, unit) { }; jrrd.RrdQueryRemote.prototype.getData = function(startTime, endTime, dsId) { + /** + * Return a Flot compatible data series asynchronously. + * + * @param startTime: The start time I{Date} + * @param endTime: The end time I{Date} + * @returns: A I{MochiKit.Async.Deferred} which calls back with a flot data + * series object I{Object} + **/ var endTimestamp = endTime.getTime()/1000; // Download the rrd if there has never been a download or if the last @@ -180,7 +220,13 @@ jrrd.RrdQueryRemote.prototype.getData = function(startTime, endTime, dsId) { return ret; }; - +/** + * Wraps a I{RrdQueryRemote} to provide access to a different RRD DSs within a + * single RrdDataSource. + * + * @param rrdQuery: An I{RrdQueryRemote} + * @param dsId: An index or keyname of an RRD DS + **/ jrrd.RrdQueryDsProxy = function(rrdQuery, dsId) { this.rrdQuery = rrdQuery; this.dsId = dsId; @@ -188,21 +234,43 @@ jrrd.RrdQueryDsProxy = function(rrdQuery, dsId) { }; jrrd.RrdQueryDsProxy.prototype.getData = function(startTime, endTime) { + /** + * Call I{RrdQueryRemote.getData} with a particular dsId + **/ return this.rrdQuery.getData(startTime, endTime, this.dsId); }; +/** + * A class for creating a Flot chart from a series of RRD Queries + * + * @param template: A I{jQuery} containing a single element into which the chart + * will be drawn + * @param options: An I{Object} containing Flot options which describe how the + * chart should be drawn. + **/ jrrd.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. $('.legend tr', this.template[0]).live('click', function(e) { self.switchDataEnabled($(this).children('.legendLabel').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', @@ -250,6 +318,16 @@ jrrd.Chart = function(template, options) { }; jrrd.Chart.prototype.addData = function(label, db, enabled) { + /** + * Add details of a remote RRD data source whose data will be added to this + * chart. + * + * @param label: A I{String} label for this data which will be shown in the + * chart legend + * @param db: The url of the remote RRD database + * @param enabled: true if you want this data plotted on the chart, false + * if not. + **/ if(typeof enabled == 'undefined') { enabled = true; } @@ -257,6 +335,12 @@ jrrd.Chart.prototype.addData = function(label, db, enabled) { }; jrrd.Chart.prototype.switchDataEnabled = function(label) { + /** + * Enable / Disable a single data source + * + * @param label: The label I{String} 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]; @@ -265,12 +349,26 @@ jrrd.Chart.prototype.switchDataEnabled = function(label) { }; jrrd.Chart.prototype.setTimeRange = function(startTime, endTime) { + /** + * Alter the time range of this chart and redraw + * + * @param startTime: The start time I{Date} + * @param endTime: The end time I{Date} + **/ this.startTime = startTime; this.endTime = endTime; this.draw(); } jrrd.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 + * + * @return: A I{MochiKit.Async.Deferred} which calls back with the chart + * data when the chart has been rendered. + **/ this.template.trigger('chart_loading'); var result; var results = []; @@ -333,6 +431,7 @@ jrrd.Chart.prototype.draw = function() { 'text-align': 'right'}); self.template.append(yaxisUnitLabel); yaxisUnitLabel.position(self.template.position()); + return data; }, this) .addErrback( function(self, failure) { @@ -347,6 +446,15 @@ jrrd.Chart.prototype.draw = function() { jrrd.Chart.fromRecipe = function(template, recipe) { + /** + * A factory function to generate a I{Chart} from a recipe + * + * @param template: A I{jQuery} containing the element in which the chart is + * drawn. + * @param A recipe I{Object} eg + * {title: '', data: [rrdUrl, rrdDs, label, unit], options: {}} + * @return: A I{Chart} instance + **/ template.find('.title').text(recipe['title']); var c = new jrrd.Chart(template.find('.chart'), recipe['options']); var dataDict = {}; @@ -365,20 +473,33 @@ jrrd.Chart.fromRecipe = function(template, recipe) { } +/** + * 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. + * + * @param ui: A one element I{jQuery} containing an input form and placeholders + * for the timeline and for the series of charts. + **/ jrrd.ChartCoordinator = function(ui) { this.ui = ui; this.charts = []; var self = this; + + // Update the time ranges and redraw charts when the form is submitted this.ui.bind('submit', function(e) { self.update(); return false; }); + // Reset all the charts to the default time range when the reset button is + // pressed. this.ui.bind('reset', function(e) { self.reset(); return false; }); + + // Style and configuration of the range timeline var rangePreviewOptions = { grid: { borderWidth: 1 @@ -400,12 +521,15 @@ jrrd.ChartCoordinator = function(ui) { var MONTH = DAY * 31; var YEAR = DAY * 365; + // Dummy data for the range timeline var data = [ [now - WEEK, null], [now, null]]; - this.rangePreview = $.plot(this.ui.find('.range-preview'), [data], rangePreviewOptions); + this.rangePreview = $.plot(this.ui.find('.range-preview'), [data], + rangePreviewOptions); + // When a selection is made on the range timeline, redraw all the charts. this.ui.bind("plotselected", function(event, ranges) { self.setTimeRange(new Date(ranges.xaxis.from), new Date(ranges.xaxis.to)); @@ -413,6 +537,10 @@ jrrd.ChartCoordinator = function(ui) { }; jrrd.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. + **/ var startTime = new Date(this.ui[0].startTime.value); var endTime = new Date(this.ui[0].endTime.value); var ranges = { @@ -428,12 +556,21 @@ jrrd.ChartCoordinator.prototype.update = function() { }; jrrd.ChartCoordinator.prototype.setTimeRange = function(startTime, endTime) { + /** + * Set the start and end time fields in the form and trigger an update + * + * @param startTime: The start time I{Date} + * @param endTime: The end time I{Date} + **/ this.ui[0].startTime.value = startTime.toString().split(' ').slice(1,5).join(' '); this.ui[0].endTime.value = endTime.toString().split(' ').slice(1,5).join(' '); this.update(); }; jrrd.ChartCoordinator.prototype.reset = function() { + /** + * Reset all charts and the input form to the default time range - last hour + **/ this.setTimeRange(new Date(new Date().getTime()-1*60*60*1000), new Date()); }; |