]> git.ipfire.org Git - collecty.git/blobdiff - src/collecty/plugins/base.py
Allow getting the standard deviation of the plotted values
[collecty.git] / src / collecty / plugins / base.py
index 04207c7594b0755e5ab74ad9cd4d3e526375099f..c1666b6b3474809d9b7ef90d78e95e0257de4f83 100644 (file)
@@ -23,6 +23,7 @@ import datetime
 import logging
 import math
 import os
+import re
 import rrdtool
 import tempfile
 import threading
@@ -30,9 +31,12 @@ import time
 import unicodedata
 
 from .. import locales
+from .. import util
 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
@@ -437,6 +441,46 @@ 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 get_stddev(self, interval=None):
+               args = self.make_rrd_defs()
+
+               # Add the correct interval
+               args += ["--start", util.make_interval(interval)]
+
+               for name in self.rrd_schema_names:
+                       args += [
+                               "VDEF:%s_stddev=%s,STDEV" % (name, name),
+                               "PRINT:%s_stddev:%%lf" % name,
+                       ]
+
+               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")
@@ -454,6 +498,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.
@@ -475,15 +522,6 @@ class GraphTemplate(object):
        # Extra arguments passed to rrdgraph.
        rrd_graph_args = []
 
-       intervals = {
-               None   : "-3h",
-               "hour" : "-1h",
-               "day"  : "-25h",
-               "month": "-30d",
-               "week" : "-360h",
-               "year" : "-365d",
-       }
-
        # Default dimensions for this graph
        height = GRAPH_DEFAULT_HEIGHT
        width  = GRAPH_DEFAULT_WIDTH
@@ -513,21 +551,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
@@ -544,16 +590,32 @@ class GraphTemplate(object):
                        if self.upper_limit is not None:
                                args += ["--upper-limit", self.upper_limit]
 
-               try:
-                       interval = self.intervals[interval]
-               except KeyError:
-                       interval = "end-%s" % interval
-
                # Add interval
-               args += ["--start", interval]
+               args += ["--start", util.make_interval(interval)]
 
                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)
 
@@ -578,24 +640,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)