# HG changeset patch # User Tero Marttila # Date 1264894368 -7200 # Node ID 58df27d54d2eda903ad7c2b53345617633251b5c # Parent eaf06ae5df16f1bbd834230003a6d42ff8d468c1 start restructuring graph definitions as a class hierarchy... diff -r eaf06ae5df16 -r 58df27d54d2e rrdweb/graph.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]() diff -r eaf06ae5df16 -r 58df27d54d2e rrdweb/rrd.py --- 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