Skip to content

Instantly share code, notes, and snippets.

@tdack
Last active Sep 2, 2018
Embed
What would you like to do?
Re-usable D3 chart to graph RRD data

Re-usable Chart for RRD data

  • data is fetched from a binary RRD file and converted to JavaScript object
  • charts are generated with re-usable chart object to reduce duplication of code
/*
* BinaryFile over XMLHttpRequest
* Part of the javascriptRRD package
* Copyright (c) 2009 Frank Wuerthwein, fkw@ucsd.edu
* MIT License [http://www.opensource.org/licenses/mit-license.php]
*
* Original repository: http://javascriptrrd.sourceforge.net/
*
* Based on:
* Binary Ajax 0.1.5
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/
* MIT License [http://www.opensource.org/licenses/mit-license.php]
*/
// ============================================================
// Exception class
function InvalidBinaryFile(msg) {
this.message=msg;
this.name="Invalid BinaryFile";
}
// pretty print
InvalidBinaryFile.prototype.toString = function() {
return this.name + ': "' + this.message + '"';
}
// =====================================================================
// BinaryFile class
// Allows access to element inside a binary stream
function BinaryFile(strData, iDataOffset, iDataLength) {
var data = strData;
var dataOffset = iDataOffset || 0;
var dataLength = 0;
// added
var doubleMantExpHi=Math.pow(2,-28);
var doubleMantExpLo=Math.pow(2,-52);
var doubleMantExpFast=Math.pow(2,-20);
var switch_endian = false;
var LastModified;
this.getLastModified = function(){
return LastModified;
}
this.setLastModified = function(iLastModified){
this.LastModified = new Date(iLastModified);
}
this.getRawData = function() {
return data;
}
if (typeof strData == "string") {
dataLength = iDataLength || data.length;
this.getByteAt = function(iOffset) {
return data.charCodeAt(iOffset + dataOffset) & 0xFF;
}
} else if (typeof strData == "unknown") {
dataLength = iDataLength || IEBinary_getLength(data);
this.getByteAt = function(iOffset) {
return IEBinary_getByteAt(data, iOffset + dataOffset);
}
} else {
throw new InvalidBinaryFile("Unsupported type " + (typeof strData));
}
this.getEndianByteAt = function(iOffset,width,delta) {
if (this.switch_endian)
return this.getByteAt(iOffset+width-delta-1);
else
return this.getByteAt(iOffset+delta);
}
this.getLength = function() {
return dataLength;
}
this.getSByteAt = function(iOffset) {
var iByte = this.getByteAt(iOffset);
if (iByte > 127)
return iByte - 256;
else
return iByte;
}
this.getShortAt = function(iOffset) {
var iShort = (this.getEndianByteAt(iOffset,2,1) << 8) + this.getEndianByteAt(iOffset,2,0)
if (iShort < 0) iShort += 65536;
return iShort;
}
this.getSShortAt = function(iOffset) {
var iUShort = this.getShortAt(iOffset);
if (iUShort > 32767)
return iUShort - 65536;
else
return iUShort;
}
this.getLongAt = function(iOffset) {
var iByte1 = this.getEndianByteAt(iOffset,4,0),
iByte2 = this.getEndianByteAt(iOffset,4,1),
iByte3 = this.getEndianByteAt(iOffset,4,2),
iByte4 = this.getEndianByteAt(iOffset,4,3);
var iLong = (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
if (iLong < 0) iLong += 4294967296;
return iLong;
}
this.getSLongAt = function(iOffset) {
var iULong = this.getLongAt(iOffset);
if (iULong > 2147483647)
return iULong - 4294967296;
else
return iULong;
}
this.getStringAt = function(iOffset, iLength) {
var aStr = [];
for (var i=iOffset,j=0;i<iOffset+iLength;i++,j++) {
aStr[j] = String.fromCharCode(this.getByteAt(i));
}
return aStr.join("");
}
// Added
this.getCStringAt = function(iOffset, iMaxLength) {
var aStr = [];
for (var i=iOffset,j=0;(i<iOffset+iMaxLength) && (this.getByteAt(i)>0);i++,j++) {
aStr[j] = String.fromCharCode(this.getByteAt(i));
}
return aStr.join("");
}
// Added
this.getDoubleAt = function(iOffset) {
var iByte1 = this.getEndianByteAt(iOffset,8,0),
iByte2 = this.getEndianByteAt(iOffset,8,1),
iByte3 = this.getEndianByteAt(iOffset,8,2),
iByte4 = this.getEndianByteAt(iOffset,8,3),
iByte5 = this.getEndianByteAt(iOffset,8,4),
iByte6 = this.getEndianByteAt(iOffset,8,5),
iByte7 = this.getEndianByteAt(iOffset,8,6),
iByte8 = this.getEndianByteAt(iOffset,8,7);
var iSign=iByte8 >> 7;
var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4);
var iMantHi=((((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5) << 8) + iByte4;
var iMantLo=((((iByte3) << 8) + iByte2) << 8) + iByte1;
if (iExpRaw==0) return 0.0;
if (iExpRaw==0x7ff) return undefined;
var iExp=(iExpRaw & 0x7FF)-1023;
var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMantLo*doubleMantExpLo + iMantHi*doubleMantExpHi);
return dDouble;
}
// added
// Extracts only 4 bytes out of 8, loosing in precision (20 bit mantissa)
this.getFastDoubleAt = function(iOffset) {
var iByte5 = this.getEndianByteAt(iOffset,8,4),
iByte6 = this.getEndianByteAt(iOffset,8,5),
iByte7 = this.getEndianByteAt(iOffset,8,6),
iByte8 = this.getEndianByteAt(iOffset,8,7);
var iSign=iByte8 >> 7;
var iExpRaw=((iByte8 & 0x7F)<< 4) + (iByte7 >> 4);
var iMant=((((iByte7 & 0x0F) << 8) + iByte6) << 8) + iByte5;
if (iExpRaw==0) return 0.0;
if (iExpRaw==0x7ff) return undefined;
var iExp=(iExpRaw & 0x7FF)-1023;
var dDouble = ((iSign==1)?-1:1)*Math.pow(2,iExp)*(1.0 + iMant*doubleMantExpFast);
return dDouble;
}
this.getCharAt = function(iOffset) {
return String.fromCharCode(this.getByteAt(iOffset));
}
}
document.write(
"<script type='text/vbscript'>\r\n"
+ "Function IEBinary_getByteAt(strBinary, iOffset)\r\n"
+ " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n"
+ "End Function\r\n"
+ "Function IEBinary_getLength(strBinary)\r\n"
+ " IEBinary_getLength = LenB(strBinary)\r\n"
+ "End Function\r\n"
+ "</script>\r\n"
);
// ===============================================================
// Load a binary file from the specified URL
// Will return an object of type BinaryFile
function FetchBinaryURL(url) {
var request = new XMLHttpRequest();
request.open("GET", url,false);
try {
request.overrideMimeType('application/octet-stream');
} catch (err) {
// ignore any error, just to make both FF and IE work
}
request.send(null);
var response=this.responseText;
try {
// for older IE versions, the value in responseText is not usable
if (IEBinary_getLength(this.responseBody)>0) {
// will get here only for older verson of IE
response=this.responseBody;
}
} catch (err) {
// not IE, do nothing
}
var bf=new BinaryFile(response);
bf.setLastModified(this.getResponseHeader("Last-Modified"))
return bf;
}
// ===============================================================
// Asyncronously load a binary file from the specified URL
//
// callback must be a function with one or two arguments:
// - bf = an object of type BinaryFile
// - optional argument object (used only if callback_arg not undefined)
function FetchBinaryURLAsync(url, callback, callback_arg) {
var callback_wrapper = function() {
if(this.readyState == 4) {
var response=this.responseText;
try {
// for older IE versions, the value in responseText is not usable
if (IEBinary_getLength(this.responseBody)>0) {
// will get here only for older verson of IE
response=this.responseBody;
}
} catch (err) {
// not IE, do nothing
}
console.log(response);
var bf=new BinaryFile(response);
console.log(bf);
bf.setLastModified(this.getResponseHeader("Last-Modified"))
if (callback_arg!=null) {
callback(bf,callback_arg);
} else {
callback(bf);
}
}
}
var request = new XMLHttpRequest();
request.onreadystatechange = callback_wrapper;
request.open("GET", url,true);
try {
request.overrideMimeType('application/octet-stream');
} catch (err) {
// ignore any error, just to make both FF and IE work
}
request.send(null);
return request
}
function multiLineChart() {
var margin = {
top: 20,
right: 85,
bottom: 60,
left: 40
},
width = 900,
height = 500,
title = "Graph",
options = {
showVoronoi: false, // add a voronoi overlay for tool tips
showLegend: false, // display a legend
legend: "name", // field to use for legend, or array of values
valueSeries: "values", // fieled to use to get values for each line
valueUnits: "", // units to display on tool tips
rollOverDay: null, // display a marker on this day every month
limitLines: [], // display an horizontal line
invertDS: "" // invert data sources ending in this value
},
xValue = function(d) { return d.date; },
yValue = function(d) { return d.value; },
xScale = d3.time.scale(),
yScale = d3.scale.linear(),
colours = d3.scale.category10(),
xTicks = 10,
yTicks = 6,
valueFormat = d3.format(".2f"),
dateFormat = d3.time.format("%H:%M");
var toType = function(obj) {
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}
function chart(selection) {
var
xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(xTicks),
yAxis = d3.svg.axis().scale(yScale).orient("left").ticks(yTicks).tickFormat(valueFormat),
xGrid = d3.svg.axis().scale(xScale).orient("bottom").ticks(xTicks).tickSize(height - (margin.bottom + margin.top), 0).tickFormat(""),
yGrid = d3.svg.axis().scale(yScale).orient("left").ticks(yTicks).tickSize(-width + (margin.right + margin.left), 0).tickFormat(""),
line = d3.svg.line().interpolate("monotone").defined(function(d) { return yValue(d) != null; }).x(X).y(Y),
voronoi = d3.geom.voronoi().x(X).y(Y)
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]),
rollOverTicks;
selection.each(function(data){
// Update the x-scale
xScale
.domain([ d3.min(data, function(s){ return d3.min(s[options.valueSeries], function(d){ return +xValue(d);} ) }),
d3.max(data, function(s){ return d3.max(s[options.valueSeries], function(d){ return +xValue(d);} ) })])
.range([0, width - margin.left - margin.right]);
var xDomain = xScale.domain();
// Invert values in data set if required
if (options.invertDS.length) {
data.forEach(function(d){
var re = new RegExp(options.invertDS + '$');
if (re.test(d.name)) {
d.values.forEach(function(v){
v.value *= -1;
});
}
});
}
// Update the y-scale
yScale
.domain([ d3.min(data, function(s){ return d3.min(s[options.valueSeries], function(d){ return +yValue(d);} ) }),
d3.max(data, function(s){ return d3.max(s[options.valueSeries], function(d){ return +yValue(d);} ) })]).nice()
.range([height - margin.top - margin.bottom, 0]);
// Select the svg element if it exists
var svg = d3.select(this).selectAll("svg")
.data([data]);
// Otherwise create the elements
var gEnter = svg.enter().append("svg").append("g");
gEnter.append("g") // Group for shading weekends
.attr("class", "grid grid--weekends");
gEnter.append("g") // Grid
.attr("class", "grid grid--x");
gEnter.append("g")
.attr("class", "grid grid--y");
gEnter.append("g") // Axis
.attr("class", "axis axis--x");
gEnter.append("g")
.attr("class", "axis axis--y");
gEnter.append("g") //
.attr("class", "title");
if (options.showLegend){
gEnter.append("g")
.attr("class", "legend");
}
gEnter.append("g") // Group to hold the lines
.attr("class", "line-group");
// Marker at roll over date every month
if (options.rollOverDay != null) {
gEnter.append("g")
.attr("class", "grid grid--rollover");
rollOverTicks = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(xTicks)
.tickSize(height - (margin.bottom + margin.top), 0)
.tickFormat("")
.tickValues(
d3.time.days(xDomain[0], xDomain[1])
.filter(function(d){ return (d.getDate() == options.rollOverDay); })
);
}
if (options.limitLines.length > 0) {
gEnter.append("g")
.attr("class", "grid grid--limit-lines");
}
if (options.showVoronoi){
gEnter.append("g")
.attr("class", "voronoi")
.attr("id", "voronoi");
}
var focus = gEnter.append("g")
.attr("transform", "translate(-100,-100)")
.attr("class", "focus");
focus.append("circle")
.attr("r", 3.5);
focus.append("line")
.attr("class", "x")
.style("stroke", "green")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.75)
.attr("y1", 0)
.attr("y2", height - (margin.top + margin.bottom));
focus.append("line")
.attr("class", "y")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.75)
.attr("x1", 0 )
.attr("x2", width - (margin.left + margin.right));
focus.append("text")
.attr("x", 5)
.attr("y", 10)
.attr("class", "text--date");
focus.append("text")
.attr("x", -5)
.attr("y", -5)
.attr("class", "text--value");
// Update outer dimensions
svg .attr("width", width)
.attr("height", height);
// Update inner dimensions
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Update the x axis
g.select(".axis.axis--x")
.transition()
.duration(750)
.attr("transform", "translate(0," + yScale.range()[0] + ")")
.call(xAxis);
// Update the y axis
g.select(".axis.axis--y")
.transition()
.duration(750)
.call(yAxis);
// Update grid
g.select(".grid.grid--x")
.transition()
.duration(750)
.call(xGrid);
g.select(".grid.grid--y")
.transition()
.duration(750)
.call(yGrid);
if (options.rollOverDay != null) {
g.select(".grid.grid--rollover")
.transition()
.duration(750)
.call(rollOverTicks)
}
// Shade grid for weekends
var gWeekends = g.select(".grid.grid--weekends");
var dataWeekends = d3.time.saturdays(xDomain[0], xDomain[1]);
if (dataWeekends.length == 0) {
if (xDomain[0].getDay() == 6) {
dataWeekends = [xDomain[0]];
} else if (xDomain[0].getDay() == 0) {
dataWeekends = [d3.time.hour.offset(d3.time.day(xDomain[0]),-24)];
} else {
dataWeekends = [];
}
}
var rectWeekends = gWeekends.selectAll("rect")
.data(dataWeekends);
// UPDATE
rectWeekends
.transition()
.duration(750)
.attr("x", function(d) { return xScale(d) < 0 ? 0 : xScale(d); })
.attr("width", function(d) {
var x = xScale(d) < 0 ? 0 : xScale(d);
var w = xScale(d3.time.hour.offset(d, 48)) - x;
return (x + w) < (width - (margin.left + margin.right)) ? w : (width - (margin.left + margin.right)) - x;
})
.attr("y", 0)
.attr("height", height - (margin.bottom + margin.top));
// APPEND
rectWeekends
.enter()
.append("rect")
.style("fill-opacity", 1e-6)
.transition()
.duration(750)
.attr("x", function(d) { return xScale(d) < 0 ? 0 : xScale(d); })
.attr("width", function(d) {
var x = xScale(d) < 0 ? 0 : xScale(d);
var w = xScale(d3.time.hour.offset(d, 48)) - x;
return (x + w) < (width - (margin.left + margin.right)) ? w : (width - (margin.left + margin.right)) - x;
})
.attr("y", 0)
.attr("height", height - (margin.bottom + margin.top))
.style("fill-opacity", 0.25);
//REMOVE
rectWeekends.exit()
.transition()
.duration(750)
.style("fill-opacity", 1e-6)
.remove();
// Draw the line(s)
// DATA JOIN
var gLines = svg.select(".line-group");
var linePaths = gLines.selectAll("path").data(data);
// UPDATE
linePaths
.transition()
.duration(1000)
.attr("d", function(d) {
d.line = this;
return line(d[options.valueSeries]);
});
// NEW VALUES
linePaths
.enter()
.append("path")
.transition()
.duration(1000)
.attr("class", "line")
.attr("style", function(d,i){ return "stroke: " + colours(i);} )
.attr("d", function(d) { d.line = this; return line(d[options.valueSeries]); })
.style("fill-opacity", 1);
//REMOVE
linePaths.exit()
.style("fill-opacity", 1e-6)
.transition()
.duration(1000)
.remove();
var titleText = svg.select(".title")
.selectAll("text")
.data([title]);
titleText.enter()
.append("text")
.attr({
"x": (width- margin.right) / 2,
"y": -(margin.top / 2),
"text-anchor": "middle",
"font-size": "14px"
})
.text(function(d) {
return d;
});
titleText.exit()
.remove();
if (options.limitLines.length > 0) {
var gLimitLines = svg.select(".grid.grid--limit-lines").selectAll("line").data(data);
gLimitLines
.transition()
.duration(1000)
.attr({ "x1": 0,
"y1": function(d, i) {return yScale(options.limitLines[i]); },
"x2": width - (margin.right + margin.left),
"y2": function(d, i) {return yScale(options.limitLines[i]); }
});
gLimitLines.enter()
.append("line")
.style("stroke", function(d, i) { return colours(i); })
.attr({ "x1": 0,
"y1": function(d, i) {return yScale(options.limitLines[i]); },
"x2": width - (margin.right + margin.left),
"y2": function(d, i) {return yScale(options.limitLines[i]); }
});
gLimitLines.exit()
.remove();
}
if (options.showLegend) {
// JOIN
var gLegend = svg.select(".legend").selectAll("g").data(data);
// APPEND
var gLegendEnter = gLegend.enter().append("g");
gLegendEnter
.append("rect")
.attr("x", width-margin.right-margin.left + 15)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i) { return colours(i); })
gLegendEnter
.append("text")
.attr("x", width-margin.right-margin.left + 27)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("class", "legend-text")
.text(function(d,i) {
// if options.legend is a String use that as the legend field,
// otherwise assume options.legend is an array of Strings
return ( toType(options.legend) == "String") ? d[options.legend] : options.legend[i];
});
// REMOVE
gLegend.exit()
.remove();
}
if (options.showVoronoi){
// update voronoi overlay
var v = svg.select("#voronoi")
// DATA JOIN
var v_paths = v.selectAll("path")
.data(voronoi(d3.nest()
.key(function(d) {
return xScale(xValue(d)) + "," + yScale(yValue(d)); })
.rollup(function(v) {
return v[0]; })
.entries(d3.merge(data.map(function(d) {
return d[options.valueSeries]; })))
.map(function(d) {
return d[options.valueSeries]; })));
// Update
v_paths.attr("d", function(d) {
return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
// Add new values
v_paths.enter()
.append("path")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
// Remove
v_paths.exit().remove();
var focus = svg.select("g.focus");
function mouseover(d) {
focus.attr("transform", "translate(" + xScale(xValue(d)) + "," + yScale(yValue(d)) + ")");
focus.select(".text--date").text(dateFormat(new Date(xValue(d))));
focus.select(".text--value").text(valueFormat(yValue(d)) + options.valueUnits);
focus.select(".x").attr("y2", height - (margin.top + margin.bottom) - yScale(yValue(d)));
focus.select(".y").attr("x2", -xScale(xValue(d)));
}
function mouseout(d) {
focus.attr("transform", "translate(-100,-100)");
}
}
})
}
// The x-accessor for the path generator; xScale ∘ xValue.
function X(d) {
return xScale(xValue(d));
}
// The x-accessor for the path generator; yScale ∘ yValue.
function Y(d) {
return yScale(yValue(d));
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.options = function(_) {
if (!arguments.length) return options;
options = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
chart.valueFormat = function(_) {
if (!arguments.length) return valueFormat;
valueFormat = _;
return chart;
};
chart.legend = function(_) {
if (!arguments.length) return options.legend;
options.legend = _;
return chart;
};
chart.valueSeries = function(_) {
if (!arguments.length) return options.valueSeries;
options.valueSeries = _;
return chart;
};
chart.showLegend = function(_) {
if (!arguments.length) return options.showLegend;
options.showLegend = _;
return chart;
};
chart.showVoronoi = function(_) {
if (!arguments.length) return options.showVoronoi;
options.showVoronoi = _;
return chart;
};
chart.rollOverDay = function(_) {
if (!arguments.length) return options.rollOverDay;
options.rollOverDay = _;
return chart;
};
chart.valueUnits = function(_) {
if (!arguments.length) return options.valueUnits;
options.valueUnits = _;
return chart;
};
chart.limitLines = function(_) {
if (!arguments.length) return options.limitLines;
options.limitLines = _;
return chart;
};
chart.title = function(_) {
if (!arguments.length) return title;
title = _;
return chart;
};
chart.invertDS = function(_) {
if (!arguments.length) return options.invertDS;
options.invertDS = _;
return chart;
}
return chart;
}
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/default.min.css">
<style>
@import url("style.css");
#form {
/*position: absolute;
top: 15px;
left: 550px;*/
}
.grid--y .tick, .grid--x .tick {
stroke: lightgrey;
stroke-opacity: 0.7;
shape-rendering: crispEdges;
}
.grid.grid--y path, .grid.grid--x path {
stroke-width: 0;
shape-rendering: crispEdges;
}
.grid.grid--weekends rect {
fill: #aabbcc;
fill-opacity: 0.25;
shape-rendering: crispEdges;
}
.grid.grid--rollover line {
stroke: red;
stroke-width: 1.5px;
stroke-dasharray: 7 5;
stroke-opacity: 0.35;
shape-rendering: crispEdges;
}
</style>
<h1>Internet Usage</h1>
<div>
<label id="form">
Select period:
<select id="period"></select>
</label>
</div>
<div id="graphs">
</div>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script>
<script src="binaryXHR.js"></script>
<script src="rrdFile.js"></script>
<script src="rrdFilter.js"></script>
<script src="rrdAsync.js"></script>
<script src="rrd2D3.js"></script>
<script src="chart.js"></script>
<script id="code">
// Retrieve data from RRD
var rrd_obj = new rrdAsync("usage.rrd",null, init);
var rrd_data; // populated in init() once RRD file has been fetched.
function init(){
rrd_data = rrd_obj.rrd_data;
var rra = 1;
var DS_Names = rrd_data.getDSNames();
// populate with RRA info
var nrRRAs=rrd_data.getNrRRAs();
var RRAs = [];
for (var i=0; i<nrRRAs; i++) {
var rra=rrd_data.getRRAInfo(i);
var step=rra.getStep();
var rows=rra.getNrRows();
var period=step*rows;
var rra_label=rfs_format_time(period) + " ("+rfs_format_time(step)+" steps)";
RRAs.push({text: rra_label, value: i});
}
// Populate period select control
var select = d3.select("#period");
var options = select.selectAll("option")
.data(RRAs)
.enter()
.append("option")
.attr('value', function(d){ return d.value; })
.text(function(d){ return d.text;});
select.property("value", 2);
select.on("change", function(){
updateCharts();
})
var charts = [ { id: "usage", title: "Internet Usage", DS_Names: ["jack_usage", "harry_usage"] },
{ id: "jack", title: "Jack Bandwidth Usage", DS_Names: ["jack_in", "jack_out"] },
{ id: "harry", title: "Harry Bandwidth Usage", DS_Names: ["harry_in", "harry_out"] }
];
var graphs = d3.select("#graphs").selectAll("div").data(charts);
graphs
.enter()
.append("div")
.attr("id", function(d) { return d.id; });
charts.forEach(function(d){
// Get the dataset
var dataset = rrdRRA2D3Obj(rrd_data, 2, d.DS_Names, true);
// Create the chart
var chart = new multiLineChart()
.x(function(d) { return d.date; })
.y(function(d) { return d.value; })
.valueFormat(d3.format(".3s"))
.width(960)
.height(500)
.legendField("name")
.valueSeries("values")
.title(d.title)
.showLegend(true)
.showVoronoi(true)
.rollOverDay(8);
var graph = d3.select("#" + d.id )
.data([dataset])
.call(chart);
d.chart = chart;
});
// Refresh data every 5 minutes
/*
setInterval(function(){
rrd_obj.customization_callback = updateCharts;
rrd_obj.reload();
}, 5 * 60 * 1000);
*/
function updateCharts() {
rrd_data = rrd_obj.rrd_data;
var RRA = select.property("value");
charts.forEach(function(d){
// Get the dataset
var dataset = rrdRRA2D3Obj(rrd_data, RRA, d.DS_Names, true);
// Update the chart
var graph = d3.select("#" + d.id )
.data([dataset])
.call(d.chart);
})
}
}
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-28825538-1', 'auto');
ga('send', 'pageview');
</script>
if (Number.prototype.toKMBT == null)
Number.prototype.toKMBT = function() {
y = this;
var abs_y = Math.abs(y);
if (abs_y >= 1000000000000) { return (y / 1000000000000).toFixed(2) + "T" }
else if (abs_y >= 1000000000) { return (y / 1000000000).toFixed(2) + "B" }
else if (abs_y >= 1000000) { return (y / 1000000).toFixed(2) + "M" }
else if (abs_y >= 1000) { return (y / 1000).toFixed(2) + "K" }
else if (abs_y < 1 && y > 0) { return y.toFixed(2) }
else if (abs_y === 0) { return ' ' }
else { return y }
};
if (Number.prototype.toBase1024KMGTP == null)
Number.prototype.toBase1024KMGTP = function() {
y = this;
var abs_y = Math.abs(y);
if (abs_y >= 1125899906842624) { return (y / 1125899906842624).toFixed(2) + "P" }
else if (abs_y >= 1099511627776){ return (y / 1099511627776).toFixed(2) + "T" }
else if (abs_y >= 1073741824) { return (y / 1073741824).toFixed(2) + "G" }
else if (abs_y >= 1048576) { return (y / 1048576).toFixed(2) + "M" }
else if (abs_y >= 1024) { return (y / 1024).toFixed(2) + "K" }
else if (abs_y < 1 && y > 0) { return y.toFixed(2) }
else if (abs_y === 0) { return ' ' }
else { return y }
};
// Retrieve data from RRD
var rrd_obj = new rrdAsync("shed_temps.rrd",null, init);
var rrd_data; // populated in init() once RRD file has been fetched.
var margin = {top: 20, right: 70, bottom: 100, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var colours = d3.scale.category10();
function init() {
// RRD file has been fetched, so data is ready to be used
rrd_data = rrd_obj.rrd_data;
populateRRASelect("#period");
drawGraph(rrdRRA2D3Obj(rrd_data,1,rrd_data.getDSNames(),true), rrd_data.getDSNames());
d3.select("#listing")
.append("pre")
.append("code")
.attr("class", "js")
.text(d3.select("#code").html());
hljs.highlightBlock(d3.select("#listing pre code").node());
}
function populateRRASelect(res_id) {
// populate with RRA info
var nrRRAs=rrd_data.getNrRRAs();
var RRAs = [];
for (var i=0; i<nrRRAs; i++) {
var rra=rrd_data.getRRAInfo(i);
var step=rra.getStep();
var rows=rra.getNrRows();
var period=step*rows;
var rra_label=rfs_format_time(period) + " ("+rfs_format_time(step)+" steps)";
RRAs.push({text: rra_label, value: i});
}
var select = d3.select(res_id)
.on("change", periodChange),
options = select
.selectAll("option")
.data(RRAs)
.enter()
.append("option")
.attr('value', function(d){ return d.value; })
.text(function(d){ return d.text;});
select.property("value", 1);
}
function periodChange(){
// Update graph with new data
var RRA = this.value ? this.value : 1;
var dataset=rrdRRA2D3Obj(rrd_data,RRA,rrd_data.getDSNames(),true);
var x = d3.time.scale()
.range([0, width])
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.date;} ) }),
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.date;} ) })]);
var y = d3.scale.linear()
.range([height, 0])
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.value;} ) }),
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.value;} ) })]).nice();
var voronoi = d3.geom.voronoi()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); })
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]);
var line = d3.svg.line()
.defined(function(d) { return d.value != null; })
.interpolate('cardinal')
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
var svg = d3.select("#temp-graph")
// DATA JOIN
var lines = svg.selectAll("path.line").data(dataset)
// Update
lines.transition()
.duration(1000)
.attr("d", function(d) { d.line = this; return line(d.values); });
// Add new values
lines.enter()
.append("path")
.attr("class", "line")
.attr("style", function(d,i){ return "stroke: " + colours(i);} )
.style("fill-opacity", 1e-6)
.transition()
.duration(1000)
.attr("d", function(d) { d.line = this; return line(d.values); })
.style("fill-opacity", 1);
// Remove
lines.exit()
.transition()
.duration(1000)
.style("fill-opacity", 1e-6)
.remove();
// Update X axis
svg.select(".axis.axis--x")
.transition()
.duration(1000)
.call(d3.svg.axis()
.scale(x)
.orient("bottom"));
// Update Y axis
svg.select(".axis.axis--y")
.transition()
.duration(1000)
.call(d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, ".2f"));
// Update stats
formatFloat = d3.format(".2f");
var stats = d3.select("#stats tbody")
.selectAll('tr')
.data(dataset)
.selectAll("td")
.data(function(l){ return [ l.name,
formatFloat(d3.min(l.values, function(d){ return d.value; })),
formatFloat(d3.max(l.values, function(d){ return d.value; })),
formatFloat(d3.mean(l.values, function(d){return d.value; }))];
})
.text(function(d){ return d;});
// update voronoi overlay
var v = svg.select("#voronoi")
// DATA JOIN
var v_paths = v.selectAll("path")
.data(voronoi(d3.nest()
.key(function(d) { return x(d.date) + "," + y(d.value); })
.rollup(function(v) { return v[0]; })
.entries(d3.merge(dataset.map(function(d) { return d.values; })))
.map(function(d) { return d.values; })));
// Update
v_paths.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
// Add new values
v_paths.enter()
.append("path")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d) { return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
// Remove
v_paths.exit().remove();
var focus = svg.select("g.focus");
function mouseover(d) {
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select("text").text(formatFloat(d.value) + "°C");
}
function mouseout(d) {
focus.attr("transform", "translate(-100,-100)");
}
}
function drawGraph(dataset, names) {
var x = d3.time.scale()
.range([0, width])
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.date;} ) }),
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.date;} ) })]);
var y = d3.scale.linear()
.range([height, 0])
.domain([ d3.min(dataset, function(s){ return d3.min(s.values, function(d){ return +d.value;} ) }),
d3.max(dataset, function(s){ return d3.max(s.values, function(d){ return +d.value;} ) })]).nice();
var color = d3.scale.category20();
var voronoi = d3.geom.voronoi()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); })
.clipExtent([[-margin.left, -margin.top], [width + margin.right, height + margin.bottom]]);
var line = d3.svg.line()
.defined(function(d) { return d.value != null; })
//.interpolate('cardinal')
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.value); });
var svg = d3.select("#temp-graph")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Draw X axis
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom"))
.append("text")
.attr("x", -margin.left * 0.75)
.attr("y", -height-10)
.text("° C");
// Draw Y axis
svg.append("g")
.attr("class", "axis axis--y")
.call(d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10, ".2f"))
// Plot the lines - first time, so have to append <g>
svg.append("g")
.attr("class", "line-group")
.selectAll("path")
.data(dataset)
.enter()
.append("path")
.attr("class", "line")
.attr("style", function(d,i){ return "stroke: " + colours(i);} )
.attr("d", function(d) { d.line = this; return line(d.values); });
// add legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 120);
legend.selectAll('rect')
.data(dataset)
.enter()
.append("rect")
.attr("x", width + 15)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i) { return colours(i); });
legend.selectAll('text')
.data(dataset)
.enter()
.append("text")
.attr("x", width + 27)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("class", "legend-text")
.text(function(d) { return d.name; });
var formatFloat = d3.format(".2f");
// Display some simple stats
var stats = d3.select("#stats tbody")
.selectAll('tr')
.data(dataset)
.enter()
.append("tr")
.style("background-color", function(d,i){ return colours(i);})
.selectAll("td")
.data(function(l){ return [ l.name,
formatFloat(d3.min(l.values, function(d){ return d.value; })),
formatFloat(d3.max(l.values, function(d){ return d.value; })),
formatFloat(d3.mean(l.values, function(d){return d.value; })) ];
})
.enter()
.append("td")
.text(function(d){ return d;})
// Element to hold voronoi overlay
var voronoiGroup = svg.append("g")
.attr("class", "voronoi")
.attr("id", "voronoi");
// Generate & add voronoi paths
voronoiGroup.selectAll("path")
.data(voronoi(d3.nest()
.key(function(d) { return x(d.date) + "," + y(d.value); })
.rollup(function(v) { return v[0]; })
.entries(d3.merge(dataset.map(function(d) { return d.values; })))
.map(function(d) { return d.values; })))
.enter().append("path")
.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.datum(function(d, i, j) { console.log(d, i, j); return d.point; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
var focus = svg.append("g")
.attr("transform", "translate(-100,-100)")
.attr("class", "focus");
focus.append("circle")
.attr("r", 3.5);
focus.append("text")
.attr("y", -10);
function mouseover(d, i, j) {
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select("text").text(formatFloat(d.value) + "°C");
}
function mouseout(d) {
focus.attr("transform", "translate(-100,-100)");
}
}
// return an array of objects containing D3elements, one per DS
//
// data = [
// {
// name: <DS name>,
// values: [{ date: <timestamp>, value: 1}, { date: <timestamp>, value: 2}]
// },
// {
// name: <DS name>,
// values: [{ date: <timestamp>, value: 1}, { date: <timestamp>, value: 2}]
// }
// ];
function rrdRRA2D3Obj(rrd_file,rra_idx,ds_list,want_rounding) {
var rra=rrd_file.getRRA(rra_idx);
var rra_rows=rra.getNrRows();
var last_update=rrd_file.getLastUpdate();
var step=rra.getStep();
if (want_rounding!=false) {
// round last_update to step
// so that all elements are sync
last_update-=(last_update%step);
}
var first_el=last_update-(rra_rows-1)*step;
var out_el = [];
var ds_list_len = ds_list.length;
for (var ds_list_idx=0; ds_list_idx<ds_list_len; ++ds_list_idx) {
var ds_id=ds_list[ds_list_idx];
var ds=rrd_file.getDS(ds_id);
var ds_name=ds.getName();
var ds_idx=ds.getIdx();
var timestamp=first_el;
var ds_series=[];
for (var i=0;i<rra_rows;i++) {
var el=rra.getEl(i,ds_idx);
if (el != undefined) {
if (Math.abs(+el) > 213000000000000) { // remove erroneous spikes from RRD data
el = 0;
}
// Convert timestamp to JS ticks
ds_series.push( { "date": timestamp * 1000, "value": +el } );
} else {
// Convert timestamp to JS ticks, return null for missing values
ds_series.push( { "date": timestamp * 1000, "value": null } );
}
timestamp+=step;
} // end for
var ds_name=ds.getName();
out_el.push({"name": ds_name,
"values": ds_series});
} //end for ds_list_idx
return out_el;
}
function rfs_format_time(s) {
if (s<120) {
return s+"s";
} else {
var s60=s%60;
var m=(s-s60)/60;
if ((m<10) && (s60>9)) {
return m+":"+s60+" min";
} if (m<120) {
return m+" min";
} else {
var m60=m%60;
var h=(m-m60)/60;
if ((h<12) && (m60>9)) {
return h+":"+m60+" hrs";
} if (h<48) {
return h+" hrs";
} else {
var h24=h%24;
var d=(h-h24)/24;
if ((d<7) && (h24>0)) {
return d+" days "+h24+"h";
} if (d<60) {
return d+" days";
} else {
var d30=d%30;
var mt=(d-d30)/30;
return mt+" months";
}
}
}
}
}
/* Callback to load RRD asynchronously */
function rrdAsyncCallback(bf,obj) {
var i_rrd_data=undefined;
if (bf.getLength()<1) {
alert("File "+obj.url+" is empty (possibly loading failed)!");
return 1;
}
try {
i_rrd_data=new RRDFile(bf,obj.file_options);
} catch(err) {
alert("File "+obj.url+" is not a valid RRD archive!\n"+err);
}
if (i_rrd_data!=undefined) {
if (obj.rrd_data!=null) delete obj.rrd_data;
obj.rrd_data=i_rrd_data;
obj.callback();
}
}
/* Use url==null if you do not know the url yet */
function rrdAsync(url,
file_options, // see rrdFile.js::RRDFile for documentation
customization_callback // if defined, see above
) {
this.url=url;
this.file_options=file_options;
this.customization_callback=customization_callback;
this.rrd_obj=null;
this.rrd_data=null;
if (url!=null) {
this.reload(url);
}
}
rrdAsync.prototype.reload = function(url) {
if (arguments.length) {
this.url=url;
}
try {
FetchBinaryURLAsync(this.url,rrdAsyncCallback,this);
} catch (err) {
alert("Failed loading "+this.url+"\n"+err);
}
};
rrdAsync.prototype.callback = function() {
if (this.rrd_flot_obj!=null) delete this.rrd_flot_obj;
if (this.customization_callback!=undefined) this.customization_callback(this);
var irrd_data=this.rrd_data;
};
/*
* Client library for access to RRD archive files
* Part of the javascriptRRD package
* Copyright (c) 2009-2010 Frank Wuerthwein, fkw@ucsd.edu
* Igor Sfiligoi, isfiligoi@ucsd.edu
*
* Original repository: http://javascriptrrd.sourceforge.net/
*
* MIT License [http://www.opensource.org/licenses/mit-license.php]
*
*/
/*
*
* RRDTool has been developed and is maintained by
* Tobias Oether [http://oss.oetiker.ch/rrdtool/]
*
* This software can be used to read files produced by the RRDTool
* but has been developed independently.
*
* Limitations:
*
* This version of the module assumes RRD files created on linux
* with intel architecture and supports both 32 and 64 bit CPUs.
* All integers in RRD files are suppoes to fit in 32bit values.
*
* Only versions 3 and 4 of the RRD archive are supported.
*
* Only AVERAGE,MAXIMUM,MINIMUM and LAST consolidation functions are
* supported. For all others, the behaviour is at the moment undefined.
*
*/
/*
* Dependencies:
*
* The data provided to this module require an object of a class
* that implements the following methods:
* getByteAt(idx) - Return a 8 bit unsigned integer at offset idx
* getLongAt(idx) - Return a 32 bit unsigned integer at offset idx
* getDoubleAt(idx) - Return a double float at offset idx
* getFastDoubleAt(idx) - Similar to getDoubleAt but with less precision
* getCStringAt(idx,maxsize) - Return a string of at most maxsize characters
* that was 0-terminated in the source
*
* The BinaryFile from binaryXHR.js implements this interface.
*
*/
// ============================================================
// Exception class
function InvalidRRD(msg) {
this.message=msg;
this.name="Invalid RRD";
}
// pretty print
InvalidRRD.prototype.toString = function() {
return this.name + ': "' + this.message + '"';
}
// ============================================================
// RRD DS Info class
function RRDDS(rrd_data,rrd_data_idx,my_idx) {
this.rrd_data=rrd_data;
this.rrd_data_idx=rrd_data_idx;
this.my_idx=my_idx;
}
RRDDS.prototype.getIdx = function() {
return this.my_idx;
}
RRDDS.prototype.getName = function() {
return this.rrd_data.getCStringAt(this.rrd_data_idx,20);
}
RRDDS.prototype.getType = function() {
return this.rrd_data.getCStringAt(this.rrd_data_idx+20,20);
}
RRDDS.prototype.getMin = function() {
return this.rrd_data.getDoubleAt(this.rrd_data_idx+48);
}
RRDDS.prototype.getMax = function() {
return this.rrd_data.getDoubleAt(this.rrd_data_idx+56);
}
// ============================================================
// RRD RRA Info class
function RRDRRAInfo(rrd_data,rra_def_idx,
int_align,row_cnt,pdp_step,my_idx) {
this.rrd_data=rrd_data;
this.rra_def_idx=rra_def_idx;
this.int_align=int_align;
this.row_cnt=row_cnt;
this.pdp_step=pdp_step;
this.my_idx=my_idx;
// char nam[20], uint row_cnt, uint pdp_cnt
this.rra_pdp_cnt_idx=rra_def_idx+Math.ceil(20/int_align)*int_align+int_align;
}
RRDRRAInfo.prototype.getIdx = function() {
return this.my_idx;
}
// Get number of rows
RRDRRAInfo.prototype.getNrRows = function() {
return this.row_cnt;
}
// Get number of slots used for consolidation
// Mostly for internal use
RRDRRAInfo.prototype.getPdpPerRow = function() {
return this.rrd_data.getLongAt(this.rra_pdp_cnt_idx);
}
// Get RRA step (expressed in seconds)
RRDRRAInfo.prototype.getStep = function() {
return this.pdp_step*this.getPdpPerRow();
}
// Get consolidation function name
RRDRRAInfo.prototype.getCFName = function() {
return this.rrd_data.getCStringAt(this.rra_def_idx,20);
}
// ============================================================
// RRD RRA handling class
function RRDRRA(rrd_data,rra_ptr_idx,
rra_info,
header_size,prev_row_cnts,ds_cnt) {
this.rrd_data=rrd_data;
this.rra_info=rra_info;
this.row_cnt=rra_info.row_cnt;
this.ds_cnt=ds_cnt;
var row_size=ds_cnt*8;
this.base_rrd_db_idx=header_size+prev_row_cnts*row_size;
// get imediately, since it will be needed often
this.cur_row=rrd_data.getLongAt(rra_ptr_idx);
// calculate idx relative to base_rrd_db_idx
// mostly used internally
this.calc_idx = function(row_idx,ds_idx) {
if ((row_idx>=0) && (row_idx<this.row_cnt)) {
if ((ds_idx>=0) && (ds_idx<ds_cnt)){
// it is round robin, starting from cur_row+1
var real_row_idx=row_idx+this.cur_row+1;
if (real_row_idx>=this.row_cnt) real_row_idx-=this.row_cnt;
return row_size*real_row_idx+ds_idx*8;
} else {
throw RangeError("DS idx ("+ row_idx +") out of range [0-" + ds_cnt +").");
}
} else {
throw RangeError("Row idx ("+ row_idx +") out of range [0-" + this.row_cnt +").");
}
}
}
RRDRRA.prototype.getIdx = function() {
return this.rra_info.getIdx();
}
// Get number of rows/columns
RRDRRA.prototype.getNrRows = function() {
return this.row_cnt;
}
RRDRRA.prototype.getNrDSs = function() {
return this.ds_cnt;
}
// Get RRA step (expressed in seconds)
RRDRRA.prototype.getStep = function() {
return this.rra_info.getStep();
}
// Get consolidation function name
RRDRRA.prototype.getCFName = function() {
return this.rra_info.getCFName();
}
RRDRRA.prototype.getEl = function(row_idx,ds_idx) {
return this.rrd_data.getDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx));
}
// Low precision version of getEl
// Uses getFastDoubleAt
RRDRRA.prototype.getElFast = function(row_idx,ds_idx) {
return this.rrd_data.getFastDoubleAt(this.base_rrd_db_idx+this.calc_idx(row_idx,ds_idx));
}
// ============================================================
// RRD Header handling class
function RRDHeader(rrd_data) {
this.rrd_data=rrd_data;
this.validate_rrd();
this.calc_idxs();
}
// Internal, used for initialization
RRDHeader.prototype.validate_rrd = function() {
if (this.rrd_data.getLength()<1) throw new InvalidRRD("Empty file.");
if (this.rrd_data.getLength()<16) throw new InvalidRRD("File too short.");
if (this.rrd_data.getCStringAt(0,4)!=="RRD") throw new InvalidRRD("Wrong magic id.");
this.rrd_version=this.rrd_data.getCStringAt(4,5);
if ((this.rrd_version!=="0003")&&(this.rrd_version!=="0004")&&(this.rrd_version!=="0001")) {
throw new InvalidRRD("Unsupported RRD version "+this.rrd_version+".");
}
this.float_width=8;
if (this.rrd_data.getLongAt(12)==0) {
// not a double here... likely 64 bit
this.float_align=8;
if (! (this.rrd_data.getDoubleAt(16)==8.642135e+130)) {
// uhm... wrong endian?
this.rrd_data.switch_endian=true;
}
if (this.rrd_data.getDoubleAt(16)==8.642135e+130) {
// now, is it all 64bit or only float 64 bit?
if (this.rrd_data.getLongAt(28)==0) {
// true 64 bit align
this.int_align=8;
this.int_width=8;
} else {
// integers are 32bit aligned
this.int_align=4;
this.int_width=4;
}
} else {
throw new InvalidRRD("Magic float not found at 16.");
}
} else {
/// should be 32 bit alignment
if (! (this.rrd_data.getDoubleAt(12)==8.642135e+130)) {
// uhm... wrong endian?
this.rrd_data.switch_endian=true;
}
if (this.rrd_data.getDoubleAt(12)==8.642135e+130) {
this.float_align=4;
this.int_align=4;
this.int_width=4;
} else {
throw new InvalidRRD("Magic float not found at 12.");
}
}
this.unival_width=this.float_width;
this.unival_align=this.float_align;
// process the header here, since I need it for validation
// char magic[4], char version[5], double magic_float
// long ds_cnt, long rra_cnt, long pdp_step, unival par[10]
this.ds_cnt_idx=Math.ceil((4+5)/this.float_align)*this.float_align+this.float_width;
this.rra_cnt_idx=this.ds_cnt_idx+this.int_width;
this.pdp_step_idx=this.rra_cnt_idx+this.int_width;
//always get only the low 32 bits, the high 32 on 64 bit archs should always be 0
this.ds_cnt=this.rrd_data.getLongAt(this.ds_cnt_idx);
if (this.ds_cnt<1) {
throw new InvalidRRD("ds count less than 1.");
}
this.rra_cnt=this.rrd_data.getLongAt(this.rra_cnt_idx);
if (this.ds_cnt<1) {
throw new InvalidRRD("rra count less than 1.");
}
this.pdp_step=this.rrd_data.getLongAt(this.pdp_step_idx);
if (this.pdp_step<1) {
throw new InvalidRRD("pdp step less than 1.");
}
// best guess, assuming no weird align problems
this.top_header_size=Math.ceil((this.pdp_step_idx+this.int_width)/this.unival_align)*this.unival_align+10*this.unival_width;
var t=this.rrd_data.getLongAt(this.top_header_size);
if (t==0) {
throw new InvalidRRD("Could not find first DS name.");
}
}
// Internal, used for initialization
RRDHeader.prototype.calc_idxs = function() {
this.ds_def_idx=this.top_header_size;
// char ds_nam[20], char dst[20], unival par[10]
this.ds_el_size=Math.ceil((20+20)/this.unival_align)*this.unival_align+10*this.unival_width;
this.rra_def_idx=this.ds_def_idx+this.ds_el_size*this.ds_cnt;
// char cf_nam[20], uint row_cnt, uint pdp_cnt, unival par[10]
this.row_cnt_idx=Math.ceil(20/this.int_align)*this.int_align;
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;
this.live_head_idx=this.rra_def_idx+this.rra_def_el_size*this.rra_cnt;
// time_t last_up, int last_up_usec
this.live_head_size=2*this.int_width;
this.pdp_prep_idx=this.live_head_idx+this.live_head_size;
// char last_ds[30], unival scratch[10]
this.pdp_prep_el_size=Math.ceil(30/this.unival_align)*this.unival_align+10*this.unival_width;
this.cdp_prep_idx=this.pdp_prep_idx+this.pdp_prep_el_size*this.ds_cnt;
// unival scratch[10]
this.cdp_prep_el_size=10*this.unival_width;
this.rra_ptr_idx=this.cdp_prep_idx+this.cdp_prep_el_size*this.ds_cnt*this.rra_cnt;
// uint cur_row
this.rra_ptr_el_size=1*this.int_width;
this.header_size=this.rra_ptr_idx+this.rra_ptr_el_size*this.rra_cnt;
}
// Optional initialization
// Read and calculate row counts
RRDHeader.prototype.load_row_cnts = function() {
this.rra_def_row_cnts=[];
this.rra_def_row_cnt_sums=[]; // how many rows before me
for (var i=0; i<this.rra_cnt; i++) {
this.rra_def_row_cnts[i]=this.rrd_data.getLongAt(this.rra_def_idx+i*this.rra_def_el_size+this.row_cnt_idx,false);
if (i==0) {
this.rra_def_row_cnt_sums[i]=0;
} else {
this.rra_def_row_cnt_sums[i]=this.rra_def_row_cnt_sums[i-1]+this.rra_def_row_cnts[i-1];
}
}
}
// ---------------------------
// Start of user functions
RRDHeader.prototype.getMinStep = function() {
return this.pdp_step;
}
RRDHeader.prototype.getLastUpdate = function() {
return this.rrd_data.getLongAt(this.live_head_idx,false);
}
RRDHeader.prototype.getNrDSs = function() {
return this.ds_cnt;
}
RRDHeader.prototype.getDSNames = function() {
var ds_names=[]
for (var idx=0; idx<this.ds_cnt; idx++) {
var ds=this.getDSbyIdx(idx);
var ds_name=ds.getName()
ds_names.push(ds_name);
}
return ds_names;
}
RRDHeader.prototype.getDSbyIdx = function(idx) {
if ((idx>=0) && (idx<this.ds_cnt)) {
return new RRDDS(this.rrd_data,this.ds_def_idx+this.ds_el_size*idx,idx);
} else {
throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_cnt +").");
}
}
RRDHeader.prototype.getDSbyName = function(name) {
for (var idx=0; idx<this.ds_cnt; idx++) {
var ds=this.getDSbyIdx(idx);
var ds_name=ds.getName()
if (ds_name==name)
return ds;
}
throw RangeError("DS name "+ name +" unknown.");
}
RRDHeader.prototype.getNrRRAs = function() {
return this.rra_cnt;
}
RRDHeader.prototype.getRRAInfo = function(idx) {
if ((idx>=0) && (idx<this.rra_cnt)) {
return new RRDRRAInfo(this.rrd_data,
this.rra_def_idx+idx*this.rra_def_el_size,
this.int_align,this.rra_def_row_cnts[idx],this.pdp_step,
idx);
} else {
throw RangeError("RRA idx ("+ idx +") out of range [0-" + this.rra_cnt +").");
}
}
// ============================================================
// RRDFile class
// Given a BinaryFile, gives access to the RRD archive fields
//
// Arguments:
// bf must be an object compatible with the BinaryFile interface
// file_options - currently no semantics... introduced for future expandability
function RRDFile(bf,file_options) {
this.file_options=file_options;
var rrd_data=bf
this.rrd_header=new RRDHeader(rrd_data);
this.rrd_header.load_row_cnts();
// ===================================
// Start of user functions
this.getMinStep = function() {
return this.rrd_header.getMinStep();
}
this.getLastUpdate = function() {
return this.rrd_header.getLastUpdate();
}
this.getNrDSs = function() {
return this.rrd_header.getNrDSs();
}
this.getDSNames = function() {
return this.rrd_header.getDSNames();
}
this.getDS = function(id) {
if (typeof id == "number") {
return this.rrd_header.getDSbyIdx(id);
} else {
return this.rrd_header.getDSbyName(id);
}
}
this.getNrRRAs = function() {
return this.rrd_header.getNrRRAs();
}
this.getRRAInfo = function(idx) {
return this.rrd_header.getRRAInfo(idx);
}
this.getRRA = function(idx) {
rra_info=this.rrd_header.getRRAInfo(idx);
return new RRDRRA(rrd_data,
this.rrd_header.rra_ptr_idx+idx*this.rrd_header.rra_ptr_el_size,
rra_info,
this.rrd_header.header_size,
this.rrd_header.rra_def_row_cnt_sums[idx],
this.rrd_header.ds_cnt);
}
}
/*
* Filter classes for rrdFile
* They implement the same interface, but changing the content
*
* Part of the javascriptRRD package
* Copyright (c) 2009 Frank Wuerthwein, fkw@ucsd.edu
*
* Original repository: http://javascriptrrd.sourceforge.net/
*
* MIT License [http://www.opensource.org/licenses/mit-license.php]
*
*/
/*
* All filter classes must implement the following interface:
* getMinStep()
* getLastUpdate()
* getNrRRAs()
* getRRAInfo(rra_idx)
* getFilterRRA(rra_idx)
* getName()
*
* Where getFilterRRA returns an object implementing the following interface:
* getIdx()
* getNrRows()
* getStep()
* getCFName()
* getEl(row_idx)
* getElFast(row_idx)
*
*/
// ================================================================
// Filter out a subset of DSs (identified either by idx or by name)
// Internal
function RRDRRAFilterDS(rrd_rra,ds_list) {
this.rrd_rra=rrd_rra;
this.ds_list=ds_list;
}
RRDRRAFilterDS.prototype.getIdx = function() {return this.rrd_rra.getIdx();}
RRDRRAFilterDS.prototype.getNrRows = function() {return this.rrd_rra.getNrRows();}
RRDRRAFilterDS.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDRRAFilterDS.prototype.getStep = function() {return this.rrd_rra.getStep();}
RRDRRAFilterDS.prototype.getCFName = function() {return this.rrd_rra.getCFName();}
RRDRRAFilterDS.prototype.getEl = function(row_idx,ds_idx) {
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
var real_ds_idx=this.ds_list[ds_idx].real_ds_idx;
return this.rrd_rra.getEl(row_idx,real_ds_idx);
} else {
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
}
}
RRDRRAFilterDS.prototype.getElFast = function(row_idx,ds_idx) {
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
var real_ds_idx=this.ds_list[ds_idx].real_ds_idx;
return this.rrd_rra.getElFast(row_idx,real_ds_idx);
} else {
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
}
}
// --------------------------------------------------
// Public
// NOTE: This class is deprecated, use RRDFilterOp instead
function RRDFilterDS(rrd_file,ds_id_list) {
this.rrd_file=rrd_file;
this.ds_list=[];
for (var i=0; i<ds_id_list.length; i++) {
var org_ds=rrd_file.getDS(ds_id_list[i]);
// must create a new copy, as the index has changed
var new_ds=new RRDDS(org_ds.rrd_data,org_ds.rrd_data_idx,i);
// then extend it to include the real RRD index
new_ds.real_ds_idx=org_ds.my_idx;
this.ds_list.push(new_ds);
}
}
RRDFilterDS.prototype.getMinStep = function() {return this.rrd_file.getMinStep();}
RRDFilterDS.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();}
RRDFilterDS.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDFilterDS.prototype.getDSNames = function() {
var ds_names=[];
for (var i=0; i<this.ds_list.length; i++) {
ds_names.push(ds_list[i].getName());
}
return ds_names;
}
RRDFilterDS.prototype.getDS = function(id) {
if (typeof id == "number") {
return this.getDSbyIdx(id);
} else {
return this.getDSbyName(id);
}
}
// INTERNAL: Do not call directly
RRDFilterDS.prototype.getDSbyIdx = function(idx) {
if ((idx>=0) && (idx<this.ds_list.length)) {
return this.ds_list[idx];
} else {
throw RangeError("DS idx ("+ idx +") out of range [0-" + this.ds_list.length +").");
}
}
// INTERNAL: Do not call directly
RRDFilterDS.prototype.getDSbyName = function(name) {
for (var idx=0; idx<this.ds_list.length; idx++) {
var ds=this.ds_list[idx];
var ds_name=ds.getName()
if (ds_name==name)
return ds;
}
throw RangeError("DS name "+ name +" unknown.");
}
RRDFilterDS.prototype.getNrRRAs = function() {return this.rrd_file.getNrRRAs();}
RRDFilterDS.prototype.getRRAInfo = function(idx) {return this.rrd_file.getRRAInfo(idx);}
RRDFilterDS.prototype.getRRA = function(idx) {return new RRDRRAFilterDS(this.rrd_file.getRRA(idx),this.ds_list);}
// ================================================================
// Filter out by using a user provided filter object
// The object must implement the following interface
// getName() - Symbolic name give to this function
// getDSName() - list of DSs used in computing the result (names or indexes)
// computeResult(val_list) - val_list contains the values of the requested DSs (in the same order)
// If the element is a string or a number, it will just use that ds
// Example class that implements the interface:
// function DoNothing(ds_name) { //Leaves the DS alone.
// this.getName = function() {return ds_name;}
// this.getDSNames = function() {return [ds_name];}
// this.computeResult = function(val_list) {return val_list[0];}
// }
// function sumDS(ds1_name,ds2_name) { //Sums the two DSs.
// this.getName = function() {return ds1_name+"+"+ds2_name;}
// this.getDSNames = function() {return [ds1_name,ds2_name];}
// this.computeResult = function(val_list) {return val_list[0]+val_list[1];}
// }
//
// So to add a summed DS of your 1st and second DS:
// var ds0_name = rrd_data.getDS(0).getName();
// var ds1_name = rrd_data.getDS(1).getName();
// rrd_data = new RRDFilterOp(rrd_data, [new DoNothing(ds0_name),
// DoNothing(ds1_name), sumDS(ds0_name, ds1_name]);
//
// You get the same resoult with
// rrd_data = new RRDFilterOp(rrd_data, [ds0_name,1,new sumDS(ds0_name, ds1_name)]);
////////////////////////////////////////////////////////////////////
// this implements the conceptual NoNothing above
function RRDFltOpIdent(ds_name) {
this.getName = function() {return ds_name;}
this.getDSNames = function() {return [ds_name];}
this.computeResult = function(val_list) {return val_list[0];}
}
// similar to the above, but extracts the name from the index
// requires two parametes, since it it need context
function RRDFltOpIdentId(rrd_data,id) {
this.ds_name=rrd_data.getDS(id).getName();
this.getName = function() {return this.ds_name;}
this.getDSNames = function() {return [this.ds_name];}
this.computeResult = function(val_list) {return val_list[0];}
}
//Private
function RRDDSFilterOp(rrd_file,op_obj,my_idx) {
this.rrd_file=rrd_file;
this.op_obj=op_obj;
this.my_idx=my_idx;
var ds_names=op_obj.getDSNames();
var ds_idx_list=[];
for (var i=0; i<ds_names.length; i++) {
ds_idx_list.push(rrd_file.getDS(ds_names[i]).getIdx());
}
this.ds_idx_list=ds_idx_list;
}
RRDDSFilterOp.prototype.getIdx = function() {return this.my_idx;}
RRDDSFilterOp.prototype.getName = function() {return this.op_obj.getName();}
RRDDSFilterOp.prototype.getType = function() {return "function";}
RRDDSFilterOp.prototype.getMin = function() {return undefined;}
RRDDSFilterOp.prototype.getMax = function() {return undefined;}
// These are new to RRDDSFilterOp
RRDDSFilterOp.prototype.getRealDSList = function() { return this.ds_idx_list;}
RRDDSFilterOp.prototype.computeResult = function(val_list) {return this.op_obj.computeResult(val_list);}
// ------ --------------------------------------------
//Private
function RRDRRAFilterOp(rrd_rra,ds_list) {
this.rrd_rra=rrd_rra;
this.ds_list=ds_list;
}
RRDRRAFilterOp.prototype.getIdx = function() {return this.rrd_rra.getIdx();}
RRDRRAFilterOp.prototype.getNrRows = function() {return this.rrd_rra.getNrRows();}
RRDRRAFilterOp.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDRRAFilterOp.prototype.getStep = function() {return this.rrd_rra.getStep();}
RRDRRAFilterOp.prototype.getCFName = function() {return this.rrd_rra.getCFName();}
RRDRRAFilterOp.prototype.getEl = function(row_idx,ds_idx) {
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
var ds_idx_list=this.ds_list[ds_idx].getRealDSList();
var val_list=[];
for (var i=0; i<ds_idx_list.length; i++) {
val_list.push(this.rrd_rra.getEl(row_idx,ds_idx_list[i]));
}
return this.ds_list[ds_idx].computeResult(val_list);
} else {
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
}
}
RRDRRAFilterOp.prototype.getElFast = function(row_idx,ds_idx) {
if ((ds_idx>=0) && (ds_idx<this.ds_list.length)) {
var ds_idx_list=this.ds_list[ds_idx].getRealDSList();
var val_list=[];
for (var i=0; i<ds_idx_list.length; i++) {
val_list.push(this.rrd_rra.getEl(row_idx,ds_idx_list[i]));
}
return this.ds_list[ds_idx].computeResult(val_list);
} else {
throw RangeError("DS idx ("+ ds_idx +") out of range [0-" + this.ds_list.length +").");
}
}
// --------------------------------------------------
//Public
function RRDFilterOp(rrd_file,op_obj_list) {
this.rrd_file=rrd_file;
this.ds_list=[];
for (i in op_obj_list) {
var el=op_obj_list[i];
var outel=null;
if (typeof(el)=="string") {outel=new RRDFltOpIdent(el);}
else if (typeof(el)=="number") {outel=new RRDFltOpIdentId(this.rrd_file,el);}
else {outel=el;}
this.ds_list.push(new RRDDSFilterOp(rrd_file,outel,i));
}
}
RRDFilterOp.prototype.getMinStep = function() {return this.rrd_file.getMinStep();}
RRDFilterOp.prototype.getLastUpdate = function() {return this.rrd_file.getLastUpdate();}
RRDFilterOp.prototype.getNrDSs = function() {return this.ds_list.length;}
RRDFilterOp.prototype.getDSNames = function() {
var ds_names=[];
for (var i=0; i<this.ds_list.length; i++) {
ds_names.push(ds_list[i].getName());
}
return ds_names;
}
RRDFilterOp.prototype.getDS = function(id) {
if (typeof id == "number") {
return this.getDSbyIdx(id);
} else {
return this.getDSbyName(id);
}
}
// INTERNAL: Do not call directly
RRDFilterOp.prototype.getDSbyIdx = function(idx) {
if ((idx>=0) && (idx<this.ds_list.length)) {
return this.ds_list[idx];
} else {