From 4ac0cdf084645e2aac15c2129bcbb1d414b4212c Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Sat, 4 Aug 2012 12:36:08 +0000 Subject: [PATCH] plugins: Restructure the plugin concept. --- collecty/plugins/base.py | 186 +++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 45 deletions(-) diff --git a/collecty/plugins/base.py b/collecty/plugins/base.py index df2a7e5..183e4c5 100644 --- a/collecty/plugins/base.py +++ b/collecty/plugins/base.py @@ -19,76 +19,176 @@ # # ############################################################################### +import logging +import os +import rrdtool +import time + from threading import Thread +from ..constants import * from ..i18n import _ class Plugin(Thread): + # The name of this plugin. + name = None + + # A description for this plugin. + description = None + + # The schema of the RRD database. + rrd_schema = None + + # The default interval of this plugin. + default_interval = 60 + def __init__(self, collecty, **kwargs): Thread.__init__(self) self.collecty = collecty - self.interval = int(kwargs.get("interval", 60)) + # Check if this plugin was configured correctly. + assert self.name, "Name of the plugin is not set: %s" % self.name + assert self.description, "Description of the plugin is not set: %s" % self.description + assert self.rrd_schema + + # Initialize the logger. + self.log = logging.getLogger("collecty.plugins.%s" % self.name) + self.log.propagate = 1 # Keepalive options self.heartbeat = 2 - self.killed = False - - self.wakeup = self.interval / self.heartbeat - - self.file = kwargs.get("file", None) - if not self.file.startswith("/"): - self.file = os.path.join("/var/rrd", self.file) + self.running = True self.data = [] + # Create the database file. self.create() + + # Run some custom initialization. + self.init() + + self.log.info(_("Successfully initialized.")) def __repr__(self): - return "" % self._type + return "" % self.name def __str__(self): - return "Plugin %s %s" % (self._type, self.file) - - def run(self): - self.collecty.debug("%s started..." % self) - - c = 0 - while True: - if self.killed: - self.update() - self.collecty.debug("%s stoppped..." % self) - return + return "Plugin %s %s" % (self.name, self.file) + + @property + def interval(self): + """ + Returns the interval in milliseconds, when the read method + should be called again. + """ + # XXX read this from the settings + + # Otherwise return the default. + return self.default_interval + + @property + def file(self): + """ + The absolute path to the RRD file of this plugin. + """ + return os.path.join(DATABASE_DIR, "%s.rrd" % self.name) - if c == 0: - self.data.append(self.collect()) - self.collecty.debug("%s collectd: %s..." % (self, self.data[-1])) + def create(self): + """ + Creates an empty RRD file with the desired data structures. + """ + # Skip if the file does already exist. + if os.path.exists(self.file): + return - self.update() + dirname = os.path.dirname(self.file) + if not os.path.exists(dirname): + os.makedirs(dirname) - c = self.wakeup + rrdtool.create(self.file, *self.rrd_schema) - c = c - 1 - time.sleep(self.heartbeat) + self.log.debug(_("Created RRD file %s.") % self.file) - def shutdown(self): - self.killed = True + def info(self): + return rrdtool.info(self.file) - def time(self): - return int(time.time()) # Should return time as int in UTC + ### Basic methods + + def init(self): + """ + Do some custom initialization stuff here. + """ + pass + + def read(self): + """ + Gathers the statistical data, this plugin collects. + """ + raise NotImplementedError + + def submit(self): + """ + Flushes the read data to disk. + """ + # Do nothing in case there is no data to submit. + if not self.data: + return + + self.collecty.debug(_("Saving data from %s...") % self) + rrdtool.update(self.file, *self.data) + self.data = [] - def create(self): - if not os.path.exists(self.file): - rrdtool.create(self.file, *self._rrd) + def __read(self, *args, **kwargs): + """ + This method catches errors from the read() method and logs them. + """ + try: + return self.read(*args, **kwargs) + + # Catch any exceptions, so collecty does not crash. + except Exception, e: + self.log.critical(_("Unhandled exception in read()!"), exc_info=True) + + def __submit(self, *args, **kwargs): + """ + This method catches errors from the submit() method and logs them. + """ + try: + return self.submit(*args, **kwargs) - def update(self): - if self.data: - self.collecty.debug("%s saving data..." % self) - rrdtool.update(self.file, *self.data) - self.data = [] + # Catch any exceptions, so collecty does not crash. + except Exception, e: + self.log.critical(_("Unhandled exception in submit()!"), exc_info=True) + + def run(self): + self.log.debug(_("Started.")) - def collect(self): - raise Exception, "Not implemented" + counter = 0 + while self.running: + if counter == 0: + self.log.debug(_("Collecting...")) + self.__read() + + self.log.debug(_("Sleeping for %.4fs.") % self.interval) + + counter = self.interval / self.heartbeat + + time.sleep(self.heartbeat) + counter -= 1 + + self.__submit() + self.log.debug(_("Stopped.")) + + def shutdown(self): + self.log.debug(_("Received shutdown signal.")) + self.running = False + + @property + def now(self): + """ + Returns the current timestamp in the UNIX timestamp format (UTC). + """ + return int(time.time()) def graph(self, file, interval=None): args = [ "--imgformat", "PNG", @@ -115,7 +215,3 @@ class Plugin(Thread): args.append(item) rrdtool.graph(file, *args) - - def info(self): - return rrdtool.info(self.file) - -- 2.39.2