Browse Source

update: process utils

dev
dnomd343 2 years ago
parent
commit
f7399482ce
  1. 90
      Utils/Process.py

90
Basis/Process.py → Utils/Process.py

@ -6,12 +6,12 @@ import copy
import time
import ctypes
import signal
from Basis.Logger import logging
from Basis.Functions import genFlag
from Basis.Exception import processException
from Utils.Logger import logger
from Utils.Common import genFlag
from Utils.Exception import processException
from subprocess import Popen, STDOUT, DEVNULL
libcPaths = [
libcSysPaths = [
'/usr/lib/libc.so.6', # CentOS
'/usr/lib64/libc.so.6',
'/lib/libc.musl-i386.so.1', # Alpine
@ -23,18 +23,18 @@ libcPaths = [
]
libcPath = None
for libc in libcPaths:
if os.path.exists(libc): # try to locate libc.so
libcPath = libc
for path in libcSysPaths:
if os.path.exists(path): # try to locate libc.so
libcPath = path
break
if libcPath is None: # lost libc.so -> unable to utilize prctl
logging.warning('libc.so not found')
logger.warning('libc.so not found')
else:
logging.info('libc.so -> ' + str(libcPath))
logger.info('libc.so -> %s' % libcPath)
class Process(object):
""" Manage a sub process and it's file.
""" Manage a sub process and it's configure file.
Arguments:
cmd: Command list, which use to start the program.
@ -65,34 +65,34 @@ class Process(object):
def __checkWorkDir(self) -> None: # check if the working directory is normal
if os.path.isdir(self.workDir):
return
logging.warning('[%s] Work directory %s not exist' % (self.id, self.workDir))
logger.warning('[%s] Work directory %s not exist' % (self.id, self.workDir))
try:
os.makedirs(self.workDir) # just like `mkdir -p ...`
logging.info('[%s] New directory -> %s' % (self.id, self.workDir))
logger.info('[%s] New directory -> %s' % (self.id, self.workDir))
except:
if os.path.exists(self.workDir):
logging.error('[%s] %s already exist but not folder' % (self.id, self.workDir))
logger.error('[%s] %s already exist but not folder' % (self.id, self.workDir))
else:
logging.error('[%s] Unable to create new folder -> %s' % (self.id, self.workDir))
logger.error('[%s] Unable to create new folder -> %s' % (self.id, self.workDir))
raise processException('Working directory error') # fatal error
def __killProcess(self, killSignal: int) -> None:
try:
pgid = os.getpgid(self.__process.pid) # progress group id
os.killpg(pgid, killSignal) # kill sub process group
logging.debug('[%s] Send signal %i to PGID %i' % (self.id, killSignal, pgid))
logger.debug('[%s] Send signal %i to PGID %i' % (self.id, killSignal, pgid))
except:
logging.warning('[%s] Failed to get PGID of sub process (PID = %i)' % (self.id, self.__process.pid))
logger.warning('[%s] Failed to get PGID of sub process (PID = %i)' % (self.id, self.__process.pid))
def __deleteFile(self, filePath: str) -> None:
if not os.path.isfile(filePath): # file not found (or not a file)
logging.warning('[%s] File %s not found' % (self.id, filePath))
logger.warning('[%s] File %s not found' % (self.id, filePath))
return
try:
os.remove(filePath) # remove config file
logging.debug('[%s] File %s deleted successfully' % (self.id, filePath))
logger.debug('[%s] File %s deleted successfully' % (self.id, filePath))
except:
logging.error('[%s] Unable to delete file %s' % (self.id, filePath))
logger.error('[%s] Unable to delete file %s' % (self.id, filePath))
def __init__(self, workDir: str, taskId: str = '', isStart: bool = True,
cmd: str or list or None = None, env: dict or None = None, file: dict or list or None = None) -> None:
@ -102,53 +102,53 @@ class Process(object):
self.cmd = copy.copy([cmd] if type(cmd) == str else cmd) # depth = 1
self.file = copy.deepcopy([file] if type(file) == dict else file) # depth = 2
self.__checkWorkDir() # ensure the working direction is normal
logging.debug('[%s] Process command -> %s (%s)' % (self.id, self.cmd, self.env))
logger.debug('[%s] Process command -> %s (%s)' % (self.id, self.cmd, self.env))
if self.file is not None:
if len(self.file) > 1:
logging.debug('[%s] Manage %i files' % (self.id, len(self.file)))
logger.debug('[%s] Manage %i files' % (self.id, len(self.file)))
for file in self.file: # traverse all files
if not isStart: # don't print log twice
logging.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content']))
logger.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content']))
if isStart:
self.start()
def setCmd(self, cmd: str or list) -> None:
self.cmd = copy.copy([cmd] if type(cmd) == str else cmd)
logging.info('[%s] Process setting command -> %s' % (self.id, self.cmd))
logger.info('[%s] Process setting command -> %s' % (self.id, self.cmd))
def setEnv(self, env: dict or None) -> None:
self.env = copy.copy(env)
logging.info('[%s] Process setting environ -> %s' % (self.id, self.env))
logger.info('[%s] Process setting environ -> %s' % (self.id, self.env))
def setFile(self, file: dict or list or None) -> None:
self.file = copy.deepcopy([file] if type(file) == dict else file)
if self.file is None:
logging.info('[%s] Process setting file -> None' % self.id)
logger.info('[%s] Process setting file -> None' % self.id)
return
for file in self.file: # traverse all files
logging.info('[%s] Process setting file %s -> %s' % (self.id, file['path'], file['content']))
logger.info('[%s] Process setting file %s -> %s' % (self.id, file['path'], file['content']))
def start(self, isCapture: bool = True) -> None:
self.__capture = isCapture
logging.debug('[%s] Process ready to start (%s)' % (
self.id, ('with' if self.__capture else 'without') + ' output capture'
logger.debug('[%s] Process ready to start (%s output capture)' % (
self.id, 'with' if self.__capture else 'without'
))
if self.cmd is None: # ERROR CASE
logging.error('[%s] Process miss start command' % self.id)
logger.error('[%s] Process miss start command' % self.id)
raise processException('Miss start command')
if self.__process is not None and self.__process.poll() is None: # ERROR CASE
logging.error('[%s] Process try to start but it is running' % self.id)
logger.error('[%s] Process try to start but it is running' % self.id)
raise processException('Process is still running')
if self.env is not None and 'PATH' not in self.env and '/' not in self.cmd[0]: # WARNING CASE
logging.warning('[%s] Executable file in relative path but miss PATH in environ' % self.id)
logger.warning('[%s] Executable file in relative path but miss PATH in environ' % self.id)
if self.file is not None: # create and write file contents
for file in self.file:
with open(file['path'], 'w', encoding = 'utf-8') as fileObject: # save file content
fileObject.write(file['content'])
logging.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content']))
logger.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content']))
if self.__capture: # with output capture
self.__logfile = os.path.join(self.workDir, self.id + '.log')
logging.debug('[%s] Process output capture -> %s' % (self.id, self.__logfile))
self.__logfile = os.path.join(self.workDir, '%s.log' % self.id)
logger.debug('[%s] Process output capture -> %s' % (self.id, self.__logfile))
stdout = open(self.__logfile, 'w', encoding = 'utf-8')
stderr = STDOUT # combine the stderr with stdout
else: # discard all the output of sub process
@ -161,48 +161,48 @@ class Process(object):
preexec_fn = None if libcPath is None else Process.__preExec
)
except Exception as exp:
logging.error('[%s] Process unable to start -> %s' % (self.id, exp))
logger.error('[%s] Process unable to start -> %s' % (self.id, exp))
raise processException('Unable to start process')
logging.info('[%s] Process running -> PID = %i' % (self.id, self.__process.pid))
logger.info('[%s] Process running -> PID = %i' % (self.id, self.__process.pid))
def signal(self, signalNum: int) -> None: # send specified signal to sub process
try:
signalName = signal.Signals(signalNum).name
except:
signalName = 'unknown'
logging.info('[%s] Send signal -> %i (%s)' % (self.id, signalNum, signalName))
logger.info('[%s] Send signal -> %i (%s)' % (self.id, signalNum, signalName))
self.__process.send_signal(signalNum)
def status(self) -> bool: # check if the sub process is still running
status = self.__process.poll() is None
logging.debug('[%s] Process check status -> %s' % (self.id, 'running' if status else 'exit'))
logger.debug('[%s] Process check status -> %s' % (self.id, 'running' if status else 'exit'))
return status
def wait(self, timeout: int or None = None) -> None: # blocking wait sub process
logging.info('[%s] Process wait -> timeout = %s' % (self.id, str(timeout)))
logger.info('[%s] Process wait -> timeout = %s' % (self.id, str(timeout)))
try:
self.__process.wait(timeout = timeout)
logging.info('[%s] Process wait timeout -> exit' % self.id)
logger.info('[%s] Process wait timeout -> exit' % self.id)
except:
logging.info('[%s] Process wait timeout -> running' % self.id)
logger.info('[%s] Process wait timeout -> running' % self.id)
def quit(self, isForce: bool = False, waitTime: int = 50) -> None: # wait 50ms in default
killSignal = signal.SIGKILL if isForce else signal.SIGTERM # 9 -> force kill / 15 -> terminate signal
logging.debug('[%s] Kill signal -> %i (%s)' % (self.id, killSignal, signal.Signals(killSignal).name))
logger.debug('[%s] Kill signal -> %i (%s)' % (self.id, killSignal, signal.Signals(killSignal).name))
self.__killProcess(killSignal)
time.sleep(waitTime / 1000) # sleep (ms -> s)
while self.__process.poll() is None: # confirm sub process exit
self.__killProcess(killSignal)
time.sleep(waitTime / 1000)
logging.info('[%s] Process terminated -> PID = %i' % (self.id, self.__process.pid))
logger.info('[%s] Process terminated -> PID = %i' % (self.id, self.__process.pid))
if self.__capture:
try:
with open(self.__logfile, 'r', encoding = 'utf-8') as fileObject: # read sub process output
self.output = fileObject.read()
logging.debug('[%s] Process output capture -> length = %s' % (self.id, len(self.output)))
logger.debug('[%s] Process output capture -> length = %s' % (self.id, len(self.output)))
self.__deleteFile(self.__logfile)
except:
logging.error('[%s] Failed to read capture file -> %s' % (self.id, self.__logfile))
logger.error('[%s] Failed to read capture file -> %s' % (self.id, self.__logfile))
if self.file is not None: # with config file
for file in self.file:
self.__deleteFile(file['path'])
Loading…
Cancel
Save