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>
Howdy! Someone in my Facebook group shared this website with us
so I came to give it a look. I’m definitely enjoying the information. I’m book-marking and will be
tweeting this to my followers! Excellent blog and terrific design.