diff --git a/GlobalRRD.py b/GlobalRRD.py new file mode 100644 index 0000000..f3f3960 --- /dev/null +++ b/GlobalRRD.py @@ -0,0 +1,35 @@ +import os +import subprocess +from RRD import RRD, DS, RRA + +class GlobalRRD(RRD): + ds_list = [ + # Number of nodes available + DS('nodes', 'GAUGE', 120, 0, float('NaN')), + # Number of client available + DS('clients', 'GAUGE', 120, 0, float('NaN')), + ] + rra_list = [ + RRA('AVERAGE', 0.5, 1, 120), # 2 hours of 1 minute samples + RRA('AVERAGE', 0.5, 60, 744), # 31 days of 1 hour samples + RRA('AVERAGE', 0.5, 1440, 1780),# ~5 years of 1 day samples + ] + + def __init__(self, directory): + super().__init__(os.path.join(directory, "nodes.rrd")) + self.ensureSanity(self.ds_list, self.rra_list, step=60) + + def update(self, nodeCount, clientCount): + super().update({'nodes': nodeCount, 'clients': clientCount}) + + def graph(self, filename, timeframe): + args = ["rrdtool", 'graph', filename, + '-s', '-' + timeframe, + '-w', '800', + '-h' '400', + 'DEF:nodes=' + self.filename + ':nodes:AVERAGE', + 'LINE1:nodes#F00:nodes\\l', + 'DEF:clients=' + self.filename + ':clients:AVERAGE', + 'LINE2:clients#00F:clients', + ] + subprocess.check_output(args) diff --git a/NodeRRD.py b/NodeRRD.py new file mode 100644 index 0000000..f53cad6 --- /dev/null +++ b/NodeRRD.py @@ -0,0 +1,54 @@ +import os +import subprocess +from node import Node +from RRD import RRD, DS, RRA + +class NodeRRD(RRD): + ds_list = [ + DS('upstate', 'GAUGE', 120, 0, 1), + DS('clients', 'GAUGE', 120, 0, float('NaN')), + ] + rra_list = [ + RRA('AVERAGE', 0.5, 1, 120), # 2 hours of 1 minute samples + RRA('AVERAGE', 0.5, 5, 1440), # 5 days of 5 minute samples + RRA('AVERAGE', 0.5, 60, 720), # 30 days of 1 hour samples + RRA('AVERAGE', 0.5, 720, 730), # 1 year of 12 hour samples + ] + + def __init__(self, filename, node = None): + """ + Create a new RRD for a given node. + + If the RRD isn't supposed to be updated, the node can be omitted. + """ + self.node = node + super().__init__(filename) + self.ensureSanity(self.ds_list, self.rra_list, step=60) + + @property + def imagename(self): + return os.path.basename(self.filename).rsplit('.', 2)[0] + ".png" + + def update(self): + super().update({'upstate': 1, 'clients': self.node.clients}) + + def graph(self, directory, timeframe): + """ + Create a graph in the given directory. The file will be named + basename.png if the RRD file is named basename.rrd + """ + args = ['rrdtool','graph', os.path.join(directory, self.imagename), + '-s', '-' + timeframe , + '-w', '800', + '-h', '400', + '-l', '0', + '-y', '1:1', + 'DEF:clients=' + self.filename + ':clients:AVERAGE', + 'VDEF:maxc=clients,MAXIMUM', + 'CDEF:c=0,clients,ADDNAN', + 'CDEF:d=clients,UN,maxc,UN,1,maxc,IF,*', + 'AREA:c#0F0:up\\l', + 'AREA:d#F00:down\\l', + 'LINE1:c#00F:clients connected\\l', + ] + subprocess.check_output(args) diff --git a/rrd.py b/rrd.py index b28b2ea..5c3330d 100755 --- a/rrd.py +++ b/rrd.py @@ -2,7 +2,8 @@ import subprocess import time import os -from RRD import RRD, DS, RRA +from GlobalRRD import GlobalRRD +from NodeRRD import NodeRRD class rrd: def __init__( self @@ -12,7 +13,7 @@ class rrd: , displayTimeNode = "1d" ): self.dbPath = databaseDirectory - self.globalDb = RRD(databaseDirectory + "/nodes.rrd") + self.globalDb = GlobalRRD(self.dbPath) self.imagePath = imagePath self.displayTimeGlobal = displayTimeGlobal self.displayTimeNode = displayTimeNode @@ -25,74 +26,6 @@ class rrd: except: os.mkdir(self.imagePath) - def updateGlobalDatabase(self,nodeCount,clientCount): - """ Adds a new (#Nodes,#Clients) entry to the global database. - """ - self.globalDb.ensureSanity( - [ - # Number of nodes available - DS('nodes', 'GAUGE', 120, 0, float('NaN')), - # Number of client available - DS('clients', 'GAUGE', 120, 0, float('NaN')), - ], [ - RRA('LAST', 0, 1, 44640), - RRA('LAST', 0, 60, 744), - RRA('LAST', 0, 1440, 1780), - ], - step=60, - ) - self.globalDb.update({'nodes': nodeCount, 'clients': clientCount}) - - def createGlobalGraph(self): - nodeGraph = self.imagePath + "/" + "globalGraph.png" - args = ["rrdtool", 'graph', nodeGraph, '-s', '-' + self.displayTimeGlobal, '-w', '800', '-h' '400' - ,'DEF:nodes=' + self.globalDb.filename + ':nodes:AVERAGE', 'LINE1:nodes#F00:nodes\\l' - ,'DEF:clients=' + self.globalDb.filename + ':clients:AVERAGE','LINE2:clients#00F:clients' - ] - subprocess.check_output(args) - - - def nodeMACToRRDFile(self,nodeMAC): - return self.dbPath + "/" + str(nodeMAC).replace(":","") + ".rrd" - - def nodeMACToPNGFile(self,nodeMAC): - return self.imagePath + "/" + str(nodeMAC).replace(":","") + ".png" - - # Call only if node is up - def updateNodeDatabase(self,nodePrimaryMAC,clientCount): - # TODO check for bad nodeNames - nodeFile = self.nodeMACToRRDFile(nodePrimaryMAC) - nodeRRD = RRD(nodeFile) - nodeRRD.ensureSanity( - [ - DS('upstate', 'GAUGE', 120, 0, 1), - DS('clients', 'GAUGE', 120, 0, float('NaN')), - ], [ - RRA('LAST', 0, 1, 44640), - ], - step=60, - ) - # Update Global RRDatabase - args = ["rrdtool",'updatev', nodeFile - # #Upstate #Clients - , self.currentTime + ":"+str(1)+":"+str(clientCount) - ] - subprocess.check_output(args) - - def createNodeGraph(self,nodePrimaryMAC): - nodeGraph = self.nodeMACToPNGFile(nodePrimaryMAC) - nodeFile = self.nodeMACToRRDFile(nodePrimaryMAC) - args = ['rrdtool','graph', nodeGraph, '-s', '-' + self.displayTimeNode , '-w', '800', '-h', '400', '-l', '0', '-y', '1:1', - 'DEF:clients=' + nodeFile + ':clients:AVERAGE', - 'VDEF:maxc=clients,MAXIMUM', - 'CDEF:c=0,clients,ADDNAN', - 'CDEF:d=clients,UN,maxc,UN,1,maxc,IF,*', - 'AREA:c#0F0:up\\l', - 'AREA:d#F00:down\\l', - 'LINE1:c#00F:clients connected\\l', - ] - subprocess.check_output(args) - def update_database(self,db): nodes = {} clientCount = 0 @@ -113,15 +46,19 @@ class rrd: elif target in nodes and not source in nodes: nodes[target].clients += 1 - self.updateGlobalDatabase(len(nodes),clientCount) - for mac in nodes: - self.updateNodeDatabase(mac,nodes[mac].clients) + self.globalDb.update(len(nodes), clientCount) + for node in nodes.values(): + rrd = NodeRRD( + os.path.join(self.dbPath, str(node.id).replace(':', '') + '.rrd'), + node + ) + rrd.update() def update_images(self): - """ Creates a image for every rrd file in the database directory. + """ Creates an image for every rrd file in the database directory. """ - self.createGlobalGraph() + self.globalDb.graph(os.path.join(self.imagePath, "globalGraph.png"), self.displayTimeGlobal) nodeDbFiles = os.listdir(self.dbPath) @@ -131,4 +68,5 @@ class rrd: nodeName = os.path.basename(fileName).split('.') if nodeName[1] == 'rrd' and not nodeName[0] == "nodes": - self.createNodeGraph(nodeName[0]) + rrd = NodeRRD(os.path.join(self.dbPath, fileName)) + rrd.graph(self.imagePath, self.displayTimeNode)