]> git.ipfire.org Git - collecty.git/blobdiff - src/collecty/plugins/base.py
psi: Add graph template
[collecty.git] / src / collecty / plugins / base.py
index 8f9cb7893ed6e3f617ee3123768d01fc365e8550..e8f8f308c0e2d6a842d3b112c1be1a8693e886cf 100644 (file)
 #                                                                             #
 ###############################################################################
 
-import datetime
 import logging
-import math
 import os
 import re
 import rrdtool
-import tempfile
-import threading
 import time
 import unicodedata
 
-from .. import locales
 from .. import util
 from ..constants import *
 from ..i18n import _
@@ -42,23 +37,25 @@ class Environment(object):
                Sets the correct environment for rrdtool to create
                localised graphs and graphs in the correct timezone.
        """
-       def __init__(self, timezone, locale):
+       def __init__(self, timezone="UTC", locale="en_US.utf-8"):
                # Build the new environment
                self.new_environment = {
-                       "TZ" : timezone or DEFAULT_TIMEZONE,
+                       "LANGUAGE" : locale,
+                       "LC_ALL"   : locale,
+                       "TZ"       : timezone,
                }
 
-               for k in ("LANG", "LC_ALL"):
-                       self.new_environment[k] = locale or DEFAULT_LOCALE
-
        def __enter__(self):
                # Save the current environment
                self.old_environment = {}
+
                for k in self.new_environment:
+                       # Store the old value
                        self.old_environment[k] = os.environ.get(k, None)
 
-               # Apply the new one
-               os.environ.update(self.new_environment)
+                       # Apply the new one
+                       if self.new_environment[k]:
+                               os.environ[k] = self.new_environment[k]
 
        def __exit__(self, type, value, traceback):
                # Roll back to the previous environment
@@ -150,25 +147,25 @@ class Plugin(object, metaclass=PluginRegistration):
                time_start = time.time()
 
                # Run through all objects of this plugin and call the collect method.
-               for o in self.objects:
-                       now = datetime.datetime.utcnow()
+               for object in self.objects:
+                       # Run collection
                        try:
-                               result = o.collect()
+                               result = object.collect()
 
-                               result = self._format_result(result)
-                       except:
-                               self.log.warning(_("Unhandled exception in %s.collect()") % o, exc_info=True)
+                       # Catch any unhandled exceptions
+                       except Exception as e:
+                               self.log.warning(_("Unhandled exception in %s.collect()") % object, exc_info=True)
                                continue
 
                        if not result:
-                               self.log.warning(_("Received empty result: %s") % o)
+                               self.log.warning(_("Received empty result: %s") % object)
                                continue
 
-                       self.log.debug(_("Collected %s: %s") % (o, result))
-
                        # Add the object to the write queue so that the data is written
                        # to the databases later.
-                       self.collecty.write_queue.add(o, now, result)
+                       result = self.collecty.write_queue.submit(object, result)
+
+                       self.log.debug(_("Collected %s: %s") % (object, result))
 
                # Returns the time this function took to complete.
                delay = time.time() - time_start
@@ -176,22 +173,8 @@ class Plugin(object, metaclass=PluginRegistration):
                # 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)
-
-       @staticmethod
-       def _format_result(result):
-               if not isinstance(result, tuple) and not isinstance(result, list):
-                       return result
-
-               # Replace all Nones by UNKNOWN
-               s = []
-
-               for e in result:
-                       if e is None:
-                               e = "U"
-
-                       s.append("%s" % e)
-
-               return ":".join(s)
+               else:
+                       self.log.debug(_("Collection finished in %.2fms") % (delay * 1000))
 
        def get_object(self, id):
                for object in self.objects:
@@ -216,7 +199,8 @@ class Plugin(object, metaclass=PluginRegistration):
 
                time_start = time.time()
 
-               graph = template.generate_graph(**kwargs)
+               with Environment(timezone=timezone, locale=locale):
+                       graph = template.generate_graph(**kwargs)
 
                duration = time.time() - time_start
                self.log.debug(_("Generated graph %s in %.1fms") \
@@ -256,9 +240,6 @@ class Object(object):
        def __init__(self, plugin, *args, **kwargs):
                self.plugin = plugin
 
-               # Indicates if this object has collected its data
-               self.collected = False
-
                # Initialise this object
                self.init(*args, **kwargs)
 
@@ -266,7 +247,7 @@ class Object(object):
                self.create()
 
        def __repr__(self):
-               return "<%s>" % self.__class__.__name__
+               return "<%s %s>" % (self.__class__.__name__, self.id)
 
        def __lt__(self, other):
                return self.id < other.id
@@ -449,16 +430,6 @@ class Object(object):
                x, y, vals = rrdtool.graph("/dev/null", *args)
                return dict(zip(self.rrd_schema_names, vals))
 
-       def execute(self):
-               if self.collected:
-                       raise RuntimeError("This object has already collected its data")
-
-               self.collected = True
-               self.now = datetime.datetime.utcnow()
-
-               # Call the collect
-               result = self.collect()
-
        def commit(self):
                """
                        Will commit the collected data to the database.
