# -*- coding: utf-8 -*- import sys sys.path.append('/home/users/jpa/remtemp/lib/python2.3/site-packages/') import rrdtool import time import os import base64, struct import tempfile import zlib # Stand-alone functions in other module from graphutils import * rrdpath = "/home/users/jpa/remtemp/rrds" class RRDObject: def getid(self, suffix = None): '''Returns rrdtool compatible id for the object, possibly with a suffix''' r = base64.b16encode(struct.pack("q", id(self))) if suffix is not None: r += '_' + str(suffix) return r class RRDDef(RRDObject): '''Data source definition''' def __init__(self, graph, name, quantity, cf, start = None, end = None, history = 0): self.graph = graph self.name = name self.path = os.path.join(rrdpath, os.path.basename(name)) + ".rrd" self.quantity = quantity self.cf = cf if start is not None: self.start = start else: self.start = graph.start if end is not None: self.end = end else: self.end = graph.end self.history = history def getcommands(self): '''Return list of commands for rrdtool''' r = [] r.append("DEF:%s=%s:%s:%s:start=%d:end=%d" % (self.getid(), self.path, self.quantity, self.cf, self.start, self.end)) if self.history: r.append("SHIFT:%s:%d" % (self.getid(), self.history)) return r class RRDLine(RRDObject): '''A graph line or area''' def __init__(self, graph, rrddef, color, description = None, writeinfo = False): '''Defid is the id of the DEF: clause. Color is a string of hexadecimal definition, eg. "FF9999". Description is a string that will be displayed in legend, or None to hide legend. If writeinfo is true, average, max, min and last will be written in legend. ''' self.graph = graph self.units = graph.units self.style = graph.defaultstyle # Style is either 'LINE1', 'LINE2', 'LINE3' or 'AREA'. self.rrddef = rrddef self.color = color self.description = description self.writeinfo = writeinfo def getcommands(self): '''Return list of commands for rrdtool''' defid = self.rrddef.getid() if self.writeinfo and self.description is not None: description = self.description + "." * (self.graph.infomargin - len(unicode(self.description, 'utf-8'))) r = [] if self.description is not None: r.append("%s:%s#%s:%s" % (self.style, defid, self.color, description)) else: r.append("%s:%s#%s" % (self.style, defid, self.color)) if self.writeinfo: infounits = self.units.replace('%', '%%') infounits += ' ' * (5 - len(infounits)) r.append("GPRINT:%s:MIN:Min %%4.1lf %s" % (defid, infounits)) r.append("GPRINT:%s:MAX:\tMax %%4.1lf %s" % (defid, infounits)) r.append("GPRINT:%s:AVERAGE:\tKa. %%4.1lf %s" % (defid, infounits)) r.append("GPRINT:%s:LAST:\tNyt %%4.1lf %s" % (defid, infounits)) r.append("COMMENT:\\n") return r class Graph: mimetypes = {'PNG' : 'image/png', 'SVG' : 'image/svg', 'EPS' : 'application/postscript', 'PDF' : 'application/pdf'} def __init__(self, format, title, units = '°C'): self.format = format self.title = title self.units = units self.defaultstyle = 'LINE1' self.width = 800 self.height = 400 self.defs = [] self.lines = [] self.infomargin = 40 # Horizontal position to print info at self.compress = False self.end = int(time.time()) self.start = self.end - 86400 def isvector(self): return self.format in ['SVG', 'EPS', 'PDF'] def setsmall(self): self.width = 650 self.height = 250 self.infomargin = 30 def hasinfo(self): for l in self.lines: if l.writeinfo: return True return False def getcommands(self, file): r = [] r.append(file) r.append('--slope-mode') r += ['--tabwidth', '30'] r += ['--imgformat', self.format] r += ['--start', str(int(self.start))] r += ['--end', str(int(self.end))] r += ['--title', self.title] r += ['--vertical-label', self.units] r += ['--width', str(self.width), '--height', str(self.height)] if self.hasinfo(): r.append("COMMENT:\\n") for d in self.defs + self.lines: r += d.getcommands() return r def graph(self): '''Create a graph and return data in a string''' file = tempfile.NamedTemporaryFile() args = self.getcommands(file.name) rrdtool.graph(*args) r = file.read() file.close() if self.compress: r = zlib.compress(r, 9) return r def cgigraph(self): '''Create a graph and print appropiate Content-type header and the data''' data = self.graph() print 'Content-type:', Graph.mimetypes[self.format] print print data def newRRDDef(self, *args, **kwargs): '''Creates a new RRDDef, adds it to the graph and returns it''' d = RRDDef(self, *args, **kwargs) self.defs.append(d) return d def newRRDLine(self, *args, **kwargs): '''Creates a new RRDLine, adds it to the graph and returns it''' d = RRDLine(self, *args, **kwargs) self.lines.append(d) return d def applycgiparams(self): import cgi f = cgi.FieldStorage() if f.has_key('start'): self.start = parsetime(f['start'].value) if f.has_key('end'): self.end = parsetime(f['end'].value) if f.has_key('width'): self.width = int(f['width'].value) if f.has_key('height'): self.width = int(f['height'].value) if f.has_key('small'): self.setsmall() if f.has_key('format'): format = f['format'].value.upper() if format in Graph.mimetypes.keys(): self.format = format if f.has_key('compress'): self.compress = True