--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/pvl.rrd-graph Sun Jan 20 15:37:59 2013 +0200
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+
+"""
+ pvl.rrd graph output
+"""
+
+__version__ = '0.1'
+
+import pvl.args
+import pvl.rrd.graph
+
+import os.path
+
+import logging, optparse
+
+log = logging.getLogger('main')
+
+def parse_options (argv) :
+ """
+ Parse command-line arguments.
+ """
+
+ prog = argv[0]
+
+ parser = optparse.OptionParser(
+ prog = prog,
+ usage = '%prog: [options]',
+ version = __version__,
+
+ # module docstring
+ description = __doc__,
+ )
+
+ # options
+ parser.add_option_group(pvl.args.parser(parser))
+
+ parser.add_option('--style', metavar='STYLE', default='detail',
+ help="overview/detail")
+ parser.add_option('--interval', metavar='INTERVAL', default='daily',
+ help="daily/weekly/yearly")
+ parser.add_option('--graph', metavar='PATH', default='.png',
+ help="output file")
+
+ # parse
+ options, args = parser.parse_args(argv[1:])
+
+ # apply
+ pvl.args.apply(options, prog)
+
+ return options, args
+
+def graph (options, rrd) :
+ """
+ Graph given rrd.
+ """
+
+ # out
+ path, ext = os.path.splitext(rrd)
+ ext = options.graph
+ out = path + ext
+
+ # graph
+ log.info("%s -> %s", rrd, out)
+
+ pvl.rrd.graph.collectd_ifoctets(options.style, options.interval, "Test", rrd, out)
+
+def main (argv) :
+ """
+ Usage: [options] rrd
+ """
+
+ options, args = parse_options(argv)
+
+ for rrd in args :
+ graph(options, rrd)
+
+ # done
+ log.info("Exiting...")
+ return 0
+
+if __name__ == '__main__':
+ import sys
+
+ sys.exit(main(sys.argv))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/rrd/__init__.py Sun Jan 20 15:37:59 2013 +0200
@@ -0,0 +1,7 @@
+"""
+ RRD Graphing
+
+ Requires:
+ python-rrdtool
+"""
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/rrd/api.py Sun Jan 20 15:37:59 2013 +0200
@@ -0,0 +1,111 @@
+import rrdtool
+
+import pvl.invoke
+
+import logging; log = logging.getLogger('pvl.rrd.api')
+
+"""
+ Wrapper around the rrdtool python interface
+"""
+
+def timestamp (time=None) :
+ """
+ Format datetime value for rrdtool.
+ """
+
+ if not time :
+ return None
+
+ elif isinstance(time, datetime.datetime) :
+ return int(time.mktime(dt.timetuple()))
+
+ elif isinstance(time, datetime.timedelta) :
+ raise NotImplementedError("pvl.rrd.api.timestamp: timedelta")
+
+ else :
+ # dunno
+ return str(dt)
+
+def cmd (func, pre, opts, post) :
+ """
+ Run the given rrdtool.* function, formatting the given positional arguments and options.
+
+ Returns the return value, which varies...
+ """
+
+ log.debug("%s: %s: %s: %s", func, pre, opts, post)
+
+ # { opt: arg } -> [ '--opt', arg ]
+ opts = pvl.invoke.optargs(**opts)
+
+ # positional arguments
+ pre = pvl.invoke.optargs(*pre)
+ post = pvl.invoke.optargs(*post)
+
+ return func(*(pre + opts + post))
+
+def graph (out=None, *args, **opts) :
+ """
+ Render a graph image and/or print a report from data stored in one or several RRDs.
+
+ out - None -> tempfile
+ - False -> stdout
+ - path -> write to file
+
+ Returns:
+ (width, height) - pixel dimensions of the resulting graph image
+ report_output - any PRINT'd output (?)
+ graph_file - file-like object containing graph image, unless out=False -> stdout
+
+ With out=None, the returned graph_file is a tempfile which will be cleaned up by Python once close()'d!
+ """
+
+ if out is None :
+ # tempfile
+ out_file = tempfile.NamedTemporaryFile(suffix='.png', delete=True) # python2.6
+ out_path = out.name
+
+ elif out is False :
+ out_file = None
+ out_path = '-'
+
+ else :
+ # for reading
+ out_path = out
+ out_file = True # open later
+
+ # XXX: handle tempfile close?
+ width, height, out_lines = cmd(rrdtool.graph, (out_path, ), opts, args)
+
+ if out_file is True :
+ out_file = open(out_path)
+
+ return (width, height), out_lines, out_file
+
+def fetch (rrd, cf, **opts) :
+ """
+ Fetch values from RRD.
+
+ Returns
+ (start, end, step) - xrange(...) for row timestamps
+ (ds, ...) - columns (ds name)
+ ((value, ...), ...) - rows (data by ds)
+ """
+
+ return cmd(rrdtool.fetch, (rrd, cf), opts, ())
+
+def _fetch (rrd, cf='AVERAGE', resolution=None, start=None, end=None, **opts) :
+ """
+ Yields (timestamp, { ds: value }) for given ds-values, or all.
+ """
+
+ steps, sources, rows = fetch(rrd, cf,
+ resolution = resolution,
+ start = timestamp(start),
+ end = timestamp(end),
+ **opts
+ )
+
+ for ts, row in zip(xrange(*steps), rows) :
+ yield datetime.fromtimestamp(ts), dict(zip(sources, row))
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pvl/rrd/graph.py Sun Jan 20 15:37:59 2013 +0200
@@ -0,0 +1,209 @@
+import pvl.rrd.api as rrd
+import time
+
+from pvl.invoke import merge # XXX
+
+"""
+ RRDTool graph output for MRTG, or collectd if_octets
+"""
+
+def timestamp () :
+ return time.strftime("%Y/%m/%d %H:%M:%S %Z")
+
+def common_opts () :
+ """
+ Common options for all views
+ """
+
+ 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
+ """
+
+ return dict(
+ width = 600,
+ height = 50,
+ ), [
+ "CDEF:all=in,out,+",
+
+ "VDEF:max=all,MAXIMUM",
+ "VDEF:avg=all,AVERAGE",
+ "VDEF:min=all,MINIMUM",
+
+ "LINE1:in#0000FF:In",
+ "LINE1:out#00CC00:Out",
+
+ "GPRINT:max:%6.2lf %Sbps max",
+ "GPRINT:avg:%6.2lf %Sbps avg",
+ "GPRINT:min:%6.2lf %Sbps min\\l",
+ ]
+
+def detail_opts () :
+ """
+ Common options for the detail graph
+ """
+
+ return dict(
+ # dimensions
+ width = 600,
+ height = 200,
+ ), [
+ # values
+ 'VDEF:in_max=in,MAXIMUM',
+ 'VDEF:in_avg=in,AVERAGE',
+ 'VDEF:in_min=in,MINIMUM',
+ 'VDEF:in_cur=in,LAST',
+ 'VDEF:out_max=out,MAXIMUM',
+ 'VDEF:out_avg=out,AVERAGE',
+ 'VDEF:out_min=out,MINIMUM',
+ 'VDEF:out_cur=out,LAST',
+
+ # legend/graph
+ "COMMENT:%4s" % "",
+ "COMMENT:%11s" % "Maximum",
+ "COMMENT:%11s" % "Average",
+ "COMMENT:%11s" % "Minimum",
+ "COMMENT:%11s\\l" % "Current",
+
+ "LINE1:in#0000FF:%4s" % "In",
+ 'GPRINT:in_max:%6.2lf %Sbps',
+ 'GPRINT:in_avg:%6.2lf %Sbps',
+ 'GPRINT:in_min:%6.2lf %Sbps',
+ 'GPRINT:in_cur:%6.2lf %Sbps\\l',
+
+ "LINE1:out#00CC00:%4s" % "Out",
+ 'GPRINT:out_max:%6.2lf %Sbps',
+ 'GPRINT:out_avg:%6.2lf %Sbps',
+ 'GPRINT:out_min:%6.2lf %Sbps',
+ 'GPRINT:out_cur:%6.2lf %Sbps\\l',
+
+ # mark
+ "COMMENT:Generated %s\\r" % timestamp().replace(':', '\\:'),
+ ]
+
+STYLE_DEFS = {
+ 'overview': overview_opts,
+ 'detail': detail_opts,
+}
+
+def daily_opts (title) :
+ """
+ Common options for the 'daily' view
+ """
+
+ return dict(
+ # labels
+ x_grid = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M",
+
+ # general info
+ title = "Daily %s" % (title, ),
+
+ # interval
+ start = "-24h",
+ )
+
+def weekly_opts (title) :
+ return dict(
+ # labels
+ # x_grid = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M",
+
+ # general info
+ title = "Weekly %s" % (title, ),
+
+ # interval
+ start = "-7d",
+ )
+
+def yearly_opts (title) :
+ return dict(
+ # labels
+ # x_grid = "MINUTE:15:HOUR:1:HOUR:4:0:%H:%M",
+
+ # general info
+ title = "Yearly %s" % (title, ),
+
+ # interval
+ start = "-1y",
+ )
+
+
+INTERVAL_DEFS = {
+ 'daily': daily_opts,
+ 'weekly': weekly_opts,
+ 'yearly': yearly_opts,
+}
+
+def mrtg_data (rrd_path) :
+ """
+ Data sources for network in/out from an MRTG rrd
+ """
+
+ 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,*',
+
+ ]
+
+def collectd_data (rrd_path) :
+ """
+ Data sources for if_octets from a collectd rrd
+ """
+
+ return [
+ # data sources, bytes/s
+ r'DEF:in0=%s:rx:AVERAGE' % rrd_path,
+ r'DEF:out0=%s:tx:AVERAGE' % rrd_path,
+
+ # data, bits/s
+ 'CDEF:in=in0,8,*',
+ 'CDEF:out=out0,8,*',
+
+ ]
+
+def _graph (style, interval, title, data_func, rrd_path, out_path) :
+ style_opts, style_vars = STYLE_DEFS[style]()
+ interval_opts = INTERVAL_DEFS[interval](title)
+
+ opts = merge(common_opts(), style_opts, interval_opts)
+ data = data_func(rrd_path) + style_vars
+
+ return rrd.graph(out_path, *data, **opts)
+
+def mrtg (style, interval, title, rrd_path, out_path) :
+ return _graph(style, interval, title, mrtg_data, rrd_path, out_path)
+
+def collectd_ifoctets (style, interval, title, rrd_path, out_path) :
+ return _graph(style, interval, title, collectd_data, rrd_path, out_path)
+