So now that I’m working full time hours, I’d like to monitor the ARM computers in our build farm more closely and in a nicer manner. These series of scripts (d3 js and Twitter Bootstrap) are based on a whole bunch of other scripts like: Koji Client Command, Icinga, and my own check_koji.pl nagios plugin. The result looks like the following:

farm.py - A python script that gets the needed info being displayed
#!/usr/bin/python
import getpass
import os
import re
import sys
import time
import subprocess
import urllib2, base64
sys.stdout.write("Username: ")
sys.stdout.flush()
user = sys.stdin.readline().strip()
spwd = getpass.unix_getpass()
while (1):
indx = 0
buil = {}
node = ""
link = ""
node += ("{\"name\":\"Hosts\",\"size\":0,\"group\":0,\"info\":\"root\"}")
link += ("{\"source\":0,\"target\":0}")
indx += 1
bsfs = base64.encodestring("%s:%s" % (user, spwd)).replace("\n", "")
try:
requ = urllib2.Request(sys.argv[1] + "/cgi-bin/status.cgi?host=all")
requ.add_header("Authorization", "Basic %s" % bsfs)
resu = urllib2.urlopen(requ)
webp = resu.read().replace("\0","").replace("\t","").replace("\r","").replace("\n","")
except:
print("urllib...")
time.sleep(60)
continue
webp = re.sub("<TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0>", "\n<beg line='true'>", webp)
for line in webp.split("\n"):
if (re.match("^.*>Koji Stat<.*$", line, re.I)):
host = re.sub("^.*host=([^&]+).*$", "\\1", line).strip()
line = re.sub("^.+(OK|WARNING|UNKNOWN|CRITICAL).+[0-9]+[0-9]+-[0-9]+[0-9]+-[0-9]+[0-9]+[0-9]+[0-9]+[ ]+[0-9]+[0-9]+:[0-9]+[0-9]+:[0-9]+[0-9]+(.+)$", "<td>\\1</td>\\2", line).strip()
line = line.replace("<TD", "\t<TD")
part = line.split("\t")
for x in range(0, len(part)):
part[x] = re.sub("<[^>]*>", " ", part[x]).strip()
stat = part[0]
info = part[3]
colr = 3
if (stat == "CRITICAL"):
colr = 3
if (stat == "WARNING"):
colr = 2
if (stat == "OK"):
colr = 1
info = "OK"
buil[host] = indx
node += (",\n{\"name\":\"%s [%s]\",\"size\":1,\"group\":%d,\"info\":\"%s\"}" % (host, stat, colr, info))
link += (",\n{\"source\":%d,\"target\":%d}" % (0, indx))
indx += 1
pipe = subprocess.Popen(["/usr/bin/koji", "-s", sys.argv[2], "list-tasks"], stdout=subprocess.PIPE)
for line in pipe.stdout.readlines():
line = line.replace("\t", " ").strip()
line = re.sub(" [ ]+", " ", line)
regx = re.match("^([^ ]+) .* (CANCELED|FAILED|OPEN|CLOSED) .* build[ ]+([^ ]+)[ ]+([^ ]+)$", line, re.I)
if (regx):
task = regx.group(4)
host = regx.group(3).replace("[","").replace("]","")
stat = regx.group(2)
info = regx.group(1)
colr = 4
if (stat == "CANCELED"):
colr = 3
if (stat == "FAILED"):
colr = 3
if (stat == "OPEN"):
colr = 5
if (stat == "CLOSED"):
colr = 1
if (not host in buil.keys()):
buil[host] = indx
node += (",\n{\"name\":\"%s [%s]\",\"size\":1,\"group\":%d,\"info\":\"%s\"}" % (host, "REMOTE", 6, "N/A"))
link += (",\n{\"source\":%d,\"target\":%d}" % (0, indx))
indx += 1
node += (",\n{\"name\":\"%s [%s]\",\"size\":1,\"group\":%d,\"info\":\"%s\"}" % (task, stat, colr, info))
link += (",\n{\"source\":%d,\"target\":%d}" % (buil[host], indx))
indx += 1
fobj = open("force.json", "w")
fobj.write("{\n\"nodes\":[\n%s\n],\n\"links\":[\n%s\n]\n}\n" % (node, link))
fobj.close()
print("sleeping...")
time.sleep(45)
force.js - Some JavaScript that initializes the force directed graph
//var width = 640, height = 560, distance = 64;
var width = 1024, height = 900, distance = 128;
var sizes = [10, 10];
// blue, green, yellow, red, purple, orange, blue
var colors = ["rgba(30, 120, 180, 1)", "rgba(0, 155, 0, 1)", "rgba(250, 195, 0, 1)", "rgba(206, 22, 32, 1)", "rgba(100, 0, 155, 1)", "rgba(255, 160, 0, 1)", "rgba(30, 120, 180, 1)"];
var nodes = "", force = null, svg = null, mous = {x:0,y:0};
function over()
{
try { var l = force.nodes(); }
catch(err) { var l = []; }
for (var i in l)
{
var r = sizes[l[i].size];
var a = (l[i].x - r);
var b = (l[i].x + r);
var c = (l[i].y - r);
var d = (l[i].y + r);
if ((a <= mous.x) && (mous.x <= b) && (c <= mous.y) && (mous.y <= d))
{
document.getElementById("info").innerHTML = ("<b>" + l[i].name + "</b> :: " + l[i].info);
}
}
setTimeout("over()", 0.15 * 1000);
}
function move(e)
{
var obj = document.getElementsByTagName("svg");
if (obj.length > 0)
{
var pt = obj[0].createSVGPoint();
var scr = obj[0].getScreenCTM();
var inv = scr.inverse();
pt.x = e.clientX; pt.y = e.clientY;
mous = pt.matrixTransform(inv);
}
}
function tick()
{
try { force.start(); }
catch(err) { console.log("tick"); }
setTimeout("tick();", 3 * 1000);
}
function loop()
{
d3.json("force.json", function(json) {
var stri = JSON.stringify(json);
if (stri == nodes)
{
return;
}
//console.log(nodes+"!="+stri);
nodes = stri;
document.getElementById("chart").innerHTML = "";
force = d3.layout.force()
.charge(-512)
.linkDistance(distance)
.size([width, height]);
svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
force
.nodes(json.nodes)
.links(json.links)
.start();
var l = force.nodes();
document.getElementById("list").innerHTML = "";
for (var i in l)
{
if (l[i].name.match(/^.*cdot.*(WARNING|CRITICAL|UNKNOWN).*$/i))
{
document.getElementById("list").innerHTML += ("<div class='alert alert-error' style='margin: 5px;'><b>" + l[i].name + "</b> :: " + l[i].info + "</div>");
}
}
var link = svg.selectAll("line.link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(4); });
var node = svg.selectAll("circle.node")
.data(json.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) { return sizes[d.size]; })
.style("fill", function(d) { return colors[d.group]; })
.call(force.drag);
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
setTimeout("loop();", 15 * 1000);
}
loop();
tick();
over();
window.onmousemove = move;
force.html - The main page that puts everything together
<html>
<head>
<title>Farm Visual</title>
<script type="text/javascript" src="d3.v2.js"></script>
<style>
circle.node
{
stroke: #000;
stroke-width: 2.0px;
}
line.link
{
stroke: #999;
stroke-opacity: 0.75;
}
svg
{
border: 1px solid black;
}
#info, #list
{
white-space: nowrap;
}
</style>
<link href="/scripts/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href="/scripts/bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
<script src="/scripts/jquery.js"></script>
<script src="/scripts/bootstrap/js/bootstrap.js"></script>
</head>
<body>
<center>
<table width="100%">
<tr><td colspan="2"><div id='info' class='alert alert-info' style='margin: 5px;'><b>Build Farm Status</b></div></td></tr>
<tr><td><div class="gallery" id="chart" style="margin: 5px;"></div></td>
<td valign="top" width="100%"><div id="list"></div></td></tr>
</table>
</center>
<link href="force.css" rel="stylesheet" type="text/css" />
<script src="force.js" type="text/javascript"></script>
</body>
</html>