Example #1
0
def graph(rrd, back=86400, title="", width=400, height=100):
    """ Make an RRD graph. The caller is responsible for
    deleting the returned path """

    tfile, tpath = tempfile.mkstemp(".gif", "oh")
    os.close(tfile)

    RRD.graph(
        tpath,
        "--start",
        "-" + str(back),
        "--title",
        title,
        "-w",
        str(width),
        "-h",
        str(height),
        "-c",
        "SHADEB#FFFFFF",
        "-c",
        "SHADEA#FFFFFF",
        "DEF:in=%s:in:AVERAGE" % rrd,
        "DEF:out=%s:out:AVERAGE" % rrd,
        "CDEF:inbits=in,8,*",
        "CDEF:outbits=out,8,*",
        'AREA:inbits#00FF00:"bps in"',
        'LINE1:outbits#0000FF:"bps out"',
    )

    return tpath
Example #2
0
def _create_rrd(server):

    path = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon', '%s.rrd' % server)

    args = [path, '-s', '60']

    for n in xrange(len(cfg.VSMON_DATA_DEF)):
        field = cfg.VSMON_DATA_DEF[n]
        if field[1]:
            args.append(
                _DS(field[0][:19],
                    dst=field[1],
                    hbeat=field[2],
                    min=field[3],
                    max=field[4]))

    # here due to the billing policy we will probably need to keep 40
    # days of 1 minute samples. at the end of 30 days they should be
    # transferred to a real database... although - what can't it be
    # transferred on daily basis?

    args.append(_RRA('AVERAGE', xff=0.5, steps=1,
                     rows=14400))  # 10 days of 1 min
    args.append(_RRA('AVERAGE', xff=0.5, steps=10,
                     rows=13392))  # 93 days of 5 min
    args.append(_RRA('AVERAGE', xff=0.5, steps=43200,
                     rows=120))  # 10 years of 30 days

    #log(`args`)

    RRD.create(args)
Example #3
0
def graph(rrd, back=86400, title='', width=400, height=100):
    """ Make an RRD graph. The caller is responsible for
    deleting the returned path """

    tfile, tpath = tempfile.mkstemp('.gif', 'oh')
    os.close(tfile)

    RRD.graph(tpath, '--start', '-' + str(back), '--title', title, '-w',
              str(width), '-h', str(height), '-c', 'SHADEB#FFFFFF', '-c',
              'SHADEA#FFFFFF', 'DEF:in=%s:in:AVERAGE' % rrd,
              'DEF:out=%s:out:AVERAGE' % rrd, 'CDEF:inbits=in,8,*',
              'CDEF:outbits=out,8,*', 'AREA:inbits#00FF00:"bps in"',
              'LINE1:outbits#0000FF:"bps out"')

    return tpath
Example #4
0
def report_sum(name, start=None, end=None):

    # fetch data from our RRD's

    rrdargs = [
        os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon', name + '.rrd'), 'AVERAGE'
    ]

    if start:
        rrdargs.append('--start')
        rrdargs.append(start)
    if end:
        rrdargs.append('--end')
        rrdargs.append(end)

    header, rows = RRD.fetch(*rrdargs)

    step = int(rows[1][0] - rows[0][0])

    result = {
        'start': rows[0][0],
        'end': rows[-1][0],
        'step': step,
        'steps': len(rows),
        'ticks': 0,
        'vm': 0,
        'rss': 0,
        'in': 0,
        'out': 0,
        'disk': 0,
    }

    for row in rows:

        ticks = _sum_none(header, row, ['vs_uticks', 'vs_sticks'])
        result['ticks'] += ticks
        result['vm'] += _sum_none(header, row, ['vs_vm'])
        result['rss'] += _sum_none(header, row, ['vs_rss'])
        result['in'] += _sum_none(header, row, ['vs_in'])
        result['out'] += _sum_none(header, row, ['vs_out'])
        result['disk'] += _sum_none(header, row, ['vs_disk_b_used'])

    # a COUNTER is always per-second. To get the actual number, it
    # is AVG * STEP (where step is in seconds)

    # a GAUGE is an average (i.e. not per-second). If our tokens are
    # based on a per-minute interval, then the total tokens would be
    # sum(averages) * (STEP/60)

    result['ticks'] = result['ticks'] * step  # counter
    result['vm'] = result['vm'] * (step / 60)  # gauge
    result['rss'] = result['rss'] * (step / 60)  # gauge
    result['in'] = result['in'] * step  # counter
    result['out'] = result['out'] * step  # counter
    result['disk'] = result['disk'] * (step / 60)  # gauge

    return result
