X-Git-Url: http://git.ipfire.org/?p=collecty.git;a=blobdiff_plain;f=src%2Fcollecty%2Fplugins%2Fbase.py;h=048ebcb9804a1787f78dc91852243eb5d4e84f68;hp=8d38cad5789d809f194378bd67ecd36a8406c58e;hb=f37913e898441be3b86180daf329cb40f362f2f3;hpb=c968f6d9744a12474be417bfca1056c44c1eadc9 diff --git a/src/collecty/plugins/base.py b/src/collecty/plugins/base.py index 8d38cad..048ebcb 100644 --- a/src/collecty/plugins/base.py +++ b/src/collecty/plugins/base.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 ############################################################################### # # # collecty - A system statistics collection daemon for IPFire # @@ -19,8 +19,6 @@ # # ############################################################################### -from __future__ import division - import datetime import logging import math @@ -29,18 +27,11 @@ import rrdtool import tempfile import threading import time +import unicodedata from ..constants import * from ..i18n import _ -_plugins = {} - -def get(): - """ - Returns a list with all automatically registered plugins. - """ - return _plugins.values() - class Timer(object): def __init__(self, timeout, heartbeat=1): self.timeout = timeout @@ -73,7 +64,30 @@ class Timer(object): return self.elapsed > self.timeout -class Plugin(threading.Thread): +class PluginRegistration(type): + plugins = {} + + def __init__(plugin, name, bases, dict): + type.__init__(plugin, name, bases, dict) + + # The main class from which is inherited is not registered + # as a plugin. + if name == "Plugin": + return + + if not all((plugin.name, plugin.description)): + raise RuntimeError(_("Plugin is not properly configured: %s") % plugin) + + PluginRegistration.plugins[plugin.name] = plugin + + +def get(): + """ + Returns a list with all automatically registered plugins. + """ + return PluginRegistration.plugins.values() + +class Plugin(object, metaclass=PluginRegistration): # The name of this plugin. name = None @@ -87,26 +101,7 @@ class Plugin(threading.Thread): # The default interval for all plugins interval = 60 - # Automatically register all providers. - class __metaclass__(type): - def __init__(plugin, name, bases, dict): - type.__init__(plugin, name, bases, dict) - - # The main class from which is inherited is not registered - # as a plugin. - if name == "Plugin": - return - - if not all((plugin.name, plugin.description)): - raise RuntimeError(_("Plugin is not properly configured: %s") \ - % plugin) - - _plugins[plugin.name] = plugin - def __init__(self, collecty, **kwargs): - threading.Thread.__init__(self, name=self.description) - self.daemon = True - self.collecty = collecty # Check if this plugin was configured correctly. @@ -122,11 +117,7 @@ class Plugin(threading.Thread): # Run some custom initialization. self.init(**kwargs) - # Keepalive options - self.running = True - self.timer = Timer(self.interval) - - self.log.info(_("Successfully initialized %s") % self.__class__.__name__) + self.log.debug(_("Successfully initialized %s") % self.__class__.__name__) @property def path(self): @@ -155,6 +146,9 @@ class Plugin(threading.Thread): now = datetime.datetime.utcnow() try: result = o.collect() + + if isinstance(result, tuple) or isinstance(result, list): + result = ":".join(("%s" % e for e in result)) except: self.log.warning(_("Unhandled exception in %s.collect()") % o, exc_info=True) continue @@ -170,32 +164,11 @@ class Plugin(threading.Thread): self.collecty.write_queue.add(o, now, result) # Returns the time this function took to complete. - return (time.time() - time_start) + delay = time.time() - time_start - def run(self): - self.log.debug(_("%s plugin has started") % self.name) - - # Initially collect everything - self.collect() - - while self.running: - # Reset the timer. - self.timer.reset() - - # Wait until the timer has successfully elapsed. - if self.timer.wait(): - delay = self.collect() - self.timer.reset(delay) - - self.log.debug(_("%s plugin has stopped") % self.name) - - def shutdown(self): - self.log.debug(_("Received shutdown signal.")) - self.running = False - - # Kill any running timers. - if self.timer: - self.timer.cancel() + # Log some warning when a collect method takes too long to return some data + if delay >= 60: + self.log.warning(_("A worker thread was stalled for %.4fs") % delay) def get_object(self, id): for object in self.objects: @@ -204,24 +177,24 @@ class Plugin(threading.Thread): return object - def get_template(self, template_name): + def get_template(self, template_name, object_id): for template in self.templates: if not template.name == template_name: continue - return template(self) + return template(self, object_id) def generate_graph(self, template_name, object_id="default", **kwargs): - template = self.get_template(template_name) + template = self.get_template(template_name, object_id=object_id) if not template: raise RuntimeError("Could not find template %s" % template_name) time_start = time.time() - graph = template.generate_graph(object_id=object_id, **kwargs) + graph = template.generate_graph(**kwargs) duration = time.time() - time_start - self.log.info(_("Generated graph %s in %.1fms") \ + self.log.debug(_("Generated graph %s in %.1fms") \ % (template, duration * 1000)) return graph @@ -272,7 +245,19 @@ class Object(object): """ The absolute path to the RRD file of this plugin. """ - return os.path.join(DATABASE_DIR, self.plugin.path, "%s.rrd" % self.id) + filename = self._normalise_filename("%s.rrd" % self.id) + + return os.path.join(DATABASE_DIR, self.plugin.path, filename) + + @staticmethod + def _normalise_filename(filename): + # Convert the filename into ASCII characters only + filename = unicodedata.normalize("NFKC", filename) + + # Replace any spaces by dashes + filename = filename.replace(" ", "-") + + return filename ### Basic methods @@ -378,6 +363,16 @@ class GraphTemplate(object): # A unique name to identify this graph template. name = None + # Headline of the graph image + graph_title = None + + # Vertical label of the graph + graph_vertical_label = None + + # Limits + lower_limit = None + upper_limit = None + # Instructions how to create the graph. rrd_graph = None @@ -396,9 +391,15 @@ class GraphTemplate(object): height = GRAPH_DEFAULT_HEIGHT width = GRAPH_DEFAULT_WIDTH - def __init__(self, plugin): + def __init__(self, plugin, object_id): self.plugin = plugin + # Get all required RRD objects + self.object_id = object_id + + # Get the main object + self.object = self.get_object(self.object_id) + def __repr__(self): return "<%s>" % self.__class__.__name__ @@ -410,18 +411,38 @@ class GraphTemplate(object): def log(self): return self.plugin.log - def _make_command_line(self, interval, width=None, height=None): + def _make_command_line(self, interval, format=DEFAULT_IMAGE_FORMAT, + width=None, height=None): args = [] args += GRAPH_DEFAULT_ARGUMENTS args += [ + "--imgformat", format, "--height", "%s" % (height or self.height), "--width", "%s" % (width or self.width), ] args += self.rrd_graph_args + # Graph title + if self.graph_title: + args += ["--title", self.graph_title] + + # Vertical label + if self.graph_vertical_label: + args += ["--vertical-label", self.graph_vertical_label] + + if self.lower_limit is not None or self.upper_limit is not None: + # Force to honour the set limits + args.append("--rigid") + + if self.lower_limit is not None: + args += ["--lower-limit", self.lower_limit] + + if self.upper_limit is not None: + args += ["--upper-limit", self.upper_limit] + # Add interval args.append("--start") @@ -432,26 +453,36 @@ class GraphTemplate(object): return args - def get_object_table(self, object_id): + def get_object(self, *args, **kwargs): + return self.plugin.get_object(*args, **kwargs) + + def get_object_table(self): return { - "file" : self.plugin.get_object(object_id), + "file" : self.object, } - def get_object_files(self, object_id): + @property + def object_table(self): + if not hasattr(self, "_object_table"): + self._object_table = self.get_object_table() + + return self._object_table + + def get_object_files(self): files = {} - for id, obj in self.get_object_table(object_id).items(): + for id, obj in self.object_table.items(): files[id] = obj.file return files - def generate_graph(self, object_id, interval=None, **kwargs): + def generate_graph(self, interval=None, **kwargs): args = self._make_command_line(interval, **kwargs) self.log.info(_("Generating graph %s") % self) self.log.debug(" args: %s" % args) - object_files = self.get_object_files(object_id) + object_files = self.get_object_files() for item in self.rrd_graph: try: @@ -459,9 +490,14 @@ class GraphTemplate(object): except TypeError: args.append(item) + self.log.debug(" %s" % args[-1]) + return self.write_graph(*args) def write_graph(self, *args): + # Convert all arguments to string + args = [str(e) for e in args] + with tempfile.NamedTemporaryFile() as f: rrdtool.graph(f.name, *args)