start restructuring graph definitions as a class hierarchy... oo-graphs
authorTero Marttila <terom@fixme.fi>
Sun, 31 Jan 2010 01:32:48 +0200
branchoo-graphs
changeset 19 58df27d54d2e
parent 18 eaf06ae5df16
start restructuring graph definitions as a class hierarchy...
rrdweb/graph.py
rrdweb/rrd.py
--- a/rrdweb/graph.py	Sat Jan 30 23:44:28 2010 +0200
+++ b/rrdweb/graph.py	Sun Jan 31 01:32:48 2010 +0200
@@ -8,182 +8,309 @@
 def timestamp () :
     return time.strftime("%Y/%m/%d %H:%M:%S %Z")
 
-def common_opts () :
-    """
-        Common options for all views
+class BaseGraph (object) :
     """
-
-    return dict(
-        # output
-        imgformat           = "PNG",
-        #        lazy                = True,
-
-        color               = [
-            # disable border
-            # border            = 0,
-            "SHADEA#ffffff00",
-            "SHADEB#ffffff00",
-
-            # keep background transparent
-            "BACK#ffffff00",
-            "SHADEB#ffffff00",
-        ],
-         
-        # labels
-        vertical_label      = "bits/s",
-        units               = "si",
-
-        # use logarithmic scaling
-        logarithmic         = True,
-        
-        # smooth out lines
-        slope_mode          = True,
-    )
-
-def overview_opts () :
-    """
-        Common options for the overview graph
+        Construct the data layout and parameters for rendering the rrdtool.graph.
     """
 
-    return dict(
-        width               = 600,
-        height              = 50,
-    ), [
-        "CDEF:all=in,out,+",
-
-        "VDEF:max=all,MAXIMUM",
-        "VDEF:avg=all,AVERAGE",
-        "VDEF:min=all,MINIMUM",
+    # the unit label, used in both the Y scale and overview labels
+    UNIT_LABEL = None
 
-        "LINE1:in#0000FF:In",
-        "LINE1:out#00CC00:Out",
+    def __init__ (self, rrd_path, title) :
+        """
+            rrd_path        - path to the .rrd file
+            title           - target name to use for the graph title
+        """
 
-        "GPRINT:max:%6.2lf %Sbps max",
-        "GPRINT:avg:%6.2lf %Sbps avg",
-        "GPRINT:min:%6.2lf %Sbps min\\l",
-    ]
+        self.rrd_path = rrd_path
+        self.title = title
 
-def detail_opts () :
+
+    def data_sources (self, label_width=0) :
+        """
+            Yield the data sources to define as a series of
+                (id,    statements)
+            tuples.
+        """
+
+        pass
+        
+
+    def overview_data (self) :
+        """
+            Yield the data lines required to create a single summary data series called 'all'
+        """
+
+        ids, statements = zip(self.data_sources())
+        
+        # draw lines
+        yield statements
+
+        # summarized data
+        assert len(ids) == 2
+
+        yield "CDEF:all=%s,+" % (ids.join(','))
+
+
+    def detail_data (self, label_width=4) :
+        """
+            Yield the data statements required to generate output for each line, as
+                (identifier, statements)
+            tuples
+        """
+
+        return data_sources(label_width=label_width) 
+
+    def data (self) :
+        """
+            Yield the data statements to be used for the rrdtool graph
+        """
+
+        return []
+
+    def options (self) :
+        """
+            Yield the options to be used for the rrdtool graph as a dict
+        """
+
+        return dict(
+            # output
+            imgformat           = "PNG",
+            #        lazy                = True,
+
+            color               = [
+                # disable border
+                # border            = 0,
+                "SHADEA#ffffff00",
+                "SHADEB#ffffff00",
+
+                # keep background transparent
+                "BACK#ffffff00",
+                "SHADEB#ffffff00",
+            ],
+             
+            # labels
+            vertical_label      = self.UNIT_LABEL,
+            units               = "si",
+
+            # use logarithmic scaling
+            logarithmic         = True,
+            
+            # smooth out lines
+            slope_mode          = True,
+        )
+
+
+    def generate (self, out_path) :
+        """
+            Generate the output PNG file to the given path, using this object's params/data
+        """
+
+        return rrd.graph(out_path, *self.data(), **self.options())
+
+class OverviewGraph (BaseGraph) :
     """
-        Common options for the detail graph
+        A small 600x50 px graph, suitable for showing as part of a long list of targets.
+
+        May include some unlabed graph lines, and then a single summarized row of output.
+    """
+    
+    def options (self) :
+        yield super(OverviewGraph, self).options()
+        
+        yield 'width',  600
+        yield 'height', 50
+
+    def data (self) :
+        """
+            A single summary line
+        """
+        
+        # recurse
+        yield super(OverviewGraph, self).data()
+        
+        # have a summarized data series?
+        summarized = self.overview_data()
+
+        if summarized :
+            # summarize -> all
+            yield summarized
+
+            # calculate min/max/avg of the summary value
+            yield "VDEF:min=all,MINIMUM"
+            yield "VDEF:max=all,MAXIMUM"
+            yield "VDEF:avg=all,AVERAGE"
+            
+            # display one line
+            yield "GPRINT:max:%%6.2lf %%S%s max" % self.UNIT_LABEL
+            yield "GPRINT:avg:%%6.2lf %%S%s avg" % self.UNIT_LABEL
+            yield "GPRINT:min:%%6.2lf %%S%s min\\l" % self.UNIT_LABEL
+
+class DetailGraph (BaseGraph) :
+    """
+        A large 600x200 px graph, suitable for showing for a specfic taget.
+
+        Can include any number of graph lines, and then a row of summarized data for each.
     """
 