Example #5
0
def report_sum(name, start=None, end=None):

    # fetch data from our RRD's

    rrdargs = [os.path.join(cfg.VAR_DB_OPENVPS, "vsmon", name + ".rrd"), "AVERAGE"]

    if start:
        rrdargs.append("--start")
        rrdargs.append(start)
    if end:
        rrdargs.append("--end")
        rrdargs.append(end)

    header, rows = RRD.fetch(*rrdargs)

    step = int(rows[1][0] - rows[0][0])

    result = {
        "start": rows[0][0],
        "end": rows[-1][0],
        "step": step,
        "steps": len(rows),
        "ticks": 0,
        "vm": 0,
        "rss": 0,
        "in": 0,
        "out": 0,
        "disk": 0,
    }

    for row in rows:

        ticks = _sum_none(header, row, ["vs_uticks", "vs_sticks"])
        result["ticks"] += ticks
        result["vm"] += _sum_none(header, row, ["vs_vm"])
        result["rss"] += _sum_none(header, row, ["vs_rss"])
        result["in"] += _sum_none(header, row, ["vs_in"])
        result["out"] += _sum_none(header, row, ["vs_out"])
        result["disk"] += _sum_none(header, row, ["vs_disk_b_used"])

    # a COUNTER is always per-second. To get the actual number, it
    # is AVG * STEP (where step is in seconds)

    # a GAUGE is an average (i.e. not per-second). If our tokens are
    # based on a per-minute interval, then the total tokens would be
    # sum(averages) * (STEP/60)

    result["ticks"] = result["ticks"] * step  # counter
    result["vm"] = result["vm"] * (step / 60)  # gauge
    result["rss"] = result["rss"] * (step / 60)  # gauge
    result["in"] = result["in"] * step  # counter
    result["out"] = result["out"] * step  # counter
    result["disk"] = result["disk"] * (step / 60)  # gauge

    return result
Example #6
0
def update_rrd(server, data):

    if not _rrd_exists(server):
        _create_rrd(server)

    # template
    tmpl = []
    for k in cfg.VSMON_DATA_DEF:
        if k[1]:
            tmpl.append(k[0][:19])

    vals = ['N']
    for k in cfg.VSMON_DATA_DEF:
        if k[1]:
            vals.append('%s' % data[k[0]])

    path = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon', '%s.rrd' % server)
    args = [path, '-t'] + [':'.join(tmpl)] + [':'.join(vals)]

    #log(`args`)
    RRD.update(args)
Example #7
0
def update_rrd(server, data):

    if not _rrd_exists(server):
        _create_rrd(server)

    # template
    tmpl = []
    for k in cfg.VSMON_DATA_DEF:
        if k[1]:
            tmpl.append(k[0][:19])

    vals = ["N"]
    for k in cfg.VSMON_DATA_DEF:
        if k[1]:
            vals.append("%s" % data[k[0]])

    path = os.path.join(cfg.VAR_DB_OPENVPS, "vsmon", "%s.rrd" % server)
    args = [path, "-t"] + [":".join(tmpl)] + [":".join(vals)]

    # log(`args`)
    RRD.update(args)
