diff options
author | Stef Walter <stef@memberwebs.com> | 2006-01-20 23:10:33 +0000 |
---|---|---|
committer | Stef Walter <stef@memberwebs.com> | 2006-01-20 23:10:33 +0000 |
commit | 9a813d3324c2dae32fcfd690591a9e31cdad649d (patch) | |
tree | 2b96e4e2008143f261721187293c2b517285400a |
Initial import
-rw-r--r-- | graphics/action-date.xcf | bin | 0 -> 1632 bytes | |||
-rw-r--r-- | graphics/action-dir.xcf | bin | 0 -> 1172 bytes | |||
-rw-r--r-- | graphics/action-zoom.xcf | bin | 0 -> 1476 bytes | |||
-rw-r--r-- | html/action-date.gif | bin | 0 -> 1024 bytes | |||
-rw-r--r-- | html/action-next.gif | bin | 0 -> 877 bytes | |||
-rw-r--r-- | html/action-prev.gif | bin | 0 -> 874 bytes | |||
-rw-r--r-- | html/action-zoom.gif | bin | 0 -> 965 bytes | |||
-rw-r--r-- | html/index.html | 24 | ||||
-rw-r--r-- | html/rrdui.js | 586 | ||||
-rw-r--r-- | html/style.css | 157 | ||||
-rwxr-xr-x | tools/rrdui-cgi.py | 109 | ||||
-rw-r--r-- | tools/rrdui-collect.py | 59 | ||||
-rw-r--r-- | tools/rrdui-create.py | 28 | ||||
-rw-r--r-- | tools/rrdui.py | 96 | ||||
-rw-r--r-- | tools/rrdui.pyc | bin | 0 -> 2659 bytes |
15 files changed, 1059 insertions, 0 deletions
diff --git a/graphics/action-date.xcf b/graphics/action-date.xcf Binary files differnew file mode 100644 index 0000000..0cb14ac --- /dev/null +++ b/graphics/action-date.xcf diff --git a/graphics/action-dir.xcf b/graphics/action-dir.xcf Binary files differnew file mode 100644 index 0000000..555a59c --- /dev/null +++ b/graphics/action-dir.xcf diff --git a/graphics/action-zoom.xcf b/graphics/action-zoom.xcf Binary files differnew file mode 100644 index 0000000..e973da0 --- /dev/null +++ b/graphics/action-zoom.xcf diff --git a/html/action-date.gif b/html/action-date.gif Binary files differnew file mode 100644 index 0000000..a249e30 --- /dev/null +++ b/html/action-date.gif diff --git a/html/action-next.gif b/html/action-next.gif Binary files differnew file mode 100644 index 0000000..c01940a --- /dev/null +++ b/html/action-next.gif diff --git a/html/action-prev.gif b/html/action-prev.gif Binary files differnew file mode 100644 index 0000000..f7eba32 --- /dev/null +++ b/html/action-prev.gif diff --git a/html/action-zoom.gif b/html/action-zoom.gif Binary files differnew file mode 100644 index 0000000..c1e9800 --- /dev/null +++ b/html/action-zoom.gif diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..43c79ed --- /dev/null +++ b/html/index.html @@ -0,0 +1,24 @@ +<html> + <head> + <script language="javascript"> + // Settings + endpoint = "http://shondo/rrduiback/"; + </script> + <title>Statistics Monitoring</title> + <link href="style.css" type="text/css" rel="stylesheet"/> + </head> + <body> + <table id="main-table" cellspacing="0" cellpadding="0"> + <tr id="top-row"><td id="headers"> + + <!-- The header area: A template of our header --> + <span class="header" id="header-template">Text</span> + + </td></tr> + <tr id="main-row"><td colspan="2" id="main-cell"> + <iframe id="main-frame"></iframe> + </td></tr> + </table> + <script language="javascript" src="rrdui.js"/> + </body> +</html> diff --git a/html/rrdui.js b/html/rrdui.js new file mode 100644 index 0000000..12bce92 --- /dev/null +++ b/html/rrdui.js @@ -0,0 +1,586 @@ + + +/* ----------------------------------------------------------------------------- + * STARTUP + */ + +/* TODO: Check setup variables */ +/* TODO: Loading indicator */ + +var categoryCurrent = null; +var categoryArea = document.getElementById("headers"); +var xmlData = null; +var graphDoc = null; + +loadXml(endpoint, loadedGraphData); +setupFrame(); + +function loadedGraphData(doc) +{ + if(!doc) + { + displayError("Couldn't load graph data from server", categoryArea); + return; + } + + xmlData = doc; + + displayCategories(); + displayCurrentPage(); +} + + +/* ----------------------------------------------------------------------------- + * GRAPHS + */ + +function setupFrame() +{ + var frame = document.getElementById("main-frame"); + graphDoc = frame.contentDocument; + + /* Get the stylesheet info for frame */ + var i, links = document.getElementsByTagName("link"); + var csslink = null; + for(i = 0; i < links.length; i++) + { + if(links.item(i).getAttribute("rel") == "stylesheet") + csslink = links.item(i); + } + + graphDoc.open(); + graphDoc.write("<html><head>\n"); + /* TODO: We probably need to write a <base> tag for relative URIs */ + if(csslink) + graphDoc.write("<link href=\"" + csslink.getAttribute("href") + + "\" type=\"text/css\" rel=\"stylesheet\"/>\n"); + graphDoc.write("</head><body class=\"in-frame\">\n"); + + /* The graph area */ + graphDoc.write("<div id=\"graphs\" align=\"center\"></div>\n"); + + /* Zoom overlay */ + graphDoc.write("<div id=\"zoom\"></div>\n"); + + /* The action buttons */ + graphDoc.write("<div id=\"actions\">\n"); + graphDoc.write("<img id=\"action-prev\" class=\"action\" src=\"action-prev.gif\"/><br/>"); + graphDoc.write("<img id=\"action-next\" class=\"action\" src=\"action-next.gif\"/><br/>"); + graphDoc.write("<img id=\"action-zoom\" class=\"action\" src=\"action-zoom.gif\"/><br/>"); + graphDoc.write("<img id=\"action-goto\" class=\"action\" src=\"action-date.gif\"/><br/>"); + graphDoc.write("</div>"); + + /* Date selection drop down */ + graphDoc.write("<div id=\"goto\">\n"); + graphDoc.write("<select id=\"goto-select\" size=\"6\">"); + graphDoc.write("<option value=\"1800:0\">Last 30 Minutes</option>"); + graphDoc.write("<option value=\"3600:0\">Last Hour</option>"); + graphDoc.write("<option value=\"10800:0\">Last 3 Hours</option>"); + graphDoc.write("<option value=\"21600:0\">Last 6 Hours</option>"); + graphDoc.write("<option value=\"86400:0\">Last Day</option>"); + graphDoc.write("<option value=\"259200:0\">Last 3 Days</option>"); + graphDoc.write("<option value=\"604800:0\">Last Week</option>"); + graphDoc.write("<option value=\"1209600:0\">Last 2 Weeks</option>"); + graphDoc.write("<option value=\"1209600:0\">Last 2 Weeks</option>"); + graphDoc.write("<option value=\"2678400:0\">Last Month</option>"); + graphDoc.write("<option value=\"5356800:2678400\">Month Before Last</option>"); + graphDoc.write("<option value=\"7948800:0\">Last 3 Months</option>"); + graphDoc.write("<option value=\"15897600:0\">Last 6 Months</option>"); + graphDoc.write("<option value=\"31536000:0\">Last Year</option>"); + graphDoc.write("</select>"); + + /* And wrap it up */ + graphDoc.write("</body></html>"); + graphDoc.close(); +} + +function displayCurrentPage() +{ + var area = graphDoc.getElementById("graphs"); + + /* Hide all images not from this category */ + var i, images = area.getElementsByTagName("img"); + for(i = 0; i < images.length; i++) + { + if(images.item(i)._category == categoryCurrent) + images.item(i).style.display = "inline"; + else + images.item(i).style.display = "none"; + } + + var data = findCategoryData(categoryCurrent); + if(!xmlData || !data) + { + displayError("Graph data isn't loaded from server", area); + return; + } + + var tend = nowTime(); + var tbeg = tend - 86400; /* One day by default */ + + var children = data.childNodes; + for(i = 0; i < children.length; i++) + { + var child = children.item(i); + if(child.nodeType != Node.ELEMENT_NODE || + child.localName != "graph") + continue; + + var name = child.getAttribute("name"); + if(!name || !name.length) + continue; + + /* Make sure we don't already have this graph */ + var id = "graph-" + categoryCurrent + "-" + name; + var img = graphDoc.getElementById(id); + if(img != null) + continue; + + img = document.createElement("img"); + + if(child.hasAttribute("title")) + { + img.setAttribute("title", child.getAttribute("title")); + img.setAttribute("alt", child.getAttribute("title")); + } + else + img.setAttribute("alt", "Graph"); + + img.setAttribute("class", "graph"); + img.setAttribute("id", id); + + area.appendChild(img); + + img._category = categoryCurrent; + img._name = name; + img._tbeg = tbeg; + img._tend = tend; + + reloadGraph(img); + + img.onmousedown = function(evt) + { return zoomGraphStart(window.event ? window.event : evt); } + img.onmouseover = function(evt) + { return actionsDisplay(window.event ? window.event : evt); } + } +} + +function reloadGraph(img) +{ + /* TODO: Move these into settings */ + /* TODO: We should encode colons properly */ + var GRAPH_COLOR_BACK = ":252424"; + var GRAPH_COLOR_FONT = ":E7E1CC"; + var GRAPH_WIDTH = 450; + var GRAPH_HEIGHT = 120 + + var uri = endpoint + "/graph?category=" + img._category + "&name=" + img._name; + + /* Add date options */ + uri += "&start=" + img._tbeg + "&end=" + img._tend; + + /* And colors */ + uri += "&color=BACK" + GRAPH_COLOR_BACK + "&color=FONT" + GRAPH_COLOR_FONT + + "&color=SHADEA" + GRAPH_COLOR_BACK + "&color=SHADEB" + GRAPH_COLOR_BACK; + + /* Size */ + uri += "&height=" + GRAPH_HEIGHT + "&width=" + GRAPH_WIDTH; + + img.setAttribute("src", uri); +} + +function zoomGraphStart(evt) +{ + if(evt.button != 0) + return; + + if(evt.target.getAttribute("class") != "graph") + return; + + var img = evt.target; + var zoom = graphDoc.getElementById("zoom"); + + zoom._img = img; + zoom._zooming = true; + zoom._from = evt.clientX + graphDoc.body.scrollLeft; + + zoom._start = zoom._from - img.offsetLeft; + zoom._end = zoom._start; + + zoom.style.width = 1; + zoom.style.left = zoom._from; + zoom.style.height = img.height; + zoom.style.top = img.offsetTop; + zoom.style.display = "block"; + + zoom.onmousemove = zoom._img.onmousemove = function(evt) + { return zoomGraphUpdate(window.event ? window.event : evt); } + zoom.onmouseup = zoom._img.onmouseup = function(evt) + { return zoomGraphDone(window.event ? window.event : evt); } + document.onmouseup = graphDoc.onmouseup = function(evt) + { return zoomGraphDone(window.event ? window.event : evt); } + + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +function zoomGraphUpdate(evt) +{ + var zoom = graphDoc.getElementById("zoom"); + if(!zoom._zooming || !zoom._img) + return; + + var to = evt.clientX + graphDoc.body.scrollLeft; + var width = to - zoom._from; + zoom._end = to - zoom._img.offsetLeft; + + if(width == 0) + width = 1; + + if(width < 0) + { + zoom.style.left = to; + zoom.style.width = -width; + } + else + { + zoom.style.left = zoom._from; + zoom.style.width = width; + } + + zoom.style.height = zoom._img.height; + zoom.style.top = zoom._img.offsetTop; + zoom.style.display = "block"; + + evt.preventDefault(); + evt.stopPropagation(); + return false; +} + +function zoomGraphDone(evt) +{ + var zoom = graphDoc.getElementById("zoom"); + if(!zoom._zooming) + return; + + if(evt.target == zoom || evt.target == zoom._img) + zoomGraphSelect(zoom); + + zoomGraphCancel(evt); +} + +function zoomGraphSelect(zoom) +{ + // Arbitrary values (TODO: Move these to CONFIG) + var GRAPH_MARGIN_LEFT = 67; + var GRAPH_MARGIN_RIGHT = 27; + + var left = GRAPH_MARGIN_LEFT; + var right = zoom._img.width - GRAPH_MARGIN_RIGHT; + + var start = zoom._start > zoom._end ? zoom._end : zoom._start; + var end = zoom._start > zoom._end ? zoom._start : zoom._end; + + /* If too small selected, then punt */ + if((end - start) <= 1) + return; + + /* If entire thing selected, then punt */ + if(start < left && end > right) + return; + + /* If only margins selected, then punt */ + if(!(start < right && end > left)) + return; + + /* Limit it to the right range */ + var range = zoom._img.width - GRAPH_MARGIN_RIGHT - GRAPH_MARGIN_LEFT; + start = start < left ? 0 : start - left; + end = end > range ? range : end - left; + + /* Compute the ratio pixels:time */ + var ratio = (zoom._img._tend - zoom._img._tbeg) / range; + + var obeg = zoom._img._tbeg; + var oend = zoom._img._tend; + + zoom._img._tend = Math.floor((end * ratio) + zoom._img._tbeg); + zoom._img._tbeg = Math.floor((start * ratio) + zoom._img._tbeg); + + reloadGraph(zoom._img); +} + +function zoomGraphCancel(evt) +{ + var zoom = graphDoc.getElementById("zoom"); + + if(zoom._zooming) { + if(zoom._img) + zoom._img.onmousemove = zoom._img.onmouseup = zoom._img.onmouseleave = null; + zoom._img = null; + zoom.onmousemove = zoom.onmouseup = null; + zoom._zooming = false; + } + + zoom.style.display = "none"; +} + +function zoomGraphOut(img) +{ + var factor = Math.floor((img._tend - img._tbeg) / 2.5); + var now = nowTime(); + + /* If near the end we have special behavior */ + if(img._tend >= now - 300 || img._tend <= now) + { + img._tbeg -= factor; + img._tend = now; + } + else + { + factor = Math.floor(factor / 2); + img._tbeg -= factor + img._tend += factor; + } + + reloadGraph(img); +} + +function zoomGraphMove(img, dir) +{ + var factor = Math.floor((img._tend - img._tbeg) / 3); + if(!dir) + factor = -factor; + img._tbeg += factor; + img._tend += factor; + reloadGraph(img); +} + +function actionsDisplay(evt) +{ + if(evt.target.getAttribute("class") != "graph") + return; + + var img = evt.target; + + var actions = graphDoc.getElementById("actions"); + actions._img = img; + actions._display = true; + + actions.style.top = img.offsetTop; + actions.style.left = img.offsetLeft + (img.width - 16); + actions.style.display = "block"; + + actions.onmouseout = img.onmouseout = function(evt) + { return actionsHide(window.event ? window.event : evt); } + + /* TODO: We only actually have to do this once */ + var i, acts = actions.getElementsByTagName("img"); + for(i = 0; i < acts.length; i++) + { + acts.item(i).onclick = function(evt) + { return actionRun(window.event ? window.event : evt); } + } +} + +function actionsHide(evt) +{ + var actions = graphDoc.getElementById("actions"); + if(!actions._display) + return; + + var x = evt.clientX - graphDoc.body.scrollLeft; + var y = evt.clientY - graphDoc.body.scrollTop; + var img = actions._img; + + /* See if we're actually still within the image */ + if(x >= img.offsetLeft && x <= img.offsetLeft + img.width && + y >= img.offsetTop && y <= img.offsetTop + img.height) + return; + + actions.style.display = "none"; + img.onmouseout = actions.onmouseout = null; + actions._img = null; + actions._display = false; +} + +function actionRun(evt) +{ + if(evt.target.getAttribute("class") != "action") + return; + + var actions = graphDoc.getElementById("actions"); + if(!actions._display) + return; + + var act = evt.target; + + switch(act.getAttribute("id")) + { + case "action-zoom": + zoomGraphOut(actions._img); + break; + case "action-next": + zoomGraphMove(actions._img, true); + break; + case "action-prev": + zoomGraphMove(actions._img, false); + break; + case "action-goto": + actionGoto(actions._img, act); + break; + } +} + +function actionGoto(img, act) +{ + /* Display the selection thingy */ + var got = graphDoc.getElementById("goto"); + + /* TODO: Make 120/170 a constant */ + got.style.top = act.offsetTop; + got.style.left = (img.offsetLeft + img.width) - 170; + got.style.width = 120; + got.style.display = "block"; + + img.onmouseclick = actionGotoCancel; + + var sel = graphDoc.getElementById("goto-select"); + sel.onchange = actionGotoChange; + sel._img = img; +} + +function actionGotoCancel() +{ + var got = graphDoc.getElementById("goto"); + got.style.display = "none"; +} + +function actionGotoChange() +{ + var sel = graphDoc.getElementById("goto-select"); + var img = sel._img; + sel.onchange = null; + sel._img = null; + + times = sel.value.split(":"); + img._tbeg = nowTime() - times[0]; + img._tend = nowTime() - times[1]; + reloadGraph(img); + + actionGotoCancel(); +} + +/* ----------------------------------------------------------------------------- + * GROUP CATEGORIES + */ + +function findCategoryData(cat) +{ + var i; + + if(!xmlData) + return null; + + var cats = xmlData.getElementsByTagName("category"); + for(i = 0; i < cats.length; i++) + { + if(cats.item(i).getAttribute("name") == cat) + return cats.item(i); + } + + return null; +} + +function changeCategory(cat) +{ + categoryCurrent = cat; + displayCurrentPage(); +} + +function displayCategories() +{ + var i; + + /* Get the template and clean it up a bit */ + var template = document.getElementById("header-template"); + while(template.hasChildNodes()) + template.removeChild(template.firstChild); + + var groups = xmlData.getElementsByTagName("category"); + + for(i = 0; i < groups.length; i++) + { + var name = groups.item(i).getAttribute("name"); + + if(!categoryCurrent) + categoryCurrent = name; + + var el = template.cloneNode(true); + el.removeAttribute("id"); + el.appendChild(document.createTextNode(name)); + el.setAttribute("group", name); + el.setAttribute("onclick", "changeCategory('" + name + "');"); + + categoryArea.appendChild(el); + } +} + +/* ----------------------------------------------------------------------------- + * HELPERS + */ + +function loadXml(uri, callback) +{ + var xmlhttp; + var xmlHttpChange = function() { + if(xmlhttp.readyState == 4) + { + if(xmlhttp.status == 200) + callback(xmlhttp.responseXML); + else + callback(null); + } + } + + /* Mozilla code */ + if(window.XMLHttpRequest) + { + xmlhttp = new XMLHttpRequest(); + xmlhttp.onreadystatechange = xmlHttpChange; + xmlhttp.open("GET", uri, true); + xmlhttp.send(null); + } + + /* And now for the Nasty */ + else if (window.ActiveXObject) + { + xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); + /* TODO: need to do something when this fails (can it fail?) */ + if(xmlhttp) + { + xmlhttp.onreadystatechange = xmlHttpChange; + xmlhttp.open("GET", uri, true); + xmlhttp.send() + } + } +} + +function displayError(message, place) +{ + var el = document.createElement("span"); + el.setAttribute("class", "error"); + var text = document.createTextNode(message); + el.appendChild(text); + + if(place) + place.appendChild(el); + + return el; +} + +function nowTime() +{ + return Math.floor((new Date()).getTime() / 1000); +} diff --git a/html/style.css b/html/style.css new file mode 100644 index 0000000..6b45978 --- /dev/null +++ b/html/style.css @@ -0,0 +1,157 @@ + +/* ----------------------------------------------------------------------------- + * MAIN LAYOUT + */ + +* +{ + color: #E7E1CC; + font-family: sans-serif; +} + +body +{ + background-color: #1A1A1A +} + +#main-table +{ + width: 100%; + height: 100%; + padding: 10px; +} + +#top-row +{ + height: 30pt; + padding: 0px; +} + +#main-row + { height: 100%; } + +#main-cell + { background-color: #252424; padding: 5pt; } + +#main-frame +{ + border: none; + height: 100%; + width: 100%; +} + +/* ----------------------------------------------------------------------------- + * GROUP HEADERS + */ + +/* A group button at the top */ +.header +{ + background-color: #3D2C2C; + padding: 4pt 6pt; + font-size: 8pt; + font-weight: bold; + -moz-border-radius: 3pt; + cursor: pointer; + margin-right: 5pt; +} + +/* Our sample header (which gets copied for each group) is hidden */ +#header-template + { display: none; } + +/* ----------------------------------------------------------------------------- + * GRAPHS/FRAME + */ + +body.in-frame +{ + background-color: #252424; +} + +img.graph +{ + cursor: crosshair; + margin: 7px; +} + +#zoom +{ + cursor: crosshair; + position: absolute; + background-color: red; + display: none; + top: 0px; + left: 0px; + width: 10px; + height: 10px; + filter: alpha(opacity=40); + -moz-opacity: 0.5; + -khtml-opacity: 0.5; + opacity: 0.5; +} + +/* ----------------------------------------------------------------------------- + * ACTION BAR + */ + +#actions +{ + background-color: #252424; + position: absolute; + left: 50px; + top: 50px; + height: 160px; + display: none; +} + +img.action +{ + margin: 3px; + cursor: pointer; + filter: alpha(opacity=75); + -moz-opacity: 0.75; + -khtml-opacity: 0.75; + opacity: 0.75; +} + +img.action:hover +{ + filter: alpha(opacity=100); + -moz-opacity: 1; + -khtml-opacity: 1; + opacity: 1; +} + +#goto +{ + position: absolute; + padding: 10px; + top: 0px; + left: 0px; + background-color: #252424; + display: none; +} + +#goto-select +{ + border: 1px solid black; + font-size: 8pt; + font-weight: normal; + width: 120px; + background-color: #252424; +} + +/* ----------------------------------------------------------------------------- + * MISC + */ + +.error +{ + background-color: #F3BDBD; + border: 1px solid red; + padding: 3pt; + font-size: 8pt; + color: black; +} + diff --git a/tools/rrdui-cgi.py b/tools/rrdui-cgi.py new file mode 100755 index 0000000..8b376f5 --- /dev/null +++ b/tools/rrdui-cgi.py @@ -0,0 +1,109 @@ +#!/usr/bin/python + +import os, sys, time +import sets, cgi, shlex +import rrdtool + +from rrdui import * + +def listGraphs(): + + graphs = loadGraphs() + categories = {} + for item in graphs: + if not categories.has_key(item.category): + categories[item.category] = [] + categories[item.category].append(item) + + groups = categories.keys() + groups.sort() + + print "Content-Type: text/xml\n" + print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + print "<data>" + for group in groups: + print " <category name=\"%s\">" % group + categories[group].sort() + for item in categories[group]: + print " <graph name=\"%s\" width=\"%d\" height=\"%d\" title=\"%s\"/>" % \ + (item.name, item.width, item.height, item.title) + print " </category>" + print "</data>" + + +def displayGraph(): + + # print "Content-Type: text/plain" + print "Content-Type: image/png" + print "" + + form = cgi.FieldStorage() + if not form.has_key("category") or not form.has_key("name"): + raise "Required arguments not specified" + + name = form["name"].value + category = form["category"].value + item = GraphDef(category, name) + + # Default to one day display + end = int(time.time()) + start = end - 86400 + if form.has_key("start"): + start = int(form["start"].value) + if form.has_key("end"): + end = int(form["end"].value) + + # Default to Height and Width in graph + height = item.height + width = item.width + if form.has_key("width"): + width = int(form["width"].value) + if form.has_key("height"): + height = int(form["height"].value) + + + args = ["-", "--imgformat=PNG", "--rigid", + "--start=%d" % start, + "--end=%d" % end, + "--title=%s" % item.title, + "--height=%d" % height, + "--width=%d" % width ] + + # TODO Check color syntax + if form.has_key("color"): + colors = form.getlist("color"); + for color in colors: + args.append("--color") + args.append(color.replace(":", "#")) + + commands = item.commands.replace("{START}", + time.strftime("%Y-%m-%d %H\\:%M", time.localtime(start))) + commands = commands.replace("{END}", + time.strftime("%Y-%m-%d %H\\:%M", time.localtime(end))) + commands = commands.replace("{RRD}", item.filedata) + args.extend(shlex.split(commands)) + + args.extend(shlex.split(item.options)) + + print >> sys.stderr, str(args) + rrdtool.graph(*args) + + +if not os.environ.has_key("PATH_INFO"): + raise "PATH_INFO not set" + +path = os.environ["PATH_INFO"].strip("/") +parts = path.split("/") + +method = parts[0] +del parts[0] +if not method: + method = "list" + + +if method == "list": + listGraphs() +elif method == "graph": + displayGraph() +else: + raise "Invalid request: %s" % method diff --git a/tools/rrdui-collect.py b/tools/rrdui-collect.py new file mode 100644 index 0000000..892fdf8 --- /dev/null +++ b/tools/rrdui-collect.py @@ -0,0 +1,59 @@ +#!/usr/bin/python + +import os, sys, time +import rrdtool + +from rrdui import * + +def readItemValue(host, community, oid): + + cmd = "snmpget -c %s -Ov -OQ -v 2c %s %s" % (community, host, oid) + p = os.popen(cmd, "r") + out = p.read() + (pid, status) = os.wait() + status = status >> 8 + + try: + p.close() + except IOError: + pass + + if status != 0: + print "Couldn't query %s for %s" % (host, oid) # XXXXXX + out = "U" + + return out.strip() + + +def updateItems(graphs): + for item in graphs: + if not os.path.exists(item.filedata): + continue + + (fields, interval) = item.getPollingInfo() + + values = {} + for fieldname in fields.keys(): + info = fields[fieldname].split(":") + if len(info) != 4: + raise "Bad src format" # XXXXXXXXX + if info[0] != "SNMP": + raise "Unknown src method: %s" % info[0] # XXXXXXXXX + + value = readItemValue(host = info[1], community = info[2], oid = info[3]) + values[fieldname] = value + + print ":".join(values.keys()), ":".join(values.values()) + args = [item.filedata, "--template", ":".join(values.keys()), + "N:%s" % ":".join(values.values())] + rrdtool.update(*args) + + +graphs = loadGraphs() +while True: + updateItems(graphs) + # TODO: Work out the interval stuff. for now all at 10 seconds + time.sleep(10) + + + diff --git a/tools/rrdui-create.py b/tools/rrdui-create.py new file mode 100644 index 0000000..3b46021 --- /dev/null +++ b/tools/rrdui-create.py @@ -0,0 +1,28 @@ +#!/usr/bin/python + +import os, sys +import rrdtool + +from rrdui import * + +def createItems(graphs): + + for item in graphs: + if os.path.exists(item.filedata): + continue + + args = [item.filedata, "-b-1y", "-s10"] + + # The creation info + (fields, rras) = item.getCreateInfo() + + # Flesh it out properly, and add it + args.extend(["RRA:%s" % r for r in rras]) + args.extend(["DS:%s:%s" % (f, fields[f]) for f in fields.keys()]) + + # And create it + rrdtool.create(*args) + +# Basics +graphs = loadGraphs() +createItems(graphs) diff --git a/tools/rrdui.py b/tools/rrdui.py new file mode 100644 index 0000000..ee10eae --- /dev/null +++ b/tools/rrdui.py @@ -0,0 +1,96 @@ +import os, sys, time +import ConfigParser + +# TODO: Temporary +CONFDIR = "/data/projects/rrdui/conf" +WORKDIR = "/data/projects/rrdui/work" + +class GraphDef: + filename = None + filedata = None + title = "" + height = 0 + width = 0 + options = "" + commands = "" + category = "" + name = "" + + __config = None + + def __init__(self, category, name): + self.filename = "%s/%s/%s" % (CONFDIR, category, name) + self.filedata = "%s/%s-%s.rrd" % (WORKDIR, category, name) + self.category = category + self.name = name + + cfg = self.__config = ConfigParser.RawConfigParser() + cfg.read(self.filename) + + # Loading general stuff + if cfg.has_option("general", "title"): + self.title = cfg.get("general", "title") + + # Loading graph stuff + if cfg.has_option("graph", "width"): + self.width = int(cfg.get("graph", "width")) + if cfg.has_option("graph", "height"): + self.height = int(cfg.get("graph", "height")) + if cfg.has_option("graph", "options"): + self.options = cfg.get("graph", "options") + if not cfg.has_option("graph", "commands"): + raise "Missing commands attribute in: %s" % self.filename + self.commands = cfg.get("graph", "commands") + + + def getCreateInfo(self): + cfg = self.__config + rra = None + fields = {} + + # The RRA info + if cfg.has_option("create", "rra"): + rra = cfg.get("create", "rra").split() + + # The various fields + for field in cfg.options("create"): + if not field.startswith("field."): + continue + fieldname = field[6:] + fields[fieldname] = cfg.get("create", field) + + return (fields, rra) + + + def getPollingInfo(self): + cfg = self.__config + interval = 300 + fields = {} + + # The interval + if cfg.has_option("poll", "interval"): + interval = int(cfg.get("poll", "interval")) + + # The various fields + for field in cfg.options("poll"): + if not field.startswith("field."): + continue + fieldname = field[6:] + fields[fieldname] = cfg.get("poll", field) + + return (fields, interval) + + +def loadGraphs(): + + # List files and add appropriate paths + graphs = [] + for category in os.listdir(CONFDIR): + dir = "%s/%s" % (CONFDIR, category) + if not os.path.isdir(dir): + continue + for f in os.listdir(dir): + graphs.append(GraphDef(category, f)) + return graphs + + diff --git a/tools/rrdui.pyc b/tools/rrdui.pyc Binary files differnew file mode 100644 index 0000000..4bd62ce --- /dev/null +++ b/tools/rrdui.pyc |