├── test ├── data │ ├── load.rrd │ ├── cpu-idle.rrd │ ├── cpu-nice.rrd │ ├── cpu-steal.rrd │ ├── cpu-user.rrd │ ├── cpu-wait.rrd │ ├── cpu-softirq.rrd │ ├── cpu-system.rrd │ ├── memory-free.rrd │ ├── memory-used.rrd │ ├── cpu-interrupt.rrd │ ├── if_octets-eth0.rrd │ ├── memory-cached.rrd │ └── memory-buffered.rrd ├── test5.html ├── test6.html ├── test2.html ├── test3.html ├── test1.html └── test4.html ├── README.md └── js ├── sprintf.js ├── Color.js ├── base64.js ├── strftime.js ├── RrdDataFile.js ├── RrdGfxCanvas.js ├── RrdGfxSvg.js ├── binaryXHR.js ├── rrdFile.js ├── RrdTime.js ├── RrdRpn.js ├── RrdJson.js └── RrdCmdLine.js /test/data/load.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/load.rrd -------------------------------------------------------------------------------- /test/data/cpu-idle.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-idle.rrd -------------------------------------------------------------------------------- /test/data/cpu-nice.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-nice.rrd -------------------------------------------------------------------------------- /test/data/cpu-steal.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-steal.rrd -------------------------------------------------------------------------------- /test/data/cpu-user.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-user.rrd -------------------------------------------------------------------------------- /test/data/cpu-wait.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-wait.rrd -------------------------------------------------------------------------------- /test/data/cpu-softirq.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-softirq.rrd -------------------------------------------------------------------------------- /test/data/cpu-system.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-system.rrd -------------------------------------------------------------------------------- /test/data/memory-free.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/memory-free.rrd -------------------------------------------------------------------------------- /test/data/memory-used.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/memory-used.rrd -------------------------------------------------------------------------------- /test/data/cpu-interrupt.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/cpu-interrupt.rrd -------------------------------------------------------------------------------- /test/data/if_octets-eth0.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/if_octets-eth0.rrd -------------------------------------------------------------------------------- /test/data/memory-cached.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/memory-cached.rrd -------------------------------------------------------------------------------- /test/data/memory-buffered.rrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manuelluis/jsrrdgraph/HEAD/test/data/memory-buffered.rrd -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #jsrrdgraph 2 | 3 | This is a translation of the C source code of rrdtool to javascript in an attempt to reproduce the command ``rrdtool graph`` in javascript. 4 | 5 | You can view some test pages in: http://manuelluis.github.io/jsrrdgraph/ 6 | 7 | The examples uses ``javascriptRRD`` for testing purposes, to load the data. 8 | -------------------------------------------------------------------------------- /js/sprintf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | **/ 18 | 19 | "use strict"; 20 | 21 | function sprintf() 22 | { 23 | var argc = 0; 24 | var args = arguments; 25 | var fmt = args[argc++]; 26 | 27 | function lpad (str, padString, length) 28 | { 29 | while (str.length < length) 30 | str = padString + str; 31 | return str; 32 | } 33 | 34 | function format (match, width, dot, precision, length, conversion) 35 | { 36 | if (match === '%%') return '%'; 37 | 38 | var value = args[argc++]; 39 | var prefix; 40 | 41 | if (width === undefined) 42 | width = 0; 43 | else 44 | width = +width; 45 | 46 | if (precision === undefined) 47 | precision = conversion == 'd' ? 0 : 6; 48 | else 49 | precision = +precision; 50 | 51 | switch (conversion) { 52 | case 's': 53 | case 'c': 54 | return value; 55 | case 'd': 56 | return parseInt(value, 10); 57 | case 'e': 58 | prefix = value < 0 ? '-' : ''; 59 | return lpad(prefix+Math.abs(value).toExponential(precision),' ',width); 60 | case 'F': 61 | case 'f': 62 | prefix = value < 0 ? '-' : ''; 63 | return lpad(prefix+Math.abs(value).toFixed(precision),' ',width); 64 | case 'g': 65 | prefix = value < 0 ? '-' : ''; 66 | return lpad(prefix+Math.abs(value).toPrecision(precision),' ',width); 67 | default: 68 | return match; 69 | } 70 | 71 | } 72 | return fmt.replace(/%(\d+)?(\.(\d+))?(l?)([%scdfFeg])/g,format); 73 | } 74 | -------------------------------------------------------------------------------- /js/Color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | * 17 | **/ 18 | 19 | "use strict"; 20 | 21 | /** 22 | * ColorError 23 | * @constructor 24 | */ 25 | var ColorError = function (message) 26 | { 27 | this.name = "ColorError"; 28 | this.message = (message) ? message : "Error"; 29 | }; 30 | ColorError.prototype = new Error(); 31 | 32 | /** 33 | * Color 34 | * @constructor 35 | */ 36 | function Color(str) 37 | { 38 | var bits; 39 | 40 | this.r = 0; 41 | this.g = 0; 42 | this.b = 0; 43 | this.a = 1.0; 44 | 45 | if ((bits = /^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/.exec(str))) { 46 | this.r = parseInt(bits[1]+bits[1], 16); 47 | this.g = parseInt(bits[2]+bits[2], 16); 48 | this.b = parseInt(bits[3]+bits[3], 16); 49 | } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) { 50 | this.r = parseInt(bits[1], 16); 51 | this.g = parseInt(bits[2], 16); 52 | this.b = parseInt(bits[3], 16); 53 | } else if ((bits = /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/.exec(str))) { 54 | this.r = parseInt(bits[1], 16); 55 | this.g = parseInt(bits[2], 16); 56 | this.b = parseInt(bits[3], 16); 57 | this.a = parseInt(bits[4], 16)/255; 58 | } else if ((bits = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\)$/.exec(str))) { 59 | this.r = parseInt(bits[1], 10); 60 | this.g = parseInt(bits[2], 10); 61 | this.b = parseInt(bits[3], 10); 62 | } else if ((bits = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([0-9.]+)\)$/.exec(str))) { 63 | this.r = parseInt(bits[1], 10); 64 | this.g = parseInt(bits[2], 10); 65 | this.b = parseInt(bits[3], 10); 66 | this.a = parseFloat(bits[4], 10); 67 | } else { 68 | throw new ColorError("Unknow color format '"+str+"'"); 69 | } 70 | }; 71 | 72 | Color.prototype.torgba = function () 73 | { 74 | return 'rgba('+this.r+','+this.g+','+this.b+','+this.a+')'; 75 | }; 76 | 77 | -------------------------------------------------------------------------------- /test/test5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RRD Canvas 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 59 | 60 | 61 | 69 |
70 | 71 |
72 | 73 |

74 |

75 | 76 | 77 | -------------------------------------------------------------------------------- /js/base64.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * Base64 encode / decode 5 | * http://www.webtoolkit.info/ 6 | * 7 | **/ 8 | 9 | var Base64 = { 10 | 11 | // private property 12 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 13 | 14 | // public method for encoding 15 | encode : function (input) { 16 | var output = ""; 17 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 18 | var i = 0; 19 | 20 | input = Base64._utf8_encode(input); 21 | 22 | while (i < input.length) { 23 | 24 | chr1 = input.charCodeAt(i++); 25 | chr2 = input.charCodeAt(i++); 26 | chr3 = input.charCodeAt(i++); 27 | 28 | enc1 = chr1 >> 2; 29 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 30 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 31 | enc4 = chr3 & 63; 32 | 33 | if (isNaN(chr2)) { 34 | enc3 = enc4 = 64; 35 | } else if (isNaN(chr3)) { 36 | enc4 = 64; 37 | } 38 | 39 | output = output + 40 | this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + 41 | this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); 42 | 43 | } 44 | 45 | return output; 46 | }, 47 | 48 | // public method for decoding 49 | decode : function (input) { 50 | var output = ""; 51 | var chr1, chr2, chr3; 52 | var enc1, enc2, enc3, enc4; 53 | var i = 0; 54 | 55 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 56 | 57 | while (i < input.length) { 58 | 59 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 60 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 61 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 62 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 63 | 64 | chr1 = (enc1 << 2) | (enc2 >> 4); 65 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 66 | chr3 = ((enc3 & 3) << 6) | enc4; 67 | 68 | output = output + String.fromCharCode(chr1); 69 | 70 | if (enc3 != 64) { 71 | output = output + String.fromCharCode(chr2); 72 | } 73 | if (enc4 != 64) { 74 | output = output + String.fromCharCode(chr3); 75 | } 76 | 77 | } 78 | 79 | output = Base64._utf8_decode(output); 80 | 81 | return output; 82 | 83 | }, 84 | 85 | // private method for UTF-8 encoding 86 | _utf8_encode : function (string) { 87 | string = string.replace(/\r\n/g,"\n"); 88 | var utftext = ""; 89 | 90 | for (var n = 0; n < string.length; n++) { 91 | 92 | var c = string.charCodeAt(n); 93 | 94 | if (c < 128) { 95 | utftext += String.fromCharCode(c); 96 | } 97 | else if((c > 127) && (c < 2048)) { 98 | utftext += String.fromCharCode((c >> 6) | 192); 99 | utftext += String.fromCharCode((c & 63) | 128); 100 | } 101 | else { 102 | utftext += String.fromCharCode((c >> 12) | 224); 103 | utftext += String.fromCharCode(((c >> 6) & 63) | 128); 104 | utftext += String.fromCharCode((c & 63) | 128); 105 | } 106 | 107 | } 108 | 109 | return utftext; 110 | }, 111 | 112 | // private method for UTF-8 decoding 113 | _utf8_decode : function (utftext) { 114 | var string = ""; 115 | var i = 0; 116 | var c, c1, c2, c3; 117 | c = c1 = c2 = 0; 118 | 119 | while ( i < utftext.length ) { 120 | 121 | c = utftext.charCodeAt(i); 122 | 123 | if (c < 128) { 124 | string += String.fromCharCode(c); 125 | i++; 126 | } 127 | else if((c > 191) && (c < 224)) { 128 | c2 = utftext.charCodeAt(i+1); 129 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 130 | i += 2; 131 | } 132 | else { 133 | c2 = utftext.charCodeAt(i+1); 134 | c3 = utftext.charCodeAt(i+2); 135 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 136 | i += 3; 137 | } 138 | 139 | } 140 | 141 | return string; 142 | } 143 | 144 | }; 145 | -------------------------------------------------------------------------------- /js/strftime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | **/ 18 | 19 | "use strict"; 20 | 21 | function strftime (fmt, time) 22 | { 23 | var d = new Date(time*1000); 24 | 25 | var days = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]; 26 | var fdays = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 27 | var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; 28 | var fmonths = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 29 | 30 | function pad2 (number) 31 | { 32 | return (number < 10 ? '0' : '') + number; 33 | } 34 | 35 | function pad3(number) 36 | { 37 | return (number < 10 ? '00' : number < 100 ? '0' : '') + number; 38 | } 39 | 40 | function format(match, opt) 41 | { 42 | if (match === '%%') return '%'; 43 | 44 | switch (opt) { 45 | case 'a': 46 | return days[d.getDay()]; 47 | case 'A': 48 | return fdays[d.getDay()]; 49 | case 'b': 50 | return months[d.getMonth()]; 51 | case 'B': 52 | return fmonths[d.getMonth()]; 53 | case 'c': 54 | return d.toLocaleString(); 55 | case 'd': 56 | return pad2(d.getDate()); 57 | case 'H': 58 | return pad2(d.getHours()); 59 | case 'I': 60 | var hours = d.getHours()%12; 61 | return pad2(hours === 0 ? 12 : hours); 62 | case 'j': 63 | var d01 = new Date (d.getFullYear(), 0, 1); 64 | return pad3(Math.ceil((d.getTime()-d01.getTime())/86400000)+1); 65 | case 'm': 66 | return pad2(d.getMonth()); 67 | case 'M': 68 | return pad2(d.getMinutes()); 69 | case 'p': 70 | return d.getHours() >= 12 ? 'PM' : 'AM'; 71 | case 's': 72 | return pad2(d.getSeconds()); 73 | case 'S': 74 | return d.getTime()/1000; 75 | case 'u': 76 | return d.getDay() === 0 ? 7 : d.getDay(); 77 | case 'U': 78 | var d01 = new Date(d.getFullYear(),0,1); 79 | return pad2(Math.round((Math.ceil((d.getTime()-d01.getTime())/86400000)+1 + 6 - d.getDay())/7)); 80 | case 'V': 81 | var d01 = new Date(d.getFullYear(), 0, 1); 82 | var w = Math.round((Math.ceil((d.getTime()-d01.getTime())/86400000)+1 + 7 - (d.getDay() === 0 ? 7 : d.getDay()))/7); 83 | var d31 = new Date(d.getFullYear(), 11, 31); 84 | if (d01.getDay() < 4 && d01.getDay() > 1) w++; 85 | if (w === 53 && d31.getDay() < 4) { 86 | w = 1; 87 | } else if (w === 0) { 88 | d31 = new Date(d.getFullYear()-1, 11, 31); 89 | d01 = new Date(d31.getFullYear(), 0, 1); 90 | w = Math.round((Math.ceil((d31.getTime()-d01.getTime())/86400000)+1 + 7 - (d31.getDay() === 0 ? 7 : d31.getDay()))/7); 91 | if (d01.getDay() < 4 && d01.getDay() > 1) w++; 92 | if (w === 53 && d31.getDay() < 4) w = 1; 93 | } 94 | return pad2(w); 95 | case 'w': 96 | return d.getDay(); 97 | case 'W': 98 | var d01 = new Date(d.getFullYear(),0,1); 99 | return pad2(Math.round((Math.ceil((d.getTime()-d01.getTime())/86400000)+1 + 7 - (d.getDay() === 0 ? 7 : d.getDay()))/7)); 100 | case 'x': 101 | return pad2(d.getDate())+'/'+pad2(d.getMonth())+'/'+d.getFullYear(); 102 | case 'X': 103 | return pad2(d.getHours())+':'+pad2(d.getMinutes())+':'+pad2(d.getSeconds()); 104 | case 'y': 105 | return pad2(d.getFullYear()%100); 106 | case 'Y': 107 | return d.getFullYear(); 108 | case 'Z': 109 | return d.toString().replace(/^.*\(([^)]+)\)$/, '$1'); 110 | default: 111 | return match; 112 | } 113 | } 114 | return fmt.replace(/%([aAbBcdHIjmMpsSUVwWxXyYZ%])/g, format); 115 | } 116 | -------------------------------------------------------------------------------- /js/RrdDataFile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | * 18 | * Manuel Sanmartin 19 | **/ 20 | 21 | "use strict"; 22 | 23 | /** 24 | * RrdDataFile 25 | * @constructor 26 | */ 27 | var RrdDataFile = function() { 28 | this.init.apply(this, arguments); 29 | }; 30 | 31 | RrdDataFile.prototype = { 32 | rrdfiles: null, 33 | 34 | init: function() 35 | { 36 | this.rrdfiles = {}; 37 | this.rrdfiles_fetching = {}; 38 | this.rrdfiles_wait = {}; 39 | }, 40 | build: function(gdp, ft_step, rrd) 41 | { 42 | var cal_start, cal_end; 43 | var best_full_rra = -1, best_part_rra = -1, chosen_rra = 0; 44 | var best_full_step_diff = Infinity, best_part_step_diff = Infinity; 45 | var tmp_step_diff = 0, tmp_match = 0, best_match = -1; 46 | var full_match; 47 | var data_ptr; 48 | var rows; 49 | var rra; 50 | var i, ii; 51 | var last_update = rrd.getLastUpdate(); 52 | 53 | var cf_idx = gdp.cf; 54 | var ds_cnt = rrd.getNrDSs(); 55 | var rra_cnt = rrd.getNrRRAs(); 56 | 57 | for (i = 0; i < ds_cnt; i++) 58 | gdp.ds_namv[i] = rrd.rrd_header.getDSbyIdx(i).getName(); 59 | 60 | /* if the requested graph starts after the last available data point, 61 | * return one big NaN instead of taking the finest (step=1) RRA */ 62 | if (gdp.start > last_update) { 63 | ft_step = gdp.end - gdp.start; 64 | gdp.ds_cnt = ds_cnt; 65 | gdp.data = []; 66 | for (ii = 0; ii < ds_cnt; ii++) 67 | gdp.data[ii] = Number.NaN; 68 | return ft_step; 69 | } 70 | 71 | for (i = 0; i < rra_cnt; i++) { 72 | rra = rrd.getRRAInfo(i); 73 | if (RrdGraphDesc.cf_conv(rra.getCFName()) === cf_idx) { 74 | /* covered seconds in this RRA */ 75 | var range_secs = rra.getStep(); 76 | cal_end = last_update - (last_update % range_secs); 77 | cal_start = cal_end - (range_secs * rra.row_cnt); 78 | full_match = gdp.end - gdp.start; 79 | 80 | tmp_step_diff = Math.abs(ft_step - range_secs); 81 | if (cal_start <= gdp.start) { 82 | if (tmp_step_diff < best_full_step_diff) { 83 | best_full_step_diff = tmp_step_diff; 84 | best_full_rra = i; 85 | } 86 | } else { 87 | tmp_match = full_match; 88 | if (cal_start > gdp.start) tmp_match -= (cal_start - gdp.start); 89 | if (best_match < tmp_match || (best_match === tmp_match && 90 | tmp_step_diff < best_part_step_diff)) { 91 | best_match = tmp_match; 92 | best_part_step_diff = tmp_step_diff; 93 | best_part_rra = i; 94 | } 95 | } 96 | } 97 | } 98 | 99 | if (best_full_rra >= 0) chosen_rra = best_full_rra; 100 | else if (best_part_rra >= 0) chosen_rra = best_part_rra; 101 | else throw "the RRD does not contain an RRA matching the chosen CF"; 102 | 103 | var rra_info = rrd.getRRAInfo(chosen_rra); 104 | rra = rrd.getRRA(chosen_rra); 105 | 106 | ft_step = rra_info.getStep(); 107 | gdp.start -= (gdp.start % ft_step); 108 | gdp.end += (ft_step - gdp.end % ft_step); 109 | rows = (gdp.end - gdp.start) / ft_step + 1; 110 | 111 | gdp.ds_cnt = ds_cnt; 112 | data_ptr = 0; 113 | 114 | var rra_end_time = (last_update - (last_update % ft_step)); 115 | var rra_start_time = (rra_end_time - (ft_step * (rra_info.row_cnt - 1))); 116 | /* here's an error by one if we don't be careful */ 117 | var start_offset = (gdp.start + ft_step - rra_start_time) / ft_step; 118 | var end_offset = (rra_end_time - gdp.end) / ft_step; 119 | 120 | gdp.data = []; 121 | 122 | for (i = start_offset; i < rra.row_cnt - end_offset; i++) { 123 | if (i < 0) { 124 | for (ii = 0; ii < ds_cnt; ii++) 125 | gdp.data[data_ptr++] = Number.NaN; 126 | } else if (i >= rra.row_cnt) { 127 | for (ii = 0; ii < ds_cnt; ii++) 128 | gdp.data[data_ptr++] = Number.NaN; 129 | } else { 130 | for(ii = 0; ii < ds_cnt; ii++) 131 | gdp.data[data_ptr++] = rra.getEl(i, ii); 132 | } 133 | } 134 | return ft_step; 135 | }, 136 | fetch: function(gdp, ft_step) 137 | { 138 | var rrd; 139 | 140 | if (gdp.rrd in this.rrdfiles) { 141 | rrd = this.rrdfiles[gdp.rrd]; 142 | } else { 143 | var bf = FetchBinaryURL(gdp.rrd); 144 | rrd = new RRDFile(bf); 145 | this.rrdfiles[gdp.rrd] = rrd; 146 | } 147 | 148 | return this.build(gdp, ft_step, rrd); 149 | }, 150 | fetch_async_callback: function(bf, args) 151 | { 152 | var rrd; 153 | 154 | rrd = new RRDFile(bf); 155 | args.this.rrdfiles[args.gdp.rrd] = rrd; 156 | args.callback(args.callback_arg, args.this.build(args.gdp, args.ft_step, rrd)); 157 | 158 | for(var vname in args.this.rrdfiles_wait) 159 | { 160 | var o_args = args.this.rrdfiles_wait[vname]; 161 | if (args.gdp.rrd == o_args.gdp.rrd) 162 | { 163 | delete args.this.rrdfiles_wait[vname]; 164 | o_args.callback(o_args.callback_arg, args.this.build(o_args.gdp, o_args.ft_step, rrd)); 165 | } 166 | } 167 | }, 168 | fetch_async: function(gdp, ft_step, callback, callback_arg) 169 | { 170 | if (gdp.rrd === null) return -1; 171 | 172 | if (gdp.rrd in this.rrdfiles) { 173 | callback(callback_arg, this.build(gdp, ft_step, this.rrdfiles[gdp.rrd])); 174 | } else if (gdp.rrd in this.rrdfiles_fetching) { 175 | this.rrdfiles_wait[gdp.vname] = { this:this, gdp: gdp, ft_step: ft_step, callback: callback, callback_arg: callback_arg }; 176 | if (gdp.rrd in this.rrdfiles) 177 | { 178 | delete this.rrdfiles_wait[gdp.vname]; 179 | callback(callback_arg, this.build(gdp, ft_step, this.rrdfiles[gdp.rrd])); 180 | } 181 | } else { 182 | this.rrdfiles_fetching[gdp.rrd] = FetchBinaryURLAsync(gdp.rrd, this.fetch_async_callback, { this:this, gdp: gdp, ft_step: ft_step, callback: callback, callback_arg: callback_arg }); 183 | } 184 | return 0; 185 | } 186 | }; 187 | -------------------------------------------------------------------------------- /test/test6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RRD Canvas 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 144 | 145 | 146 | 173 |
174 | 175 |
176 | 177 |
178 | 179 |
180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /test/test2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RRD Canvas 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 144 | 145 | 146 | 173 |
174 | 175 |
176 | 177 |