Example #8
0
def _create_rrd(server):

    path = os.path.join(cfg.VAR_DB_OPENVPS, "vsmon", "%s.rrd" % server)

    args = [path, "-s", "60"]

    for n in xrange(len(cfg.VSMON_DATA_DEF)):
        field = cfg.VSMON_DATA_DEF[n]
        if field[1]:
            args.append(_DS(field[0][:19], dst=field[1], hbeat=field[2], min=field[3], max=field[4]))

    # here due to the billing policy we will probably need to keep 40
    # days of 1 minute samples. at the end of 30 days they should be
    # transferred to a real database... although - what can't it be
    # transferred on daily basis?

    args.append(_RRA("AVERAGE", xff=0.5, steps=1, rows=14400))  # 10 days of 1 min
    args.append(_RRA("AVERAGE", xff=0.5, steps=10, rows=13392))  # 93 days of 5 min
    args.append(_RRA("AVERAGE", xff=0.5, steps=43200, rows=120))  # 10 years of 30 days

    # log(`args`)

    RRD.create(args)
Example #9
0
def period_total(rrd, start, end, dslist=['in', 'out']):

    start, end = int(float(start)), int(float(end))

    header, rows = RRD.fetch(rrd, 'AVERAGE', '-s', str(start), '-e', str(end))

    step = rows[1][0] - rows[0][0]

    # convert DS names to numeric indecies
    dslist = [list(header).index(x) for x in dslist]

    totals = [0] * len(dslist)

    for row in rows:
        n = 0
        for ds_idx in dslist:
            if row[ds_idx]:
                totals[n] += row[ds_idx]
            n += 1

    return step, totals
Example #10
0
def period_total(rrd, start, end, dslist=["in", "out"]):

    start, end = int(float(start)), int(float(end))

    header, rows = RRD.fetch(rrd, "AVERAGE", "-s", str(start), "-e", str(end))

    step = rows[1][0] - rows[0][0]

    # convert DS names to numeric indecies
    dslist = [list(header).index(x) for x in dslist]

    totals = [0] * len(dslist)

    for row in rows:
        n = 0
        for ds_idx in dslist:
            if row[ds_idx]:
                totals[n] += row[ds_idx]
            n += 1

    return step, totals
Example #11
0
def uptime(req, s='-2592000'): # 30 days

    args = ['/dev/null', '--start', s,
            '--width=500'] # bizzare, but we need --width to match

    rrds = _list_server_rrds()

    # list of rrds, each like
    # 'DEF:s0=/var/db/openvps/iad01-06-04-01.openvps.net-uptime.rrd:admin_uptime:AVERAGE',

    for n in xrange(len(rrds)):
        args.append('DEF:s%d=%s:admin_uptime:AVERAGE' % (n, rrds[n]))

    # now the tricky part - construct sum in RPN
    #'CDEF:avg=s0,s1,+,s2,+,s3,+,s4,+,s5,+,s6,+,s7,+,8,/',

    rpn = ['s0']
    for n in xrange(1, len(rrds)):
        rpn.append('s%d' % n)
        rpn.append('+')
    rpn.append('%d' % len(rrds))
    rpn.append('/')

    args.append('CDEF:avg=' + (','.join(rpn)))
    
    # the average
    args.append('VDEF:tot_avg=avg,AVERAGE')

    # the output
    args.append('PRINT:tot_avg:"average uptime\\: %9.6lf"')

    uptime = RRD.graph(*args)[2][0][1:-1]

    #req.log_error(`args`)

    return uptime
