terom@5: import rrd terom@5: import time terom@5: terom@5: """ terom@5: RRDTool graph output terom@5: """ terom@5: terom@5: def timestamp () : terom@5: return time.strftime("%Y/%m/%d %H:%M:%S %Z") terom@5: terom@5: def common_opts () : terom@5: """ terom@5: Common options for all views terom@5: """ terom@5: terom@5: return dict( terom@5: # output terom@5: imgformat = "PNG", terom@5: # lazy = True, terom@5: terom@5: color = [ terom@5: # disable border terom@5: # border = 0, terom@5: "SHADEA#ffffff00", terom@5: "SHADEB#ffffff00", terom@5: terom@5: # keep background transparent terom@5: "BACK#ffffff00", terom@5: "SHADEB#ffffff00", terom@5: ], terom@5: terom@5: # labels terom@5: vertical_label = "bits/s", terom@5: units = "si", terom@5: terom@5: # use logarithmic scaling terom@5: logarithmic = True, terom@5: terom@5: # smooth out lines terom@5: slope_mode = True, terom@5: ) terom@5: terom@5: def overview_opts () : terom@5: """ terom@32: Graph statements for a single-source overview graph terom@5: """ terom@5: terom@5: return dict( terom@5: width = 600, terom@5: height = 50, terom@5: ), [ terom@32: "CDEF:all=in0,out0,+", terom@7: terom@7: "VDEF:max=all,MAXIMUM", terom@7: "VDEF:avg=all,AVERAGE", terom@7: "VDEF:min=all,MINIMUM", terom@7: terom@32: "LINE1:in0#0000FF:In", terom@32: "LINE1:out0#00CC00:Out", terom@7: terom@7: "GPRINT:max:%6.2lf %Sbps max", terom@7: "GPRINT:avg:%6.2lf %Sbps avg", terom@7: "GPRINT:min:%6.2lf %Sbps min\\l", terom@5: ] terom@5: terom@5: def detail_opts () : terom@5: """ terom@32: Common options for a single-source detail graph terom@5: """ terom@5: terom@5: return dict( terom@5: # dimensions terom@5: width = 600, terom@5: height = 200, terom@5: ), [ terom@5: # values terom@32: 'VDEF:in_max=in0,MAXIMUM', terom@32: 'VDEF:in_avg=in0,AVERAGE', terom@32: 'VDEF:in_min=in0,MINIMUM', terom@32: 'VDEF:in_cur=in0,LAST', terom@32: 'VDEF:out_max=out0,MAXIMUM', terom@32: 'VDEF:out_avg=out0,AVERAGE', terom@32: 'VDEF:out_min=out0,MINIMUM', terom@32: 'VDEF:out_cur=out0,LAST', terom@5: terom@5: # legend/graph terom@5: "COMMENT:%4s" % "", terom@5: "COMMENT:%11s" % "Maximum", terom@5: "COMMENT:%11s" % "Average", terom@28: "COMMENT:%11s" % "Minimum", terom@28: "COMMENT:%11s\\l" % "Current", terom@5: terom@32: "LINE1:in0#0000FF:%4s" % "In", terom@5: 'GPRINT:in_max:%6.2lf %Sbps', terom@5: 'GPRINT:in_avg:%6.2lf %Sbps', terom@28: 'GPRINT:in_min:%6.2lf %Sbps', terom@28: 'GPRINT:in_cur:%6.2lf %Sbps\\l', terom@5: terom@32: "LINE1:out0#00CC00:%4s" % "Out", terom@5: 'GPRINT:out_max:%6.2lf %Sbps', terom@5: 'GPRINT:out_avg:%6.2lf %Sbps', terom@28: 'GPRINT:out_min:%6.2lf %Sbps', terom@28: 'GPRINT:out_cur:%6.2lf %Sbps\\l', terom@5: terom@5: # mark terom@5: "COMMENT:Generated %s\\r" % timestamp().replace(':', '\\:'), terom@5: ] terom@5: terom@32: # set of line colors used for a multi-source graph, in rgb hex form (no prefix) terom@32: MULTI_COLORS = ( terom@32: '00cc00', terom@32: '0000ff', terom@32: 'cc0000', terom@32: ) terom@32: terom@32: def multi_graph_source_defs (idx, name, color) : terom@32: """ terom@32: Render graph and legend summary for a single source in a multi-source graph. terom@32: """ terom@32: terom@32: params = dict( terom@32: idx = idx, name = name, color = color terom@32: ) terom@32: terom@32: return [stmt.format(**params) for stmt in ( terom@32: "CDEF:all{idx}=in{idx},out{idx},+", terom@32: terom@32: "VDEF:max{idx}=all{idx},MAXIMUM", terom@32: "VDEF:avg{idx}=all{idx},AVERAGE", terom@32: "VDEF:cur{idx}=all{idx},LAST", terom@32: terom@32: "LINE1:all{idx}#{color}: ", terom@32: terom@32: "GPRINT:max{idx}:%6.2lf %Sbps", terom@32: "GPRINT:avg{idx}:%6.2lf %Sbps", terom@32: "GPRINT:cur{idx}:%6.2lf %Sbps", terom@32: "COMMENT:\t{name}\\l", terom@32: )] terom@32: terom@32: terom@32: def multi_graph (sources) : terom@32: """ terom@32: Graph definition for a multi-source overview graph. terom@32: terom@32: Uses combined in/out totals, giving a single line per source. terom@32: """ terom@32: terom@32: # defs for each source terom@32: sources_defs = [multi_graph_source_defs(idx, name, color) for (idx, name), color in zip(sources, MULTI_COLORS)] terom@32: terom@32: return dict( terom@32: # dimensions terom@32: width = 600, terom@32: height = 200, terom@32: terom@32: ), [ terom@32: # legend header terom@32: "COMMENT: ", terom@32: "COMMENT:%11s" % "Maximum", terom@32: "COMMENT:%11s" % "Average", terom@32: "COMMENT:%11s" % "Current", terom@32: "COMMENT:\t%s\\l" % "", terom@32: terom@32: ] + [ terom@32: # each source terom@32: stmt for defs in sources_defs for stmt in defs terom@32: ] terom@32: terom@5: STYLE_DEFS = { terom@5: 'overview': overview_opts, terom@5: 'detail': detail_opts, terom@5: } terom@5: terom@32: def hourly_opts (title) : terom@32: terom@32: return dict( terom@32: # labels terom@32: x_grid = None, terom@32: terom@32: # general info terom@32: title = "Hourly %s" % (title, ), terom@32: terom@32: # interval terom@32: start = "-1h", terom@32: ) terom@32: terom@5: def daily_opts (title) : terom@5: """ terom@5: Common options for the 'daily' view terom@5: """ terom@5: terom@5: return dict( terom@5: # labels terom@5: x_grid = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M", terom@5: terom@5: # general info terom@5: title = "Daily %s" % (title, ), terom@5: terom@5: # interval terom@5: start = "-24h", terom@5: ) terom@5: terom@5: def weekly_opts (title) : terom@5: return dict( terom@5: # labels terom@5: # x_grid = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M", terom@5: terom@5: # general info terom@5: title = "Weekly %s" % (title, ), terom@5: terom@5: # interval terom@5: start = "-7d", terom@5: ) terom@5: terom@5: def yearly_opts (title) : terom@5: return dict( terom@5: # labels terom@5: # x_grid = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M", terom@5: terom@5: # general info terom@5: title = "Yearly %s" % (title, ), terom@5: terom@5: # interval terom@5: start = "-1y", terom@5: ) terom@5: terom@5: terom@5: INTERVAL_DEFS = { terom@32: 'hourly': hourly_opts, terom@5: 'daily': daily_opts, terom@5: 'weekly': weekly_opts, terom@5: 'yearly': yearly_opts, terom@5: } terom@5: terom@32: def data_defs (idx, rrd, ds_in, ds_out, bytes=True, cf='AVERAGE') : terom@5: """ terom@32: Generate the DEF/CDEF statements for the in{idx}/out{idx} data sources for the given RRD and DS names. terom@32: terom@32: If bytes is given, convert the value into bits. terom@5: """ terom@5: terom@32: params = dict( terom@32: ds_in=ds_in, ds_out=ds_out, cf=cf, terom@32: idx=idx, rrd=rrd, terom@32: ) terom@32: terom@32: if bytes : terom@5: # data sources, bytes/s terom@32: yield 'DEF:in_raw{idx}={rrd}:{ds_in}:{cf}'.format(**params) terom@32: yield 'DEF:out_raw{idx}={rrd}:{ds_out}:{cf}'.format(**params) terom@32: terom@5: # data, bits/s terom@32: yield 'CDEF:in{idx}=in_raw{idx},8,*'.format(**params) terom@32: yield 'CDEF:out{idx}=out_raw{idx},8,*'.format(**params) terom@32: terom@32: else : terom@32: # data sources, bits/s terom@32: yield 'DEF:in{idx}={rrd}:{ds_in}:{cf}'.format(**params) terom@32: yield 'DEF:out{idx}={rrd}:{ds_out}:{cf}'.format(**params) terom@5: terom@5: terom@32: def mrtg_data (idx, rrd) : terom@32: """ terom@32: Generate the in{idx}/out{idx} data sources fro the given MRTG rrd. terom@32: """ terom@32: terom@32: return data_defs(idx, rrd, 'ds0', 'ds1') terom@32: terom@32: def collectd_data (idx, rrd) : terom@17: """ terom@17: Data sources for if_octets from a collectd rrd terom@17: """ terom@32: terom@32: return data_defs(idx, rrd, 'rx', 'tx') terom@17: terom@32: def pmacct_data (idx, rrd) : terom@32: """ terom@32: Data sources for in/out bytes from a pmacct rrd terom@32: """ terom@32: terom@32: return data_defs(idx, rrd, 'in', 'out') terom@17: terom@32: def graph_single (style, interval, title, data_func, rrd_path, out_path) : terom@32: """ terom@32: Render graph. terom@32: terom@32: Returns a (width, height, print_lines, graph_file) tuple. terom@32: """ terom@32: terom@5: style_opts, style_vars = STYLE_DEFS[style]() terom@5: interval_opts = INTERVAL_DEFS[interval](title) terom@5: terom@5: opts = rrd.merge_opts(common_opts(), style_opts, interval_opts) terom@32: data = list(data_func(0, rrd_path)) + style_vars terom@5: terom@5: return rrd.graph(out_path, *data, **opts) terom@17: terom@32: def graph_multi (interval, title, rrd_list, data_func, out) : terom@32: """ terom@32: Render a multi-source graph. terom@32: terom@32: interval - the name of the time interval to use terom@32: title - graph title terom@32: rrd_list - sequence of (rrd, name) tuples for each source to draw terom@32: data_func - the data source definition to use terom@32: out - output path for graph terom@32: """ terom@32: terom@32: # data sources terom@32: data_defs = [stmt for idx, (path, name) in enumerate(rrd_list) for stmt in data_func(idx, path)] terom@32: terom@32: # options terom@32: graph_opts, graph_defs = multi_graph([(idx, name) for idx, (path, name) in enumerate(rrd_list)]) terom@32: interval_opts = INTERVAL_DEFS[interval](title) terom@32: terom@32: # combine terom@32: opts = rrd.merge_opts(common_opts(), graph_opts, interval_opts) terom@32: defs = data_defs + graph_defs terom@32: terom@32: # graph terom@32: return rrd.graph(out, *defs, **opts) terom@5: terom@17: def mrtg (style, interval, title, rrd_path, out_path) : terom@17: return _graph(style, interval, title, mrtg_data, rrd_path, out_path) terom@17: terom@17: def collectd_ifoctets (style, interval, title, rrd_path, out_path) : terom@17: return _graph(style, interval, title, collectd_data, rrd_path, out_path) terom@17: terom@32: def pmacct_bytes (style, interval, title, rrd_path, out_path) : terom@32: return _graph(style, interval, title, pmacct_data, rrd_path, out_path) terom@32: terom@32: