PK!O88psmon/limiters/base.pyclass Limiter(object): def __init__(self, *args, **kwargs): pass def preexec(self): pass def tick(self, process_info): pass def update(self, state): return state def accumulate_reducer(self, a, b): return a + b def teardown(self) pass PK!' psmon/limiters/cpu_times.py# class CpuTimeLimiter(): # def __init__(self, cpu_time_limit=None, **kwargs): # self.cpu_time_limit = cpu_time_limit PK!'** psmon/main.pyimport os import multiprocessing import time import signal import resource from subprocess import Popen, PIPE from loguru import logger from collections import namedtuple import subprocess import psutil from psmon.process import ProcessNode from psmon.utils import graceful_kill, human2bytes WALL_EXCEEDED_STR = "Wall time limit exceeded!" CPU_EXCEEDED_STR = "CPU time limit exceeded!" MEMORY_EXCEEDED_STR = "Memory usage limit exceeded!" def get_processes_info(processes): return [ p.info for p in psutil.process_iter( attrs=["pid", "ppid", "cpu_times", "memory_info", "status"] ) if p.info["pid"] in processes or p.info["ppid"] in processes ] def set_limits(wall_time, cpu_time, memory, output): def fn(): os.setpgrp() MB = 1024 * 1024 resource.setrlimit(resource.RLIMIT_NPROC, (1024, 1024)) if memory: resource.setrlimit(resource.RLIMIT_AS, (memory, memory + 100 * MB)) if output: resource.setrlimit(resource.RLIMIT_FSIZE, (output, output + 100 * MB)) return fn def run( args, *popenargs, wall_time_limit=None, cpu_time_limit=None, memory_limit=None, output_limit=None, stdout=PIPE, stderr=PIPE, freq=10, **kwargs, ): if type(memory_limit) == str: memory_limit = human2bytes(memory_limit) processes = {} error = None error_str = None start_time = time.monotonic() with Popen( args, *popenargs, stdout=stdout, stderr=stdout, preexec_fn=set_limits( wall_time_limit, cpu_time_limit, memory_limit, output_limit ), **kwargs, ) as p: root_pid = p.pid pid, ret, res = os.wait4(root_pid, os.WNOHANG | os.WUNTRACED) if pid == root_pid: # Already terminated return dict( wall_time=time.monotonic() - start_time, cpu_time=res.ru_utime + res.ru_stime, max_memory=res.ru_maxrss, return_code=ret, ) root_process = psutil.Process(root_pid) pinfo = root_process.as_dict(attrs=["cpu_times", "memory_info"]) processes[root_pid] = ProcessNode(root_pid) cpu_time = 0 memory = 0 if pinfo["cpu_times"] is not None: cpu_time = pinfo["cpu_times"].user + pinfo["cpu_times"].system if pinfo["memory_info"] is not None: memory = pinfo["memory_info"].rss processes[root_pid].update(cpu_time, memory) while ( root_process.is_running() and not root_process.status() == psutil.STATUS_ZOMBIE ): processes_info = get_processes_info(processes) for pinfo in processes_info: if pinfo["pid"] not in processes: processes[pinfo["pid"]] = ProcessNode(pinfo["pid"]) processes[pinfo["ppid"]].add_child(processes[pinfo["pid"]]) if pinfo["cpu_times"] is None or pinfo["memory_info"] is None: continue cpu_time = pinfo["cpu_times"].user + pinfo["cpu_times"].system memory = pinfo["memory_info"].rss processes[pinfo["pid"]].update(cpu_time, memory) root_process_stats = processes[root_pid].get_accumulated_stats() if wall_time_limit and time.monotonic() - start_time > wall_time_limit: error = TimeoutError error_str = WALL_EXCEEDED_STR break if cpu_time_limit and root_process_stats["cpu_time"] > cpu_time_limit: error = TimeoutError error_str = CPU_EXCEEDED_STR break if memory_limit and root_process_stats["max_memory"] > memory_limit: error = MemoryError error_str = MEMORY_EXCEEDED_STR break time.sleep(1.0 / freq) end_time = time.monotonic() - start_time root_process_stats = processes[root_pid].get_accumulated_stats() stats = dict( wall_time=end_time, cpu_time=root_process_stats["cpu_time"], max_memory=root_process_stats["max_memory"], error_str=None, error=None, status_code=None, ) if error: stats["error"] = error stats["error_str"] = error_str return_codes = graceful_kill(processes) stats["return_code"] = return_codes[root_pid] if stats["error_str"]: logger.warning(error_str) return stats PK!ipsmon/process.pyclass ProcessNode: def __init__(self, pid): self.pid = pid self.parent = None self.children = [] self.max_memory = 0 self.cpu_time = 0 self.finished = False def add_child(self, child): self.children.append(child) child.parent = self def update(self, cpu_time, memory): self.max_memory = max(self.max_memory, memory) self.cpu_time = cpu_time def get_accumulated_stats(self): max_memory = self.max_memory cpu_time = self.cpu_time for child in self.children: child_stats = child.get_accumulated_stats() max_memory += child_stats["max_memory"] cpu_time += child_stats["cpu_time"] return dict(max_memory=max_memory, cpu_time=cpu_time) PK!%$2 psmon/utils.pyimport os import time import psutil def _is_running(pid): try: id, sts = os.waitpid(pid, os.WNOHANG) return id == pid except ChildProcessError: try: return psutil.Process(pid).is_running() except: return False def graceful_kill(pids, timeout=3): """ Terminate then kill pids after ${timeout} s. Also return returncode as dict([pid]: returncode) """ stopped = [] procs = [psutil.Process(pid) for pid in pids] for proc in procs: proc.terminate() gone, alive = psutil.wait_procs(procs, timeout=timeout) stopped += gone if len(alive) > 0: for proc in alive: proc.kill() gone, alive = psutil.wait_procs(procs, timeout=timeout) assert len(alive) > 0 stopped += gone return {proc.pid: proc.returncode for proc in stopped} ## {{{ http://code.activestate.com/recipes/578019/ (r15) """ Bytes-to-human / human-to-bytes converter. Based on: http://goo.gl/kTQMs Working with Python 2.x and 3.x. Author: Giampaolo Rodola' License: MIT """ # see: http://goo.gl/kTQMs SYMBOLS = { "customary": ("B", "K", "M", "G", "T", "P", "E", "Z", "Y"), "customary_ext": ( "byte", "kilo", "mega", "giga", "tera", "peta", "exa", "zetta", "iotta", ), "iec": ("Bi", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"), "iec_ext": ("byte", "kibi", "mebi", "gibi", "tebi", "pebi", "exbi", "zebi", "yobi"), } def human2bytes(s): """ Attempts to guess the string format based on default symbols set and return the corresponding bytes as an integer. When unable to recognize the format ValueError is raised. >>> human2bytes('0 B') 0 >>> human2bytes('1 K') 1024 >>> human2bytes('1 M') 1048576 >>> human2bytes('1 Gi') 1073741824 >>> human2bytes('1 tera') 1099511627776 >>> human2bytes('0.5kilo') 512 >>> human2bytes('0.1 byte') 0 >>> human2bytes('1 k') # k is an alias for K 1024 >>> human2bytes('12 foo') Traceback (most recent call last): ... ValueError: can't interpret '12 foo' """ init = s num = "" while s and s[0:1].isdigit() or s[0:1] == ".": num += s[0] s = s[1:] num = float(num) letter = s.strip() for name, sset in SYMBOLS.items(): if letter in sset: break else: if letter == "k": # treat 'k' as an alias for 'K' as per: http://goo.gl/kTQMs sset = SYMBOLS["customary"] letter = letter.upper() else: raise ValueError("can't interpret %r" % init) prefix = {sset[0]: 1} for i, s in enumerate(sset[1:]): prefix[s] = 1 << (i + 1) * 10 return int(num * prefix[letter]) ## end of http://code.activestate.com/recipes/578019/ }}} PK!XB33psmon-0.2.0.dist-info/LICENSEMIT License Copyright (c) 2019 Rakha Kanz Kautsar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!Hu)GTUpsmon-0.2.0.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O-)$qzd&Y)r$UV&UrPK!HuXpsmon-0.2.0.dist-info/METADATAMO1Mh"L!8H%2.T_T'lgޙS@(AY#x+?gR.hk![y-oy[B+Sk!s5 (*V~NxJ0x >7|އ2RՂTt^ٖPjk]6eIq!by OÿRhKi2>4XwEҩ_OSd\L7Rm}^ZHqy/BDU'nB3'lH6BCB6`:Z;ʾNaӝ1fk>m%0 PK!Hpsmon-0.2.0.dist-info/RECORDu9@V! +,'r/5yKܧ#ΚEYNiH5PoFݵž97۟t籣KB_2%mo 1Njk[Y?y$R|M(YF@j}z%`ӯ- NwdXJp5kBq~)MW)ؙN9U6T.h*|r s:!QKQ|9COe ^G%h5- -g9omP(@Ϙ=K0.\ds[5[ӗCpxD?2g&b<$ hXF/j"Ͷ9[EFEɶ/zp-L^ީrJoИ4y̑:7٠5;F۳Nc{yR])_TɦPK!O88psmon/limiters/base.pyPK!' lpsmon/limiters/cpu_times.pyPK!'** (psmon/main.pyPK!i}psmon/process.pyPK!%$2 psmon/utils.pyPK!XB33#psmon-0.2.0.dist-info/LICENSEPK!Hu)GTU (psmon-0.2.0.dist-info/WHEELPK!HuX(psmon-0.2.0.dist-info/METADATAPK!H,*psmon-0.2.0.dist-info/RECORDPK l,