aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rcynic/rcynic.py121
1 files changed, 69 insertions, 52 deletions
diff --git a/rcynic/rcynic.py b/rcynic/rcynic.py
index 743ea2cf..8155129d 100644
--- a/rcynic/rcynic.py
+++ b/rcynic/rcynic.py
@@ -4,7 +4,7 @@ reimplementation of rcynic.xsl, which had gotten too slow and complex.
$Id$
-Copyright (C) 2010-2011 Internet Systems Consortium, Inc. ("ISC")
+Copyright (C) 2010-2012 Internet Systems Consortium, Inc. ("ISC")
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -25,8 +25,12 @@ import os
import getopt
import time
import subprocess
+import copy
-from xml.etree.ElementTree import (ElementTree, Element, SubElement, Comment)
+try:
+ from lxml.etree import (ElementTree, Element, SubElement, Comment)
+except ImportError:
+ from xml.etree.ElementTree import (ElementTree, Element, SubElement, Comment)
opt = {
"refresh" : 1800,
@@ -105,15 +109,21 @@ def parse_utc(s):
class Label(object):
- def __init__(self, elt):
+ def __init__(self, elt, pos):
self.code = elt.tag
self.mood = elt.get("kind")
self.text = elt.text.strip()
+ self.pos = pos
self.sum = 0
+ def __cmp__(self, other):
+ return cmp(self.pos, other.pos)
+
class Validation_Status(object):
- label_map = None
+ @classmethod
+ def set_label_map(cls, labels):
+ cls.label_map = dict((l.code, l) for l in labels)
def __init__(self, elt):
self.uri = elt.text.strip()
@@ -151,7 +161,7 @@ class Validation_Status(object):
return self.generation == "backup"
-class RRDHost(object):
+class Host(object):
def __init__(self):
self.elapsed = 0
@@ -159,6 +169,7 @@ class RRDHost(object):
self.failures = 0
self.uris = set()
self.graph = None
+ self.counters = {}
def add_connection(self, elt):
self.elapsed += parse_utc(elt.get("finished")) - parse_utc(elt.get("started"))
@@ -166,8 +177,17 @@ class RRDHost(object):
if elt.get("error") is not None:
self.failures += 1
- def add_object_uri(self, u):
- self.uris.add(u)
+ def add_validation_status(self, v):
+ if v.generation == "current":
+ self.uris.add(v.uri)
+ self.counters[(v.fn2, v.generation, v.code)] = self.get_counter(v.fn2, v.generation, v.code) + 1
+ self.counters[v.code] = self.get_total(v.code) + 1
+
+ def get_counter(self, fn2, generation, code):
+ return self.counters.get((fn2, generation, code), 0)
+
+ def get_total(self, code):
+ return self.counters.get(code, 0)
@property
def failed(self):
@@ -198,24 +218,25 @@ class RRDHost(object):
def save_graph_maybe(self, elt):
if self.graph is None:
- self.graph = elt.copy()
+ self.graph = copy.copy(elt)
-class RRDSession(dict):
+class Session(dict):
def __init__(self, timestamp):
dict.__init__(self)
self.timestamp = timestamp
+ def maybe_add_host(self, hostname):
+ if hostname not in self:
+ self[hostname] = Host()
+ return self[hostname]
+
def add_connection(self, elt):
hostname = urlparse.urlparse(elt.text.strip()).hostname
- if hostname not in self:
- self[hostname] = RRDHost()
- self[hostname].add_connection(elt)
+ self.maybe_add_host(hostname).add_connection(elt)
- def add_object_uri(self, u):
- h = urlparse.urlparse(u).hostname
- if h and h in self:
- self[h].add_object_uri(u)
+ def add_validation_status(self, v):
+ self.maybe_add_host(v.hostname).add_validation_status(v)
def run(self, *cmd):
try:
@@ -230,7 +251,7 @@ class RRDSession(dict):
filename = os.path.join(output_directory, hostname) + ".rrd"
if not os.path.exists(filename):
cmd = ["create", filename, "--start", self.timestamp - 1, "--step", "3600"]
- cmd.extend(RRDHost.field_ds_specifiers())
+ cmd.extend(Host.field_ds_specifiers())
cmd.extend(self.rras)
self.run(*cmd)
self.run("update", filename,
@@ -295,7 +316,7 @@ class RRDSession(dict):
"--start", start,
"--imginfo", "@imginfo %s %lu %lu" ]
cmds.extend(self.graph_opts)
- cmds.extend(RRDHost.field_defs(filebase))
+ cmds.extend(Host.field_defs(filebase))
cmds.extend(self.graph_cmds)
imginfo = [i for i in self.run(*cmds) if i.startswith("@imginfo")]
assert len(imginfo) == 1
@@ -366,31 +387,33 @@ os.putenv("TZ", "UTC")
time.tzset()
input = ElementTree(file = sys.stdin if input_file is None else input_file)
-labels = [Label(elt) for elt in input.find("labels")]
-Validation_Status.label_map = dict((l.code, l) for l in labels)
-validation_status = [Validation_Status(elt) for elt in input.findall("validation_status")]
-if opt["show-graphs"]:
- rrds = RRDSession(parse_utc(input.getroot().get("date")))
- for elt in input.findall("rsync_history"):
- rrds.add_connection(elt)
- for elt in input.findall("validation_status"):
- if elt.get("generation") == "current":
- rrds.add_object_uri(elt.text.strip())
- rrds.save()
- rrds.graph()
+session = Session(parse_utc(input.getroot().get("date")))
-if opt["suppress-backup-whining"]:
+labels = [Label(elt, i) for i, elt in enumerate(input.find("labels"))]
+Validation_Status.set_label_map(labels)
+validation_status = [Validation_Status(elt) for elt in input.findall("validation_status")]
+
+if opt["suppress-backup-whining"]:
accepted_current = set(v.uri for v in validation_status if v.is_current and v.accepted)
validation_status = [v for v in validation_status if not v.is_backup or v.uri not in accepted_current]
+ del accepted_current
+
+for elt in input.findall("rsync_history"):
+ session.add_connection(elt)
for v in validation_status:
v.stand_up_and_be_counted()
+ session.add_validation_status(v)
if opt["suppress-zero-columns"]:
labels = [l for l in labels if l.sum > 0]
+if opt["show-graphs"]:
+ session.save()
+ session.graph()
+
if not opt["one-file-per-section"]:
start_html("rcynic summary")
@@ -416,15 +439,15 @@ if opt["show-summary"]:
SubElement(tr, "th").text = l.text
for fn2 in unique_fn2s:
for generation in unique_generations:
- if any(v.fn2 == fn2 and v.generation == generation for v in validation_status):
+ counters = [sum(h.get_counter(fn2, generation, l.code) for h in session.itervalues()) for l in labels]
+ if sum(counters) > 0:
tr = SubElement(tbody, "tr")
SubElement(tr, "td").text = ((generation or "") + " " + (fn2 or "")).strip()
- for l in labels:
- value = sum(int(v.fn2 == fn2 and v.generation == generation and v.code == l.code) for v in validation_status)
+ for l, c in zip(labels, counters):
td = SubElement(tr, "td")
- if value > 0:
+ if c > 0:
td.set("class", l.mood)
- td.text = str(value)
+ td.text = str(c)
tr = SubElement(tfoot, "tr")
SubElement(tr, "td").text = "Total"
for l in labels:
@@ -452,32 +475,26 @@ if opt["show-summary"]:
SubElement(tr, "th").text = l.text
for fn2 in unique_fn2s:
for generation in unique_generations:
- if any(v.hostname == hostname and v.fn2 == fn2 and v.generation == generation
- for v in validation_status):
+ counters = [session[hostname].get_counter(fn2, generation, l.code) for l in labels]
+ if sum(counters) > 0:
tr = SubElement(tbody, "tr")
SubElement(tr, "td").text = ((generation or "") + " " + (fn2 or "")).strip()
- for l in labels:
- value = sum(int(v.hostname == hostname and
- v.fn2 == fn2 and
- v.generation == generation and
- v.code == l.code)
- for v in validation_status)
+ for l, c in zip(labels, counters):
td = SubElement(tr, "td")
- if value > 0:
+ if c > 0:
td.set("class", l.mood)
- td.text = str(value)
+ td.text = str(c)
tr = SubElement(tfoot, "tr")
SubElement(tr, "td").text = "Total"
- for l in labels:
- value = sum(int(v.hostname == hostname and v.code == l.code)
- for v in validation_status)
+ counters = [session[hostname].get_total(l.code) for l in labels]
+ for l, c in zip(labels, counters):
td = SubElement(tr, "td")
- if value > 0:
+ if c > 0:
td.set("class", l.mood)
- td.text = str(value)
+ td.text = str(c)
if opt["show-graphs"]:
SubElement(body, "br")
- SubElement(body, "a", href = "%s_graphs.html" % hostname).append(rrds[hostname].graph)
+ SubElement(body, "a", href = "%s_graphs.html" % hostname).append(session[hostname].graph)
if opt["one-file-per-section"]:
finish_html("%s_summary" % hostname)