mirror of
https://github.com/django-q2/django-q2.git
synced 2026-09-24 04:08:12 +08:00
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.
185 lines
6.7 KiB
Python
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
|