From f400c03da3da541b291a7051b30c320764f6d200 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Tue, 22 Jul 2008 01:41:51 +0000 Subject: move xml statistics over to new typed-plugin system (enables direct-to-django statistics plugin) git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@4817 ce84e21b-d406-0410-9b95-82705330c041 --- src/lib/Server/Core.py | 14 +++- src/lib/Server/Plugin.py | 3 + src/lib/Server/Plugins/Statistics.py | 120 +++++++++++++++++++++++++++++++++++ src/lib/Server/Statistics.py | 104 ------------------------------ 4 files changed, 134 insertions(+), 107 deletions(-) create mode 100644 src/lib/Server/Plugins/Statistics.py delete mode 100644 src/lib/Server/Statistics.py (limited to 'src/lib') diff --git a/src/lib/Server/Core.py b/src/lib/Server/Core.py index 2bbbbc4f4..d7086aa96 100644 --- a/src/lib/Server/Core.py +++ b/src/lib/Server/Core.py @@ -3,7 +3,6 @@ __revision__ = '$Revision$' from time import time from Bcfg2.Server.Plugin import PluginInitError, PluginExecutionError -from Bcfg2.Server.Statistics import Statistics import logging, lxml.etree, os, stat import Bcfg2.Server.Plugins.Metadata @@ -222,8 +221,6 @@ class Core(object): except: self.svn = False - self.stats = Statistics("%s/etc/statistics.xml" % (self.datastore)) - [data.remove('') for data in [structures, generators] if '' in data] @@ -242,6 +239,17 @@ class Core(object): self.metadata = self.plugins["Metadata"] break + plugins = self.plugins.values() + while True: + plugin = plugins.pop() + if isinstance(plugin, Bcfg2.Server.Plugin.StatisticsPlugin): + self.stats = plugin + break + if not plugins: + self.init_plugins("Statistics") + self.stats = self.plugins["Statistics"] + break + for plug_names, plug_tname, plug_type, collection in \ [(structures, 'structure', Bcfg2.Server.Plugin.StructurePlugin, self.structures), diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index 63af1d886..0f9464a47 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -96,6 +96,9 @@ class StatisticsPlugin(Plugin): def StoreStatistics(self, client, xdata): pass + def WriteBack(self): + pass + # the rest of the file contains classes for coherent file caching class FileBacked(object): diff --git a/src/lib/Server/Plugins/Statistics.py b/src/lib/Server/Plugins/Statistics.py new file mode 100644 index 000000000..bc6e648bf --- /dev/null +++ b/src/lib/Server/Plugins/Statistics.py @@ -0,0 +1,120 @@ +'''This file manages the statistics collected by the BCFG2 Server''' +__revision__ = '$Revision$' + +from lxml.etree import XML, SubElement, Element, XMLSyntaxError +from time import asctime, localtime, time, strptime, mktime + +import logging, lxml.etree, os + +import Bcfg2.Server.Plugin + +class StatisticsStore(object): + '''Manages the memory and file copy of statistics collected about client runs''' + __min_write_delay__ = 0 + + def __init__(self, filename): + self.filename = filename + self.element = Element('Dummy') + self.dirty = 0 + self.lastwrite = 0 + self.logger = logging.getLogger('Bcfg2.Server.Statistics') + self.ReadFromFile() + + def WriteBack(self, force=0): + '''Write statistics changes back to persistent store''' + if (self.dirty and (self.lastwrite + self.__min_write_delay__ <= time()) ) \ + or force: + #syslog(LOG_INFO, "Statistics: Updated statistics.xml") + try: + fout = open(self.filename + '.new', 'w') + except IOError, ioerr: + self.logger.error("Failed to open %s for writing: %s" % (self.filename + '.new', ioerr)) + else: + fout.write(lxml.etree.tostring(self.element, encoding='UTF-8', xml_declaration=True)) + fout.close() + os.rename(self.filename + '.new', self.filename) + self.dirty = 0 + self.lastwrite = time() + + def ReadFromFile(self): + '''Reads current state regarding statistics''' + try: + fin = open(self.filename, 'r') + data = fin.read() + fin.close() + self.element = XML(data) + self.dirty = 0 + #syslog(LOG_INFO, "Statistics: Read in statistics.xml") + except (IOError, XMLSyntaxError): + self.logger.error("Creating new statistics file %s"%(self.filename)) + self.element = Element('ConfigStatistics') + self.WriteBack() + self.dirty = 0 + + def updateStats(self, xml, client): + '''Updates the statistics of a current node with new data''' + + # Current policy: + # - Keep anything less than 24 hours old + # - Keep latest clean run for clean nodes + # - Keep latest clean and dirty run for dirty nodes + newstat = xml.find('Statistics') + + if newstat.get('state') == 'clean': + node_dirty = 0 + else: + node_dirty = 1 + + # Find correct node entry in stats data + # The following list comprehension should be guarenteed to return at + # most one result + nodes = [elem for elem in self.element.findall('Node') if elem.get('name') == client] + nummatch = len(nodes) + if nummatch == 0: + # Create an entry for this node + node = SubElement(self.element, 'Node', name=client) + elif nummatch == 1 and not node_dirty: + # Delete old instance + node = nodes[0] + [node.remove(elem) for elem in node.findall('Statistics') if self.isOlderThan24h(elem.get('time'))] + elif nummatch == 1 and node_dirty: + # Delete old dirty statistics entry + node = nodes[0] + [node.remove(elem) for elem in node.findall('Statistics') if (elem.get('state') == 'dirty' and self.isOlderThan24h(elem.get('time')))] + else: + # Shouldn't be reached + self.logger.error("Duplicate node entry for %s"%(client)) + + # Set current time for stats + newstat.set('time', asctime(localtime())) + + # Add statistic + node.append(newstat) + + # Set dirty + self.dirty = 1 + self.WriteBack() + + + def isOlderThan24h(self, testTime): + '''Helper function to determine if