--- a/rrdweb/wsgi.py Tue Jan 25 01:19:40 2011 +0200
+++ b/rrdweb/wsgi.py Tue Jan 25 01:28:06 2011 +0200
@@ -7,7 +7,7 @@
from werkzeug import Request, Response
from werkzeug.routing import Map, Rule
-from rrdweb import html, graph
+from rrdweb import html, graph, backend
import os, os.path
import errno
@@ -19,20 +19,22 @@
class WSGIApp (object) :
- def __init__ (self, rrdpath, tplpath, imgpath) :
+ def __init__ (self, rrdpath, tplpath, imgpath, rrdgraph=graph.pmacct_bytes) :
"""
Configure
rrdpath - path to directory containing *.rrd files
tplpath - path to HTML templates
imgpath - path to generated PNG images. Must be writeable
+
+ rrdgraph - the graph.*_data function for rendering the graphs.
"""
self.rrdpath = os.path.abspath(rrdpath)
self.templates = html.BaseFormatter(tplpath)
+ self.imgpath = os.path.abspath(imgpath)
- # XXX: some kind of fancy cache thingie :)
- self.imgpath = os.path.abspath(imgpath)
+ self.rrd_graph_func = rrdgraph
# wrap to use werkzeug's Request/Response
@@ -79,6 +81,25 @@
return response
+ def fs_path (self, name) :
+ """
+ Lookup and return the full filesystem path to the given relative RRD file/dir.
+
+ The given name must be a relative path (no leading /).
+
+ Raises NotFound for invalid paths.
+ """
+
+ # full path
+ path = os.path.normpath(os.path.join(self.rrdpath, name))
+
+ # check inside base path
+ if not path.startswith(self.rrdpath) :
+ # not found
+ raise exceptions.NotFound(name)
+
+ # ok
+ return path
def scan_dir (self, dir) :
"""
@@ -253,29 +274,13 @@
return self.templates.render('target',
title = self.rrd_title(rrd),
+ hourly_img = url(self.graph, rrd=rrd, style='detail', interval='hourly'),
daily_img = url(self.graph, rrd=rrd, style='detail', interval='daily'),
weekly_img = url(self.graph, rrd=rrd, style='detail', interval='weekly'),
yearly_img = url(self.graph, rrd=rrd, style='detail', interval='yearly'),
)
- def fs_path (self, node) :
- """
- Lookup and return the full filesystem path to the given relative RRD/dir path.
- """
-
- # dir is relative (no leading slash)
- # full path
- path = os.path.normpath(os.path.join(self.rrdpath, node))
-
- # check inside base path
- if not path.startswith(self.rrdpath) :
- # mask
- raise exceptions.NotFound(node)
-
- # ok
- return path
-
def rrd_path (self, rrd) :
"""
Lookup and return the full filesystem path to the given RRD name.
@@ -300,7 +305,7 @@
def render_graph (self, rrd, style, interval, png_path) :
"""
- Render the given graph for the given RRD to the given path.
+ Render the given graph for the given RRD to the given path, returning the opened file object.
"""
rrd_path = self.rrd_path(rrd)
@@ -311,8 +316,11 @@
log.debug("%s -> %s", rrd_path, png_path)
- # XXX: always generate
- graph.mrtg(style, interval, title, rrd_path, png_path)
+ # generate using the variant function given
+ w, h, report, png_file = graph.graph_single(style, interval, title, self.rrd_graph_func, rrd_path, png_path)
+
+ # the open'd .tmp file
+ return png_file
def rrd_graph (self, rrd, style, interval, flush=False) :
"""
@@ -356,41 +364,32 @@
# re-generate to tmp file
tmp_path = os.path.join(self.imgpath, style, interval, rrd) + '.tmp'
-
- self.render_graph(rrd, style, interval, tmp_path)
+
+ # open and write the graph image
+ img_file = self.render_graph(rrd, style, interval, tmp_path)
# replace .png with .tmp (semi-atomic, but atomic enough..)
+ # XXX: probably not portable to windows, what with img_file
os.rename(tmp_path, img_path)
-
- # open the now-fresh .png and return that
- return open(img_path)
-
-
+
+ else :
+ # use existing file
+ img_file = open(img_path, 'rb')
+
+ # return the now-fresh .png and return that
+ return img_file
### Request handlers
-
-
-# def node (self, url, path) :
-# """
-# Arbitrate between URLs to dirs and to RRDs.
-# """
-#
-
-
def index (self, req, url, dir = '') :
"""
Directory overview
dir - (optional) relative path to subdir from base rrdpath
"""
-
- # lookup fs path
+
+ # lookup
path = self.fs_path(dir)
- # found?
- if not os.path.isdir(path) :
- raise exceptions.NotFound("No such RRD directory: %s" % (dir, ))
-
# scan
subdirs, rrds = self.scan_dir(path)
@@ -444,14 +443,42 @@
# respond with file wrapper
return Response(response_file, mimetype='image/png', direct_passthrough=True)
+
+ def graph_top (self, req, url, dir = '', count=5) :
+ """
+ Show top N hosts by peak/average.
+ """
+ # find
+ path = self.fs_path(dir)
+
+ # scan
+ subdirs, rrds = self.scan_dir(path)
+
+ # get top N hosts
+ hosts = backend.calc_top_hosts(path, rrds, 'in', 'out', count=count)
+
+ # draw graph with hosts
+ w, h, data, img = graph.graph_multi('hourly',
+ "Top %d hosts" % count,
+ [('%s/%s.rrd' % (path, name), name) for name in hosts],
+ self.rrd_graph_func,
+ None # to tmpfile
+ )
+
+ # wrap file output
+ response_file = werkzeug.wrap_file(req.environ, img)
+
+ return Response(response_file, mimetype='image/png', direct_passthrough=True)
# map URLs to various methods
# XXX: this uses the method object as the endpoint, which is a bit silly, since it's not bound and we need to pass
# in self explicitly..
URLS = Map((
Rule('/', endpoint=index, defaults=dict(dir = '')),
+ Rule('/top.png', endpoint=graph_top, defaults=dict(dir = '')),
Rule('/<path:dir>/', endpoint=index),
+ Rule('/<path:dir>/top.png', endpoint=graph_top),
Rule('/<path:rrd>.rrd', endpoint=target),
Rule('/<path:rrd>.rrd/<string:style>.png', endpoint=graph, defaults=dict(interval = 'daily')),
Rule('/<path:rrd>.rrd/<string:style>/<string:interval>.png', endpoint=graph),