Example #12
0
def disk(req, name, params):

    if params.startswith('graph'):

        if not req.args:
            return error(req, 'Not sure what you mean')

        qargs = util.parse_qs(req.args)
        
        if not qargs.has_key('s'):
            return error(req, 'Where do I start?')

        start = '-'+qargs['s'][0]
        width = 484
        height = 70
        nolegend = ''
        if qargs.has_key('l'):
            nolegend = '-g'  # no legend

        # how many days back?
        secs = abs(int(start))
        if secs < 60*60*24:
            # we're talking hours
            title = 'last %d hours' % (secs/(60*60))
        else:
            title = 'last %d days' % (secs/(60*60*24))

        rrd = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon/%s.rrd' % name)
        tfile, tpath = tempfile.mkstemp('.gif', 'oh')
        os.close(tfile)

        args = [tpath, '--start', start,
                '--title', title,
                '-w', str(width),
                '-h', str(height),
                '-c', 'SHADEB#FFFFFF',
                '-c', 'SHADEA#FFFFFF',
                '-l', '0',
                'DEF:d=%s:vs_disk_b_used:AVERAGE' % rrd,
                'CDEF:db=d,1024,*',
                'AREA:db#4eee94:bytes used']

        if qargs.has_key('l'):
            args.append('-g')  # no legend
        
        RRD.graph(*args)
        
        req.content_type = 'image/gif'
        req.sendfile(tpath)
        os.unlink(tpath)
        
        return apache.OK

    else:

        location = 'stats:disk'

        body_tmpl = _tmpl_path('disk_body.html')

        rrd = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon/%s.rrd' % name)
        data = _load_rrd_data(rrd, ['vs_disk_b_used'])

        body_vars = {'data':data}

        vars = {'global_menu': _global_menu(req, name, location),
                'body':psp.PSP(req, body_tmpl, vars=body_vars),
                'name':name}

        p = psp.PSP(req, _tmpl_path('main_frame.html'),
                    vars=vars)

        p.run()

        return apache.OK
Example #13
0
def graph(req, name, command):

    if not req.args:
        return error(req, 'Not sure what you mean')

    qargs = util.parse_qs(req.args)
        
    if not qargs.has_key('s'):
        return error(req, 'Where do I start?')
    start = '-'+qargs['s'][0]

    # exclude these vps's
    exclude = []
    if qargs.has_key('exclude'):
        exclude = qargs['exclude'][0].split()

    # limit to only these vps's
    limit = []
    if qargs.has_key('limit'):
        limit = qargs['limit'][0].split()

    width = 600
    if qargs.has_key('w'):
        width = int(qargs['w'][0])

    height = 400
    if qargs.has_key('h'):
        height = int(qargs['h'][0])

    # how many days back?
    secs = abs(int(start))
    if secs < 60*60*24:
        # we're talking hours
        title = 'last %d hours' % (secs/(60*60))
    else:
        title = 'last %d days' % (secs/(60*60*24))

    if command in ['bwidth', 'mem']:
        # here we need to draw a nice little graph....

        tfile, tpath = tempfile.mkstemp('.gif', 'oh')
        os.close(tfile)

        args = [tpath, '--start', start,
                '--title', title,
                '-w', str(width),
                '-h', str(height),
                '-c', 'SHADEB#FFFFFF',
                '-c', 'SHADEA#FFFFFF',
                '-l', '0']

        if qargs.has_key('l'):
            args.append('-g')  # no legend
        
        # list vservers
        vservers = vsutil.list_vservers()
        keys = vservers.keys()

        # assign colors
        colors = {}
        ci = 0
        for vs in keys:
            colors[vs.replace('-', '')] = COLORS[ci]
            ci += 1

        # process limit and exclude
        if limit:
            keys = [k for k in keys if k in limit]
        keys = [k for k in keys if k not in exclude]

        # we only have so many colors
        if len(keys) > len(COLORS):
            return error(req, 'Not enough colors for VPSs, exclude some:\n%s' % `keys`)

        keys.sort()

        for vs in keys:

            rrd = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon/%s.rrd' % vs)

            vs = vs.replace('-', '') # rrdtool does not like dashes

            if command == 'bwidth':

                args = args + [
                    'DEF:%s_in=%s:vs_in:AVERAGE' % (vs, rrd),
                    'DEF:%s_out=%s:vs_out:AVERAGE' % (vs, rrd),
                    'CDEF:%s_inb=%s_in,-8,*' % (vs, vs),
                    'CDEF:%s_outb=%s_out,8,*' % (vs, vs) ]
                
            elif command == 'mem':

                args = args + [
                    'DEF:%s_vm=%s:vs_vm:AVERAGE' % (vs, rrd),
                    'DEF:%s_rss=%s:vs_rss:AVERAGE' % (vs, rrd),
                    'CDEF:%s_vmb=%s_vm,1024,*' % (vs, vs),
                    'CDEF:%s_rssb=%s_rss,1024,*' % (vs, vs),
                    'CDEF:%s_rssbg=%s_rss,-1024,*' % (vs, vs),
                    ]

        if command == 'bwidth':

            # incoming
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_outb#%s:%s bps out' % (vs, colors[vs], vs.ljust(10)),
                'GPRINT:%s_inb:MAX:Max IN\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_inb:AVERAGE:Avg IN\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_outb:MAX:Max OUT\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_outb:AVERAGE:Avg OUT\\: %%8.2lf%%s\\n' % (vs, )
                ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_outb#%s:%s bps out' % (vs, colors[vs], vs.ljust(10)),
                    'GPRINT:%s_inb:MAX:Max IN\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_inb:AVERAGE:Avg IN\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_outb:MAX:Max OUT\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_outb:AVERAGE:Avg OUT\\: %%8.2lf%%s\\n' % (vs, )
                    ]

            # outgoing
            keys.reverse()
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_inb#%s::' % (vs, colors[vs]),
                ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_inb#%s::' % (vs, colors[vs]),
                    ]

        elif command == 'mem':

            # rss (displayed at bottom)
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_rssbg#%s:%s RSS bytes' % (vs, colors[vs], vs.ljust(10)),
                'GPRINT:%s_rssb:MAX:Max RSS\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_rssb:AVERAGE:Avg RSS\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_vmb:MAX:Max VM\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_vmb:AVERAGE:Avg VM\\: %%8.2lf%%s\\n' % (vs, )
                ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_rssbg#%s:%s RSS bytes' % (vs, colors[vs], vs.ljust(10)),
                    'GPRINT:%s_rssb:MAX:Max RSS\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_rssb:AVERAGE:Avg RSS\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_vmb:MAX:Max VM\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_vmb:AVERAGE:Avg VM\\: %%8.2lf%%s\\n' % (vs, )
                    ]
            # vm
            keys.reverse()
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_vmb#%s::' % (vs, colors[vs]),
                ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_vmb#%s::' % (vs, colors[vs]),
                    ]

        RRD.graph(*args)
        
        req.content_type = 'image/gif'
        req.sendfile(tpath)
        os.unlink(tpath)
        
        return apache.OK
    else:
        return error(req, 'request not understood')
