]> git.ipfire.org Git - collecty.git/blobdiff - src/collecty/plugins/base.py
Add code to localise graph templates
[collecty.git] / src / collecty / plugins / base.py
index 048ebcb9804a1787f78dc91852243eb5d4e84f68..c9e815c52d48ebd831af1a5a05f188adc0473df9 100644 (file)
@@ -29,6 +29,7 @@ import threading
 import time
 import unicodedata
 
+from .. import locales
 from ..constants import *
 from ..i18n import _
 
@@ -64,6 +65,41 @@ class Timer(object):
                return self.elapsed > self.timeout
 
 
+class Environment(object):
+       """
+               Sets the correct environment for rrdtool to create
+               localised graphs and graphs in the correct timezone.
+       """
+       def __init__(self, timezone, locale):
+               # Build the new environment
+               self.new_environment = {
+                       "TZ" : timezone or DEFAULT_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:
+                       self.old_environment[k] = os.environ.get(k, None)
+
+               # Apply the new one
+               os.environ.update(self.new_environment)
+
+       def __exit__(self, type, value, traceback):
+               # Roll back to the previous environment
+               for k, v in self.old_environment.items():
+                       if v is None:
+                               try:
+                                       del os.environ[k]
+                               except KeyError:
+                                       pass
+                       else:
+                               os.environ[k] = v
+
+
 class PluginRegistration(type):
        plugins = {}
 
@@ -147,8 +183,7 @@ class Plugin(object, metaclass=PluginRegistration):
                        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
@@ -170,6 +205,25 @@ class Plugin(object, metaclass=PluginRegistration):
                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 NaN
+               s = []
+
+               for e in result:
+                       if e is None:
+                               e = "NaN"
+
+                       # Format as string
+                       e = "%s" % e
+
+                       s.append(e)
+
+               return ":".join(s)
+
        def get_object(self, id):
                for object in self.objects:
                        if not object.id == id:
@@ -177,15 +231,17 @@ class Plugin(object, metaclass=PluginRegistration):
 
                        return object
 
-       def get_template(self, template_name, object_id):
+       def get_template(self, template_name, object_id, locale=None, timezone=None):
                for template in self.templates:
                        if not template.name == template_name:
                                continue
 
-                       return template(self, object_id)
+                       return template(self, object_id, locale=locale, timezone=timezone)
 
-       def generate_graph(self, template_name, object_id="default", **kwargs):
-               template = self.get_template(template_name, object_id=object_id)
+       def generate_graph(self, template_name, object_id="default",
+                       timezone=None, locale=None, **kwargs):
+               template = self.get_template(template_name, object_id=object_id,
+                       timezone=timezone, locale=locale)
                if not template:
                        raise RuntimeError("Could not find template %s" % template_name)
 
@@ -205,9 +261,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
@@ -323,21 +382,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
 
@@ -383,6 +430,7 @@ class GraphTemplate(object):
                None   : "-3h",
                "hour" : "-1h",
                "day"  : "-25h",
+               "month": "-30d",
                "week" : "-360h",
                "year" : "-365d",
        }
@@ -391,9 +439,13 @@ class GraphTemplate(object):
        height = GRAPH_DEFAULT_HEIGHT
        width  = GRAPH_DEFAULT_WIDTH
 
-       def __init__(self, plugin, object_id):
+       def __init__(self, plugin, object_id, locale=None, timezone=None):
                self.plugin = plugin
 
+               # Save localisation parameters
+               self.locale = locales.get(locale)
+               self.timezone = timezone
+
                # Get all required RRD objects
                self.object_id = object_id
 
@@ -443,13 +495,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
 
@@ -492,17 +544,10 @@ 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)
+               with Environment(self.timezone, self.locale.lang):
+                       graph = rrdtool.graphv("-", *args)
 
-                       # Return all the content
-                       return f.read()
+               return graph.get("image")