-    return dict(
-        # dimensions
-        width               = 600,
-        height              = 200,
-    ), [
-        # values
-        'VDEF:in_max=in,MAXIMUM',
-        'VDEF:in_avg=in,AVERAGE',
-        'VDEF:in_min=in,MINIMUM',
-        'VDEF:out_max=out,MAXIMUM',
-        'VDEF:out_avg=out,AVERAGE',
-        'VDEF:out_min=out,MINIMUM',
+    def options (self) :
+        yield super(OverviewGraph, self).options()
 
-        # legend/graph
-        "COMMENT:%4s" % "",
-        "COMMENT:%11s" % "Maximum",
-        "COMMENT:%11s" % "Average",
-        "COMMENT:%11s\\l" % "Minimum",
+        yield 'width',      600
+        yield 'height',     200
 
-        "LINE1:in#0000FF:%4s" % "In",
-        'GPRINT:in_max:%6.2lf %Sbps',
-        'GPRINT:in_avg:%6.2lf %Sbps',
-        'GPRINT:in_min:%6.2lf %Sbps\\l',
+    def data (self) :
+        """
+            A complex breakdown into min/max/avg by line
+        """
+        
+        # column titles
+        yield "COMMENT:%4s" % ""
+        yield "COMMENT:%11s" % "Minimum"
+        yield "COMMENT:%11s" % "Maximum"
+        yield "COMMENT:%11s\\l" % "Average"
+        
+        # each row
+        # label isn't wide enough?
+        for id, data in self.detail_data(label_width=4) :
+            # the defs
+            yield data
 
-        "LINE1:out#00CC00:%4s" % "Out",
-        'GPRINT:out_max:%6.2lf %Sbps',
-        'GPRINT:out_avg:%6.2lf %Sbps',
-        'GPRINT:out_min:%6.2lf %Sbps\\l',
-        
-        # mark
-        "COMMENT:Generated %s\\r" % timestamp().replace(':', '\\:'),
-    ]
+            # summarize data
+            yield 'VDEF:%s_min=%s,MINIMUM' % (id, id)
+            yield 'VDEF:%s_max=%s,MAXIMUM' % (id, id)
+            yield 'VDEF:%s_avg=%s,AVERAGE' % (id, id)
+
+            # the output
+            'GPRINT:%s_min:%%6.2lf %%S%s' % (id, self.UNIT_LABEL)
+            'GPRINT:%s_max:%%6.2lf %%S%s' % (id, self.UNIT_LABEL)
+            'GPRINT:%s_avg:%%6.2lf %%S%s\\l' % (id, self.UNIT_LABEL)
+
+        # timestamp
+        yield "COMMENT:Generated %s\\r" % timestamp().replace(':', '\\:')
+
 
 STYLE_DEFS = {
-    'overview': overview_opts,
-    'detail':   detail_opts,
+    'overview'  : OverviewGraph,
+    'detail'    : DetailGraph,
 }
 
-def daily_opts (title) :
+class DailyGraph (BaseGraph) :
     """
-        Common options for the 'daily' view
+        Provides a grid/interval/title suitable for a 24-hour graph
     """
 
-    return dict(
+    def options (self) :
+        yield super(DailyGraph, self).options()
+
+        
         # labels
-        x_grid              = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M",
+        yield 'x_grid',     "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M"
     
-        # general info
-        title               = "Daily %s" % (title, ),
+        # title
+        yield 'title',      "Daily %s" % (self.target_title, )
     
         # interval
-        start               = "-24h",
-    )
+        yield 'start',      "-24h"
 
-def weekly_opts (title) :
-    return dict(
+
+class WeeklyGraph (BaseGraph) :
+    """
+        Provides a grid/interval/title suitable for a 7-day graph
+    """
+
+    def options (self) :
+        yield super(DailyGraph, self).options()
+
         # labels
         #        x_grid              = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M",
     
         # general info
-        title               = "Weekly %s" % (title, ),
+        yield 'title',              "Weekly %s" % (self.target_title, )
     
         # interval
-        start               = "-7d",
-    )
+        yield 'start',              "-7d"
 
