import logging
import math
import os
+import re
import rrdtool
import tempfile
import threading
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
return graph
+ def graph_info(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)
+
+ 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.
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
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")
# 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.
# 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
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
+
+ # A thumbnail doesn't have a legend and other labels
+ if thumbnail:
+ args.append("--only-graph")
- args += GRAPH_DEFAULT_ARGUMENTS
+ 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
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)
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)
- return graph.get("image")
+ return {
+ "image" : graph.get("image"),
+ "image_height" : graph.get("image_height"),
+ "image_width" : graph.get("image_width"),
+ }
+
+ def graph_info(self):
+ """
+ Returns a dictionary with useful information
+ about this graph.
+ """
+ return {
+ "title" : self.graph_title,
+ "object_id" : self.object_id or "",
+ "template" : self.name,
+ }