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!
 
     
    