178 |

179 | 180 | 181 | -------------------------------------------------------------------------------- /test/test3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RRD Canvas 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 144 | 145 | 146 | 173 |
174 | 175 |
176 | 177 |
178 | 179 |
180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /js/RrdGfxCanvas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | * 18 | * Manuel Sanmartin 19 | **/ 20 | 21 | "use strict"; 22 | 23 | /** 24 | * RrdGfxCanvas 25 | * @constructor 26 | */ 27 | var RrdGfxCanvas = function(canvasId) 28 | { 29 | this.canvas = document.getElementById(canvasId); 30 | this.ctx = this.canvas.getContext('2d'); 31 | this.dash = false; 32 | this.dash_offset = null; 33 | this.dash_array = null; 34 | }; 35 | 36 | RrdGfxCanvas.prototype.size = function (width, height) 37 | { 38 | this.canvas.width = width; 39 | this.canvas.height = height; 40 | }; 41 | 42 | RrdGfxCanvas.prototype.set_dash = function (dashes, n, offset) 43 | { 44 | this.dash = true; 45 | this.dash_array = dashes; 46 | this.dash_offset = offset; 47 | }; 48 | 49 | RrdGfxCanvas.prototype._set_dash = function () 50 | { 51 | if (this.dash_array != undefined && this.dash_array.length > 0) { 52 | this.ctx.setLineDash(this.dash_array); 53 | if (this.dash_offset > 0) { 54 | this.ctx.lineDashOffset = this.dash_offset; 55 | } 56 | } 57 | this.dash = false; 58 | this.dash_array = null; 59 | this.dash_offset = 0; 60 | }; 61 | 62 | RrdGfxCanvas.prototype.line = function (X0, Y0, X1, Y1, width, color) 63 | { 64 | X0 = Math.round(X0); 65 | Y0 = Math.round(Y0); 66 | X1 = Math.round(X1); 67 | Y1 = Math.round(Y1); 68 | 69 | if (Y0 === Y1) { 70 | Y0 += 0.5; 71 | Y1 += 0.5; 72 | } else if (X0 === X1) { 73 | X0 += 0.5; 74 | X1 += 0.5; 75 | } 76 | this.ctx.save(); 77 | this.ctx.lineWidth = width; 78 | this.ctx.strokeStyle = color; 79 | if (this.dash) this._set_dash(); 80 | this.ctx.beginPath(); 81 | this.ctx.moveTo(X0, Y0); 82 | this.ctx.lineTo(X1, Y1); 83 | this.ctx.stroke(); 84 | this.ctx.restore(); 85 | }; 86 | 87 | RrdGfxCanvas.prototype.dashed_line = function (X0, Y0, X1, Y1, width, color, dash_on, dash_off) 88 | { 89 | var swap, n; 90 | X0 = Math.round(X0); 91 | Y0 = Math.round(Y0); 92 | X1 = Math.round(X1); 93 | Y1 = Math.round(Y1); 94 | 95 | this.ctx.save(); 96 | this.ctx.lineWidth = width; 97 | this.ctx.strokeStyle = color; 98 | this.ctx.setLineDash([ dash_on, dash_off ]); 99 | this.ctx.lineDashOffset = dash_on; 100 | this.ctx.beginPath(); 101 | 102 | if (Y0 === Y1) { 103 | Y0 += 0.5; 104 | Y1 += 0.5; 105 | } else if (X0 === X1) { 106 | X0 += 0.5; 107 | X1 += 0.5; 108 | } 109 | 110 | this.ctx.moveTo(X0, Y0); 111 | this.ctx.lineTo(X1, Y1); 112 | /* 113 | if (Y0 === Y1) { 114 | Y0 += 0.5; 115 | Y1 += 0.5; 116 | if (X0 > X1) { 117 | swap = X0; 118 | X0 = X1; 119 | X1 = swap; 120 | } 121 | this.ctx.moveTo(X0, Y0); 122 | n = 0; 123 | while(X0<=X1) { 124 | if (n%2 === 1) { 125 | X0 += dash_on; 126 | this.ctx.lineTo(X0, Y0); 127 | } else { 128 | X0 += dash_off; 129 | this.ctx.moveTo(X0, Y0); 130 | } 131 | n++; 132 | } 133 | } else if (X0 === X1) { 134 | X0 += 0.5; 135 | X1 += 0.5; 136 | if (Y0 > Y1) { 137 | swap = Y0; 138 | Y0 = Y1; 139 | Y1 = swap; 140 | } 141 | this.ctx.moveTo(X0, Y0); 142 | n = 0; 143 | while(Y0<=Y1) { 144 | if (n%2 === 1) { 145 | Y0 += dash_on; 146 | this.ctx.lineTo(X0, Y0); 147 | } else { 148 | Y0 += dash_off; 149 | this.ctx.moveTo(X0, Y0); 150 | } 151 | n++; 152 | } 153 | 154 | } else { 155 | this.ctx.moveTo(X0, Y0); 156 | this.ctx.lineTo(X1, Y1); 157 | } 158 | */ 159 | this.ctx.stroke(); 160 | this.ctx.restore(); 161 | }; 162 | 163 | RrdGfxCanvas.prototype.rectangle = function (X0, Y0, X1, Y1, width, style) 164 | { 165 | X0 = Math.round(X0)+0.5; 166 | X1 = Math.round(X1)+0.5; 167 | Y0 = Math.round(Y0)+0.5; 168 | Y1 = Math.round(Y1)+0.5; 169 | 170 | this.ctx.save(); 171 | this.ctx.beginPath(); 172 | if (this.dash) this._set_dash(); 173 | this.ctx.lineWidth = width; 174 | this.ctx.moveTo(X0, Y0); 175 | this.ctx.lineTo(X1, Y0); 176 | this.ctx.lineTo(X1, Y1); 177 | this.ctx.lineTo(X0, Y1); 178 | this.ctx.closePath(); 179 | this.ctx.strokeStyle = style; 180 | this.ctx.stroke(); 181 | this.ctx.restore(); 182 | }; 183 | 184 | RrdGfxCanvas.prototype.new_area = function (X0, Y0, X1, Y1, X2, Y2, color) 185 | { 186 | X0 = Math.round(X0)+0.5; 187 | Y0 = Math.round(Y0)+0.5; 188 | X1 = Math.round(X1)+0.5; 189 | Y1 = Math.round(Y1)+0.5; 190 | X2 = Math.round(X2)+0.5; 191 | Y2 = Math.round(Y2)+0.5; 192 | this.ctx.fillStyle = color; 193 | this.ctx.beginPath(); 194 | this.ctx.moveTo(X0, Y0); 195 | this.ctx.lineTo(X1, Y1); 196 | this.ctx.lineTo(X2, Y2); 197 | }; 198 | 199 | RrdGfxCanvas.prototype.add_point = function (x, y) 200 | { 201 | x = Math.round(x)+0.5; 202 | y = Math.round(y)+0.5; 203 | this.ctx.lineTo(x, y); 204 | }; 205 | 206 | RrdGfxCanvas.prototype.close_path = function () 207 | { 208 | this.ctx.closePath(); 209 | this.ctx.fill(); 210 | }; 211 | 212 | RrdGfxCanvas.prototype.stroke_begin = function (width, style) 213 | { 214 | this.ctx.save(); 215 | this.ctx.beginPath(); 216 | if (this.dash) this._set_dash(); 217 | this.ctx.lineWidth = width; 218 | this.ctx.strokeStyle = style; 219 | this.ctx.lineCap = 'round'; 220 | this.ctx.round = 'round'; 221 | }; 222 | 223 | RrdGfxCanvas.prototype.stroke_end = function () 224 | { 225 | this.ctx.stroke(); 226 | this.ctx.restore(); 227 | }; 228 | 229 | RrdGfxCanvas.prototype.moveTo = function (x,y) 230 | { 231 | x = Math.round(x)+0.5; 232 | y = Math.round(y)+0.5; 233 | this.ctx.moveTo(x, y); 234 | }; 235 | 236 | RrdGfxCanvas.prototype.lineTo = function (x,y) 237 | { 238 | x = Math.round(x)+0.5; 239 | y = Math.round(y)+0.5; 240 | this.ctx.lineTo(x, y); 241 | }; 242 | 243 | RrdGfxCanvas.prototype.text = function (x, y, color, font, tabwidth, angle, h_align, v_align, text) 244 | { 245 | x = Math.round(x); 246 | y = Math.round(y); 247 | 248 | this.ctx.save(); 249 | this.ctx.font = font.size+'px '+font.font; 250 | 251 | switch (h_align) { 252 | case RrdGraph.GFX_H_LEFT: 253 | this.ctx.textAlign = 'left'; 254 | break; 255 | case RrdGraph.GFX_H_RIGHT: 256 | this.ctx.textAlign = 'right'; 257 | break; 258 | case RrdGraph.GFX_H_CENTER: 259 | this.ctx.textAlign = 'center'; 260 | break; 261 | } 262 | 263 | switch (v_align) { 264 | case RrdGraph.GFX_V_TOP: 265 | this.ctx.textBaseline = 'top'; 266 | break; 267 | case RrdGraph.GFX_V_BOTTOM: 268 | this.ctx.textBaseline = 'bottom'; 269 | break; 270 | case RrdGraph.GFX_V_CENTER: 271 | this.ctx.textBaseline = 'middle'; 272 | break; 273 | } 274 | 275 | this.ctx.fillStyle = color; 276 | this.ctx.translate(x,y); 277 | this.ctx.rotate(-angle*Math.PI/180.0); 278 | this.ctx.fillText(text, 0, 0); 279 | this.ctx.restore(); 280 | }; 281 | 282 | RrdGfxCanvas.prototype.get_text_width = function(start, font, tabwidth, text) 283 | { 284 | this.ctx.save(); 285 | this.ctx.font = font.size+'px '+font.font; 286 | var width = this.ctx.measureText(text); 287 | this.ctx.restore(); 288 | return width.width; 289 | }; 290 | 291 | -------------------------------------------------------------------------------- /js/RrdGfxSvg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | * 18 | * Manuel Sanmartin 19 | **/ 20 | 21 | "use strict"; 22 | 23 | /** 24 | * RrdGfxSvg 25 | * @constructor 26 | */ 27 | var RrdGfxSvg = function(svgId) 28 | { 29 | this.svg = document.getElementById(svgId); 30 | this.svgns = "http://www.w3.org/2000/svg"; 31 | this.xmlns = "http://www.w3.org/XML/1998/namespace"; 32 | this.path = null; 33 | this.path_color = null; 34 | this.path_width = null; 35 | this.dash = false; 36 | this.dash_offset = null; 37 | this.dash_array = null; 38 | }; 39 | 40 | RrdGfxSvg.prototype.size = function (width, height) 41 | { 42 | while(this.svg.lastChild) 43 | this.svg.removeChild(this.svg.lastChild); 44 | 45 | this.svg.setAttribute("width", width+"px"); 46 | this.svg.setAttribute("height", height+"px"); 47 | this.svg.setAttribute("viewBox", "0 0 "+width+" "+height); 48 | }; 49 | 50 | RrdGfxSvg.prototype.set_dash = function (dashes, n, offset) 51 | { 52 | this.dash = true; 53 | this.dash_array = dashes; 54 | this.dash_offset = offset; 55 | }; 56 | 57 | RrdGfxSvg.prototype._set_dash = function (shape) 58 | { 59 | if (this.dash_array != undefined && this.dash_array.length > 0) { 60 | shape.setAttributeNS(null, "stroke-dasharray", this.dash_array.join(',')); 61 | if (this.dash_offset > 0) { 62 | shape.setAttributeNS(null, "stroke-dashoffset", this.dash_offset); 63 | } 64 | } 65 | this.dash = false; 66 | this.dash_array = null; 67 | this.dash_offset = 0; 68 | } 69 | 70 | RrdGfxSvg.prototype.line = function (X0, Y0, X1, Y1, width, color) 71 | { 72 | var shape = document.createElementNS(this.svgns, "line"); 73 | 74 | X0 = Math.round(X0)+0.5; 75 | Y0 = Math.round(Y0)+0.5; 76 | X1 = Math.round(X1)+0.5; 77 | Y1 = Math.round(Y1)+0.5; 78 | 79 | shape.setAttributeNS(null, "x1", X0); 80 | shape.setAttributeNS(null, "y1", Y0); 81 | shape.setAttributeNS(null, "x2", X1); 82 | shape.setAttributeNS(null, "y2", Y1); 83 | shape.setAttributeNS(null, "stroke-width", width); 84 | shape.setAttributeNS(null, "stroke", color); 85 | if (this.dash) 86 | this._set_dash(shape); 87 | 88 | this.svg.appendChild(shape); 89 | }; 90 | 91 | RrdGfxSvg.prototype.dashed_line = function (X0, Y0, X1, Y1, width, color, dash_on, dash_off) 92 | { 93 | var shape = document.createElementNS(this.svgns, "line"); 94 | 95 | X0 = Math.round(X0)+0.5; 96 | Y0 = Math.round(Y0)+0.5; 97 | X1 = Math.round(X1)+0.5; 98 | Y1 = Math.round(Y1)+0.5; 99 | 100 | shape.setAttributeNS(null, "x1", X0); 101 | shape.setAttributeNS(null, "y1", Y0); 102 | shape.setAttributeNS(null, "x2", X1); 103 | shape.setAttributeNS(null, "y2", Y1); 104 | shape.setAttributeNS(null, "stroke-width", width); 105 | shape.setAttributeNS(null, "stroke", color); 106 | shape.setAttributeNS(null, "stroke-dasharray", dash_on+','+dash_off); 107 | 108 | this.svg.appendChild(shape); 109 | }; 110 | 111 | RrdGfxSvg.prototype.rectangle = function (X0, Y0, X1, Y1, width, style) 112 | { 113 | var shape = document.createElementNS(this.svgns, "rect"); 114 | 115 | var rwidth = Math.abs(X1-X0); 116 | var rheight = Math.abs(Y1-Y0); 117 | 118 | shape.setAttributeNS(null, "x", Math.round(X0)+0.5); 119 | shape.setAttributeNS(null, "y", Math.round(Y0-rheight)+0.5); 120 | shape.setAttributeNS(null, "width", rwidth); 121 | shape.setAttributeNS(null, "height", rheight); 122 | shape.setAttributeNS(null, "stroke-width", width); 123 | shape.setAttributeNS(null, "stroke", style); 124 | shape.setAttributeNS(null, "fill", "none"); 125 | if (this.dash) 126 | this._set_dash(shape); 127 | 128 | this.svg.appendChild(shape); 129 | }; 130 | 131 | RrdGfxSvg.prototype.new_area = function (X0, Y0, X1, Y1, X2, Y2, color) 132 | { 133 | X0 = Math.round(X0)+0.5; 134 | Y0 = Math.round(Y0)+0.5; 135 | X1 = Math.round(X1)+0.5; 136 | Y1 = Math.round(Y1)+0.5; 137 | X2 = Math.round(X2)+0.5; 138 | Y2 = Math.round(Y2)+0.5; 139 | 140 | this.path_color = color; 141 | this.path = 'M'+X0+','+Y0; 142 | this.path += ' L'+X1+','+Y1; 143 | this.path += ' L'+X2+','+Y2; 144 | }; 145 | 146 | RrdGfxSvg.prototype.add_point = function (x, y) 147 | { 148 | x = Math.round(x)+0.5; 149 | y = Math.round(y)+0.5; 150 | 151 | this.path += ' L'+x+','+y; 152 | }; 153 | 154 | RrdGfxSvg.prototype.close_path = function () 155 | { 156 | var shape = document.createElementNS(this.svgns, "path"); 157 | 158 | this.path += ' Z'; 159 | 160 | shape.setAttributeNS(null, "d", this.path); 161 | shape.setAttributeNS(null, "fill", this.path_color); 162 | shape.setAttributeNS(null, "stroke", 'none'); 163 | 164 | this.svg.appendChild(shape); 165 | }; 166 | 167 | RrdGfxSvg.prototype.stroke_begin = function (width, style) 168 | { 169 | this.path_width = width; 170 | this.path_color = style; 171 | this.path = ''; 172 | }; 173 | 174 | RrdGfxSvg.prototype.stroke_end = function () 175 | { 176 | var shape = document.createElementNS(this.svgns, "path"); 177 | 178 | shape.setAttributeNS(null, "d", this.path); 179 | shape.setAttributeNS(null, "fill", 'none'); 180 | shape.setAttributeNS(null, "stroke", this.path_color); 181 | shape.setAttributeNS(null, "stroke-width", this.path_width); 182 | shape.setAttributeNS(null, "stroke-linecap", 'round'); 183 | shape.setAttributeNS(null, "stroke-linejoin", 'round'); 184 | if (this.dash) 185 | this._set_dash(shape); 186 | 187 | this.svg.appendChild(shape); 188 | }; 189 | 190 | RrdGfxSvg.prototype.moveTo = function (x,y) 191 | { 192 | x = Math.round(x)+0.5; 193 | y = Math.round(y)+0.5; 194 | 195 | this.path += ' M'+x+','+y; 196 | }; 197 | 198 | RrdGfxSvg.prototype.lineTo = function (x,y) 199 | { 200 | x = Math.round(x)+0.5; 201 | y = Math.round(y)+0.5; 202 | 203 | this.path += ' L'+x+','+y; 204 | }; 205 | 206 | RrdGfxSvg.prototype.text = function (x, y, color, font, tabwidth, angle, h_align, v_align, text) 207 | { 208 | x = Math.round(x); 209 | y = Math.round(y); 210 | 211 | var svgtext = document.createElementNS(this.svgns, "text"); 212 | 213 | var data = document.createTextNode(text); 214 | 215 | svgtext.setAttributeNS(null, "x", x); 216 | svgtext.setAttributeNS(null, "y", y); 217 | svgtext.setAttributeNS(null, "fill", color); 218 | svgtext.setAttributeNS(null, "stroke", "none"); 219 | svgtext.setAttributeNS(null, "font-family", font.font); 220 | svgtext.setAttributeNS(null, "font-size", font.size+"px"); 221 | svgtext.setAttributeNS(this.xmlns, "xml:space", "preserve"); 222 | 223 | angle=-angle; 224 | svgtext.setAttributeNS(null, "transform", 'rotate('+angle+' '+x+','+y+')' ); 225 | 226 | switch (h_align) { 227 | case RrdGraph.GFX_H_LEFT: 228 | svgtext.setAttributeNS(null, "text-anchor", 'start'); 229 | break; 230 | case RrdGraph.GFX_H_RIGHT: 231 | svgtext.setAttributeNS(null, "text-anchor", 'end'); 232 | break; 233 | case RrdGraph.GFX_H_CENTER: 234 | svgtext.setAttributeNS(null, "text-anchor", 'middle'); 235 | break; 236 | } 237 | svgtext.appendChild(data); 238 | this.svg.appendChild(svgtext); 239 | 240 | var bbox = svgtext.getBBox(); 241 | 242 | switch (v_align) { // FIXME 243 | case RrdGraph.GFX_V_TOP: 244 | svgtext.setAttributeNS(null, "y", y+bbox.height/2); 245 | break; 246 | case RrdGraph.GFX_V_BOTTOM: 247 | svgtext.setAttributeNS(null, "y", y-bbox.height/6); 248 | break; 249 | case RrdGraph.GFX_V_CENTER: 250 | svgtext.setAttributeNS(null, "y", y+bbox.height/4); 251 | break; 252 | } 253 | }; 254 | 255 | RrdGfxSvg.prototype.get_text_width = function(start, font, tabwidth, text) 256 | { 257 | var svgtext = document.createElementNS(this.svgns, "text"); 258 | var data = document.createTextNode(text); 259 | svgtext.setAttributeNS(null, "x", 0); 260 | svgtext.setAttributeNS(null, "y", 0); 261 | svgtext.setAttributeNS(null, "fill", 'none'); 262 | svgtext.setAttributeNS(null, "stroke", 'none'); 263 | svgtext.setAttributeNS(null, "font-family", font.font); 264 | svgtext.setAttributeNS(null, "font-size", font.size+"px"); 265 | svgtext.setAttributeNS(this.xmlns, "xml:space", "preserve"); 266 | svgtext.appendChild(data); 267 | this.svg.appendChild(svgtext); 268 | 269 | var bbox = svgtext.getBBox(); 270 | 271 | svgtext.removeChild(data); 272 | this.svg.removeChild(svgtext); 273 | 274 | return bbox.width; 275 | }; 276 | 277 | -------------------------------------------------------------------------------- /js/binaryXHR.js: -------------------------------------------------------------------------------- 1 | // jshint browser:true 2 | /* 3 | * BinaryFile over XMLHttpRequest 4 | * Part of the javascriptRRD package 5 | * Copyright (c) 2009 Frank Wuerthwein, fkw@ucsd.edu 6 | * MIT License [http://www.opensource.org/licenses/mit-license.php] 7 | * 8 | * Original repository: http://javascriptrrd.sourceforge.net/ 9 | * 10 | * Based on: 11 | * Binary Ajax 0.1.5 12 | * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/ 13 | * MIT License [http://www.opensource.org/licenses/mit-license.php] 14 | */ 15 | 16 | // ============================================================ 17 | // Exception class 18 | function InvalidBinaryFile(msg) { 19 | "use strict"; 20 | this.message = msg; 21 | this.name = "Invalid BinaryFile"; 22 | } 23 | 24 | // pretty print 25 | InvalidBinaryFile.prototype.toString = function() { 26 | "use strict"; 27 | return this.name + ': "' + this.message + '"'; 28 | }; 29 | 30 | // ===================================================================== 31 | // BinaryFile class 32 | // Allows access to element inside a binary stream 33 | function BinaryFile(data) { 34 | "use strict"; 35 | var dataLength; 36 | // whether the data is in little endian format 37 | var littleEndian = true; 38 | 39 | this.getRawData = function() { 40 | return data; 41 | }; 42 | 43 | if (typeof data === "string") { 44 | dataLength = data.length; 45 | 46 | this.getByteAt = function(iOffset) { 47 | return data.charCodeAt(iOffset) & 0xFF; 48 | }; 49 | } else if (typeof DataView != "undefined" && data instanceof ArrayBuffer) { 50 | dataLength = data.dataLength; 51 | /*@cc_on 52 | } else if (typeof data === "unknown") { 53 | // Correct. "unknown" as type. MS JScript 8 added this. 54 | dataLength = IEBinary_getLength(data); 55 | 56 | this.getByteAt = function(iOffset) { 57 | return IEBinary_getByteAt(data, iOffset); 58 | }; 59 | @*/ 60 | } else { 61 | throw new InvalidBinaryFile("Unsupported type " + (typeof data)); 62 | } 63 | 64 | this.getLength = function() { 65 | return dataLength; 66 | }; 67 | 68 | if (typeof DataView != "undefined" && data instanceof ArrayBuffer) { 69 | // not an antique browser, use faster TypedArrays 70 | this.extendWithDataView(data, littleEndian); 71 | // other functions here do not need these 72 | data = null; 73 | } else { 74 | // antique browser, use slower fallback implementation 75 | this.extendWithFallback(data, littleEndian); 76 | } 77 | } 78 | 79 | BinaryFile.prototype.extendWithFallback = function(data, littleEndian) { 80 | "use strict"; 81 | var doubleMantExpHi = Math.pow(2,-28); 82 | var doubleMantExpLo = Math.pow(2,-52); 83 | var doubleMantExpFast = Math.pow(2,-20); 84 | 85 | // private function for getting bytes depending on endianess 86 | var that = this, getEndianByteAt; 87 | if (littleEndian) { 88 | getEndianByteAt = function(iOffset, width, delta) { 89 | return that.getByteAt(iOffset + delta); 90 | }; 91 | } else { 92 | getEndianByteAt = function(iOffset, width, delta) { 93 | return that.getByteAt(iOffset + width - delta - 1); 94 | }; 95 | } 96 | 97 | this.getSByteAt = function(iOffset) { 98 | var iByte = this.getByteAt(iOffset); 99 | if (iByte > 127) 100 | return iByte - 256; 101 | else 102 | return iByte; 103 | }; 104 | this.getShortAt = function(iOffset) { 105 | var iShort = (getEndianByteAt(iOffset,2,1) << 8) + getEndianByteAt(iOffset,2,0); 106 | if (iShort < 0) iShort += 65536; 107 | return iShort; 108 | }; 109 | this.getSShortAt = function(iOffset) { 110 | var iUShort = this.getShortAt(iOffset); 111 | if (iUShort > 32767) 112 | return iUShort - 65536; 113 | else 114 | return iUShort; 115 | }; 116 | this.getLongAt = function(iOffset) { 117 | var iByte1 = getEndianByteAt(iOffset,4,0), 118 | iByte2 = getEndianByteAt(iOffset,4,1), 119 | iByte3 = getEndianByteAt(iOffset,4,2), 120 | iByte4 = getEndianByteAt(iOffset,4,3); 121 | 122 | var iLong = (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; 123 | if (iLong < 0) iLong += 4294967296; 124 | return iLong; 125 | }; 126 | this.getSLongAt = function(iOffset) { 127 | var iULong = this.getLongAt(iOffset); 128 | if (iULong > 2147483647) 129 | return iULong - 4294967296; 130 | else 131 | return iULong; 132 | }; 133 | this.getCharAt = function(iOffset) { 134 | return String.fromCharCode(this.getByteAt(iOffset)); 135 | }; 136 | this.getCStringAt = function(iOffset, iMaxLength) { 137 | var aStr = []; 138 | for (var i=iOffset,j=0;(i0);i++,j++) { 139 | aStr[j] = String.fromCharCode(this.getByteAt(i)); 140 | } 141 | return aStr.join(""); 142 | }; 143 | this.getDoubleAt = function(iOffset) { 144 | var iByte1 = getEndianByteAt(iOffset,8,0), 145 | iByte2 = getEndianByteAt(iOffset,8,1), 146 | iByte3 = getEndianByteAt(iOffset,8,2), 147 | iByte4 = getEndianByteAt(iOffset,8,3), 148 | iByte5 = getEndianByteAt(iOffset,8,4), 149 | iByte6 = getEndianByteAt(iOffset,8,5), 150 | iByte7 = getEndianByteAt(iOffset,8,6), 151 | iByte8 = getEndianByteAt(iOffset,8,7); 152 | var iSign=iByte8 >> 7; 153 | var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4); 154 | var iMantHi=((((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5) << 8) + iByte4; 155 | var iMantLo=((((iByte3) << 8) + iByte2) << 8) + iByte1; 156 | 157 | if (iExpRaw === 0) return 0.0; 158 | if (iExpRaw === 0x7ff) return undefined; 159 | 160 | var iExp=(iExpRaw & 0x7FF)-1023; 161 | 162 | var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMantLo*doubleMantExpLo + iMantHi*doubleMantExpHi); 163 | return dDouble; 164 | }; 165 | // Extracts only 4 bytes out of 8, loosing in precision (20 bit mantissa) 166 | this.getFastDoubleAt = function(iOffset) { 167 | var iByte5 = getEndianByteAt(iOffset,8,4), 168 | iByte6 = getEndianByteAt(iOffset,8,5), 169 | iByte7 = getEndianByteAt(iOffset,8,6), 170 | iByte8 = getEndianByteAt(iOffset,8,7); 171 | var iSign=iByte8 >> 7; 172 | var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4); 173 | var iMant=((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5; 174 | 175 | if (iExpRaw === 0) return 0.0; 176 | if (iExpRaw === 0x7ff) return undefined; 177 | 178 | var iExp=(iExpRaw & 0x7FF)-1023; 179 | 180 | var dDouble = ((iSign === 1) ? -1 : 1); 181 | dDouble *= Math.pow(2,iExp) * (1.0 + iMant*doubleMantExpFast); 182 | return dDouble; 183 | }; 184 | }; 185 | 186 | BinaryFile.prototype.extendWithDataView = function(data, littleEndian) { 187 | "use strict"; 188 | var dv = new DataView(data); 189 | 190 | this.getByteAt = dv.getUint8.bind(dv); 191 | this.getSByteAt = dv.getInt8.bind(dv); 192 | this.getShortAt = function(iOffset) { 193 | return dv.getUint16(iOffset, littleEndian); 194 | }; 195 | this.getSShortAt = function(iOffset) { 196 | return dv.getInt16(iOffset, littleEndian); 197 | }; 198 | this.getLongAt = function(iOffset) { 199 | return dv.getUint32(iOffset, littleEndian); 200 | }; 201 | this.getSLongAt = function(iOffset) { 202 | return dv.getInt32(iOffset, littleEndian); 203 | }; 204 | this.getCharAt = function(iOffset) { 205 | return String.fromCharCode(this.getByteAt(iOffset)); 206 | }; 207 | this.getCStringAt = function(iOffset, iMaxLength) { 208 | var str = ""; 209 | do { 210 | var b = this.getByteAt(iOffset++); 211 | if (b === 0) 212 | break; 213 | str += String.fromCharCode(b); 214 | } while (--iMaxLength > 0); 215 | return str; 216 | }; 217 | this.getDoubleAt = function(iOffset) { 218 | return dv.getFloat64(iOffset, littleEndian); 219 | }; 220 | this.getFastDoubleAt = this.getDoubleAt.bind(this); 221 | }; 222 | 223 | 224 | // Use document.write only for stone-age browsers. 225 | /*@cc on 226 | document.write( 227 | "\r\n" 235 | ); 236 | @*/ 237 | 238 | 239 | // =============================================================== 240 | // Load a binary file from the specified URL 241 | // Will return an object of type BinaryFile 242 | function FetchBinaryURL(url) { 243 | "use strict"; 244 | var request = new XMLHttpRequest(); 245 | request.open("GET", url,false); 246 | try { 247 | request.overrideMimeType('text/plain; charset=x-user-defined'); 248 | } catch (err) { 249 | // ignore any error, just to make both FF and IE work 250 | } 251 | request.send(null); 252 | 253 | var response = request.responseText; 254 | /*@cc_on 255 | try { 256 | // for older IE versions, the value in responseText is not usable 257 | if (IEBinary_getLength(this.responseBody)>0) { 258 | // will get here only for older verson of IE 259 | response=this.responseBody; 260 | } 261 | } catch (err) { 262 | } 263 | @*/ 264 | 265 | // cannot use responseType == "arraybuffer" for synchronous requests, so 266 | // convert it afterwards 267 | if (typeof ArrayBuffer != "undefined") { 268 | var buffer = new ArrayBuffer(response.length); 269 | var bv = new Uint8Array(buffer); 270 | for (var i = 0; i < response.length; i++) { 271 | bv[i] = response.charCodeAt(i); 272 | } 273 | response = buffer; 274 | } 275 | 276 | var bf = new BinaryFile(response); 277 | return bf; 278 | } 279 | 280 | 281 | // =============================================================== 282 | // Asyncronously load a binary file from the specified URL 283 | // 284 | // callback must be a function with one or two arguments: 285 | // - bf = an object of type BinaryFile 286 | // - optional argument object (used only if callback_arg not undefined) 287 | function FetchBinaryURLAsync(url, callback, callback_arg) { 288 | "use strict"; 289 | var callback_wrapper = function() { 290 | if(this.readyState === 4) { 291 | // ArrayBuffer response or just the response as string 292 | var response = this.response || this.responseText; 293 | /*@cc_on 294 | try { 295 | // for older IE versions, the value in responseText is not usable 296 | if (IEBinary_getLength(this.responseBody)>0) { 297 | // will get here only for older verson of IE 298 | response=this.responseBody; 299 | } 300 | } catch (err) { 301 | } 302 | @*/ 303 | 304 | var bf = new BinaryFile(response); 305 | if (callback_arg) { 306 | callback(bf, callback_arg); 307 | } else { 308 | callback(bf); 309 | } 310 | } 311 | }; 312 | 313 | var request = new XMLHttpRequest(); 314 | request.onreadystatechange = callback_wrapper; 315 | request.open("GET", url, true); 316 | // Supported since Chrome 10, FF 6, IE 10, Opera 11.60 (source: MDN) 317 | request.responseType = "arraybuffer"; 318 | try { 319 | request.overrideMimeType('text/plain; charset=x-user-defined'); 320 | } catch (err) { 321 | // ignore any error, just to make both FF and IE work 322 | } 323 | request.send(null); 324 | return request; 325 | } 326 | -------------------------------------------------------------------------------- /test/test1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RRD Canvas 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 |
27 | 28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 |
36 | 37 |
38 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /js/rrdFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Client library for access to RRD archive files 3 | * Part of the javascriptRRD package 4 | * Copyright (c) 2009-2010 Frank Wuerthwein, fkw@ucsd.edu 5 | * Igor Sfiligoi, isfiligoi@ucsd.edu 6 | * 7 | * Original repository: http://javascriptrrd.sourceforge.net/ 8 | * 9 | * MIT License [http://www.opensource.org/licenses/mit-license.php] 10 | * 11 | */ 12 | 13 | /* 14 | * 15 | * RRDTool has been developed and is maintained by 16 | * Tobias Oether [http://oss.oetiker.ch/rrdtool/] 17 | * 18 | * This software can be used to read files produced by the RRDTool 19 | * but has been developed independently. 20 | * 21 | * Limitations: 22 | * 23 | * This version of the module assumes RRD files created on linux 24 | * with intel architecture and supports both 32 and 64 bit CPUs. 25 | * All integers in RRD files are suppoes to fit in 32bit values. 26 | * 27 | * Only versions 3 and 4 of the RRD archive are supported. 28 | * 29 | * Only AVERAGE,MAXIMUM,MINIMUM and LAST consolidation functions are 30 | * supported. For all others, the behaviour is at the moment undefined. 31 | * 32 | */ 33 | 34 | /* 35 | * Dependencies: 36 | * 37 | * The data provided to this module require an object of a class 38 | * that implements the following methods: 39 | * getByteAt(idx) - Return a 8 bit unsigned integer at offset idx 40 | * getLongAt(idx) - Return a 32 bit unsigned integer at offset idx 41 | * getDoubleAt(idx) - Return a double float at offset idx 42 | * getFastDoubleAt(idx) - Similar to getDoubleAt but with less precision 43 | * getCStringAt(idx,maxsize) - Return a string of at most maxsize characters 44 | * that was 0-terminated in the source 45 | * 46 | * The BinaryFile from binaryXHR.js implements this interface. 47 | * 48 | */ 49 | 50 | 51 | // ============================================================ 52 | // Exception class 53 | function InvalidRRD(msg) { 54 | this.message=msg; 55 | this.name="Invalid RRD"; 56 | } 57 | 58 | // pretty print 59 | InvalidRRD.prototype.toString = function() { 60 | return this.name + ': "' + this.message + '"'; 61 | } 62 | 63 | 64 | // ============================================================ 65 | // RRD DS Info class 66 | function RRDDS(rrd_data,rrd_data_idx,my_idx) { 67 | this.rrd_data=rrd_data; 68 | this.rrd_data_idx=rrd_data_idx; 69 | this.my_idx=my_idx; 70 | } 71 | 72 | RRDDS.prototype.getIdx = function() { 73 | return this.my_idx; 74 | } 75 | RRDDS.prototype.getName = function() { 76 | return this.rrd_data.getCStringAt(this.rrd_data_idx,20); 77 | } 78 | RRDDS.prototype.getType = function() { 79 | return this.rrd_data.getCStringAt(this.rrd_data_idx+20,20); 80 | } 81 | RRDDS.prototype.getMin = function() { 82 | return this.rrd_data.getDoubleAt(this.rrd_data_idx+48); 83 | } 84 | RRDDS.prototype.getMax = function() { 85 | return this.rrd_data.getDoubleAt(this.rrd_data_idx+56); 86 | } 87 | 88 | 89 | // ============================================================ 90 | // RRD RRA Info class 91 | function RRDRRAInfo(rrd_data,rra_def_idx, 92 | int_align,row_cnt,pdp_step,my_idx) { 93 | this.rrd_data=rrd_data; 94 | this.rra_def_idx=rra_def_idx; 95 | this.int_align=int_align; 96 | this.row_cnt=row_cnt; 97 | this.pdp_step=pdp_step; 98 | this.my_idx=my_idx; 99 | 100 | // char nam[20], uint row_cnt, uint pdp_cnt 101 | this.rra_pdp_cnt_idx=rra_def_idx+Math.ceil(20/int_align)*int_align+int_align; 102 | } 103 | 104 | RRDRRAInfo.prototype.getIdx = function() { 105 | return this.my_idx; 106 | } 107 | 108 | // Get number of rows 109 | RRDRRAInfo.prototype.getNrRows = function() { 110 | return this.row_cnt; 111 | } 112 | 113 | // Get number of slots used for consolidation 114 | // Mostly for internal use 115 | RRDRRAInfo.prototype.getPdpPerRow = function() { 116 | return this.rrd_data.getLongAt(this.rra_pdp_cnt_idx); 117 | } 118 | 119 | // Get RRA step (expressed in seconds) 120 | RRDRRAInfo.prototype.getStep = function() { 121 | return this.pdp_step*this.getPdpPerRow(); 122 | } 123 | 124 | // Get consolidation function name 125 | RRDRRAInfo.prototype.getCFName = function() { 126 | return this.rrd_data.getCStringAt(this.rra_def_idx,20); 127 | } 128 | 129 | 130 | // ============================================================ 131 | // RRD RRA handling class 132 | function RRDRRA(rrd_data,rra_ptr_idx, 133 | rra_info, 134 | header_size,prev_row_cnts,ds_cnt) { 135 | this.rrd_data=rrd_data; 136 | this.rra_info=rra_info; 137 | this.row_cnt=rra_info.row_cnt; 138 | this.ds_cnt=ds_cnt; 139 | 140 | var row_size=ds_cnt*8; 141 | 142 | this.base_rrd_db_idx=header_size+prev_row_cnts*row_size; 143 | 144 | // get imediately, since it will be needed often 145 | this.cur_row=rrd_data.getLongAt(rra_ptr_idx); 146 | 147 | // calculate idx relative to base_rrd_db_idx 148 | // mostly used internally 149 | this.calc_idx = function(row_idx,ds_idx) { 150 | if ((row_idx>=0) && (row_idx=0) && (ds_idx=this.row_cnt) real_row_idx-=this.row_cnt; 155 | return row_size*real_row_idx+ds_idx*8; 156 | } else { 157 | throw RangeError("DS idx ("+ row_idx +") out of range [0-" + ds_cnt +")."); 158 | } 159 | } else { 160 | throw RangeError("Row idx ("+ row_idx +") out of range [0-" + this.row_cnt +")."); 161 | } 162 | } 163 | } 164 | 165 | RRDRRA.prototype.getIdx = function() { 166 | return this.rra_info.getIdx(); 167 | } 168 | 169 | // Get number of rows/columns 170 | RRDRRA.prototype.getNrRows = function() { 171 | return this.row_cnt; 172 | } 173 | RRDRRA.prototype.getNrDSs = function() { 174 | return this.ds_cnt; 175 | } 176 | 177 | // Get RRA step (expressed in seconds) 178 | RRDRRA.prototype.getStep = function() { 179 | return this.rra_info.getStep(); 180 | } 181 | 182 | // Get consolidation function name 183 | RRDRRA.prototype.getCFName = function() { 184 | return this.rra_info.getCFName(); 185 | } 186 | 187 | RRDRRA.prototype.getEl = function(row_idx,ds_idx) { 188 | return this.rrd_data.getDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx)); 189 | } 190 | 191 | // Low precision version of getEl 192 | // Uses getFastDoubleAt 193 | RRDRRA.prototype.getElFast = function(row_idx,ds_idx) { 194 | return this.rrd_data.getFastDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx)); 195 | } 196 | 197 | // ============================================================ 198 | // RRD Header handling class 199 | function RRDHeader(rrd_data) { 200 | this.rrd_data=rrd_data; 201 | this.validate_rrd(); 202 | this.calc_idxs(); 203 | } 204 | 205 | // Internal, used for initialization 206 | RRDHeader.prototype.validate_rrd = function() { 207 | if (this.rrd_data.getLength()<1) throw new InvalidRRD("Empty file."); 208 | if (this.rrd_data.getLength()<16) throw new InvalidRRD("File too short."); 209 | if (this.rrd_data.getCStringAt(0,4)!=="RRD") throw new InvalidRRD("Wrong magic id."); 210 | 211 | this.rrd_version=this.rrd_data.getCStringAt(4,5); 212 | if ((this.rrd_version!=="0003")&&(this.rrd_version!=="0004")&&(this.rrd_version!=="0001")) { 213 | throw new InvalidRRD("Unsupported RRD version "+this.rrd_version+"."); 214 | } 215 | 216 | this.float_width=8; 217 | if (this.rrd_data.getLongAt(12)==0) { 218 | // not a double here... likely 64 bit 219 | this.float_align=8; 220 | if (! (this.rrd_data.getDoubleAt(16)==8.642135e+130)) { 221 | // uhm... wrong endian? 222 | this.rrd_data.switch_endian=true; 223 | } 224 | if (this.rrd_data.getDoubleAt(16)==8.642135e+130) { 225 | // now, is it all 64bit or only float 64 bit? 226 | if (this.rrd_data.getLongAt(28)==0) { 227 | // true 64 bit align 228 | this.int_align=8; 229 | this.int_width=8; 230 | } else { 231 | // integers are 32bit aligned 232 | this.int_align=4; 233 | this.int_width=4; 234 | } 235 | } else { 236 | throw new InvalidRRD("Magic float not found at 16."); 237 | } 238 | } else { 239 | /// should be 32 bit alignment 240 | if (! (this.rrd_data.getDoubleAt(12)==8.642135e+130)) { 241 | // uhm... wrong endian? 242 | this.rrd_data.switch_endian=true; 243 | } 244 | if (this.rrd_data.getDoubleAt(12)==8.642135e+130) { 245 | this.float_align=4; 246 | this.int_align=4; 247 | this.int_width=4; 248 | } else { 249 | throw new InvalidRRD("Magic float not found at 12."); 250 | } 251 | } 252 | this.unival_width=this.float_width; 253 | this.unival_align=this.float_align; 254 | 255 | // process the header here, since I need it for validation 256 | 257 | // char magic[4], char version[5], double magic_float 258 | 259 | // long ds_cnt, long rra_cnt, long pdp_step, unival par[10] 260 | this.ds_cnt_idx=Math.ceil((4+5)/this.float_align)*this.float_align+this.float_width; 261 | this.rra_cnt_idx=this.ds_cnt_idx+this.int_width; 262 | this.pdp_step_idx=this.rra_cnt_idx+this.int_width; 263 | 264 | //always get only the low 32 bits, the high 32 on 64 bit archs should always be 0 265 | this.ds_cnt=this.rrd_data.getLongAt(this.ds_cnt_idx); 266 | if (this.ds_cnt<1) { 267 | throw new InvalidRRD("ds count less than 1."); 268 | } 269 | 270 | this.rra_cnt=this.rrd_data.getLongAt(this.rra_cnt_idx); 271 | if (this.ds_cnt<1) { 272 | throw new InvalidRRD("rra count less than 1."); 273 | } 274 | 275 | this.pdp_step=this.rrd_data.getLongAt(this.pdp_step_idx); 276 | if (this.pdp_step<1) { 277 | throw new InvalidRRD("pdp step less than 1."); 278 | } 279 | 280 | // best guess, assuming no weird align problems 281 | this.top_header_size=Math.ceil((this.pdp_step_idx+this.int_width)/this.unival_align)*this.unival_align+10*this.unival_width; 282 | var t=this.rrd_data.getLongAt(this.top_header_size); 283 | if (t==0) { 284 | throw new InvalidRRD("Could not find first DS name."); 285 | } 286 | } 287 | 288 | // Internal, used for initialization 289 | RRDHeader.prototype.calc_idxs = function() { 290 | this.ds_def_idx=this.top_header_size; 291 | // char ds_nam[20], char dst[20], unival par[10] 292 | this.ds_el_size=Math.ceil((20+20)/this.unival_align)*this.unival_align+10*this.unival_width; 293 | 294 | this.rra_def_idx=this.ds_def_idx+this.ds_el_size*this.ds_cnt; 295 | // char cf_nam[20], uint row_cnt, uint pdp_cnt, unival par[10] 296 | this.row_cnt_idx=Math.ceil(20/this.int_align)*this.int_align; 297 | this.rra_def_el_size=Math.ceil((this.row_cnt_idx+2*this.int_width)/this.unival_align)*this.unival_align+10*this.unival_width; 298 | 299 | this.live_head_idx=this.rra_def_idx+this.rra_def_el_size*this.rra_cnt; 300 | // time_t last_up, int last_up_usec 301 | this.live_head_size=2*this.int_width; 302 | 303 | this.pdp_prep_idx=this.live_head_idx+this.live_head_size; 304 | // char last_ds[30], unival scratch[10] 305 | this.pdp_prep_el_size=Math.ceil(30/this.unival_align)*this.unival_align+10*this.unival_width; 306 | 307 | this.cdp_prep_idx=this.pdp_prep_idx+this.pdp_prep_el_size*this.ds_cnt; 308 | // unival scratch[10] 309 | this.cdp_prep_el_size=10*this.unival_width; 310 | 311 | this.rra_ptr_idx=this.cdp_prep_idx+this.cdp_prep_el_size*this.ds_cnt*this.rra_cnt; 312 | // uint cur_row 313 | this.rra_ptr_el_size=1*this.int_width; 314 | 315 | this.header_size=this.rra_ptr_idx+this.rra_ptr_el_size*this.rra_cnt; 316 | } 317 | 318 | // Optional initialization 319 | // Read and calculate row counts 320 | RRDHeader.prototype.load_row_cnts = function() { 321 | this.rra_def_row_cnts=[]; 322 | this.rra_def_row_cnt_sums=[]; // how many rows before me 323 | for (var i=0; i=0) && (idx=0) && (idx 2 | 3 | 4 | 5 | RRD Canvas 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 | full 33 | 34 | 35 |
36 | 37 |
38 |
39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 | 383 | 384 | 385 | -------------------------------------------------------------------------------- /js/RrdTime.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | * RRDtool 1.4.5 Copyright by Tobi Oetiker, 1997-2010 18 | * 19 | * Convert to javascript: Manuel Sanmartin 20 | **/ 21 | 22 | "use strict"; 23 | 24 | /** 25 | * RrdTimeError 26 | * @constructor 27 | */ 28 | var RrdTimeError = function (message) 29 | { 30 | this.name = "RrdTimeError"; 31 | this.message = (message) ? message : "Error"; 32 | }; 33 | RrdTimeError.prototype = new Error(); 34 | 35 | /** 36 | * RrdTime 37 | * @constructor 38 | */ 39 | var RrdTime = function(tspec) /* parser */ 40 | { 41 | var date = new Date(); 42 | var hr = 0; 43 | 44 | this.tspec = tspec; 45 | 46 | this.tokens = (tspec+'').match(/[0-9]+|[A-Za-z]+|[:.+-\/]/g); 47 | this.toklen = this.tokens.length; 48 | this.tokidx = 0; 49 | 50 | this.token = null; 51 | this.tokid = 0; 52 | 53 | this.specials = RrdTime.VARIOUSWORDS; 54 | 55 | /* establish the default time reference */ 56 | this.type = RrdTime.ABSOLUTE_TIME; 57 | this.offset = 0; 58 | this.tm_sec = date.getSeconds(); 59 | this.tm_min = date.getMinutes(); 60 | this.tm_hour = date.getHours(); 61 | this.tm_mday = date.getDate(); 62 | this.tm_mon = date.getMonth(); 63 | this.tm_year = date.getFullYear()-1900; 64 | this.tm_wday = date.getDay(); 65 | 66 | this.gettok(); 67 | switch (this.tokid) { 68 | case RrdTime.PLUS: 69 | case RrdTime.MINUS: 70 | break; /* jump to OFFSET-SPEC part */ 71 | case RrdTime.EPOCH: 72 | this.type = RrdTime.RELATIVE_TO_EPOCH; 73 | /* falls through */ 74 | case RrdTime.START: 75 | case RrdTime.END: 76 | if (this.tokid === RrdTime.EPOCH) 77 | this.type = RrdTime.RELATIVE_TO_START_TIME; 78 | else 79 | this.type = RrdTime.RELATIVE_TO_END_TIME; 80 | this.tm_sec = 0; 81 | this.tm_min = 0; 82 | this.tm_hour = 0; 83 | this.tm_mday = 0; 84 | this.tm_mon = 0; 85 | this.tm_year = 0; 86 | /* falls through */ 87 | case RrdTime.NOW: 88 | var time_reference = this.tokid; 89 | this.gettok(); 90 | if (this.tokid == RrdTime.PLUS || this.tokid == RrdTime.MINUS) 91 | break; 92 | if (time_reference != RrdTime.NOW) { 93 | throw new RrdTimeError("'start' or 'end' MUST be followed by +|- offset"); 94 | } else if (this.tokid != RrdTime.EOF) { 95 | throw new RrdTimeError("if 'now' is followed by a token it must be +|- offset"); 96 | } 97 | break; 98 | case RrdTime.NUMBER: /* Only absolute time specifications below */ 99 | var hour_sv = this.tm_hour; 100 | var year_sv = this.tm_year; 101 | this.tm_hour = 30; 102 | this.tm_year = 30000; 103 | this.tod(); 104 | this.day(); 105 | if (this.tm_hour == 30 && this.tm_year != 30000) 106 | this.tod(); 107 | if (this.tm_hour == 30) 108 | this.tm_hour = hour_sv; 109 | if (this.tm_year == 30000) 110 | this.tm_year = year_sv; 111 | break; 112 | case RrdTime.JAN: 113 | case RrdTime.FEB: 114 | case RrdTime.MAR: 115 | case RrdTime.APR: 116 | case RrdTime.MAY: 117 | case RrdTime.JUN: 118 | case RrdTime.JUL: 119 | case RrdTime.AUG: 120 | case RrdTime.SEP: 121 | case RrdTime.OCT: 122 | case RrdTime.NOV: 123 | case RrdTime.DEC: 124 | this.day(); 125 | if (this.tokid != RrdTime.NUMBER) 126 | break; 127 | this.tod(); 128 | break; 129 | case RrdTime.TEATIME: 130 | hr += 4; 131 | /* falls through */ 132 | case RrdTime.NOON: 133 | hr += 12; 134 | /* falls through */ 135 | case RrdTime.MIDNIGHT: 136 | this.tm_hour = hr; 137 | this.tm_min = 0; 138 | this.tm_sec = 0; 139 | this.gettok(); 140 | this.day(); 141 | break; 142 | default: 143 | throw new RrdTimeError("unparsable time: "+this.token+" "+this.sct); 144 | } /* ugly case statement */ 145 | 146 | /* 147 | * the OFFSET-SPEC part 148 | * (NOTE, the sc_tokid was prefetched for us by the previous code) 149 | */ 150 | if (this.tokid == RrdTime.PLUS || this.tokid == RrdTime.MINUS) { 151 | this.specials = RrdTime.TIMEMULTIPLIERS; /* switch special words context */ 152 | while (this.tokid == RrdTime.PLUS || this.tokid == RrdTime.MINUS || this.tokid == RrdTime.NUMBER) { 153 | if (this.tokid == RrdTime.NUMBER) { 154 | this.plus_minus(-1); 155 | } else { 156 | this.plus_minus(this.tokid); 157 | } 158 | this.gettok(); /* We will get EOF eventually but that's OK, since token() will return us as many EOFs as needed */ 159 | } 160 | } 161 | 162 | /* now we should be at EOF */ 163 | if (this.tokid != RrdTime.EOF) 164 | throw new RrdTimeError("unparsable trailing text: '..."+this.token+"'"); 165 | // if (this.type == RrdTime.ABSOLUTE_TIME) 166 | // if (mktime(&ptv->tm) == -1) // FIXME ?? 167 | // panic(e("the specified time is incorrect (out of range?)")); 168 | }; 169 | 170 | RrdTime.EOF = -1; 171 | RrdTime.MIDNIGHT = 0; 172 | RrdTime.NOON = 1; 173 | RrdTime.TEATIME = 2; 174 | RrdTime.PM = 3; 175 | RrdTime.AM = 4; 176 | RrdTime.YESTERDAY = 5; 177 | RrdTime.TODAY = 6; 178 | RrdTime.TOMORROW = 7; 179 | RrdTime.NOW = 8; 180 | RrdTime.START = 9; 181 | RrdTime.END = 10; 182 | RrdTime.EPOCH = 11; 183 | RrdTime.SECONDS = 12; 184 | RrdTime.MINUTES = 13; 185 | RrdTime.HOURS = 14; 186 | RrdTime.DAYS = 15; 187 | RrdTime.WEEKS = 16; 188 | RrdTime.MONTHS = 17; 189 | RrdTime.YEARS = 18; 190 | RrdTime.MONTHS_MINUTES = 19; 191 | RrdTime.NUMBER = 20; 192 | RrdTime.PLUS = 21; 193 | RrdTime.MINUS = 22; 194 | RrdTime.DOT = 23; 195 | RrdTime.COLON = 24; 196 | RrdTime.SLASH = 25; 197 | RrdTime.ID = 26; 198 | RrdTime.JUNK = 27; 199 | RrdTime.JAN = 28; 200 | RrdTime.FEB = 29; 201 | RrdTime.MAR = 30; 202 | RrdTime.APR = 31; 203 | RrdTime.MAY = 32; 204 | RrdTime.JUN = 33; 205 | RrdTime.JUL = 34; 206 | RrdTime.AUG = 35; 207 | RrdTime.SEP = 36; 208 | RrdTime.OCT = 37; 209 | RrdTime.NOV = 38; 210 | RrdTime.DEC = 39; 211 | RrdTime.SUN = 40; 212 | RrdTime.MON = 41; 213 | RrdTime.TUE = 42; 214 | RrdTime.WED = 43; 215 | RrdTime.THU = 44; 216 | RrdTime.FRI = 45; 217 | RrdTime.SAT = 46; 218 | 219 | RrdTime.VARIOUSWORDS = { 220 | "midnight": RrdTime.MIDNIGHT, /* 00:00:00 of today or tomorrow */ 221 | "noon": RrdTime.NOON, /* 12:00:00 of today or tomorrow */ 222 | "teatime": RrdTime.TEATIME, /* 16:00:00 of today or tomorrow */ 223 | "am": RrdTime.AM, /* morning times for 0-12 clock */ 224 | "pm": RrdTime.PM, /* evening times for 0-12 clock */ 225 | "tomorrow": RrdTime.TOMORROW, 226 | "yesterday": RrdTime.YESTERDAY, 227 | "today": RrdTime.TODAY, 228 | "now": RrdTime.NOW, 229 | "n": RrdTime.NOW, 230 | "start": RrdTime.START, 231 | "s": RrdTime.START, 232 | "end": RrdTime.END, 233 | "e": RrdTime.END, 234 | "epoch": RrdTime.EPOCH, 235 | "jan": RrdTime.JAN, 236 | "feb": RrdTime.FEB, 237 | "mar": RrdTime.MAR, 238 | "apr": RrdTime.APR, 239 | "may": RrdTime.MAY, 240 | "jun": RrdTime.JUN, 241 | "jul": RrdTime.JUL, 242 | "aug": RrdTime.AUG, 243 | "sep": RrdTime.SEP, 244 | "oct": RrdTime.OCT, 245 | "nov": RrdTime.NOV, 246 | "dec": RrdTime.DEC, 247 | "january": RrdTime.JAN, 248 | "february": RrdTime.FEB, 249 | "march": RrdTime.MAR, 250 | "april": RrdTime.APR, 251 | // "may": RrdTime.MAY, 252 | "june": RrdTime.JUN, 253 | "july": RrdTime.JUL, 254 | "august": RrdTime.AUG, 255 | "september": RrdTime.SEP, 256 | "october": RrdTime.OCT, 257 | "november": RrdTime.NOV, 258 | "december": RrdTime.DEC, 259 | "sunday": RrdTime.SUN, 260 | "sun": RrdTime.SUN, 261 | "monday": RrdTime.MON, 262 | "mon": RrdTime.MON, 263 | "tuesday": RrdTime.TUE, 264 | "tue": RrdTime.TUE, 265 | "wednesday": RrdTime.WED, 266 | "wed": RrdTime.WED, 267 | "thursday": RrdTime.THU, 268 | "thu": RrdTime.THU, 269 | "friday": RrdTime.FRI, 270 | "fri": RrdTime.FRI, 271 | "saturday": RrdTime.SAT, 272 | "sat": RrdTime.SAT 273 | }; 274 | 275 | RrdTime.TIMEMULTIPLIERS = { 276 | "second": RrdTime.SECONDS, /* seconds multiplier */ 277 | "seconds": RrdTime.SECONDS, /* (pluralized) */ 278 | "sec": RrdTime.SECONDS, /* (generic) */ 279 | "s": RrdTime.SECONDS, /* (short generic) */ 280 | "minute": RrdTime.MINUTES, /* minutes multiplier */ 281 | "minutes": RrdTime.MINUTES, /* (pluralized) */ 282 | "min": RrdTime.MINUTES, /* (generic) */ 283 | "m": RrdTime.MONTHS_MINUTES, /* (short generic) */ 284 | "hour": RrdTime.HOURS, /* hours ... */ 285 | "hours": RrdTime.HOURS, /* (pluralized) */ 286 | "hr": RrdTime.HOURS, /* (generic) */ 287 | "h": RrdTime.HOURS, /* (short generic) */ 288 | "day": RrdTime.DAYS, /* days ... */ 289 | "days": RrdTime.DAYS, /* (pluralized) */ 290 | "d": RrdTime.DAYS, /* (short generic) */ 291 | "week": RrdTime.WEEKS, /* week ... */ 292 | "weeks": RrdTime.WEEKS, /* (pluralized) */ 293 | "wk": RrdTime.WEEKS, /* (generic) */ 294 | "w": RrdTime.WEEKS, /* (short generic) */ 295 | "month": RrdTime.MONTHS, /* week ... */ 296 | "months": RrdTime.MONTHS, /* (pluralized) */ 297 | "mon": RrdTime.MONTHS, /* (generic) */ 298 | "year": RrdTime.YEARS, /* year ... */ 299 | "years": RrdTime.YEARS, /* (pluralized) */ 300 | "yr": RrdTime.YEARS, /* (generic) */ 301 | "y": RrdTime.YEARS /* (short generic) */ 302 | }; 303 | 304 | RrdTime.ABSOLUTE_TIME = 0; 305 | RrdTime.RELATIVE_TO_START_TIME = 1; 306 | RrdTime.RELATIVE_TO_END_TIME = 2; 307 | RrdTime.RELATIVE_TO_EPOCH = 3; 308 | 309 | RrdTime.prototype.gettok = function () 310 | { 311 | if (this.tokidx >= this.toklen) { 312 | this.tokid = RrdTime.EOF; 313 | } else { 314 | this.token = this.tokens[this.tokidx]; 315 | this.tokidx++; 316 | if (!isNaN(this.token)) { 317 | this.tokid = RrdTime.NUMBER; 318 | this.token = parseInt(this.token, 10); 319 | } else if (this.token === ':') { 320 | this.tokid = RrdTime.COLON; 321 | } else if (this.token === '.') { 322 | this.tokid = RrdTime.DOT; 323 | } else if (this.token === '+') { 324 | this.tokid = RrdTime.PLUS; 325 | } else if (this.token === '/') { 326 | this.tokid = RrdTime.SLASH; 327 | } else if (this.token === '-') { 328 | this.tokid = RrdTime.MINUS; 329 | } else { 330 | this.tokid = RrdTime.ID; 331 | if (this.token in this.specials) 332 | this.tokid = this.specials[this.token]; 333 | } 334 | } 335 | return this.tokid; 336 | }; 337 | 338 | RrdTime.prototype.plus_minus = function (doop) 339 | { 340 | var op = RrdTime.PLUS; 341 | var prev_multiplier = -1; 342 | var delta; 343 | 344 | if (doop >= 0) { 345 | op = doop; 346 | if (this.gettok() != RrdTime.NUMBER) 347 | throw new RrdTimeError("There should be number after '"+(op == RrdTime.PLUS ? '+' : '-')+"'"); 348 | prev_multiplier = -1; /* reset months-minutes guessing mechanics */ 349 | } 350 | /* if doop is < 0 then we repeat the previous op with the prefetched number */ 351 | delta = this.token; 352 | if (this.gettok() == RrdTime.MONTHS_MINUTES) { 353 | /* hard job to guess what does that -5m means: -5mon or -5min? */ 354 | switch (prev_multiplier) { 355 | case RrdTime.DAYS: 356 | case RrdTime.WEEKS: 357 | case RrdTime.MONTHS: 358 | case RrdTime.YEARS: 359 | this.tokid = RrdTime.MONTHS; 360 | break; 361 | case RrdTime.SECONDS: 362 | case RrdTime.MINUTES: 363 | case RrdTime.HOURS: 364 | this.tokid = RrdTime.MINUTES; 365 | break; 366 | default: 367 | if (delta < 6) /* it may be some other value but in the context of RRD who needs less than 6 min deltas? */ 368 | this.tokid = RrdTime.MONTHS; 369 | else 370 | this.tokid = RrdTime.MINUTES; 371 | } 372 | } 373 | prev_multiplier = this.tokid; 374 | switch (this.tokid) { 375 | case RrdTime.YEARS: 376 | this.tm_year += ( op == RrdTime.PLUS) ? delta : -delta; 377 | return; 378 | case RrdTime.MONTHS: 379 | this.tm_mon += ( op == RrdTime.PLUS) ? delta : -delta; 380 | return; 381 | case RrdTime.WEEKS: 382 | delta *= 7; 383 | /* falls through */ 384 | case RrdTime.DAYS: 385 | this.tm_mday += ( op == RrdTime.PLUS) ? delta : -delta; 386 | return; 387 | case RrdTime.HOURS: 388 | this.offset += (op == RrdTime.PLUS) ? delta * 60 * 60 : -delta * 60 * 60; 389 | return; 390 | case RrdTime.MINUTES: 391 | this.offset += (op == RrdTime.PLUS) ? delta * 60 : -delta * 60; 392 | return; 393 | case RrdTime.SECONDS: 394 | this.offset += (op == RrdTime.PLUS) ? delta : -delta; 395 | return; 396 | default: /*default unit is seconds */ 397 | this.offset += (op == RrdTime.PLUS) ? delta : -delta; 398 | return; 399 | } 400 | throw new RrdTimeError("well-known time unit expected after "+delta); 401 | }; 402 | 403 | RrdTime.prototype.tod = function() /* tod() computes the time of day (TIME-OF-DAY-SPEC) */ 404 | { 405 | var hour, minute = 0; 406 | var tlen; 407 | /* save token status in case we must abort */ 408 | var tokid_sv = this.tokid; 409 | 410 | tlen = (this.token+"").length; 411 | /* first pick out the time of day - we assume a HH (COLON|DOT) MM time */ 412 | if (tlen > 2) 413 | return; 414 | hour = this.token; 415 | this.gettok(); 416 | if (this.tokid == RrdTime.SLASH || this.tokid == RrdTime.DOT) { 417 | /* guess we are looking at a date */ 418 | this.tokid = tokid_sv; 419 | this.token = hour; 420 | return; 421 | } 422 | if (this.tokid == RrdTime.COLON) { 423 | if (this.gettok() != RrdTime.NUMBER) 424 | throw new RrdTimeError("Parsing HH:MM syntax, expecting MM as number, got none"); 425 | minute = this.token; 426 | if (minute > 59) 427 | throw new RrdTimeError("parsing HH:MM syntax, got MM = "+minute+" (>59!)"); 428 | this.gettok(); 429 | } 430 | /* check if an AM or PM specifier was given */ 431 | if (this.tokid == RrdTime.AM || this.tokid == RrdTime.PM) { 432 | if (hour > 12) { 433 | throw new RrdTimeError("there cannot be more than 12 AM or PM hours"); 434 | } 435 | if (this.tokid == RrdTime.PM) { 436 | if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ 437 | hour += 12; 438 | } else { 439 | if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ 440 | hour = 0; 441 | } 442 | this.gettok(); 443 | } else if (hour > 23) { 444 | /* guess it was not a time then ... */ 445 | this.tokid = tokid_sv; 446 | this.token = hour; 447 | return; 448 | } 449 | this.tm_hour = hour; 450 | this.tm_min = minute; 451 | this.tm_sec = 0; 452 | if (this.tm_hour == 24) { 453 | this.tm_hour = 0; 454 | this.tm_mday++; 455 | } 456 | }; 457 | 458 | RrdTime.prototype.assign_date = function(mday, mon, year) 459 | { 460 | if (year > 138) { 461 | if (year > 1970) { 462 | year -= 1900; 463 | } else { 464 | throw new RrdTimeError("invalid year "+year+" (should be either 00-99 or >1900)"); 465 | } 466 | } else if (year >= 0 && year < 38) { 467 | year += 100; /* Allow year 2000-2037 to be specified as */ 468 | } 469 | /* 00-37 until the problem of 2038 year will */ 470 | /* arise for unices with 32-bit time_t :) */ 471 | if (year < 70) 472 | throw new RrdTimeError("won't handle dates before epoch (01/01/1970), sorry"); 473 | 474 | this.tm_mday = mday; 475 | this.tm_mon = mon; 476 | this.tm_year = year; 477 | }; 478 | 479 | RrdTime.prototype.day = function () 480 | { 481 | var mday = 0, wday, mon, year = this.tm_year; 482 | 483 | switch (this.tokid) { 484 | case RrdTime.YESTERDAY: 485 | this.tm_mday--; 486 | /* falls through */ 487 | case RrdTime.TODAY: 488 | this.gettok(); 489 | break; 490 | case RrdTime.TOMORROW: 491 | this.tm_mday++; 492 | this.gettok(); 493 | break; 494 | case RrdTime.JAN: 495 | case RrdTime.FEB: 496 | case RrdTime.MAR: 497 | case RrdTime.APR: 498 | case RrdTime.MAY: 499 | case RrdTime.JUN: 500 | case RrdTime.JUL: 501 | case RrdTime.AUG: 502 | case RrdTime.SEP: 503 | case RrdTime.OCT: 504 | case RrdTime.NOV: 505 | case RrdTime.DEC: 506 | mon = (this.tokid - RrdTime.JAN); 507 | if (this.gettok() != RrdTime.NUMBER) 508 | throw new RrdTimeError("the day of the month should follow month name"); 509 | mday = this.token; 510 | if (this.gettok() == RrdTime.NUMBER) { 511 | year = this.token; 512 | this.gettok(); 513 | } else { 514 | year = this.tm_year; 515 | } 516 | this.assign_date(mday, mon, year); 517 | break; 518 | case RrdTime.SUN: 519 | case RrdTime.MON: 520 | case RrdTime.TUE: 521 | case RrdTime.WED: 522 | case RrdTime.THU: 523 | case RrdTime.FRI: 524 | case RrdTime.SAT: 525 | wday = (this.tokid - RrdTime.SUN); 526 | this.tm_mday += (wday - this.tm_wday); 527 | this.gettok(); 528 | break; 529 | case RrdTime.NUMBER: 530 | mon = this.token; 531 | if (mon > 10 * 365 * 24 * 60 * 60) { 532 | this.localtime(mon); 533 | this.gettok(); 534 | break; 535 | } 536 | if (mon > 19700101 && mon < 24000101) { /*works between 1900 and 2400 */ 537 | var str = this.token + ''; 538 | year = parseInt(str.substr(0,4),10); 539 | mon = parseInt(str.substr(4,2),10); 540 | mday = parseInt(str.substr(6,2),10); 541 | this.gettok(); 542 | } else { 543 | this.gettok(); 544 | if (mon <= 31 && (this.tokid == RrdTime.SLASH || this.tokid == RrdTime.DOT)) { 545 | var sep = this.tokid; 546 | if (this.gettok() != RrdTime.NUMBER) 547 | throw new RrdTimeError("there should be "+(RrdTime.DOT ? "month" : "day")+" number after '"+(RrdTime.DOT ? '.' : '/')+"'"); 548 | mday = this.token; 549 | if (this.gettok() == sep) { 550 | if (this.gettok() != RrdTime.NUMBER) 551 | throw new RrdTimeError("there should be year number after '"+(sep == RrdTime.DOT ? '.' : '/')+"'"); 552 | year = this.token; 553 | this.gettok(); 554 | } 555 | if (sep == RrdTime.DOT) { 556 | var x = mday; 557 | mday = mon; 558 | mon = x; 559 | } 560 | } 561 | } 562 | mon--; 563 | if (mon < 0 || mon > 11) 564 | throw new RrdTimeError("did you really mean month "+(mon+1)+"?"); 565 | if (mday < 1 || mday > 31) 566 | throw new RrdTimeError("I'm afraid that "+mday+" is not a valid day of the month"); 567 | this.assign_date(mday, mon, year); 568 | break; 569 | } 570 | }; 571 | 572 | RrdTime.prototype.localtime = function (tm) 573 | { 574 | var date = new Date(tm*1000); 575 | this.tm_sec = date.getSeconds(); 576 | this.tm_min = date.getMinutes(); 577 | this.tm_hour = date.getHours(); 578 | this.tm_mday = date.getDate(); 579 | this.tm_mon = date.getMonth(); 580 | this.tm_year = date.getFullYear()-1900; 581 | this.tm_wday = date.getDay(); 582 | }; 583 | 584 | RrdTime.prototype.mktime = function() 585 | { 586 | var date = new Date(this.tm_year+1900, this.tm_mon, this.tm_mday, this.tm_hour, this.tm_min, this.tm_sec); 587 | return Math.round(date.getTime()/1000.0); 588 | }; 589 | 590 | RrdTime.proc_start_end = function(start_t, end_t) 591 | { 592 | var start, end; 593 | 594 | if (start_t.type == RrdTime.RELATIVE_TO_END_TIME && end_t.type == RrdTime.RELATIVE_TO_START_TIME) 595 | throw new RrdTimeError("the start and end times cannot be specified relative to each other"); 596 | if (start_t.type == RrdTime.RELATIVE_TO_START_TIME) 597 | throw new RrdTimeError("the start time cannot be specified relative to itself"); 598 | if (end_t.type == RrdTime.RELATIVE_TO_END_TIME) 599 | throw new RrdTimeError("the end time cannot be specified relative to itself"); 600 | 601 | if (start_t.type == RrdTime.RELATIVE_TO_END_TIME) { 602 | end = end_t.mktime() + end_t.offset; 603 | var tmtmp = new Date(end*1000); 604 | tmtmp.setDate(tmtmp.getDate()+start_t.tm_mday); 605 | tmtmp.setMonth(tmtmp.getMonth()+start_t.tm_mon); 606 | tmtmp.setFullYear(tmtmp.getFullYear()+start_t.tm_year); 607 | start = Math.round(tmtmp.getTime()/1000.0) + start_t.offset; 608 | } else { 609 | start = start_t.mktime() + start_t.offset; 610 | } 611 | 612 | if (end_t.type == RrdTime.RELATIVE_TO_START_TIME) { 613 | start = start_t.mktime() + start_t.offset; 614 | var tmtmp = new Date(start*1000); 615 | tmtmp.setDate(tmtmp.getDate()+end_t.tm_mday); 616 | tmtmp.setMonth(tmtmp.getMonth()+end_t.tm_mon); 617 | tmtmp.setFullYear(tmtmp.getFullYear()+end_t.tm_year); 618 | end = Math.round(tmtmp.getTime()/1000.0) + end_t.offset; 619 | } else { 620 | end = end_t.mktime() + end_t.offset; 621 | } 622 | 623 | return [start, end]; 624 | }; 625 | 626 | -------------------------------------------------------------------------------- /js/RrdRpn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | * RRDtool 1.4.5 Copyright by Tobi Oetiker, 1997-2010 18 | * 19 | * Convert to javascript: Manuel Sanmartin 20 | **/ 21 | 22 | "use strict"; 23 | 24 | /** 25 | * RrdRpnError 26 | * @constructor 27 | */ 28 | var RrdRpnError = function (message) 29 | { 30 | this.name = "RrdRpnError"; 31 | this.message = (message) ? message : "RPN stack underflow"; 32 | }; 33 | RrdRpnError.prototype = new Error(); 34 | 35 | /** 36 | * RrdRpn 37 | * @constructor 38 | */ 39 | var RrdRpn = function (str_expr, gdes) /* parser */ 40 | { 41 | var steps = -1; 42 | var expr; 43 | var exprs = str_expr.split(','); 44 | 45 | this.rpnexpr = str_expr; 46 | this.rpnp = []; 47 | this.rpnstack = null; 48 | 49 | for(var i=0, len=exprs.length; i < len; i++) { 50 | expr=exprs[i].toUpperCase(); 51 | 52 | steps++; 53 | this.rpnp[steps] = {}; 54 | 55 | if (!isNaN(expr)) { 56 | this.rpnp[steps].op = RrdRpn.OP_NUMBER; 57 | this.rpnp[steps].val = parseFloat(expr); 58 | } 59 | else if (expr === '+') this.rpnp[steps].op = RrdRpn.OP_ADD; 60 | else if (expr === '-') this.rpnp[steps].op = RrdRpn.OP_SUB; 61 | else if (expr === '*') this.rpnp[steps].op = RrdRpn.OP_MUL; 62 | else if (expr === '/') this.rpnp[steps].op = RrdRpn.OP_DIV; 63 | else if (expr === '%') this.rpnp[steps].op = RrdRpn.OP_MOD; 64 | else if (expr === 'SIN') this.rpnp[steps].op = RrdRpn.OP_SIN; 65 | else if (expr === 'COS') this.rpnp[steps].op = RrdRpn.OP_COS; 66 | else if (expr === 'LOG') this.rpnp[steps].op = RrdRpn.OP_LOG; 67 | else if (expr === 'FLOOR') this.rpnp[steps].op = RrdRpn.OP_FLOOR; 68 | else if (expr === 'CEIL') this.rpnp[steps].op = RrdRpn.OP_CEIL; 69 | else if (expr === 'EXP') this.rpnp[steps].op = RrdRpn.OP_EXP; 70 | else if (expr === 'DUP') this.rpnp[steps].op = RrdRpn.OP_DUP; 71 | else if (expr === 'EXC') this.rpnp[steps].op = RrdRpn.OP_EXC; 72 | else if (expr === 'POP') this.rpnp[steps].op = RrdRpn.OP_POP; 73 | else if (expr === 'LTIME') this.rpnp[steps].op = RrdRpn.OP_LTIME; 74 | else if (expr === 'LT') this.rpnp[steps].op = RrdRpn.OP_LT; 75 | else if (expr === 'LE') this.rpnp[steps].op = RrdRpn.OP_LE; 76 | else if (expr === 'GT') this.rpnp[steps].op = RrdRpn.OP_GT; 77 | else if (expr === 'GE') this.rpnp[steps].op = RrdRpn.OP_GE; 78 | else if (expr === 'EQ') this.rpnp[steps].op = RrdRpn.OP_EQ; 79 | else if (expr === 'IF') this.rpnp[steps].op = RrdRpn.OP_IF; 80 | else if (expr === 'MIN') this.rpnp[steps].op = RrdRpn.OP_MIN; 81 | else if (expr === 'MAX') this.rpnp[steps].op = RrdRpn.OP_MAX; 82 | else if (expr === 'LIMIT') this.rpnp[steps].op = RrdRpn.OP_LIMIT; 83 | else if (expr === 'UNKN') this.rpnp[steps].op = RrdRpn.OP_UNKN; 84 | else if (expr === 'UN') this.rpnp[steps].op = RrdRpn.OP_UN; 85 | else if (expr === 'NEGINF') this.rpnp[steps].op = RrdRpn.OP_NEGINF; 86 | else if (expr === 'NE') this.rpnp[steps].op = RrdRpn.OP_NE; 87 | else if (expr === 'COUNT') this.rpnp[steps].op = RrdRpn.OP_COUNT; 88 | else if (/PREV\([-_A-Za-z0-9]+\)/.test(expr)) { 89 | var match = exprs[i].match(/PREV\(([-_A-Za-z0-9]+)\)/i); 90 | if (match.length == 2) { 91 | this.rpnp[steps].op = RrdRpn.OP_PREV_OTHER; 92 | this.rpnp[steps].ptr = this.find_var(gdes, match[1]); // FIXME if -1 93 | } 94 | } 95 | else if (expr === 'PREV') this.rpnp[steps].op = RrdRpn.OP_PREV; 96 | else if (expr === 'INF') this.rpnp[steps].op = RrdRpn.OP_INF; 97 | else if (expr === 'ISINF') this.rpnp[steps].op = RrdRpn.OP_ISINF; 98 | else if (expr === 'NOW') this.rpnp[steps].op = RrdRpn.OP_NOW; 99 | else if (expr === 'TIME') this.rpnp[steps].op = RrdRpn.OP_TIME; 100 | else if (expr === 'ATAN2') this.rpnp[steps].op = RrdRpn.OP_ATAN2; 101 | else if (expr === 'ATAN') this.rpnp[steps].op = RrdRpn.OP_ATAN; 102 | else if (expr === 'SQRT') this.rpnp[steps].op = RrdRpn.OP_SQRT; 103 | else if (expr === 'SORT') this.rpnp[steps].op = RrdRpn.OP_SORT; 104 | else if (expr === 'REV') this.rpnp[steps].op = RrdRpn.OP_REV; 105 | else if (expr === 'TREND') this.rpnp[steps].op = RrdRpn.OP_TREND; 106 | else if (expr === 'TRENDNAN') this.rpnp[steps].op = RrdRpn.OP_TRENDNAN; 107 | else if (expr === 'PREDICT') this.rpnp[steps].op = RrdRpn.OP_PREDICT; 108 | else if (expr === 'PREDICTSIGMA') this.rpnp[steps].op = RrdRpn.OP_PREDICTSIGMA; 109 | else if (expr === 'RAD2DEG') this.rpnp[steps].op = RrdRpn.OP_RAD2DEG; 110 | else if (expr === 'DEG2RAD') this.rpnp[steps].op = RrdRpn.OP_DEG2RAD; 111 | else if (expr === 'AVG') this.rpnp[steps].op = RrdRpn.OP_AVG; 112 | else if (expr === 'ABS') this.rpnp[steps].op = RrdRpn.OP_ABS; 113 | else if (expr === 'ADDNAN') this.rpnp[steps].op = RrdRpn.OP_ADDNAN; 114 | else if (/[-_A-Za-z0-9]+/.test(expr)) { 115 | this.rpnp[steps].ptr = this.find_var(gdes, exprs[i]); // FIXME if -1 116 | this.rpnp[steps].op = RrdRpn.OP_VARIABLE; 117 | } else { 118 | return; 119 | } 120 | } 121 | this.rpnp[steps + 1] = {}; 122 | this.rpnp[steps + 1].op = RrdRpn.OP_END; 123 | }; 124 | 125 | RrdRpn.OP_NUMBER= 0; 126 | RrdRpn.OP_VARIABLE = 1; 127 | RrdRpn.OP_INF = 2; 128 | RrdRpn.OP_PREV = 3; 129 | RrdRpn.OP_NEGINF = 4; 130 | RrdRpn.OP_UNKN = 5; 131 | RrdRpn.OP_NOW = 6; 132 | RrdRpn.OP_TIME = 7; 133 | RrdRpn.OP_ADD = 8; 134 | RrdRpn.OP_MOD = 9; 135 | RrdRpn.OP_SUB = 10; 136 | RrdRpn.OP_MUL = 11; 137 | RrdRpn.OP_DIV = 12; 138 | RrdRpn.OP_SIN = 13; 139 | RrdRpn.OP_DUP = 14; 140 | RrdRpn.OP_EXC = 15; 141 | RrdRpn.OP_POP = 16; 142 | RrdRpn.OP_COS = 17; 143 | RrdRpn.OP_LOG = 18; 144 | RrdRpn.OP_EXP = 19; 145 | RrdRpn.OP_LT = 20; 146 | RrdRpn.OP_LE = 21; 147 | RrdRpn.OP_GT = 22; 148 | RrdRpn.OP_GE = 23; 149 | RrdRpn.OP_EQ = 24; 150 | RrdRpn.OP_IF = 25; 151 | RrdRpn.OP_MIN = 26; 152 | RrdRpn.OP_MAX = 27; 153 | RrdRpn.OP_LIMIT = 28; 154 | RrdRpn.OP_FLOOR = 29; 155 | RrdRpn.OP_CEIL = 30; 156 | RrdRpn.OP_UN = 31; 157 | RrdRpn.OP_END = 32; 158 | RrdRpn.OP_LTIME = 33; 159 | RrdRpn.OP_NE = 34; 160 | RrdRpn.OP_ISINF = 35; 161 | RrdRpn.OP_PREV_OTHER = 36; 162 | RrdRpn.OP_COUNT = 37; 163 | RrdRpn.OP_ATAN = 38; 164 | RrdRpn.OP_SQRT = 39; 165 | RrdRpn.OP_SORT = 40; 166 | RrdRpn.OP_REV = 41; 167 | RrdRpn.OP_TREND = 42; 168 | RrdRpn.OP_TRENDNAN = 43; 169 | RrdRpn.OP_ATAN2 = 44; 170 | RrdRpn.OP_RAD2DEG = 45; 171 | RrdRpn.OP_DEG2RAD = 46; 172 | RrdRpn.OP_PREDICT = 47; 173 | RrdRpn.OP_PREDICTSIGMA = 48; 174 | RrdRpn.OP_AVG = 49; 175 | RrdRpn.OP_ABS = 50; 176 | RrdRpn.OP_ADDNAN = 51 ; 177 | 178 | RrdRpn.prototype.find_var = function(gdes, key) 179 | { 180 | for (var ii = 0, gdes_c = gdes.length; ii < gdes_c; ii++) { 181 | if ((gdes[ii].gf == RrdGraphDesc.GF_DEF || 182 | gdes[ii].gf == RrdGraphDesc.GF_VDEF || 183 | gdes[ii].gf == RrdGraphDesc.GF_CDEF) 184 | && gdes[ii].vname == key) { 185 | return ii; 186 | } 187 | } 188 | return -1; 189 | }; 190 | 191 | RrdRpn.prototype.compare_double = function(x, y) 192 | { 193 | var diff = x - y; 194 | return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; 195 | }; 196 | 197 | RrdRpn.prototype.fmod = function (x, y) 198 | { 199 | // http://kevin.vanzonneveld.net 200 | // + original by: Onno Marsman 201 | // + input by: Brett Zamir (http://brett-zamir.me) 202 | // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 203 | // * example 1: fmod(5.7, 1.3); 204 | // * returns 1: 0.5 205 | var tmp, tmp2, p = 0, 206 | pY = 0, 207 | l = 0.0, 208 | l2 = 0.0; 209 | 210 | tmp = x.toExponential().match(/^.\.?(.*)e(.+)$/); 211 | p = parseInt(tmp[2], 10) - (tmp[1] + '').length; 212 | tmp = y.toExponential().match(/^.\.?(.*)e(.+)$/); 213 | pY = parseInt(tmp[2], 10) - (tmp[1] + '').length; 214 | 215 | if (pY > p) p = pY; 216 | 217 | tmp2 = (x % y); 218 | 219 | if (p < -100 || p > 20) { 220 | l = Math.round(Math.log(tmp2) / Math.log(10)); 221 | l2 = Math.pow(10, l); 222 | return (tmp2 / l2).toFixed(l - p) * l2; 223 | } else { 224 | return parseFloat(tmp2.toFixed(-p)); 225 | } 226 | }; 227 | 228 | RrdRpn.prototype.calc = function (data_idx, output, output_idx) 229 | { 230 | var stptr = -1; 231 | 232 | this.rpnstack = []; 233 | 234 | for (var rpi = 0; this.rpnp[rpi].op != RrdRpn.OP_END; rpi++) { 235 | switch (this.rpnp[rpi].op) { 236 | case RrdRpn.OP_NUMBER: 237 | this.rpnstack[++stptr] = this.rpnp[rpi].val; 238 | break; 239 | case RrdRpn.OP_VARIABLE: 240 | case RrdRpn.OP_PREV_OTHER: 241 | if (this.rpnp[rpi].ds_cnt == 0) { 242 | throw new RrdRpnError("VDEF made it into rpn_calc... aborting"); 243 | } else { 244 | if (this.rpnp[rpi].op == RrdRpn.OP_VARIABLE) { 245 | this.rpnstack[++stptr] = this.rpnp[rpi].data[this.rpnp[rpi].pdata]; 246 | } else { 247 | if ((output_idx) <= 0) this.rpnstack[++stptr] = Number.NaN; 248 | else this.rpnstack[++stptr] = this.rpnp[rpi].data[this.rpnp[rpi].pdata - this.rpnp[rpi].ds_cnt]; 249 | } 250 | if (data_idx % this.rpnp[rpi].step == 0) { 251 | this.rpnp[rpi].pdata += this.rpnp[rpi].ds_cnt; 252 | } 253 | } 254 | break; 255 | case RrdRpn.OP_COUNT: 256 | this.rpnstack[++stptr] = (output_idx + 1); /* Note: Counter starts at 1 */ 257 | break; 258 | case RrdRpn.OP_PREV: 259 | if ((output_idx) <= 0) this.rpnstack[++stptr] = Number.NaN; 260 | else this.rpnstack[++stptr] = output[output_idx - 1]; 261 | break; 262 | case RrdRpn.OP_UNKN: 263 | this.rpnstack[++stptr] = Number.NaN; 264 | break; 265 | case RrdRpn.OP_INF: 266 | this.rpnstack[++stptr] = Infinity; 267 | break; 268 | case RrdRpn.OP_NEGINF: 269 | this.rpnstack[++stptr] = -Infinity; 270 | break; 271 | case RrdRpn.OP_NOW: 272 | this.rpnstack[++stptr] = Math.round((new Date()).getTime() / 1000); 273 | break; 274 | case RrdRpn.OP_TIME: 275 | this.rpnstack[++stptr] = data_idx; 276 | break; 277 | case RrdRpn.OP_LTIME: 278 | var date = new Date(data_idx*1000); // FIXME XXX 279 | this.rpnstack[++stptr] = date.getTimezoneOffset() * 60 + data_idx; 280 | break; 281 | case RrdRpn.OP_ADD: 282 | if(stptr < 1) throw new RrdRpnError(); 283 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] + this.rpnstack[stptr]; 284 | stptr--; 285 | break; 286 | case RrdRpn.OP_ADDNAN: 287 | if(stptr < 1) throw new RrdRpnError(); 288 | if (isNaN(this.rpnstack[stptr - 1])) { 289 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 290 | } else if (isNaN(this.rpnstack[stptr])) { 291 | /* NOOP */ 292 | /* this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1]; */ 293 | } else { 294 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] + this.rpnstack[stptr]; 295 | } 296 | stptr--; 297 | break; 298 | case RrdRpn.OP_SUB: 299 | if(stptr < 1) throw new RrdRpnError(); 300 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] - this.rpnstack[stptr]; 301 | stptr--; 302 | break; 303 | case RrdRpn.OP_MUL: 304 | if(stptr < 1) throw new RrdRpnError(); 305 | this.rpnstack[stptr - 1] = (this.rpnstack[stptr - 1]) * (this.rpnstack[stptr]); 306 | stptr--; 307 | break; 308 | case RrdRpn.OP_DIV: 309 | if(stptr < 1) throw new RrdRpnError(); 310 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] / this.rpnstack[stptr]; 311 | stptr--; 312 | break; 313 | case RrdRpn.OP_MOD: 314 | if(stptr < 1) throw new RrdRpnError(); 315 | this.rpnstack[stptr - 1] = this.fmod(this.rpnstack[stptr - 1] , this.rpnstack[stptr]); 316 | stptr--; 317 | break; 318 | case RrdRpn.OP_SIN: 319 | if(stptr < 0) throw new RrdRpnError(); 320 | this.rpnstack[stptr] = Math.sin(this.rpnstack[stptr]); 321 | break; 322 | case RrdRpn.OP_ATAN: 323 | if(stptr < 0) throw new RrdRpnError(); 324 | this.rpnstack[stptr] = Math.atan(this.rpnstack[stptr]); 325 | break; 326 | case RrdRpn.OP_RAD2DEG: 327 | if(stptr < 0) throw new RrdRpnError(); 328 | this.rpnstack[stptr] = 57.29577951 * this.rpnstack[stptr]; 329 | break; 330 | case RrdRpn.OP_DEG2RAD: 331 | if(stptr < 0) throw new RrdRpnError(); 332 | this.rpnstack[stptr] = 0.0174532952 * this.rpnstack[stptr]; 333 | break; 334 | case RrdRpn.OP_ATAN2: 335 | if(stptr < 1) throw new RrdRpnError(); 336 | this.rpnstack[stptr - 1] = Math.atan2(this.rpnstack[stptr - 1], this.rpnstack[stptr]); 337 | stptr--; 338 | break; 339 | case RrdRpn.OP_COS: 340 | if(stptr < 0) throw new RrdRpnError(); 341 | this.rpnstack[stptr] = Math.cos(this.rpnstack[stptr]); 342 | break; 343 | case RrdRpn.OP_CEIL: 344 | if(stptr < 0) throw new RrdRpnError(); 345 | this.rpnstack[stptr] = Math.ceil(this.rpnstack[stptr]); 346 | break; 347 | case RrdRpn.OP_FLOOR: 348 | if(stptr < 0) throw new RrdRpnError(); 349 | this.rpnstack[stptr] = Math.floor(this.rpnstack[stptr]); 350 | break; 351 | case RrdRpn.OP_LOG: 352 | if(stptr < 0) throw new RrdRpnError(); 353 | this.rpnstack[stptr] = Math.log(this.rpnstack[stptr]); 354 | break; 355 | case RrdRpn.OP_DUP: 356 | if(stptr < 0) throw new RrdRpnError(); 357 | this.rpnstack[stptr + 1] = this.rpnstack[stptr]; 358 | stptr++; 359 | break; 360 | case RrdRpn.OP_POP: 361 | if(stptr < 0) throw new RrdRpnError(); 362 | stptr--; 363 | break; 364 | case RrdRpn.OP_EXC: 365 | if(stptr < 1) throw new RrdRpnError(); { 366 | var dummy = this.rpnstack[stptr]; 367 | this.rpnstack[stptr] = this.rpnstack[stptr - 1]; 368 | this.rpnstack[stptr - 1] = dummy; 369 | } 370 | break; 371 | case RrdRpn.OP_EXP: 372 | if(stptr < 0) throw new RrdRpnError(); 373 | this.rpnstack[stptr] = Math.exp(this.rpnstack[stptr]); 374 | break; 375 | case RrdRpn.OP_LT: 376 | if(stptr < 1) throw new RrdRpnError(); 377 | if (isNaN(this.rpnstack[stptr - 1])) { 378 | } else if (isNaN(this.rpnstack[stptr])) { 379 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 380 | } else { 381 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] < this.rpnstack[stptr] ? 1.0 : 0.0; 382 | } 383 | stptr--; 384 | break; 385 | case RrdRpn.OP_LE: 386 | if(stptr < 1) throw new RrdRpnError(); 387 | if (isNaN(this.rpnstack[stptr - 1])) { 388 | } else if (isNaN(this.rpnstack[stptr])) { 389 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 390 | } else { 391 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] <= this.rpnstack[stptr] ? 1.0 : 0.0; 392 | } 393 | stptr--; 394 | break; 395 | case RrdRpn.OP_GT: 396 | if(stptr < 1) throw new RrdRpnError(); 397 | if (isNaN(this.rpnstack[stptr - 1])) { 398 | } else if (isNaN(this.rpnstack[stptr])) { 399 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 400 | } else { 401 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] > this.rpnstack[stptr] ? 1.0 : 0.0; 402 | } 403 | stptr--; 404 | break; 405 | case RrdRpn.OP_GE: 406 | if(stptr < 1) throw new RrdRpnError(); 407 | if (isNaN(this.rpnstack[stptr - 1])) { 408 | } else if (isNaN(this.rpnstack[stptr])) { 409 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 410 | } else { 411 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] >= this.rpnstack[stptr] ? 1.0 : 0.0; 412 | } 413 | stptr--; 414 | break; 415 | case RrdRpn.OP_NE: 416 | if(stptr < 1) throw new RrdRpnError(); 417 | if (isNaN(this.rpnstack[stptr - 1])) { 418 | } else if (isNaN(this.rpnstack[stptr])) { 419 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 420 | } else { 421 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] == this.rpnstack[stptr] ? 0.0 : 1.0; 422 | } 423 | stptr--; 424 | break; 425 | case RrdRpn.OP_EQ: 426 | if(stptr < 1) throw new RrdRpnError(); 427 | if (isNaN(this.rpnstack[stptr - 1])) { 428 | } else if (isNaN(this.rpnstack[stptr])) { 429 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 430 | } else { 431 | this.rpnstack[stptr - 1] = this.rpnstack[stptr - 1] == this.rpnstack[stptr] ? 1.0 : 0.0; 432 | } 433 | stptr--; 434 | break; 435 | case RrdRpn.OP_IF: 436 | if(stptr < 2) throw new RrdRpnError(); 437 | this.rpnstack[stptr - 2] = (isNaN(this.rpnstack[stptr - 2]) || this.rpnstack[stptr - 2] == 0.0) ? this.rpnstack[stptr] : this.rpnstack[stptr - 1]; 438 | stptr--; 439 | stptr--; 440 | break; 441 | case RrdRpn.OP_MIN: 442 | if(stptr < 1) throw new RrdRpnError(); 443 | if (isNaN(this.rpnstack[stptr - 1])) { 444 | } else if (isNaN(this.rpnstack[stptr])) { 445 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 446 | } else if (this.rpnstack[stptr - 1] > this.rpnstack[stptr]) { 447 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 448 | } 449 | stptr--; 450 | break; 451 | case RrdRpn.OP_MAX: 452 | if(stptr < 1) throw new RrdRpnError(); 453 | if (isNaN(this.rpnstack[stptr - 1])) { 454 | } else if (isNaN(this.rpnstack[stptr])) { 455 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 456 | } else if (this.rpnstack[stptr - 1] < this.rpnstack[stptr]) { 457 | this.rpnstack[stptr - 1] = this.rpnstack[stptr]; 458 | } 459 | stptr--; 460 | break; 461 | case RrdRpn.OP_LIMIT: 462 | if(stptr < 2) throw new RrdRpnError(); 463 | if (isNaN(this.rpnstack[stptr - 2])) { 464 | } else if (isNaN(this.rpnstack[stptr - 1])) { 465 | this.rpnstack[stptr - 2] = this.rpnstack[stptr - 1]; 466 | } else if (isNaN(this.rpnstack[stptr])) { 467 | this.rpnstack[stptr - 2] = this.rpnstack[stptr]; 468 | } else if (this.rpnstack[stptr - 2] < this.rpnstack[stptr - 1]) { 469 | this.rpnstack[stptr - 2] = Number.NaN; 470 | } else if (this.rpnstack[stptr - 2] > this.rpnstack[stptr]) { 471 | this.rpnstack[stptr - 2] = Number.NaN; 472 | } 473 | stptr -= 2; 474 | break; 475 | case RrdRpn.OP_UN: 476 | if(stptr < 0) throw new RrdRpnError(); 477 | this.rpnstack[stptr] = isNaN(this.rpnstack[stptr]) ? 1.0 : 0.0; 478 | break; 479 | case RrdRpn.OP_ISINF: 480 | if(stptr < 0) throw new RrdRpnError(); 481 | this.rpnstack[stptr] = !isFinite(this.rpnstack[stptr]) ? 1.0 : 0.0; 482 | break; 483 | case RrdRpn.OP_SQRT: 484 | if(stptr < 0) throw new RrdRpnError(); 485 | this.rpnstack[stptr] = Math.sqrt(this.rpnstack[stptr]); 486 | break; 487 | case RrdRpn.OP_SORT: 488 | if(stptr < 0) throw new RrdRpnError(); 489 | var spn = this.rpnstack[stptr--]; 490 | if(stptr < spn - 1) throw new RrdRpnError(); 491 | var array = this.rpnstack.slice(stptr - spn + 1, stptr +1); 492 | array.sort(this.compare_double); 493 | for (var i=stptr - spn + 1, ii=0; i < (stptr +1) ; i++, ii++) 494 | this.rpnstack[i] = array[ii]; 495 | // qsort(this.rpnstack + stptr - spn + 1, spn, sizeof(double), rpn_compare_double); 496 | break; 497 | case RrdRpn.OP_REV: 498 | if(stptr < 0) throw new RrdRpnError(); 499 | var spn = this.rpnstack[stptr--]; 500 | if(stptr < spn - 1) throw new RrdRpnError(); 501 | var array = this.rpnstack.slice(stptr - spn + 1, stptr +1); 502 | array.reverse(); 503 | for (var i=stptr - spn + 1, ii=0; i < (stptr +1) ; i++, ii++) 504 | this.rpnstack[i] = array[ii]; 505 | break; 506 | case RrdRpn.OP_PREDICT: 507 | case RrdRpn.OP_PREDICTSIGMA: 508 | if(stptr < 2) throw new RrdRpnError(); 509 | var locstepsize = this.rpnstack[--stptr]; 510 | var shifts = this.rpnstack[--stptr]; 511 | if(stptr < shifts) throw new RrdRpnError(); 512 | if (shifts<0) stptr--; 513 | else stptr-=shifts; 514 | var val=Number.NaN; 515 | var dsstep = this.rpnp[rpi - 1].step; 516 | var dscount = this.rpnp[rpi - 1].ds_cnt; 517 | var locstep = Math.ceil(locstepsize/dsstep); 518 | var sum = 0; 519 | var sum2 = 0; 520 | var count = 0; 521 | /* now loop for each position */ 522 | var doshifts=shifts; 523 | if (shifts<0) doshifts=-shifts; 524 | for(var loop=0;loop=0)&&(offset0) val = sum/count; 547 | } else { 548 | if (count>1) { 549 | val=count*sum2-sum*sum; 550 | if (val<0) { 551 | val=Number.NaN; 552 | } else { 553 | val=Math.sqrt(val/(count*(count-1.0))); 554 | } 555 | } 556 | } 557 | this.rpnstack[stptr] = val; 558 | break; 559 | case RrdRpn.OP_TREND: 560 | case RrdRpn.OP_TRENDNAN: 561 | if(stptr < 1) throw new RrdRpnError(); 562 | if ((rpi < 2) || (this.rpnp[rpi - 2].op != RrdRpn.OP_VARIABLE)) { 563 | throw new RrdRpnError("malformed trend arguments"); 564 | } else { 565 | var dur = this.rpnstack[stptr]; 566 | var step = this.rpnp[rpi - 2].step; 567 | 568 | if (output_idx + 1 >= Math.ceil(dur / step)) { 569 | var ignorenan = (this.rpnp[rpi].op == RrdRpn.OP_TREND); 570 | var accum = 0.0; 571 | var i = 0; 572 | var count = 0; 573 | 574 | do { 575 | var val = this.rpnp[rpi - 2].data[this.rpnp[rpi - 2].ds_cnt * i--]; 576 | if (ignorenan || !isNaN(val)) { 577 | accum += val; 578 | ++count; 579 | } 580 | dur -= step; 581 | } while (dur > 0); 582 | 583 | this.rpnstack[--stptr] = (count == 0) ? Number.NaN : (accum / count); 584 | } else this.rpnstack[--stptr] = Number.NaN; 585 | } 586 | break; 587 | case RrdRpn.OP_AVG: 588 | if(stptr < 0) throw new RrdRpnError(); 589 | var i = this.rpnstack[stptr--]; 590 | var sum = 0; 591 | var count = 0; 592 | 593 | if(stptr < i - 1) throw new RrdRpnError(); 594 | while (i > 0) { 595 | var val = this.rpnstack[stptr--]; 596 | i--; 597 | if (isNaN(val)) continue; 598 | count++; 599 | sum += val; 600 | } 601 | if (count > 0) this.rpnstack[++stptr] = sum / count; 602 | else this.rpnstack[++stptr] = Number.NaN; 603 | break; 604 | case RrdRpn.OP_ABS: 605 | if(stptr < 0) throw new RrdRpnError(); 606 | this.rpnstack[stptr] = Math.abs(this.rpnstack[stptr]); 607 | break; 608 | case RrdRpn.OP_END: 609 | break; 610 | } 611 | } 612 | if (stptr != 0) throw new RrdRpnError("RPN final stack size != 1"); 613 | output[output_idx] = this.rpnstack[0]; 614 | return 0; 615 | }; 616 | 617 | -------------------------------------------------------------------------------- /js/RrdJson.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * This program is free software; you can redistribute it and/or modify it 4 | * under the terms of the GNU General Public License as published by the Free 5 | * Software Foundation; either version 2 of the License, or (at your option) 6 | * any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, but WITHOUT 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 11 | * more details. 12 | 13 | * You should have received a copy of the GNU General Public License along 14 | * with this program; if not, write to the Free Software Foundation, Inc., 15 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA 16 | 17 | * 18 | * Manuel Sanmartin 19 | **/ 20 | 21 | "use strict"; 22 | 23 | /** 24 | * RrdJson 25 | * @constructor 26 | */ 27 | var RrdJson = function() { 28 | if (arguments.length == 1) { 29 | this.init1.apply(this, arguments); 30 | } else if (arguments.length == 2) { 31 | this.init2.apply(this, arguments); 32 | } else if (arguments.length == 3) { 33 | this.init3.apply(this, arguments); 34 | } 35 | }; 36 | 37 | RrdJson.prototype = { 38 | graph: null, 39 | json: null, 40 | 41 | init1: function (rrdgraph) 42 | { 43 | this.graph = rrdgraph; 44 | }, 45 | init2: function (rrdgraph, jsonstr) 46 | { 47 | this.json = JSON.parse(jsonstr); 48 | this.graph = rrdgraph; 49 | }, 50 | init3: function (gfx, fetch, jsonstr) 51 | { 52 | this.json = JSON.parse(jsonstr); 53 | this.graph = new RrdGraph(gfx, fetch); 54 | }, 55 | parse: function() 56 | { 57 | for (var option in this.json) { 58 | switch(option) { 59 | case 'alt_autoscale': 60 | this.graph.alt_autoscale = this.json.alt_autoscale; 61 | break; 62 | case 'base': 63 | this.graph.base = parseInt(this.json.base, 10); 64 | if (this.graph.base !== 1000 && this.graph.base !== 1024) 65 | throw 'the only sensible value for base apart from 1000 is 1024'; 66 | break; 67 | case 'color': 68 | for (var color in this.json.color) { 69 | if (color in this.graph.GRC) { 70 | this.graph.GRC[color] = this.json.color[color]; 71 | } else { 72 | throw "invalid color '" + color + "'"; 73 | } 74 | } 75 | break; 76 | case 'full_size_mode': 77 | this.graph.full_size_mode = this.json.full_size_mode; 78 | break; 79 | case 'slope_mode': 80 | this.graph.slopemode = this.json.slope_mode; 81 | break; 82 | case 'end': 83 | this.graph.end_t = new RrdTime(this.json.end); 84 | break; 85 | case 'force_rules_legend': 86 | this.graph.force_rules_legend = this.json.force_rules_legend; 87 | break; 88 | case 'no_legend': 89 | this.graph.no_legend = this.json.no_legend; 90 | break; 91 | case 'height': 92 | this.graph.ysize = this.json.height; 93 | break; 94 | case 'no_minor': 95 | this.graph.no_minor = this.json.no_minor; 96 | break; 97 | case 'alt_autoscale_min': 98 | this.graph.alt_autoscale_min = this.json.alt_autoscale_min; 99 | break; 100 | case 'only_graph': 101 | this.graph.only_graph = this.json.only_graph; 102 | break; 103 | case 'units_length': 104 | this.graph.unitslength = this.json.units_length; // FIXME 105 | this.graph.forceleftspace = true; 106 | break; 107 | case 'lower_limit': 108 | if (this.json.lower_limit === null) this.graph.setminval = Number.NaN; 109 | else this.graph.setminval = this.json.lower_limit; 110 | break; 111 | case 'alt_autoscale_max': 112 | this.graph.alt_autoscale_max = this.json.alt_autoscale_max; 113 | break; 114 | case 'zoom': 115 | this.graph.zoom = this.json.zoom; 116 | if (this.graph.zoom <= 0.0) 117 | throw "zoom factor must be > 0"; 118 | break; 119 | case 'no_gridfit': 120 | this.graph.gridfit = this.json.no_gridfit; 121 | break; 122 | case 'font': 123 | for (var font in this.json.font) { 124 | if (font in this.graph.TEXT) { 125 | if (this.json.font[font].size !== undefined) 126 | this.graph.TEXT[font].size = this.json.font[font].size; 127 | if (this.json.font[font].font !== undefined) 128 | this.graph.TEXT[font].font = this.json.font[font].font; 129 | } else { 130 | throw "invalid text property name"; 131 | } 132 | } 133 | break; 134 | case 'logarithmic': 135 | this.graph.logarithmic = this.json.logarithmic; 136 | break; 137 | case 'rigid': 138 | this.graph.rigid = this.json.rigid; 139 | break; 140 | case 'step': 141 | this.graph.step = this.json.step; 142 | this.graph.step_orig = this.json.step; 143 | break; 144 | case 'start': 145 | this.graph.start_t = new RrdTime(this.json.start); 146 | break; 147 | case 'tabwidth': 148 | this.graph.tabwidth = this.json.tabwidth; 149 | break; 150 | case 'title': 151 | this.graph.title = this.json.title; 152 | break; 153 | case 'upper_limit': 154 | if (this.json.upper_limit === null) this.graph.setmaxval = Number.NaN; 155 | else this.graph.setmaxval = this.json.upper_limit; 156 | break; 157 | case 'vertical_label': 158 | this.graph.ylegend = this.json.vertical_label; 159 | break; 160 | case 'watermark': 161 | this.graph.watermark = this.json.watermark; 162 | break; 163 | case 'width': 164 | this.graph.xsize = this.json.width; 165 | if (this.graph.xsize < 10) 166 | throw "width below 10 pixels"; 167 | break; 168 | case 'units_exponent': 169 | this.graph.unitsexponent = this.json.units_exponent; 170 | break; 171 | case 'x_grid': 172 | break; 173 | case 'alt_ygrid': 174 | this.graph.alt_ygrid = this.json.alt_ygrid; 175 | break; 176 | case 'y_grid': 177 | break; 178 | case 'lazy': 179 | this.graph.lazy = this.json.lazy; 180 | break; 181 | case 'units': 182 | break; 183 | case 'disable_rrdtool_tag': 184 | this.graph.no_rrdtool_tag = this.json.disable_rrdtool_tag; 185 | break; 186 | case 'right_axis': 187 | break; 188 | case 'right_axis_label': 189 | this.graph.second_axis_legend = this.json.right_axis_label; 190 | break; 191 | case 'right_axis_format': 192 | this.graph.second_axis_format = this.json.right_axis_format; 193 | break; 194 | case 'legend_position': 195 | switch (this.json.legend_position) { 196 | case "north": 197 | this.graph.legendposition = this.graph.LEGEND_POS.NORTH; 198 | break; 199 | case "west": 200 | this.graph.legendposition = this.graph.LEGEND_POS.WEST; 201 | break; 202 | case "south": 203 | this.graph.legendposition = this.graph.LEGEND_POS.SOUTH; 204 | break; 205 | case "east": 206 | this.graph.legendposition = this.graph.LEGEND_POS.EAST; 207 | break; 208 | default: 209 | throw "unknown legend-position '" + this.json.legend_position + "'"; 210 | } 211 | break; 212 | case 'legend_direction': 213 | if (this.json.legend_direction === "topdown") { 214 | this.graph.legenddirection = this.graph.LEGEND_DIR.TOP_DOWN; 215 | } else if (this.json.legend_direction === "bottomup") { 216 | this.graph.legenddirection = this.graph.LEGEND_DIR.BOTTOM_UP; 217 | } else { 218 | throw "unknown legend-direction'" + this.json.legend_direction + "'"; 219 | } 220 | break; 221 | case 'border': 222 | this.graph.draw_3d_border = this.json.border; 223 | break; 224 | case 'grid_dash': 225 | if (this.json.grid_dash.length !== 2) 226 | throw "expected grid-dash format float:float"; 227 | this.graph.grid_dash_on = this.json.grid_dash[0]; 228 | this.graph.grid_dash_off = this.json.grid_dash[1]; 229 | break; 230 | case 'dynamic_labels': 231 | this.graph.dynamic_labels = this.json.dynamic_labels; 232 | break; 233 | case 'gdes': 234 | this.parse_gdes(this.json.gdes); 235 | break; 236 | default: 237 | throw 'Unknow option "'+option+'"'; 238 | } 239 | } 240 | var start_end = RrdTime.proc_start_end(this.graph.start_t, this.graph.end_t); // FIXME here? 241 | this.graph.start = start_end[0]; 242 | this.graph.end = start_end[1]; 243 | }, 244 | parse_gdes: function (gdes) 245 | { 246 | for (var i = 0, gdes_c = gdes.length; i < gdes_c; i++) { 247 | switch (gdes[i].type) { 248 | // GPRINT:vname:format 249 | case 'GPRINT': 250 | this.graph.gdes_add_gprint(gdes[i].vname, gdes[i].cf, gdes[i].format, gdes[i].strftm); 251 | break; 252 | // LINE[width]:value[#color][:[legend][:STACK]][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]] 253 | case 'LINE': 254 | this.graph.gdes_add_line(gdes[i].width, gdes[i].value, gdes[i].color, gdes[i].legend, gdes[i].stack, gdes[i].dashes, gdes[i].dash_offset); 255 | break; 256 | // AREA:value[#color][:[legend][:STACK]] 257 | case 'AREA': 258 | this.graph.gdes_add_area(gdes[i].value, gdes[i].color, gdes[i].legend, gdes[i].stack); 259 | break; 260 | // TICK:vname#rrggbb[aa][:fraction[:legend]] 261 | case 'TICK': 262 | this.graph.gdes_add_tick(gdes[i].vname, gdes[i].color, gdes[i].fraction, gdes[i].legend); 263 | break; 264 | // HRULE:value#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]] 265 | case 'HRULE': 266 | this.graph.gdes_add_hrule(gdes[i].value, gdes[i].color, gdes[i].legend, gdes[i].dashes, gdes[i].dash_offset); 267 | break; 268 | // VRULE:time#color[:legend][:dashes[=on_s[,off_s[,on_s,off_s]...]][:dash-offset=offset]] 269 | case 'VRULE': 270 | this.graph.gdes_add_vrule(gdes[i].time, gdes[i].color, gdes[i].legend, gdes[i].dashes, gdes[i].dash_offset); 271 | break; 272 | // COMMENT:text 273 | case 'COMMENT': 274 | this.graph.gdes_add_comment(gdes[i].legend); 275 | break; 276 | // TEXTALIGN:{left|right|justified|center} 277 | case 'TEXTALIGN': 278 | switch (gdes[i].align) { 279 | case 'left': 280 | this.graph.gdes_add_textalign(RrdGraphDesc.TXA_LEFT); 281 | break; 282 | case 'right': 283 | this.graph.gdes_add_textalign(RrdGraphDesc.TXA_RIGHT); 284 | break; 285 | case 'justified': 286 | this.graph.gdes_add_textalign(RrdGraphDesc.TXA_JUSTIFIED); 287 | break; 288 | case 'center': 289 | this.graph.gdes_add_textalign(RrdGraphDesc.TXA_CENTER); 290 | break; 291 | } 292 | break; 293 | // DEF:=::[:step=][:start=