Files
django-q2/django_q/monitor.py
T
Ilan Steemers b113a52825 Restructured all the modules
The core module was becoming too large.
By splitting it in  cluster, monitor and tasks it becomes more manageable. It also reflects the documentation structure.
2015-07-03 15:12:11 +02:00

185 lines
6.7 KiB
Python

import socket
# external
from blessed import Terminal
# django
from django.core import signing
from django.utils import timezone
from django.utils.translation import ugettext as _
# local
from .conf import Conf, redis_client
from .tasks import SignedPackage
def monitor(run_once=False):
term = Terminal()
r = redis_client
with term.fullscreen(), term.hidden_cursor(), term.cbreak():
val = None
start_width = int(term.width / 8)
while val not in (u'q', u'Q',):
col_width = int(term.width / 8)
# In case of resize
if col_width != start_width:
print(term.clear)
start_width = col_width
print(term.move(0, 0) + term.black_on_green(term.center(_('Host'), width=col_width - 1)))
print(term.move(0, 1 * col_width) + term.black_on_green(term.center(_('Id'), width=col_width - 1)))
print(term.move(0, 2 * col_width) + term.black_on_green(term.center(_('State'), width=col_width - 1)))
print(term.move(0, 3 * col_width) + term.black_on_green(term.center(_('Pool'), width=col_width - 1)))
print(term.move(0, 4 * col_width) + term.black_on_green(term.center(_('TQ'), width=col_width - 1)))
print(term.move(0, 5 * col_width) + term.black_on_green(term.center(_('RQ'), width=col_width - 1)))
print(term.move(0, 6 * col_width) + term.black_on_green(term.center(_('RC'), width=col_width - 1)))
print(term.move(0, 7 * col_width) + term.black_on_green(term.center(_('Up'), width=col_width - 1)))
i = 2
stats = Stat.get_all(r=r)
print(term.clear_eos())
for stat in stats:
# color status
if stat.status == Conf.WORKING:
status = term.green(Conf.WORKING)
elif stat.status == Conf.STOPPED:
status = term.red(Conf.STOPPED)
elif stat.status == Conf.IDLE:
status = Conf.IDLE
else:
status = term.yellow(stat.status)
# color q's
tasks = stat.task_q_size
if tasks > 0:
tasks = term.cyan(str(tasks))
results = stat.done_q_size
if results > 0:
results = term.cyan(str(results))
# color workers
workers = len(stat.workers)
if workers < Conf.WORKERS:
workers = term.yellow(str(workers))
# format uptime
uptime = (timezone.now() - stat.tob).total_seconds()
hours, remainder = divmod(uptime, 3600)
minutes, seconds = divmod(remainder, 60)
uptime = '%d:%02d:%02d' % (hours, minutes, seconds)
# print to the terminal
print(term.move(i, 0) + term.center(stat.host[:col_width - 1], width=col_width - 1))
print(term.move(i, 1 * col_width) + term.center(stat.cluster_id, width=col_width - 1))
print(term.move(i, 2 * col_width) + term.center(status, width=col_width - 1))
print(term.move(i, 3 * col_width) + term.center(workers, width=col_width - 1))
print(term.move(i, 4 * col_width) + term.center(tasks, width=col_width - 1))
print(term.move(i, 5 * col_width) + term.center(results, width=col_width - 1))
print(term.move(i, 6 * col_width) + term.center(stat.reincarnations, width=col_width - 1))
print(term.move(i, 7 * col_width) + term.center(uptime, width=col_width - 1))
i += 1
# for testing
if run_once:
return Stat.get_all(r=r)
print(term.move(i + 2, 0) + term.center(_('[Press q to quit]')))
val = term.inkey(timeout=1)
class Status(object):
"""
Cluster status base class
"""
def __init__(self, pid):
self.workers = []
self.tob = None
self.reincarnations = 0
self.cluster_id = pid
self.sentinel = 0
self.status = 'Idle'
self.done_q_size = 0
self.host = socket.gethostname()
self.monitor = 0
self.task_q_size = 0
self.pusher = 0
self.timestamp = timezone.now()
class Stat(Status):
"""
Status object for Cluster monitoring
"""
def __init__(self, sentinel):
super(Stat, self).__init__(sentinel.parent_pid)
self.r = sentinel.r
self.tob = sentinel.tob
self.reincarnations = sentinel.reincarnations
self.sentinel = sentinel.pid
self.status = sentinel.status()
self.done_q_size = sentinel.done_queue.qsize()
if sentinel.monitor:
self.monitor = sentinel.monitor.pid
self.task_q_size = sentinel.task_queue.qsize()
if sentinel.pusher:
self.pusher = sentinel.pusher.pid
for w in sentinel.pool:
self.workers.append(w.pid)
def uptime(self):
return (timezone.now() - self.tob).total_seconds()
@property
def key(self):
"""
:return: redis key for this cluster statistic
"""
return self.get_key(self.cluster_id)
@staticmethod
def get_key(cluster_id):
"""
:param cluster_id: cluster ID
:return: redis key for the cluster statistic
"""
return '{}:{}'.format(Conf.Q_STAT, cluster_id)
def save(self):
self.r.set(self.key, SignedPackage.dumps(self, True), 3)
def empty_queues(self):
return self.done_q_size + self.task_q_size == 0
@staticmethod
def get(cluster_id, r=redis_client):
"""
gets the current status for the cluster
:param cluster_id: id of the cluster
:return: Stat or Status
"""
key = Stat.get_key(cluster_id)
if r.exists(key):
pack = r.get(key)
try:
return SignedPackage.loads(pack)
except signing.BadSignature:
return None
return Status(cluster_id)
@staticmethod
def get_all(r=redis_client):
"""
Gets status for all currently running clusters with the same prefix and secret key
:return: list of type Stat
"""
stats = []
keys = r.keys(pattern='{}:*'.format(Conf.Q_STAT))
if keys:
packs = r.mget(keys)
for pack in packs:
try:
stats.append(SignedPackage.loads(pack))
except signing.BadSignature:
continue
return stats
def __getstate__(self):
# Don't pickle the redis connection
state = dict(self.__dict__)
del state['r']
return state