-def yearly_opts (title) :
-    return dict(
+
+class YearlyGraph (BaseGraph) :
+    """
+        Provides a grid/interval/title suitable for a 1-year graph
+    """
+
+    def options (self) :
+        yield super(DailyGraph, self).option()
+
         # labels
         #        x_grid              = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M",
     
         # general info
-        title               = "Yearly %s" % (title, ),
+        yield 'title'               "Yearly %s" % (self.target_title, )
     
         # interval
-        start               = "-1y",
-    )
+        yield 'start',              "-1y"
 
 
 INTERVAL_DEFS = {
-    'daily':    daily_opts,
-    'weekly':   weekly_opts,
-    'yearly':   yearly_opts,
+    'daily'     : DailyGraph,
+    'weekly'    : WeeklyGraph,
+    'yearly'    : YearlyGraph,
 }
 
-def mrtg_data (rrd_path) :
-    """
-        Data sources for network in/out from an MRTG rrd
+class IfOctetGraph (BaseGraph) :
     """
-
-    return [
-        # data sources, bytes/s
-        r'DEF:in0=%s:ds0:AVERAGE' % rrd_path,
-        r'DEF:out0=%s:ds1:AVERAGE' % rrd_path,
-
-        # data, bits/s
-        'CDEF:in=in0,8,*',
-        'CDEF:out=out0,8,*',
+        Defines the data sources for a single bidirectional octet counter
+    """
+    
+    # bits/s
+    UNIT_LABEL = "bps"
+    
+    # the names for the in/out DS's
+    RRD_DS_RX = None
+    RRD_DS_TX = None
 
-    ]        
-
-def collectd_data (rrd_path) :
-    """
-        Data sources for if_octets from a collectd rrd
-    """
+    def data_source (self, id, ds, color, label_width, label) :
+        # data source, bytes/s
+        yield "DEF:%s0=%s:%s:AVERAGE" % (id, self.rrd_path, ds)
 
-    return [
-        # data sources, bytes/s
-        r'DEF:in0=%s:rx:AVERAGE' % rrd_path,
-        r'DEF:out0=%s:tx:AVERAGE' % rrd_path,
+        # convert to bits/s
+        yield "CDEF:%s=%s0,8,*" % (id, id)
+        
+        # draw line with label
+        yield "LINE1:%s%s:%*s" % (id, color, label_width, label)
+       
 
-        # data, bits/s
-        'CDEF:in=in0,8,*',
-        'CDEF:out=out0,8,*',
+    def data_sources (self, label_width=0) :
+        """
+            Yield the data sources to define as a series of
+                (id,    line-statements)
+            tuples.
+        """
+        
+        # yield the table of values
+        for      id,    ds,             color,      label       in (
+                ('rx',  self.RRD_DS_RX, "#0000FF",  "In"    ),
+                ('tx',  self.RRD_DS_TX, "#00CC00",  "Out"   ),
+        ) :
+            yield id, self.data_source(id, ds, color, label_width, label)
 
-    ]        
+class MRTGGraph (IfOctetGraph) :
+    """
+        Data sources for an MRTG .rrd
+    """
+    
+    # the DS names
+    RRD_DS_RX = 'ds0'
+    RRD_DS_TX = 'ds1'
+
+class CollectdIfOctetGraph (IfOctetGraph) :
+    """
+        Data sources for an if_octets .rrd from collectd
+    """
+    
+    # the DS names
+    RRD_DS_RX = 'rx'
+    RRD_DS_TX = 'tx'
+
     
 def _graph (style, interval, title, data_func, rrd_path, out_path) :
     style_opts, style_vars = STYLE_DEFS[style]()
--- a/rrdweb/rrd.py	Sat Jan 30 23:44:28 2010 +0200
+++ b/rrdweb/rrd.py	Sun Jan 31 01:32:48 2010 +0200
@@ -63,6 +63,22 @@
 
     return out
 
+def normalize_args (args) :
+    """
+        Flatten the given nested iterables list into a flat one
+    """
+
+    for arg in args :
+        # naasty type-checks :)
+        if isinstance(arg, (list, tuple, types.GeneratorType)) :
+            # recurse -> flatten
+            for subarg in normalize_args(arg) :
+                yield subarg
+
+        else :
+            # flat
+            yield str(arg)
+
 def run_cmd (func, pre_args, opts, post_args) :
     """
         Run the given rrdtool.* function, formatting the given positional arguments and options.
@@ -75,8 +91,8 @@
     opt_args = [item for items in opt_items for item in items]
 
     # positional arguments
-    pre_args = [str(arg) for arg in pre_args]
-    post_args = [str(arg) for arg in post_args]
+    pre_args = normalize_args(pre_args)
+    post_args = normalize_args(post_args)
 
     # full arguments
     args = pre_args + opt_args + post_args