@@ -477,8 +448,11 @@ class Object(object):
                """
                filename = os.path.join(*args)
 
-               with open(filename) as f:
-                       value = f.read()
+               try:
+                       with open(filename) as f:
+                               value = f.read()
+               except FileNotFoundError as e:
+                       return None
 
                # Strip any excess whitespace
                if strip:
@@ -494,9 +468,50 @@ class Object(object):
 
                try:
                        return int(value)
-               except ValueError:
+               except (TypeError, ValueError):
                        return None
 
+       def read_proc_stat(self):
+               """
+                       Reads /proc/stat and returns it as a dictionary
+               """
+               ret = {}
+
+               with open("/proc/stat") as f:
+                       for line in f:
+                               # Split the key from the rest of the line
+                               key, line = line.split(" ", 1)
+
+                               # Remove any line breaks
+                               ret[key] = line.rstrip()
+
+               return ret
+
+       def read_proc_meminfo(self):
+               ret = {}
+
+               with open("/proc/meminfo") as f:
+                       for line in f:
+                               # Split the key from the rest of the line
+                               key, line = line.split(":", 1)
+
+                               # Remove any whitespace
+                               line = line.strip()
+
+                               # Remove any trailing kB
+                               if line.endswith(" kB"):
+                                       line = line[:-3]
+
+                               # Try to convert to integer
+                               try:
+                                       line = int(line)
+                               except (TypeError, ValueError):
+                                       continue
+
+                               ret[key] = line
+
+               return ret
+
 
 class GraphTemplate(object):
        # A unique name to identify this graph template.
@@ -518,15 +533,11 @@ class GraphTemplate(object):
        # Extra arguments passed to rrdgraph.
        rrd_graph_args = []
 
-       # Default dimensions for this graph
-       height = GRAPH_DEFAULT_HEIGHT
-       width  = GRAPH_DEFAULT_WIDTH
-
        def __init__(self, plugin, object_id, locale=None, timezone=None):
                self.plugin = plugin
 
                # Save localisation parameters
-               self.locale = locales.get(locale)
+               self.locale = locale
                self.timezone = timezone
 
                # Get all required RRD objects
@@ -557,17 +568,34 @@ class GraphTemplate(object):
 
        def _make_command_line(self, interval, format=DEFAULT_IMAGE_FORMAT,
                        width=None, height=None, with_title=True, thumbnail=False):
-               args = [e for e in GRAPH_DEFAULT_ARGUMENTS]
+               args = [
+                       # Change the background colour
+                       "--color", "BACK#FFFFFFFF",
+
+                       # Disable the border around the image
+                       "--border", "0",
+
+                       # Let's width and height define the size of the entire image
+                       "--full-size-mode",
+
+                       # Gives the curves a more organic look
+                       "--slope-mode",
+
+                       # Show nicer labels
+                       "--dynamic-labels",
+
+                       # Brand all generated graphs
+                       "--watermark", _("Created by collecty"),
+               ]
 
                # Set the default dimensions
-               default_height, default_width = GRAPH_DEFAULT_HEIGHT, GRAPH_DEFAULT_WIDTH
+               default_width, default_height = 960, 480
 
                # A thumbnail doesn't have a legend and other labels
                if thumbnail:
                        args.append("--only-graph")
 
-                       default_height = THUMBNAIL_DEFAULT_HEIGHT
-                       default_width = THUMBNAIL_DEFAULT_WIDTH
+                       default_width, default_height = 80, 20
 
                args += [
                        "--imgformat", format,
@@ -668,8 +696,7 @@ class GraphTemplate(object):
                for arg in args:
                        self.log.debug("  %s" % arg)
 
-               with Environment(self.timezone, self.locale.lang):
-                       graph = rrdtool.graphv("-", *args)
+               graph = rrdtool.graphv("-", *args)
 
                return {
                        "image"        : graph.get("image"),