From aebfe74630da0e042038d958bdc1e870fbc28f76 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Fri, 18 Nov 2011 01:56:23 +0100 Subject: [PATCH] Add polling/scaling graphs. --- bin/rqinfo | 98 +++++++++++++++++++++++++++++++++++----------------- rq/utils.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 rq/utils.py diff --git a/bin/rqinfo b/bin/rqinfo index 663b5e5..df4e892 100755 --- a/bin/rqinfo +++ b/bin/rqinfo @@ -1,8 +1,19 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os +import time import optparse -from rq import use_redis, Queue, Worker -from rq.dummy import * +from rq import use_redis, Queue +from rq.utils import gettermsize, colorize + + +def get_scale(x): + """Finds the lowest scale where x <= scale.""" + scales = [20, 50, 100, 200, 400, 600, 800, 1000] + for scale in scales: + if x <= scale: + return scale + return x def parse_args(): @@ -13,9 +24,59 @@ def parse_args(): parser.add_option('-w', '--workers', dest='subcmd', action='store_const', const='workers', help='Shows stats for workers.') + parser.add_option('-n', '--interval', dest='interval', + type='float', + help='The interval between polls, in seconds. Does not poll if 0.') + parser.add_option('-g', '--graph', dest='graph', + action='store_true', default=False, + help='Shows bar chart graphs where possible.') opts, args = parser.parse_args() return (opts, args, parser) +def show_queues(opts, args, parser): + while True: + if len(args): + qs = map(Queue, args) + else: + qs = Queue.all() + + num_jobs = 0 + termwidth, _ = gettermsize() + chartwidth = min(20, termwidth - 20) + + max_count = 0 + counts = dict() + for q in qs: + count = q.count + counts[q] = count + max_count = max(max_count, count) + scale = get_scale(max_count) + ratio = chartwidth * 1.0 / scale + + if opts.interval: + os.system('clear') + + print('scale = %d' % scale) + for q in qs: + count = counts[q] + if opts.graph: + chart = colorize('darkgreen', '|' + '█' * int(ratio * count)) + line = '%-12s %s %d' % (q.name, chart, count) + else: + line = '%-12s %d' % (q.name, count) + print(line) + + num_jobs += count + print('%d queues, %d jobs total' % (len(qs), num_jobs)) + + if opts.interval: + time.sleep(opts.interval) + else: + break + +def show_workers(opts, args, parser): + raise NotImplementedError() + def main(): opts, args, parser = parse_args() @@ -25,37 +86,10 @@ def main(): use_redis() if opts.subcmd == 'workers': - raise NotImplementedError() + show_workers(opts, args, parser) elif opts.subcmd == 'queues': - #q = Queue('foo') - #q.enqueue(do_nothing) - #q.enqueue(sleep, 3) - #q = Queue('bar') - #q.enqueue(yield_stuff) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - #q.enqueue(do_nothing) - qs = Queue.all() - num_jobs = 0 - for q in qs: - count = q.count - chart = '█' * count - print('%-12s %3d |%s' % (q.name, count, chart)) - num_jobs += count - print('%d queues, %d jobs total' % (len(qs), num_jobs)) + show_queues(opts, args, parser) + if __name__ == '__main__': main() diff --git a/rq/utils.py b/rq/utils.py new file mode 100644 index 0000000..a43f435 --- /dev/null +++ b/rq/utils.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +""" + pygments.console + ~~~~~~~~~~~~~~~~ + + Format colored console output. + + :copyright: Copyright 2006-2011 by the Pygments team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +import os + +def gettermsize(): + def ioctl_GWINSZ(fd): + try: + import fcntl, termios, struct + cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, + '1234')) + except: + return None + return cr + cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) + if not cr: + try: + fd = os.open(os.ctermid(), os.O_RDONLY) + cr = ioctl_GWINSZ(fd) + os.close(fd) + except: + pass + if not cr: + try: + cr = (os.environ['LINES'], os.environ['COLUMNS']) + except: + cr = (25, 80) + return int(cr[1]), int(cr[0]) + +esc = "\x1b[" + +codes = {} +codes[""] = "" +codes["reset"] = esc + "39;49;00m" + +codes["bold"] = esc + "01m" +codes["faint"] = esc + "02m" +codes["standout"] = esc + "03m" +codes["underline"] = esc + "04m" +codes["blink"] = esc + "05m" +codes["overline"] = esc + "06m" + +dark_colors = ["black", "darkred", "darkgreen", "brown", "darkblue", + "purple", "teal", "lightgray"] +light_colors = ["darkgray", "red", "green", "yellow", "blue", + "fuchsia", "turquoise", "white"] + +x = 30 +for d, l in zip(dark_colors, light_colors): + codes[d] = esc + "%im" % x + codes[l] = esc + "%i;01m" % x + x += 1 + +del d, l, x + +codes["darkteal"] = codes["turquoise"] +codes["darkyellow"] = codes["brown"] +codes["fuscia"] = codes["fuchsia"] +codes["white"] = codes["bold"] + + +def reset_color(): + return codes["reset"] + + +def colorize(color_key, text): + return codes[color_key] + text + codes["reset"] + + +def ansiformat(attr, text): + """ + Format ``text`` with a color and/or some attributes:: + + color normal color + *color* bold color + _color_ underlined color + +color+ blinking color + """ + result = [] + if attr[:1] == attr[-1:] == '+': + result.append(codes['blink']) + attr = attr[1:-1] + if attr[:1] == attr[-1:] == '*': + result.append(codes['bold']) + attr = attr[1:-1] + if attr[:1] == attr[-1:] == '_': + result.append(codes['underline']) + attr = attr[1:-1] + result.append(codes[attr]) + result.append(text) + result.append(codes['reset']) + return ''.join(result)