Example #14
0
def uptime_graph(req, s='-2592000'): # 30 days

    # generate a temporary path
    tfile, tpath = tempfile.mkstemp('.png', 'ova')
    os.close(tfile)

    args = [tpath, '--start', s, '--imgformat=PNG', '--width=600',
            '--base=1000', '--height=120', '--interlaced',
            '--lower-limi', '80',
            '--upper-limit', '101', '--rigid']

    rrds = _list_server_rrds()

    # list of rrds, each like
    # 'DEF:s0=/var/db/openvps/iad01-06-04-01.openvps.net-uptime.rrd:admin_uptime:AVERAGE',

    for n in xrange(len(rrds)):
        args.append('DEF:s%d=%s:admin_uptime:AVERAGE' % (n, rrds[n]))

    # now the tricky part - construct sum in RPN
    #'CDEF:avg=s0,s1,+,s2,+,s3,+,s4,+,s5,+,s6,+,s7,+,8,/',

    rpn = ['s0']
    for n in xrange(1, len(rrds)):
        rpn.append('s%d' % n)
        rpn.append('+')
    rpn.append('%d' % len(rrds))
    rpn.append('/')

    args.append('CDEF:avg=' + (','.join(rpn)))
    
    # the average
    args.append('VDEF:tot_avg=avg,AVERAGE')

    trend = 60*60*24*7 # 7 days

    # trended average
    args.append('CDEF:tavg=avg,%d,TREND' % trend)

    degr = '99.99'
    
    # degraded - anything less than 99.99%
    args.append('CDEF:degr=avg,%s,GE,0,INF,IF' % degr)
    
    # degraded shade
    args.append('AREA:degr#ffff99:degraded\\n')

    # tranded average
    args.append('LINE2:tavg#0000FF:%d day moving average\\n' % (trend / (60*60*24)))
                
    # real uptime
    args.append('LINE:avg#00FF00:simple average\\n')
                
    # the output
    args.append('COMMENT:\\n')
    args.append('GPRINT:tot_avg:Last %d Day Average\\: %%9.6lf\\n' % (abs(int(s)) / (60*60*24)))

    # run it
    RRD.graph(*args)

    req.content_type = 'image/png'
    req.sendfile(tpath)
    os.unlink(tpath)
                            
    return 
