Search code examples
pythonpython-multiprocessingpython-curses

Python, show output of multiple processes in different parts of the terminal


I'm running a multiprocessing system in Python, and I was planning to divide the terminal window in 4 quadrants, and display the output of each of the processes in one of them.

So, the final output should look something like:

----------------------------------
|                |                |
|   PROCESS_01   |   PROCESS_02   |
|                |                |
----------------------------------
|                |                |
|   PROCESS_03   |   PROCESS_04   |
|                |                |
----------------------------------

I have a main file, from where the single processes start:

if __name__ == "__main__":
    set_start_method("spawn")

    p01 = mp.Process(target=p01_Initializer, args=(...))
    p01.daemon = True
    p01.start()

    p02 = mp.Process(target=p02_Initializer, args=(...))
    p02.daemon = True
    p02.start()

    p03 = mp.Process(target=p03_Initializer, args=(...))
    p03.daemon = True
    p03.start()

    p04 = mp.Process(target=p04_Initializer, args=(...))
    p04.daemon = True
    p04.start()


and I was planning to use the curses module to achieve this visualization.
Basically in each process I created a different window. Both have the same height/width (rows_mid, cols_mid), but the idea is that the window for process 01 should originate at (0,0), while the one for process02 at coordinates (0, cols_mid), like this:

PROCESS_01


    curses.initscr()
    cols_tot = curses.COLS
    rows_tot = curses.LINES
    x_mid = int(0.5*cols_tot)
    y_mid = int(0.5*rows_tot)
    self.win = curses.newwin(y_mid, x_mid, 0, 0)
    self.win.addstr(0, 0, "*** PROCESS 01 ***")
    self.win. addstr(whatever)

PROCESS_02


    curses.initscr()
    cols_tot = curses.COLS
    rows_tot = curses.LINES
    x_mid = int(0.5*cols_tot)
    y_mid = int(0.5*rows_tot)
    self.win = curses.newwin(y_mid, x_mid, 0, x_mid)
    self.win.addstr(0, 0, "*** PROCESS 02 ***")
    self.win. addstr(whatever)

But it's not really working. At the beginning, only the output of PROCESS_02 is visualized, in the correct position. Then, only PROCESS_01 appears, but some of the stuff is visualized in the space where the output from PROCESS_02 is supposed to be, like this

Initial behavior Fianl output

Can I fix it somehow? Is there a better/easier alternative to using curses?


Solution

  • As mentioned in the comments:

    1. I could obtain the same result in a simpler way by writing the output to log files and using tmux;

    2. In case one wanted to stick to the 'manual' approach, the problem seems to be that the various processes are interfering with each other, so the screen gets overwritten by each of them.

    I solved it by collecting all the output of each process in an array, which is then sent back to the main process through queues and printed there.

    PROCESS_01

    
        output_messages_01 = []
        output_messages_01.append('first output of P01')
        output_messages_01.append('second output of P01')
        ....
    
        ## limiting overall number of messages in the array (if too many, it gets out of the window row limit when printing them in terminal)
        if len(output_messages_01)>15:
                    output_messages_01 = output_messages_01[-15:]
        output_queue_p01.put((output_messages_01))
    
    

    PROCESS_02

    
        output_messages_02 = []
        output_messages_02.append('first output of P02')
        output_messages_02.append('second output of P02')
        ....
        if len(output_messages_02)>15:
                    output_messages_02 = output_messages_02[-15:]
        output_queue_p02.put((output_messages_02))
    
    

    and, in the main process, I colllect and print everything

    MAIN PROCESS

        ## creating panels
        win11 = curses.newwin(rows_mid, cols_mid, 0, 0)
        win12 = curses.newwin(rows_mid, cols_mid, 0, cols_mid)
        win21 = curses.newwin(rows_mid, cols_mid, rows_mid, 0)
        win22 = curses.newwin(rows_mid, cols_mid, rows_mid, cols_mid)
        win11.addstr(0, 0, "*** PROCESS 01 ***", curses.A_BOLD)
        win12.addstr(0, 0, "*** PROCESS 02 ***", curses.A_BOLD)
        win21.addstr(0, 0, "*** PROCESS 03 ***", curses.A_BOLD)
        win22.addstr(0, 0, "*** PROCESS 04 ***", curses.A_BOLD)
    
        ## getting array with output message from process01
        p01_messages = output_queue_p01.get()
        for row, message in enumerate(p01_messages):
            win11.addstr(row, 0, message)
            win11.clrtoeol()
        
        ## getting array with output message from process02
        p02_messages = output_queue_p02.get()
        for row, message in enumerate(p02_messages):
            win12.addstr(row, 0, message)
            win12.clrtoeol()
    
       ## same thing for processes 03 and 04 ...
    
        win11.refresh()
        win12.refresh()
        win21.refresh()
        win22.refresh()
    
        
    

    and similarly for the other processes