
| Current Path : /var/www/web-klick.de/dsh/50_dev2017/1313__procpyjs/src/python/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/web-klick.de/dsh/50_dev2017/1313__procpyjs/src/python/processing.py |
import multiprocessing
import base_process
from redis import Redis
import imp
import os
import sys
import time
import inspect
import logging
from operator import itemgetter
logger = logging.getLogger()
level = logging.DEBUG # alternatives: logging.DEBUG, logging.INFO, logging.WARNING
logger.setLevel(level)
def log(msg, debug=False, exception=False):
callee_name = sys._getframe().f_back.f_code.co_name
lg = logging.debug if debug else logging.exception if exception else logging.info
lg("%s->%s: %s" % (multiprocessing.current_process().name, callee_name, msg))
redis_settings = {'host': 'localhost', 'port': 6379, 'db': 0}
# prefix for storing obj instances in redis
proc_key_prefix = "proc_"
# prefix for storing module path of a process in redis
proc_paths_key_prefix = "path_"
# prefix for storing a job queue list in redis
job_queue_prefix = "job_queue_"
# prefix for jobs that are currently being executed
running_jobs_prefix = "running_jobs_"
class ProcessManager(object):
"""
ProcessManager can be used to manage process instances using a redis database.
Usage:
Register a process by using :func:`ProcessManager.register_process` which will return a process id
this process id can be used to run the process, retaining the instance over each run
"""
def __init__(self, db_options=redis_settings):
self.db_options = db_options
self.redis = Redis(**db_options)
def _get_proc_id_from_class(self, cls):
i = 0
proc_id = "%s.%s.%03d" % (cls.__module__, cls.__name__, i)
while self.redis.exists(proc_key_prefix + proc_id):
i += 1
proc_id = "%s.%s.%03d" % (cls.__module__, cls.__name__, i)
return proc_id
def store_process(self, proc, proc_id=None, result=None):
"""
:param proc: the process instance to store
:param proc_id: the process id to be used (proc_key_prefix will be prepended), is generated if its None
:return: the process id under which the process can be accessed using the ProcessManager functions
"""
proc_id = self._get_proc_id_from_class(proc.__class__) if proc_id is None else proc_id
self.redis.hmset(name=proc_key_prefix + proc_id, mapping=proc.dump())
proc_path = inspect.getfile(proc.__class__).replace(".pyc", ".py")
self.redis.set(name=proc_paths_key_prefix + proc_id, value=proc_path)
log("stored process %s" % proc_id, debug=True)
return proc_id
def get_process(self, proc_id):
"""
:param proc_id: the process id which identifies the process
:return: the restored instance of the process
"""
if not self.redis.exists(proc_key_prefix+proc_id):
return log("no process found given proc_id %s" % proc_id, exception=True)
obj_dump = self.redis.hgetall(proc_key_prefix + proc_id)
proc_path = self.redis.get(proc_paths_key_prefix + proc_id)
cls_name, mod_name = proc_id.split(".")[-2], ".".join(proc_id.split(".")[:-2])
if proc_path.endswith(".pyc"):
proc_path = proc_path.replace(".pyc", ".py")
if not os.path.isfile(proc_path):
raise RuntimeError("Path to process source file does not exist: %s" % proc_path)
module = imp.load_source(mod_name, proc_path)
cls = getattr(module, cls_name)
return object.__new__(cls)._restore(obj_dump)
def register_process(self, path_to_file, class_name=None):
"""
:param path_to_file: path to the python source file
:param class_name: class which should be registered
:return: the process id under which the registered process can be addressed
"""
module_name = "".join(os.path.basename(path_to_file).split(".py")[:-1])
module = imp.load_source(module_name, path_to_file)
if class_name is None:
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, base_process.BaseProcess):
class_name, cls = name, obj
cls = getattr(module, class_name)
proc_interface = ProcessingInterface(self._get_proc_id_from_class(cls), self.db_options)
return self.store_process(cls(proc_interface))
def query_proc_instances(self, proc_class):
"""
:param proc_class: the class of the process (as a valid python reference)
:return: all instances of the given class stored in the db
"""
pattern = proc_class.__module__ + "." + proc_class.__name__ + "*"
return self.query_processes(pattern)
def query_processes(self, pattern="*"):
"""
:param pattern: a pattern for querying the db
:return: all processes matching given pattern
"""
return [res[len(proc_key_prefix):] for res in self.redis.keys("%s%s" % (proc_key_prefix, pattern))]
def run_process(self, proc_id, synchronous=False):
"""
runs a previously registered process by
:param proc_id: the process id which identifies the process
:return: the result of the called state function if process is run synchronously
"""
log("starting process %s %s" % (proc_id, "synchronously" if synchronous else "asynchronously"), debug=True)
if synchronous:
try:
self.redis.set(running_jobs_prefix + proc_id, os.getpid())
proc = self.get_process(proc_id)
res = proc._run(proc_man=self)
self.store_process(proc, proc_id)
return res
except Exception as e:
return e
finally:
self.redis.delete(running_jobs_prefix + proc_id)
else:
self.redis.hset(proc_key_prefix + proc_id, key="WAKETIME", value=time.time())
def get_running_processes(self, pattern="*"):
return self.redis.keys(pattern=running_jobs_prefix + pattern)
def add_job(self, proc_id, waketime):
""" adds a job to the job queue (sorted set) with proc_id as name and waketime as score"""
self.redis.zadd(job_queue_prefix, proc_id, float(waketime))
def get_jobs(self, start_index=0, end_index=-1):
""" return (proc_id, waketime) tuples in job_queue ordered by their corresponding waketime """
jobs = self.redis.zrange(job_queue_prefix, start_index, end_index, withscores=True)
return map(lambda (proc_id, waketime): (proc_id, float(waketime)), jobs)
def remove_job(self, proc_id):
return self.redis.zrem(job_queue_prefix, proc_id)
def execute_job(self):
command_pipeline = self.redis.pipeline()
command_pipeline.zrange(job_queue_prefix, 0, 0, withscores=True)
command_pipeline.zremrangebyrank(job_queue_prefix, 0, 0)
jobs, _ = command_pipeline.execute()
if len(jobs) > 0:
proc_id, waketime = jobs[0]
if float(waketime) < time.time():
self.run_process(proc_id, synchronous=True)
else:
self.add_job(proc_id, waketime)
def get_waketime(self, proc_id):
"""return the waketime for proc identified by proc_id"""
if self.redis.hexists(proc_key_prefix + proc_id, key="WAKETIME"):
return float(self.redis.hget(proc_key_prefix + proc_id, key="WAKETIME"))
else:
return float("inf")
def get_graph(self, proc_id):
proc = self.get_process(proc_id)
return proc._get_graph()
class WorkerProcess(multiprocessing.Process):
def __init__(self, db_options=redis_settings):
super(WorkerProcess, self).__init__(target=self.worker_loop)
self.db_options = db_options
self.run_marker = multiprocessing.Queue(1)
self.run_marker.put(True)
log("created worker process", debug=True)
def stop(self):
log("stopping worker..", debug=True)
if self.run_marker.empty():
self.run_marker.get()
self.run_marker.put(False)
def check_running(self, run_marker):
if run_marker.empty():
return True
is_running = run_marker.get()
run_marker.put(is_running)
return is_running
def worker_loop(self):
"""checks for job in redis job_queue and executes it if present"""
try:
proc_man = ProcessManager(self.db_options)
while self.check_running(self.run_marker):
proc_man.execute_job()
time.sleep(.1)
log("exiting...")
except KeyboardInterrupt:
log("exiting...")
return
except Exception as e:
log(e, exception=True)
class ProcessServer(multiprocessing.Process):
def __init__(self, db_options=redis_settings):
super(ProcessServer, self).__init__(target=self.master_loop, args=[db_options])
def master_loop(self, db_options):
""" queries db for entries with waketime < 0 and puts those entries in the job_queue """
try:
proc_man = ProcessManager(db_options=db_options)
log("starting master loop")
while True:
entries = proc_man.query_processes()
waketimes = [(proc_man.get_waketime(proc_id), proc_id) for proc_id in entries]
running = proc_man.get_running_processes()
jobs = proc_man.get_jobs()
filter_fct = lambda w: 0 < w[0] < time.time() and w[1] not in running and w[1] not in jobs
waketimes = sorted(filter(filter_fct, waketimes), key=itemgetter(0))
for waketime, proc_id in waketimes:
proc_man.add_job(proc_id, waketime)
time.sleep(.2)
log("stopped master loop")
except KeyboardInterrupt:
log("exiting..")
except Exception as e:
log(e, exception=True)
class ProcessingInterface(object):
def __init__(self, proc_id=None, procman_settings=redis_settings):
self.proc_id = proc_id
self._procman = ProcessManager(procman_settings)
def query_processes(self, pattern="*"):
return self._procman.query_processes(pattern)