X-Git-Url: http://git.ipfire.org/?p=collecty.git;a=blobdiff_plain;f=src%2Fcollecty%2Fplugins%2Fbase.py;h=cf9c3b402410bf085fd7b0e825644aef42d6fba4;hp=6c31edebf9d5969e02f6e601994192e679f59f7d;hb=a9af411f0703eac939e0df5d5f75b46d35f531bc;hpb=f648421abc982b94bc1db15cedccf6c177ae1a25 diff --git a/src/collecty/plugins/base.py b/src/collecty/plugins/base.py index 6c31ede..cf9c3b4 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,10 +117,6 @@ class Plugin(threading.Thread): # Run some custom initialization. self.init(**kwargs) - # Keepalive options - self.running = True - self.timer = Timer(self.interval) - self.log.debug(_("Successfully initialized %s") % self.__class__.__name__) @property @@ -156,8 +147,7 @@ class Plugin(threading.Thread): try: result = o.collect() - if isinstance(result, tuple) or isinstance(result, list): - result = ":".join(("%s" % e for e in result)) + result = self._format_result(result) except: self.log.warning(_("Unhandled exception in %s.collect()") % o, exc_info=True) continue @@ -173,32 +163,30 @@ 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) + # 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) - # Initially collect everything - self.collect() + @staticmethod + def _format_result(result): + if not isinstance(result, tuple) and not isinstance(result, list): + return result - while self.running: - # Reset the timer. - self.timer.reset() + # Replace all Nones by NaN + s = [] - # Wait until the timer has successfully elapsed. - if self.timer.wait(): - delay = self.collect() - self.timer.reset(delay) + for e in result: + if e is None: + e = "NaN" - self.log.debug(_("%s plugin has stopped") % self.name) + # Format as string + e = "%s" % e - def shutdown(self): - self.log.debug(_("Received shutdown signal.")) - self.running = False + s.append(e) - # Kill any running timers. - if self.timer: - self.timer.cancel() + return ":".join(s) def get_object(self, id): for object in self.objects: @@ -235,9 +223,12 @@ class Object(object): rrd_schema = None # RRA properties. - rra_types = ["AVERAGE", "MIN", "MAX"] - rra_timespans = [3600, 86400, 604800, 2678400, 31622400] - rra_rows = 2880 + rra_types = ("AVERAGE", "MIN", "MAX") + rra_timespans = ( + ("1m", "10d"), + ("1h", "18M"), + ("1d", "5y"), + ) def __init__(self, plugin, *args, **kwargs): self.plugin = plugin @@ -275,7 +266,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 @@ -341,21 +344,9 @@ class Object(object): xff = 0.1 - cdp_length = 0 - for rra_timespan in self.rra_timespans: - if (rra_timespan / self.stepsize) < self.rra_rows: - rra_timespan = self.stepsize * self.rra_rows - - if cdp_length == 0: - cdp_length = 1 - else: - cdp_length = rra_timespan // (self.rra_rows * self.stepsize) - - cdp_number = math.ceil(rra_timespan / (cdp_length * self.stepsize)) - - for rra_type in self.rra_types: - schema.append("RRA:%s:%.10f:%d:%d" % \ - (rra_type, xff, cdp_length, cdp_number)) + for steps, rows in self.rra_timespans: + for type in self.rra_types: + schema.append("RRA:%s:%s:%s:%s" % (type, xff, steps, rows)) return schema @@ -401,6 +392,7 @@ class GraphTemplate(object): None : "-3h", "hour" : "-1h", "day" : "-25h", + "month": "-30d", "week" : "-360h", "year" : "-365d", } @@ -461,13 +453,13 @@ class GraphTemplate(object): if self.upper_limit is not None: args += ["--upper-limit", self.upper_limit] - # Add interval - args.append("--start") - try: - args.append(self.intervals[interval]) + interval = self.intervals[interval] except KeyError: - args.append(str(interval)) + interval = "end-%s" % interval + + # Add interval + args += ["--start", interval] return args @@ -479,10 +471,17 @@ class GraphTemplate(object): "file" : self.object, } + @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().items(): + for id, obj in self.object_table.items(): files[id] = obj.file return files @@ -503,17 +502,9 @@ class GraphTemplate(object): self.log.debug(" %s" % args[-1]) - return self.write_graph(*args) - - def write_graph(self, *args): - # Convert all arguments to string + # Convert arguments to string args = [str(e) for e in args] - with tempfile.NamedTemporaryFile() as f: - rrdtool.graph(f.name, *args) - - # Get back to the beginning of the file - f.seek(0) + graph = rrdtool.graphv("-", *args) - # Return all the content - return f.read() + return graph.get("image")