-#!/usr/bin/python
+#!/usr/bin/python3
###############################################################################
# #
# collecty - A system statistics collection daemon for IPFire #
# #
###############################################################################
-from __future__ import division
-
import datetime
import logging
import math
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
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
# 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.
# 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):
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
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:
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
"""
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
# 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
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__
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")
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:
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)