]> git.ipfire.org Git - collecty.git/blobdiff - src/collecty/plugins/base.py
Make RRD key names accessible as a list
[collecty.git] / src / collecty / plugins / base.py
index ca98ef721df25085098e1a744e2b860a15d8cd18..dd7f0d99156695c437c2035e18ab0aefe7b80368 100644 (file)
@@ -23,6 +23,7 @@ import datetime
 import logging
 import math
 import os
+import re
 import rrdtool
 import tempfile
 import threading
@@ -33,6 +34,8 @@ from .. import locales
 from ..constants import *
 from ..i18n import _
 
+DEF_MATCH = re.compile(r"C?DEF:([A-Za-z0-9_]+)=")
+
 class Timer(object):
        def __init__(self, timeout, heartbeat=1):
                self.timeout = timeout
@@ -264,6 +267,13 @@ class Plugin(object, metaclass=PluginRegistration):
 
                return template.graph_info()
 
+       def last_update(self, object_id="default"):
+               object = self.get_object(object_id)
+               if not object:
+                       raise RuntimeError("Could not find object %s" % object_id)
+
+               return object.last_update()
+
 
 class Object(object):
        # The schema of the RRD database.
@@ -359,6 +369,39 @@ class Object(object):
        def info(self):
                return rrdtool.info(self.file)
 
+       def last_update(self):
+               """
+                       Returns a dictionary with the timestamp and
+                       data set of the last database update.
+               """
+               return {
+                       "dataset"   : self.last_dataset,
+                       "timestamp" : self.last_updated,
+               }
+
+       def _last_update(self):
+               return rrdtool.lastupdate(self.file)
+
+       @property
+       def last_updated(self):
+               """
+                       Returns the timestamp when this database was last updated
+               """
+               lu = self._last_update()
+
+               if lu:
+                       return lu.get("date")
+
+       @property
+       def last_dataset(self):
+               """
+                       Returns the latest dataset in the database
+               """
+               lu = self._last_update()
+
+               if lu:
+                       return lu.get("ds")
+
        @property
        def stepsize(self):
                return self.plugin.interval
@@ -397,6 +440,31 @@ class Object(object):
 
                return schema
 
+       @property
+       def rrd_schema_names(self):
+               ret = []
+
+               for line in self.rrd_schema:
+                       (prefix, name, type, lower_limit, upper_limit) = line.split(":")
+                       ret.append(name)
+
+               return ret
+
+       def make_rrd_defs(self, prefix=None):
+               defs = []
+
+               for name in self.rrd_schema_names:
+                       if prefix:
+                               p = "%s_%s" % (prefix, name)
+                       else:
+                               p = name
+
+                       defs += [
+                               "DEF:%s=%s:%s:AVERAGE" % (p, self.file, name),
+                       ]
+
+               return defs
+
        def execute(self):
                if self.collected:
                        raise RuntimeError("This object has already collected its data")
@@ -414,6 +482,9 @@ class Object(object):
                # Make sure that the RRD database has been created
                self.create()
 
+               # Write everything to disk that is in the write queue
+               self.collecty.write_queue.commit_file(self.file)
+
 
 class GraphTemplate(object):
        # A unique name to identify this graph template.
@@ -473,21 +544,29 @@ class GraphTemplate(object):
                return self.plugin.log
 
        def _make_command_line(self, interval, format=DEFAULT_IMAGE_FORMAT,
-                       width=None, height=None):
-               args = []
+                       width=None, height=None, with_title=True, thumbnail=False):
+               args = [e for e in GRAPH_DEFAULT_ARGUMENTS]
+
+               # Set the default dimensions
+               default_height, default_width = GRAPH_DEFAULT_HEIGHT, GRAPH_DEFAULT_WIDTH
 
-               args += GRAPH_DEFAULT_ARGUMENTS
+               # 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
 
                args += [
                        "--imgformat", format,
-                       "--height", "%s" % (height or self.height),
-                       "--width", "%s" % (width or self.width),
+                       "--height", "%s" % (height or default_height),
+                       "--width", "%s" % (width or default_width),
                ]
 
                args += self.rrd_graph_args
 
                # Graph title
-               if self.graph_title:
+               if with_title and self.graph_title:
                        args += ["--title", self.graph_title]
 
                # Vertical label
@@ -514,6 +593,27 @@ class GraphTemplate(object):
 
                return args
 
+       def _add_vdefs(self, args):
+               ret = []
+
+               for arg in args:
+                       ret.append(arg)
+
+                       # Search for all DEFs and CDEFs
+                       m = re.match(DEF_MATCH, "%s" % arg)
+                       if m:
+                               name = m.group(1)
+
+                               # Add the VDEFs for minimum, maximum, etc. values
+                               ret += [
+                                       "VDEF:%s_cur=%s,LAST" % (name, name),
+                                       "VDEF:%s_avg=%s,AVERAGE" % (name, name),
+                                       "VDEF:%s_max=%s,MAXIMUM" % (name, name),
+                                       "VDEF:%s_min=%s,MINIMUM" % (name, name),
+                               ]
+
+               return ret
+
        def get_object(self, *args, **kwargs):
                return self.plugin.get_object(*args, **kwargs)
 
@@ -538,24 +638,29 @@ class GraphTemplate(object):
                return files
 
        def generate_graph(self, interval=None, **kwargs):
+               # Make sure that all collected data is in the database
+               # to get a recent graph image
+               if self.object:
+                       self.object.commit()
+
                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_files = self.get_object_files()
 
-               for item in self.rrd_graph:
-                       try:
-                               args.append(item % object_files)
-                       except TypeError:
-                               args.append(item)
+               if self.object:
+                       args += self.object.make_rrd_defs()
 
-                       self.log.debug("  %s" % args[-1])
+               args += self.rrd_graph
+               args = self._add_vdefs(args)
 
                # Convert arguments to string
                args = [str(e) for e in args]
 
+               for arg in args:
+                       self.log.debug("  %s" % arg)
+
                with Environment(self.timezone, self.locale.lang):
                        graph = rrdtool.graphv("-", *args)