rrdweb/wsgi.py
changeset 32 47e977c23ba2
parent 24 29a523db66a8
--- 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),