pvl.web.rrd: split out pvl.rrd.args + pvl.rrd.rrds; bugfix
authorTero Marttila <terom@paivola.fi>
Sun, 20 Jan 2013 22:46:17 +0200
changeset 156 999ae3e9fdec
parent 155 9f2967ba81ef
child 157 2cc65177260c
pvl.web.rrd: split out pvl.rrd.args + pvl.rrd.rrds; bugfix
bin/pvl.rrd-graph
bin/pvl.verkko-rrd
pvl/rrd/__init__.py
pvl/rrd/args.py
pvl/rrd/graph.py
pvl/rrd/rrds.py
pvl/verkko/rrd.py
pvl/web/__init__.py
pvl/web/urls.py
--- a/bin/pvl.rrd-graph	Sun Jan 20 19:52:41 2013 +0200
+++ b/bin/pvl.rrd-graph	Sun Jan 20 22:46:17 2013 +0200
@@ -7,6 +7,7 @@
 __version__ = '0.1'
 
 import pvl.args
+import pvl.rrd.args
 import pvl.rrd.graph
 
 import os.path
@@ -33,13 +34,13 @@
     
     # options
     parser.add_option_group(pvl.args.parser(parser))
+    parser.add_option_group(pvl.rrd.args.parser(parser))
 
     parser.add_option('--style',        metavar='STYLE',        default='detail',
             help="overview/detail")
+
     parser.add_option('--interval',     metavar='INTERVAL',     default='daily',
             help="daily/weekly/yearly")
-    parser.add_option('--graph',        metavar='PATH',         default='.png',
-            help="output file")
 
     # parse
     options, args = parser.parse_args(argv[1:])
@@ -49,11 +50,12 @@
 
     return options, args
 
-def graph (options, rrd) :
+def graph (options, rrds, rrd) :
     """
         Graph given rrd.
     """
 
+
     # out
     path, ext = os.path.splitext(rrd)
     ext = options.graph
@@ -70,9 +72,12 @@
     """
 
     options, args = parse_options(argv)
+
+    # RRDDatabase
+    rrds = pvl.rrd.args.apply(options)
     
     for rrd in args :
-        graph(options, rrd)
+        graph(options, rrds, rrd)
 
     # done
     log.info("Exiting...")
--- a/bin/pvl.verkko-rrd	Sun Jan 20 19:52:41 2013 +0200
+++ b/bin/pvl.verkko-rrd	Sun Jan 20 22:46:17 2013 +0200
@@ -8,6 +8,7 @@
 
 from pvl import __version__
 import pvl.args
+import pvl.rrd.args
 import pvl.verkko.rrd
 
 import optparse
@@ -31,15 +32,7 @@
 
     # common
     parser.add_option_group(pvl.args.parser(parser))
-
-    parser.add_option('--rrd-type', metavar='TYPE', default='collectd',
-        help="mrtg/collectd")
-
-    parser.add_option('--rrd', metavar='PATH',
-        help="Find RRD files")
-
-    parser.add_option('--cache', metavar='PATH',
-        help="Cache RRD graphs")
+    parser.add_option_group(pvl.rrd.args.parser(parser))
 
     # parse
     options, args = parser.parse_args(args)
@@ -49,7 +42,6 @@
 
     return options, args
 
-
 def main (argv) :
     """
         pvl.verkko wsgi development server.
@@ -59,14 +51,8 @@
     options, args = parse_argv(argv, doc=__doc__)
 
     # rrd
-    rrd_type = pvl.rrd.graph.interface(options.rrd_type)
-
-    if not options.rrd :
-        log.error("no --rrd given")
-        return 2
-
-    rrd = pvl.verkko.rrd.RRDDatabase(rrd_type, options.rrd, options.cache)
-
+    rrd = pvl.rrd.args.apply(options)
+ 
     # app
     application = pvl.verkko.rrd.Application(rrd)
 
