pvl/rrd/rrds.py
author Tero Marttila <terom@paivola.fi>
Sun, 10 Feb 2013 13:20:29 +0200
changeset 205 f7658198c224
parent 156 999ae3e9fdec
permissions -rw-r--r--
pvl.verkko.hosts: refactor RealtimeHandler to use HostsTable
"""
    RRD file on filesystem.
"""

import os, os.path, errno

import logging; log = logging.getLogger('pvl.rrd.rrds')

class RRDDatabase (object) :
    """
        A filesystem directory containing .rrd files.
    """

    def __init__ (self, path, graph_type, cache=None) :
        """
            path        - path to rrd dirs
            graph_type  - pvl.rrd.graph.InterfaceGraph type
            cache       - optional RRDCache
        """

        if not path :
            raise ValueError("RRDDatabase: no path given")

        log.info("%s: graph_type=%s, cache=%s", path, graph_type, cache)
        
        self.graph_type = graph_type
        self._path = path
        self.cache = cache

    def path (self, *nodes) :
        """
            Lookup and full filesystem path to the given relative RRD/dir path.

            Raises ValueError if invalid path.
        """
        
        # relative dir (no leading slash) -> absolute path
        path = os.path.normpath(os.path.join(self._path, *nodes))

        log.debug("%s: %s -> %s", self, nodes, path)

        # check inside base path
        if not path.startswith(self._path.rstrip('/')) :
            # mask
            raise ValueError("%s: Invalid path: %s" % (self, nodes))

        # ok
        return path

    def tree (self, node=None) :
        """
            Lookup and return XXX:RRDTree for given node, or root tree.

            Raises ValueError if invalid path, or no such tree.
        """
        
        # lookup fs path
        if node :
            path = self.path(node)
        else :
            path = self.path()

        # found?
        if not os.path.isdir(path) :
            raise ValueError("%s: Invalid tree: %s: %s" % (self, node, path))
        
        if node :
            return node
        else :
            return '' # equivalent

    def rrd (self, node, tree=None) :
        """
            Lookup and return RRD for given node.
        """

        if tree :
            node = os.path.join(tree, node)
        
        path = self.path(node) + '.rrd'

        if not os.path.isfile(path) :
            raise ValueError("%: Invalid rrd: %s: %s" % (self, node, path))

        return RRD(self, node, self.graph_type)

    def list (self, tree=None) :
        """
            List (trees, rrds) under given tree.
        """

        dirs = []
        rrds = []

        path = self.path(tree)
        
        for name in os.listdir(path) :
            if name.startswith('.') :
                continue
            
            path = self.path(tree, name)

            basename, extname = os.path.splitext(name)
            
            log.debug("%s: %s: %s: %s", self, tree, name, path)

            if os.path.isdir(path) :
                dirs.append(name)

            elif extname == '.rrd' :
                # without the .rrd
                rrds.append(basename)

        # return sorted lists
        return sorted(dirs), sorted(rrds)
    
    def graph (self, rrd, style, interval, cache=True) :
        """
            Cached RRD.graph(), returning outfile.

            Uses self.cache if cache, otherwise graphs to tempfile.
        """
        
        path = rrd.path()
        
        if self.cache and cache :
            # path in cache
            out = self.cache.path(style, interval, str(rrd))

            # hit/miss?
            cache = self.cache.lookup(path, out)

        else :
            # tempfile
            out = None
            cache = None

        log.debug("%s: %s: %s", self, rrd, out)
        
        if cache :
            # from cache
            outfile = open(out)

        else :
            # to cache/tempfile
            dimensions, lines, outfile = rrd.graph(style, interval).graph(out)

        return outfile

    def __str__ (self) :
        return self._path

class RRD (object) :
    """
        An .rrd file on the filesystem, graphed using pvl.rrd.graph.Interface.
    """

    def __init__ (self, db, rrd, graph_type) :
        self.db = db
        self.rrd = rrd
        self.graph_type = graph_type

    def path (self) :
        return self.db.path(self.rrd) + '.rrd'

    def graph (self, style, interval) :
        """
            Build Graph given rrd using given style/interval.
        """
        
        path = self.path()

        title = str(self) # " / ".join(rrd.split('/'))
        return self.graph_type.build(title, path, style, interval)

    def __str__ (self) :
        return self.rrd

class RRDCache (object) :
    """
        Cache graph output in the filesystem.
    """

    def __init__ (self, path) :
        log.info("%s", path)

        self._path = path

    def path (self, *key) :
        """
            Return cache path for key.
        """

        return os.path.join(self._path, *key) + '.png'

    def _stat (self, path) :
        """
            os.stat or None.
        """

        try :
            return os.stat(path)

        except OSError as ex :
            if ex.errno == errno.ENOENT :
                return None
            else :
                raise

    def lookup (self, source, path) :
        """
            Return hit from cache.
        """

        # create
        dir = os.path.dirname(path)
        if not os.path.isdir(dir) :
            log.warn("makedirs %s", dir)
            os.makedirs(dir)
        
        # stats's
        src = self._stat(source)
        dst = self._stat(path)

        if dst and src.st_mtime < dst.st_mtime :
            log.debug("%s: %s: %s: hit", self, source, path)

            return True

        elif not dst:
            log.debug("%s: %s: %s: miss", self, source, path)
            return False
        
        else :
            log.debug("%s: %s: %s: update", self, source, path)
            return None

    def __str__ (self) :
        return self._path