In this script I was looking to launch a given program and monitor it as long as the program exists. Thus, I reached the point where I got to use the threading's module Timer method for controlling a loop that writes to a file and prints out to the console a specific stat of the launched process (for this case, mspaint).
The problem arises when I'm hitting CTRL + C in the console or when I close mspaint, with the script capturing any of the 2 events only after the time defined for the interval has completely ran out. These events make the script stop.
For example, if a 20 seconds time is set for the interval, once the script has started, if at second 5 I either hit CTRL + C or close mspaint, the script will stop only after the remaining 15 seconds will have passed.
I would like for the script to stop right away when I either hit CTRL + C or close mspaint (or any other process launched through this script).
The script can be used with the following command, according to the example:
python.exe mon_tool.py -p "C:\Windows\System32\mspaint.exe" -i 20
I'd really appreciate if you could come up with a working example.
I had used python 3.10.4 and psutil 5.9.0 .
This is the code:
# mon_tool.py
import psutil, sys, os, argparse
from subprocess import Popen
from threading import Timer
debug = False
def parse_args(args):
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--path", type=str, required=True)
parser.add_argument("-i", "--interval", type=float, required=True)
return parser.parse_args(args)
def exceptionHandler(exception_type, exception, traceback, debug_hook=sys.excepthook):
'''Print user friendly error messages normally, full traceback if DEBUG on.
Adapted from http://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set
'''
if debug:
print('\n*** Error:')
debug_hook(exception_type, exception, traceback)
else:
print("%s: %s" % (exception_type.__name__, exception))
sys.excepthook = exceptionHandler
def validate(data):
try:
if data.interval < 0:
raise ValueError
except ValueError:
raise ValueError(f"Time has a negative value: {data.interval}. Please use a positive value")
def main():
args = parse_args(sys.argv[1:])
validate(args)
# creates the "Process monitor data" folder in the "Documents" folder
# of the current Windows profile
default_path: str = f"{os.path.expanduser('~')}\\Documents\Process monitor data"
if not os.path.exists(default_path):
os.makedirs(default_path)
abs_path: str = f'{default_path}\data_test.txt'
print("data_test.txt can be found in: " + default_path)
# launches the provided process for the path argument, and
# it checks if the process was indeed launched
p: Popen[bytes] = Popen(args.path)
PID = p.pid
isProcess: bool = True
while isProcess:
for proc in psutil.process_iter():
if(proc.pid == PID):
isProcess = False
process_stats = psutil.Process(PID)
# creates the data_test.txt and it erases its content
with open(abs_path, 'w', newline='', encoding='utf-8') as testfile:
testfile.write("")
# loop for writing the handles count to data_test.txt, and
# for printing out the handles count to the console
def process_monitor_loop():
with open(abs_path, 'a', newline='', encoding='utf-8') as testfile:
testfile.write(f"{process_stats.num_handles()}\n")
print(process_stats.num_handles())
Timer(args.interval, process_monitor_loop).start()
process_monitor_loop()
if __name__ == '__main__':
main()
Thank you!
I think you could use python-worker (link) for the alternatives
import time
from datetime import datetime
from worker import worker, enableKeyboardInterrupt
# make sure to execute this before running the worker to enable keyboard interrupt
enableKeyboardInterrupt()
# your codes
...
# block lines with periodic check
def block_next_lines(duration):
t0 = time.time()
while time.time() - t0 <= duration:
time.sleep(0.05) # to reduce resource consumption
def main():
# your codes
...
#worker(keyboard_interrupt=True)
def process_monitor_loop():
while True:
print("hii", datetime.now().isoformat())
block_next_lines(3)
return process_monitor_loop()
if __name__ == '__main__':
main_worker = main()
main_worker.wait()
here your process_monitor_loop will be able to stop even if it's not exactly 20 sec of interval
You can try registering a signal handler for SIGINT, that way whenever the user presses Ctrl+C you can have a custom handler to clean all of your dependencies, like the interval, and exit gracefully.
See this for a simple implementation.
This is the solution for the second part of the problem, which checks if the launched process exists. If it doesn't exist, it stops the script.
This solution comes on top of the solution, for the first part of the problem, provided above by #danangjoyoo, which deals with stopping the script when CTRL + C is used.
Thank you very much once again, #danangjoyoo! :)
This is the code for the second part of the problem:
import time, psutil, sys, os
from datetime import datetime
from worker import worker, enableKeyboardInterrupt, abort_all_thread, ThreadWorkerManager
from threading import Timer
# make sure to execute this before running the worker to enable keyboard interrupt
enableKeyboardInterrupt()
# block lines with periodic check
def block_next_lines(duration):
t0 = time.time()
while time.time() - t0 <= duration:
time.sleep(0.05) # to reduce resource consumption
def main():
# launches mspaint, gets its PID and checks if it was indeed launched
path = f"C:\Windows\System32\mspaint.exe"
p = psutil.Popen(path)
PID = p.pid
isProcess: bool = True
while isProcess:
for proc in psutil.process_iter():
if(proc.pid == PID):
isProcess = False
interval = 5
global counter
counter = 0
#allows for sub_process to run only once
global run_sub_process_once
run_sub_process_once = 1
#worker(keyboard_interrupt=True)
def process_monitor_loop():
while True:
print("hii", datetime.now().isoformat())
def sub_proccess():
'''
Checks every second if the launched process still exists.
If the process doesn't exist anymore, the script will be stopped.
'''
print("Process online:", psutil.pid_exists(PID))
t = Timer(1, sub_proccess)
t.start()
global counter
counter += 1
print(counter)
# Checks if the worker thread is alive.
# If it is not alive, it will kill the thread spawned by sub_process
# hence, stopping the script.
for _, key in enumerate(ThreadWorkerManager.allWorkers):
w = ThreadWorkerManager.allWorkers[key]
if not w.is_alive:
t.cancel()
if not psutil.pid_exists(PID):
abort_all_thread()
t.cancel()
global run_sub_process_once
if run_sub_process_once:
run_sub_process_once = 0
sub_proccess()
block_next_lines(interval)
return process_monitor_loop()
if __name__ == '__main__':
main_worker = main()
main_worker.wait()
Also, I have to note that #danangjoyoo's solution comes as an alternative to signal.pause() for Windows. This only deals with CTRL + C problem part. signal.pause() works only for Unix systems. This is how it was supposed for its usage, for my case, in case it were a Unix system:
import signal, sys
from threading import Timer
def main():
def signal_handler(sig, frame):
print('\nYou pressed Ctrl+C!')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
print('Press Ctrl+C')
def process_monitor_loop():
try:
print("hi")
except KeyboardInterrupt:
signal.pause()
Timer(10, process_monitor_loop).start()
process_monitor_loop()
if __name__ == '__main__':
main()
The code above is based on this.
I have a code that print something in the console and after some works it clear console and print something new
so putting input() not works for me cause then it will wait user to do something but I want this printing stuff continue
how should I do it?
my code is exctracting a zip file and in the same time printing how much of it is done
import threading
def Ex():
from zipfile import ZipFile
with ZipFile(my zip file) as zf:
zf.extractall(path,pwd=my password)
def loading():
done=False
f=[]
T=time.time()
while not done:
try:
f=os.listdir(my path)
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
clearConsole()
print('Installing resources : '+str(int(len(f)/25*100))+'%')
except:
pass
if len(f)==25:
done=True
time.sleep(5)
time.sleep(10)
sys.exit()
threading.Thread(target=Ex).start()
threading.Thread(target=loading).start()
If you want to move the cursor to the first of line, use \r.
f = os.listdir("path/to/file")
print('Installing resources : '+str(int(len(f)/25*100))+'%', end='\r')
os.system does not run the commend in the current shell. It opens a new one. So you are just clearing a new shell every 5 seconds. If you really want to hold that, you can use pause command in Windows, which waits for user input and then close it. (cls; pause)
I am trying to write a program in Python that will open a random video file on a keypress(for me F8). I am very new to coding and currently stuck. I got it to the point where I am not getting any errors but now the program is not sticking around. Not sure what to do next. My code:
import os
import keyboard
import string
import random
from openfile import openfile
path = r"C:\Users\Rudy\Videos\GIFS"
letter = string.ascii_letters
digit = string.digits
def keyboardPress():
mp3Select = random.choice(os.listdir(path))
keypress = str(path + "\\" + mp3Select)
while True:
if keyboard.is_pressed('F8'):
openfile(keypress)
break
def main():
while True:
try:
keyboardPress()
except:
pass
main()
To open a random video on key press, you can use the event hooks in the keyboard module, one such event hook is keyboard.on_press(callback) which invokes a callback for every keydown event.
To prevent the program from terminating, you can use the method keyboard.wait(hotkey=None) which blocks the program execution until the given hotkey is pressed.
Use:
def keyPress(event):
if event.name == 'f8': # filter the `f8` key press event
fileName = random.choice(os.listdir(path))
filePath = os.path.join(path, fileName)
openfile(filePath) # open the video file
def main():
keyboard.on_press(keyPress) # hook up the event handler
keyboard.wait('esc') # blocks the program execution until `escape` key is pressed.
main()
Edit (see comments):
Use this while calling main():
try:
main()
except Exception as ex:
print(ex)
I'm trying to create a gnome-terminal windows and after that I want to write something at terminal window (echo) like a buffering screen in a while loop.
import os
x = os.system("gnome-terminal -e")
while True:
x.write("echo % s "%(buffering))
if progress == 0:
break
So, I have to open gnome-terminal window before while loop because if I don't gnome terminal opens and close at each loop.
You could use a named pipe:
import os,time
def client():
try:
os.mkfifo("named_pipe")
except OSError as e:
print(e.message)
named_pipe = os.open("named_pipe", os.O_WRONLY)
while True:
time.sleep(1)
os.write(named_pipe, 'Time {}\n'.format(time.asctime()))
def server():
from subprocess import check_call
check_call(['gnome-terminal', '-e', "python script.py"])
pid = os.fork()
foo() if pid != 0 else bar()
In script.py:
import os
with open('named_pipe') as f:
for line in iter(f.readline, ""):
print(line.rstrip())
os.unlink("named_pipe")
You could also use a unix domain socket or tcp.
I'm trying to use zc.lockfile. I see that a lockfile is created in the same directory as my python script, but when I press ctrl+C, the file is NOT removed. I have a callback registered and have even tested given a long time (not sure if zc.lockfile spawns a new thread and needed time to complete).
import os
import sys
import signal
import time
import zc.lockfile
program_lock = None
def onExitCodePressed(signal, frame):
"""Callback run on a premature user exit."""
global program_lock
print '\r\nYou pressed Ctrl+C'
program_lock.close()
time.sleep(5)
sys.exit(0)
def main():
signal.signal(signal.SIGINT, onExitCodePressed)
if os.path.exists('myapp_lock'):
print "\nAnother instance of the program is already running.\n"
sys.exit(0)
else:
program_lock = zc.lockfile.LockFile('myapp_lock')
while True:
continue
if __name__ == '__main__':
main()