--- a/pvl/rrd/__init__.py	Sun Jan 20 19:52:41 2013 +0200
+++ b/pvl/rrd/__init__.py	Sun Jan 20 22:46:17 2013 +0200
@@ -4,6 +4,10 @@
     Requires:
         python-rrdtool
 """
+from pvl.rrd.rrds import (
+    RRDDatabase,
+    RRDCache,
+)
 
 from pvl.rrd import (
     api as rrd,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/rrd/args.py	Sun Jan 20 22:46:17 2013 +0200
@@ -0,0 +1,43 @@
+import optparse
+
+import pvl.rrd.graph # interface
+from pvl.rrd import RRDDatabase, RRDCache
+
+def parser (parser) :
+    """
+        optparse OptionGroup.
+    """
+
+    parser = optparse.OptionGroup(parser, "RRD options")
+
+    parser.add_option('--rrd-type', metavar='TYPE', default='collectd',
+        help="mrtg/collectd")
+
+    parser.add_option('--rrd', metavar='PATH',
+        help="Find RRD files")
+
+    parser.add_option('--rrd-cache', metavar='PATH',
+        help="Cache RRD graphs")
+
+    return parser
+
+def apply (options) :
+    """
+        Return RRDDatabase from options.
+    """
+    # path
+    if not options.rrd :
+        log.error("no --rrd given")
+        sys.exit(2)
+    
+    # type
+    graph_type = pvl.rrd.graph.interface_type(options.rrd_type)
+    
+    # cache
+    if options.rrd_cache :
+        cache = RRDCache(options.rrd_cache)
+    else :
+        cache = None
+
+    return RRDDatabase(options.rrd, graph_type, cache)
+
--- a/pvl/rrd/graph.py	Sun Jan 20 19:52:41 2013 +0200
+++ b/pvl/rrd/graph.py	Sun Jan 20 22:46:17 2013 +0200
@@ -3,6 +3,8 @@
 
 from pvl.invoke import merge # XXX
 
+import logging; log = logging.getLogger('pvl.rrd.graph')
+
 """
     RRDTool graph builders.
 
@@ -70,13 +72,7 @@
                 interval    - interval(interval=...)
         """
 
-        graph = cls()
-        graph = graph.common(title)
-        graph = graph.source(rrd)
-        graph = graph.style(style)
-        graph = graph.interval(interval)
-
-        return graph
+        return cls().common(title).source(rrd).style(style).interval(interval)
 
     # builders
     def common (self, title) :
@@ -124,6 +120,8 @@
         if not (self.IN and self.OUT) :
             raise TypeError("No IN/OUT DS names for %s" % (self, ))
 
+        log.debug("%s", rrd)
+
         return self(
             # data sources, bytes/s
             r'DEF:in0={rrd}:{ds_in}:AVERAGE'.format(rrd=rrd, ds_in=self.IN),
@@ -282,7 +280,7 @@
     'collectd':     CollectdIfOctets,
 }
 
-def interface (type) :
+def interface_type (type) :
     """
         Lookup Interface -subclass for given type.
     """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/rrd/rrds.py	Sun Jan 20 22:46:17 2013 +0200
@@ -0,0 +1,239 @@
+"""
+    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
--- a/pvl/verkko/rrd.py	Sun Jan 20 19:52:41 2013 +0200
+++ b/pvl/verkko/rrd.py	Sun Jan 20 22:46:17 2013 +0200
@@ -3,7 +3,7 @@
     http://verkko.paivola.fi/rrd
 """
 
-import pvl.web.application as web
+from pvl import web
 from pvl.web import urls
 from pvl.web.html import tags as html
 
@@ -11,186 +11,6 @@
 
 import logging; log = logging.getLogger('pvl.verkko.rrd')
 
-# Model
-import os, os.path, errno
-
-class RRDDatabase (object) :
-    """
-        A filesystem directory containing .rrd files.
-    """
-
-    def __init__ (self, graph, path, cache=None) :
-        """
-            graph   - pvl.rrd.graph.InterfaceGraph type
-            path    - path to rrd dirs
-            cache   - path to cache dirs
-        """
-
-        if not path :
-            raise ValueError("RRDDatabase: no path given")
-
-        log.info("%s: type=%s, cache=%s", path, graph, cache)
-        
-        self._graph = graph
-        self._path = path
-        self._cache = cache
-
-    def path (self, node=None, *subnodes) :
-        """
-            Lookup and full filesystem path to the given relative RRD/dir path.
-
-            Raises ValueError if invalid path.
-        """
-        
-        if node :
-            # relative dir (no leading slash) -> absolute path
-            path = os.path.normpath(os.path.join(self._path, node, *subnodes))
-        else :
-            path = self._path
-
-        log.debug("%s: %s -> %s", self, node, path)
-
-        # check inside base path
-        if not path.startswith(self._path) :
-            # mask
-            raise ValueError("%s: Invalid path: %s" % (self, node))
-
-        # ok
-        return path
-
-    def tree (self, node=None) :
-        """
-            Lookup and return RRDTree for given node, or root tree.
-
-            Raises ValueError if invalid path, or no such tree.
-        """
-
-        # lookup fs path
-        path = self.path(node)
-
-        # found?
-        if not os.path.isdir(path) :
-            raise ValueError("%s: Invalid tree: %s: %s" % (self, node, path))
-
-        return node
-
-    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 node
-
-    def list (self, tree) :
-        """
-            List (trees, rrds) under given tree.
-        """
-
-        dirs = []
-        rrds = []
-        
-        for name in os.listdir(self.path(tree)) :
-            if name.startswith('.') :
-                continue
-
-            log.debug("%s: %s: %s", self, tree, name)
-            
-            path = self.path(tree, name)
-            basename, extname = os.path.splitext(name)
-
-            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 _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 cache (self, source, *key) :
-        """
-            Lookup given key from cache, returning (hit, file).
-        """
-
-        # output
-        if not self._cache :
-            return None, None
-
-        # cache path
-        path = os.path.join(self._cache, *key)
-        
-        # 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 not dst:
-            log.debug("%s: %s: %s: miss", self._cache, source, path)
-            return None, path
-            
-        elif dst and src.st_mtime < dst.st_mtime :
-            log.debug("%s: %s: %s: hit", self._cache, source, path)
-
-            return True, path
-
-        else :
-            log.debug("%s: %s: %s: update", self._cache, source, path)
-            return False, path
-
-    def graph (self, rrd, style, interval) :
-        """
-            Graph given rrd using given style/interval, returning the opened png data file.
-        """
-        
-        title = str(rrd) # " / ".join(rrd.split('/'))
-        
-        path = self.path(rrd) + '.rrd'
-
-        cached, out = self.cache(path, style, interval, rrd + '.png')
-        
-        log.debug("%s: %s: %s", self, rrd, out)
-        
-        if cached :
-            # from cache
-            outfile = open(out)
-
-        else :
-            # to cache
-            dimensions, lines, outfile = self._graph.build(title, path, style, interval).graph(out)
-
-        return outfile
-
-    def __str__ (self) :
-        return str(self._path)
-
 # View/Controller
 class Handler (web.Handler) :
     CSS = (
@@ -211,7 +31,7 @@
             tree = ''
             
             for part in _tree.split('/') :
-                tree = os.path.join(tree, part)
+                tree = urls.join(tree, part)
 
                 yield part, self.url(Index, tree=tree)
 
@@ -245,7 +65,7 @@
         """
         
         if self.tree :
