--- 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)