Example #15
0
def disk(req, name, params):

    if params.startswith('graph'):

        if not req.args:
            return error(req, 'Not sure what you mean')

        qargs = util.parse_qs(req.args)

        if not qargs.has_key('s'):
            return error(req, 'Where do I start?')

        start = '-' + qargs['s'][0]
        width = 484
        height = 70
        nolegend = ''
        if qargs.has_key('l'):
            nolegend = '-g'  # no legend

        # how many days back?
        secs = abs(int(start))
        if secs < 60 * 60 * 24:
            # we're talking hours
            title = 'last %d hours' % (secs / (60 * 60))
        else:
            title = 'last %d days' % (secs / (60 * 60 * 24))

        rrd = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon/%s.rrd' % name)
        tfile, tpath = tempfile.mkstemp('.gif', 'oh')
        os.close(tfile)

        args = [
            tpath, '--start', start, '--title', title, '-w',
            str(width), '-h',
            str(height), '-c', 'SHADEB#FFFFFF', '-c', 'SHADEA#FFFFFF', '-l',
            '0',
            'DEF:d=%s:vs_disk_b_used:AVERAGE' % rrd, 'CDEF:db=d,1024,*',
            'AREA:db#4eee94:bytes used'
        ]

        if qargs.has_key('l'):
            args.append('-g')  # no legend

        RRD.graph(*args)

        req.content_type = 'image/gif'
        req.sendfile(tpath)
        os.unlink(tpath)

        return apache.OK

    else:

        location = 'stats:disk'

        body_tmpl = _tmpl_path('disk_body.html')

        rrd = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon/%s.rrd' % name)
        data = _load_rrd_data(rrd, ['vs_disk_b_used'])

        body_vars = {'data': data}

        vars = {
            'global_menu': _global_menu(req, name, location),
            'body': psp.PSP(req, body_tmpl, vars=body_vars),
            'name': name
        }

        p = psp.PSP(req, _tmpl_path('main_frame.html'), vars=vars)

        p.run()

        return apache.OK
