rrdweb/graph.py
author Tero Marttila <terom@fixme.fi>
Tue, 25 Jan 2011 01:28:06 +0200
changeset 32 47e977c23ba2
parent 28 89a4d9879171
permissions -rw-r--r--
implement rendering of pmacct rrd graphs, and a dir/top.png feature
import rrd
import time

"""
    RRDTool graph output
"""

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 () :
    """
        Graph statements for a single-source overview graph
    """

    return dict(
        width               = 600,
        height              = 50,
    ), [
        "CDEF:all=in0,out0,+",

        "VDEF:max=all,MAXIMUM",
        "VDEF:avg=all,AVERAGE",
        "VDEF:min=all,MINIMUM",

        "LINE1:in0#0000FF:In",
        "LINE1:out0#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 a single-source detail graph
    """

    return dict(
        # dimensions
        width               = 600,
        height              = 200,
    ), [
        # values
        'VDEF:in_max=in0,MAXIMUM',
        'VDEF:in_avg=in0,AVERAGE',
        'VDEF:in_min=in0,MINIMUM',
        'VDEF:in_cur=in0,LAST',
        'VDEF:out_max=out0,MAXIMUM',
        'VDEF:out_avg=out0,AVERAGE',
        'VDEF:out_min=out0,MINIMUM',
        'VDEF:out_cur=out0,LAST',

        # legend/graph
        "COMMENT:%4s" % "",
        "COMMENT:%11s" % "Maximum",
        "COMMENT:%11s" % "Average",
        "COMMENT:%11s" % "Minimum",
        "COMMENT:%11s\\l" % "Current",

        "LINE1:in0#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:out0#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(':', '\\:'),
    ]

# set of line colors used for a multi-source graph, in rgb hex form (no prefix)
MULTI_COLORS = (
    '00cc00',
    '0000ff',
    'cc0000',
)

def multi_graph_source_defs (idx, name, color) :
    """
        Render graph and legend summary for a single source in a multi-source graph.
    """

    params = dict(
        idx = idx, name = name, color = color
    )

    return [stmt.format(**params) for stmt in (
        "CDEF:all{idx}=in{idx},out{idx},+",

        "VDEF:max{idx}=all{idx},MAXIMUM",
        "VDEF:avg{idx}=all{idx},AVERAGE",
        "VDEF:cur{idx}=all{idx},LAST",

        "LINE1:all{idx}#{color}: ",

        "GPRINT:max{idx}:%6.2lf %Sbps",
        "GPRINT:avg{idx}:%6.2lf %Sbps",
        "GPRINT:cur{idx}:%6.2lf %Sbps",
        "COMMENT:\t{name}\\l",
    )]


def multi_graph (sources) :
    """
        Graph definition for a multi-source overview graph.

        Uses combined in/out totals, giving a single line per source.
    """

    # defs for each source
    sources_defs = [multi_graph_source_defs(idx, name, color) for (idx, name), color in zip(sources, MULTI_COLORS)]

    return dict(
        # dimensions
        width               = 600,
        height              = 200,

    ), [
        # legend header
        "COMMENT: ",
        "COMMENT:%11s" % "Maximum",
        "COMMENT:%11s" % "Average",
        "COMMENT:%11s" % "Current",
        "COMMENT:\t%s\\l" % "",
            
    ] + [
        # each source
        stmt for defs in sources_defs for stmt in defs
    ]

STYLE_DEFS = {
    'overview': overview_opts,
    'detail':   detail_opts,
}

def hourly_opts (title) :

    return dict(
        # labels
        x_grid              = None,

        # general info
        title               = "Hourly %s" % (title, ),

        # interval
        start               = "-1h",
    )

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 = {
    'hourly':   hourly_opts,
    'daily':    daily_opts,
    'weekly':   weekly_opts,
    'yearly':   yearly_opts,
}

def data_defs (idx, rrd, ds_in, ds_out, bytes=True, cf='AVERAGE') :
    """
        Generate the DEF/CDEF statements for the in{idx}/out{idx} data sources for the given RRD and DS names.

        If bytes is given, convert the value into bits.
    """

    params = dict(
        ds_in=ds_in, ds_out=ds_out, cf=cf,
        idx=idx, rrd=rrd, 
    )

    if bytes :
        # data sources, bytes/s
        yield 'DEF:in_raw{idx}={rrd}:{ds_in}:{cf}'.format(**params)
        yield 'DEF:out_raw{idx}={rrd}:{ds_out}:{cf}'.format(**params)
    
        # data, bits/s
        yield 'CDEF:in{idx}=in_raw{idx},8,*'.format(**params)
        yield 'CDEF:out{idx}=out_raw{idx},8,*'.format(**params)
    
    else :
        # data sources, bits/s
        yield 'DEF:in{idx}={rrd}:{ds_in}:{cf}'.format(**params)
        yield 'DEF:out{idx}={rrd}:{ds_out}:{cf}'.format(**params)


def mrtg_data (idx, rrd) :
    """
        Generate the in{idx}/out{idx} data sources fro the given MRTG rrd.
    """

    return data_defs(idx, rrd, 'ds0', 'ds1')

def collectd_data (idx, rrd) :
    """
        Data sources for if_octets from a collectd rrd
    """
    
    return data_defs(idx, rrd, 'rx', 'tx')

def pmacct_data (idx, rrd) :
    """
        Data sources for in/out bytes from a pmacct rrd
    """
    
    return data_defs(idx, rrd, 'in', 'out')

def graph_single (style, interval, title, data_func, rrd_path, out_path) :
    """
        Render graph.

        Returns a (width, height, print_lines, graph_file) tuple.
    """

    style_opts, style_vars = STYLE_DEFS[style]()
    interval_opts = INTERVAL_DEFS[interval](title)

    opts = rrd.merge_opts(common_opts(), style_opts, interval_opts)
    data = list(data_func(0, rrd_path)) + style_vars

    return rrd.graph(out_path, *data, **opts)
   
def graph_multi (interval, title, rrd_list, data_func, out) :
    """
        Render a multi-source graph.

            interval        - the name of the time interval to use
            title           - graph title
            rrd_list        - sequence of (rrd, name) tuples for each source to draw
            data_func       - the data source definition to use
            out             - output path for graph
    """

    # data sources
    data_defs = [stmt for idx, (path, name) in enumerate(rrd_list) for stmt in data_func(idx, path)]
    
    # options
    graph_opts, graph_defs = multi_graph([(idx, name) for idx, (path, name) in enumerate(rrd_list)])
    interval_opts = INTERVAL_DEFS[interval](title)
    
    # combine
    opts = rrd.merge_opts(common_opts(), graph_opts, interval_opts)
    defs = data_defs + graph_defs
    
    # graph
    return rrd.graph(out, *defs, **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)

def pmacct_bytes (style, interval, title, rrd_path, out_path) :
    return _graph(style, interval, title, pmacct_data, rrd_path, out_path)