-            path = os.path.join(self.tree, node)
+            path = urls.join(self.tree, node)
         else :
             path = node
         
@@ -260,6 +80,7 @@
             try :
                 # XXX: unicode?
                 self.tree = self.app.rrd.tree(tree)
+
             except ValueError as ex :
                 # mask
                 raise web.NotFound(tree)
@@ -368,7 +189,7 @@
             self.rrd = self.app.rrd.rrd(target, self.tree)
 
         except ValueError as ex :
-            raise web.NotFound(tree, target)
+            raise web.NotFound(tree + '/' + target)
         
         self.style = style
         self.interval = interval
@@ -401,6 +222,7 @@
 
 # WSGI
 class Application (web.Application) :
+    # dispatch
     urls = urls.Map((
         urls.rule('/',                              Index),
         urls.rule('/<target>',                      Target),
@@ -411,7 +233,7 @@
 
     def __init__ (self, rrd) :
         """
-            Initialize app with given RRDDatabase
+            rrd     - pvl.rrd.RRDDatabase
         """
 
         self.rrd = rrd
--- a/pvl/web/__init__.py	Sun Jan 20 19:52:41 2013 +0200
+++ b/pvl/web/__init__.py	Sun Jan 20 22:46:17 2013 +0200
@@ -6,6 +6,7 @@
 from pvl.web.application import Application, Handler
 
 # werkzeug
+from werkzeug.wrappers import Request, Response
 from werkzeug.exceptions import (
         HTTPException, 
         BadRequest,         # 400
--- a/pvl/web/urls.py	Sun Jan 20 19:52:41 2013 +0200
+++ b/pvl/web/urls.py	Sun Jan 20 22:46:17 2013 +0200
@@ -1,5 +1,8 @@
 from werkzeug.routing import Map, Rule
 
+# url_join
+from os.path import join 
+
 def rule (string, endpoint, **opts) :
     return Rule(string, endpoint=endpoint, defaults=opts)