Example #16
0
def graph(req, name, command):

    if not req.args:
        return error(req, 'Not sure what you mean')

    qargs = util.parse_qs(req.args)

    if not qargs.has_key('s'):
        return error(req, 'Where do I start?')
    start = '-' + qargs['s'][0]

    # exclude these vps's
    exclude = []
    if qargs.has_key('exclude'):
        exclude = qargs['exclude'][0].split()

    # limit to only these vps's
    limit = []
    if qargs.has_key('limit'):
        limit = qargs['limit'][0].split()

    width = 600
    if qargs.has_key('w'):
        width = int(qargs['w'][0])

    height = 400
    if qargs.has_key('h'):
        height = int(qargs['h'][0])

    # how many days back?
    secs = abs(int(start))
    if secs < 60 * 60 * 24:
        # we're talking hours
        title = 'last %d hours' % (secs / (60 * 60))
    else:
        title = 'last %d days' % (secs / (60 * 60 * 24))

    if command in ['bwidth', 'mem']:
        # here we need to draw a nice little graph....

        tfile, tpath = tempfile.mkstemp('.gif', 'oh')
        os.close(tfile)

        args = [
            tpath, '--start', start, '--title', title, '-w',
            str(width), '-h',
            str(height), '-c', 'SHADEB#FFFFFF', '-c', 'SHADEA#FFFFFF', '-l',
            '0'
        ]

        if qargs.has_key('l'):
            args.append('-g')  # no legend

        # list vservers
        vservers = vsutil.list_vservers()
        keys = vservers.keys()

        # assign colors
        colors = {}
        ci = 0
        for vs in keys:
            colors[vs.replace('-', '')] = COLORS[ci]
            ci += 1

        # process limit and exclude
        if limit:
            keys = [k for k in keys if k in limit]
        keys = [k for k in keys if k not in exclude]

        # we only have so many colors
        if len(keys) > len(COLORS):
            return error(
                req,
                'Not enough colors for VPSs, exclude some:\n%s' % ` keys `)

        keys.sort()

        for vs in keys:

            rrd = os.path.join(cfg.VAR_DB_OPENVPS, 'vsmon/%s.rrd' % vs)

            vs = vs.replace('-', '')  # rrdtool does not like dashes

            if command == 'bwidth':

                args = args + [
                    'DEF:%s_in=%s:vs_in:AVERAGE' % (vs, rrd),
                    'DEF:%s_out=%s:vs_out:AVERAGE' % (vs, rrd),
                    'CDEF:%s_inb=%s_in,-8,*' % (vs, vs),
                    'CDEF:%s_outb=%s_out,8,*' % (vs, vs)
                ]

            elif command == 'mem':

                args = args + [
                    'DEF:%s_vm=%s:vs_vm:AVERAGE' % (vs, rrd),
                    'DEF:%s_rss=%s:vs_rss:AVERAGE' % (vs, rrd),
                    'CDEF:%s_vmb=%s_vm,1024,*' % (vs, vs),
                    'CDEF:%s_rssb=%s_rss,1024,*' % (vs, vs),
                    'CDEF:%s_rssbg=%s_rss,-1024,*' % (vs, vs),
                ]

        if command == 'bwidth':

            # incoming
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_outb#%s:%s bps out' % (vs, colors[vs], vs.ljust(10)),
                'GPRINT:%s_inb:MAX:Max IN\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_inb:AVERAGE:Avg IN\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_outb:MAX:Max OUT\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_outb:AVERAGE:Avg OUT\\: %%8.2lf%%s\\n' % (vs, )
            ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_outb#%s:%s bps out' %
                    (vs, colors[vs], vs.ljust(10)),
                    'GPRINT:%s_inb:MAX:Max IN\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_inb:AVERAGE:Avg IN\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_outb:MAX:Max OUT\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_outb:AVERAGE:Avg OUT\\: %%8.2lf%%s\\n' % (vs, )
                ]

            # outgoing
            keys.reverse()
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_inb#%s::' % (vs, colors[vs]),
            ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_inb#%s::' % (vs, colors[vs]),
                ]

        elif command == 'mem':

            # rss (displayed at bottom)
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_rssbg#%s:%s RSS bytes' %
                (vs, colors[vs], vs.ljust(10)),
                'GPRINT:%s_rssb:MAX:Max RSS\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_rssb:AVERAGE:Avg RSS\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_vmb:MAX:Max VM\\: %%8.2lf%%s' % (vs, ),
                'GPRINT:%s_vmb:AVERAGE:Avg VM\\: %%8.2lf%%s\\n' % (vs, )
            ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_rssbg#%s:%s RSS bytes' %
                    (vs, colors[vs], vs.ljust(10)),
                    'GPRINT:%s_rssb:MAX:Max RSS\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_rssb:AVERAGE:Avg RSS\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_vmb:MAX:Max VM\\: %%8.2lf%%s' % (vs, ),
                    'GPRINT:%s_vmb:AVERAGE:Avg VM\\: %%8.2lf%%s\\n' % (vs, )
                ]
            # vm
            keys.reverse()
            vs = keys[0].replace('-', '')
            args = args + [
                'AREA:%s_vmb#%s::' % (vs, colors[vs]),
            ]

            for vs in keys[1:]:
                vs = vs.replace('-', '')
                args = args + [
                    'STACK:%s_vmb#%s::' % (vs, colors[vs]),
                ]

        RRD.graph(*args)

        req.content_type = 'image/gif'
        req.sendfile(tpath)
        os.unlink(tpath)

        return apache.OK
    else:
        return error(req, 'request not understood')