LoginSignup
0
1

More than 1 year has passed since last update.

Pythonのmultiprocessを使ってリアルタイムにメモリ使用量をプロット

Last updated at Posted at 2023-02-28

main

関数のメモリ使用量をリアルタイムでプロットするコードを書いた。
アプリケーションのリソースを調査する過程で製作。
実際のところログで吐き出せばいいのでリアルタイムでプロットする意味はあまりないのだが、勉強になった。

リアルタイムプロットの参考
https://qiita.com/dendensho/items/79e9d2e3d4e8eb5061bc

from multiprocessing import Process, Value
import os
import psutil
import matplotlib.pyplot as plt

def memory_check(pid=os.getpid()):
    '''
    与えられたPIDの物理メモリと仮想メモリ使用量を返す。
    Windowsの場合は仮想メモリ=物理メモリ(共有部分を除く)+ページファイル
    '''
    unit = 1024**2
    mem_info = psutil.Process(pid=pid).memory_info()
    pmem = mem_info.rss / unit
    vmem = mem_info.vms / unit
    return pmem, vmem

def plotting(status, PID):
    '''グラフプロット関数
    status :
        0-5の整数値
        plotting関数のステータス
    PID :
        PID
    '''

    # 初期化
    times = [0 for i in range(100)]
    pmems = [0 for i in range(100)]
    vmems = [0 for i in range(100)]

    plt.ion()
    plt.figure()
    li_p, = plt.plot(times, pmems, label='Physical')
    li_v, = plt.plot(times, vmems, label='Virtual')

    plt.legend()

    plt.xlabel('time')
    plt.ylabel('used memory (MB)')
    plt.title('memory real time plot')

    current_time = 0
    status.value = 1 # 初期化終了フラグ
    # メインループ
    while True:
        if status.value == 1:
            # アプリケーション起動待ち
            plt.pause(0.1)
        elif status.value == 2:
            # リアルタイムプロット
            current_time += 0.1
            mem = memory_check(PID.value)

            times.append(current_time)
            times.pop(0)
            pmems.append(mem[0])
            pmems.pop(0)
            vmems.append(mem[1])
            vmems.pop(0)

            li_p.set_xdata(times)
            li_p.set_ydata(pmems)
            li_v.set_xdata(times)
            li_v.set_ydata(vmems)

            plt.xlim(min(times), max(times))
            plt.ylim(0, max(vmems)*1.1)
            plt.draw()

            plt.pause(0.1)
        elif status.value == 3:
            # グラフの更新を停止
            plt.ioff()
            status.value = 4
        elif status.value == 4:
            # 終了待ち
            plt.pause(0.1)
        elif status.value == 5:
            # グラフを閉じる
            plt.clf()
            plt.close()
            return 0
        else:
            raise ValueError("statusが異常です")

def realtime_mem_plot(func):
    def wrapper(*args):
        status = Value('i', 0)
        PID = Value('i', 0)
        p_plot = Process(target=plotting, args=(status, PID))
        p_plot.start()

        p_main = Process(target=func, args=(*args,))
        p_main.start()

        print('start plotting')
        parent = psutil.Process(os.getpid())
        children = parent.children()
        PID.value = children[-1].pid
        print(f'PID: {children[-1].pid}')

        status.value = 2

        p_main.join()
        status.value = 3

        input("Please press Enter key to close")

        status.value = 5
        p_plot.join()
    return wrapper

from time import sleep

# @realtime_mem_plot
def f(name):
    print(name)
    for j in range(10):
        k = []
        for i in range(100):
            k.append([0]*10000*j)
            sleep(0.01)
        del k
    return 10

if __name__=='__main__':
    realtime_mem_plot(f)('test')
    # f('test')

wrapper化不可能?

wrapper化すると便利かと思い試行錯誤するも、以下のエラーがでて止まっている。
(環境: Windows 10, Python 3.10.10)

Exception has occurred: PicklingError
Can't pickle <function f at 0x000001E06FFB7F40>: it's not the same object as __main__.f
  File "C:\Users\kubot\Documents\temp\realtime_mem.py", line 95, in wrapper
    p_main.start()
  File "C:\Users\kubot\Documents\temp\realtime_mem.py", line 129, in <module>
    f('test')
_pickle.PicklingError: Can't pickle <function f at 0x000001E06FFB7F40>: it's not the same object as __main__.f

wrapper化していない現行のコードだとなぜか実行可能

start_methodを'fork'にすれば行けるという記事もみたが ( https://medium.com/devopss-hole/python-multiprocessing-pickle-issue-e2d35ccf96a9 )、Windowsではこの方法が使えない・・・

改良点

  • plt.pause(0.1)が必ずしも0.1秒ちょうどとは限らないので実行時間と横軸がずれる
    →timeを計測して横軸に設定する?
  • 関数の返り値を受け取れるようにする
    →wrapperの中にさらにwrapperを用いる構造にする
  • 測定関数で子プロセスを呼び出したときの動作が未確認
    →実際のところ、子プロセスのメモリ使用量が追えないだけだと予想

まとめ

wrapper化で便利ツール化できないかと試行するも、思わぬとろころでつまづいた…
解決策がみつかれば追記しようと思う

また、どなたかご存知の方がいらっしゃればコメントください。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1