どすえのブログ

京都在住プログラマーの開発ブログ。バイクとキャンプが趣味。

PythonのpsutilでCPU使用率を並列的に監視する

Pythonで何か処理を走らせている時に, CPUの使用率を定期的に監視するスクリプトを書いた.
マルチスレッドにすることで, メインの処理を止めずに定期実行できる. 重い処理の計算機負荷の時系列データをレポートしたい時とかに使えるかもしれない.
コード全文は最下に.

ライブラリのインストール

psutilというライブラリを使うことでCPUの使用率をPythonで取得することができる. pip経由でインストールできる.

pip install psutil

基本的な使い方は以下の通り.

# percpu=Trueでコアごとの使用率(%)をリストで返す
>>> psutil.cpu_percent(interval=1, percpu=True)
[7.0, 1.0, 8.9, 0.0, 5.0, 1.0, 4.0, 0.0]

# percpu=Falseで平均の使用率となる
>>> psutil.cpu_percent(interval=1, percpu=False)
8.9

# interval(秒)を小さくすると計測時間が短くなるが, 誤差も大きくなる.
>>> psutil.cpu_percent(interval=0.5, percpu=True) 
[6.0, 0.0, 7.8, 1.9, 4.0, 0.0, 5.8, 0.0]

並列処理

今回はネイティブのライブラリであるthreadingを使って並列処理を実装する.
以下のようにスレッドインスタンスの作成と実行を行うことができる.

m = threading.Thread(target=monitor_cpu,args=((initial_time,)))
m.start()

threading.Thread()に渡す引数として, targetには実行するメソッドを渡し, argsには実行メソッドへの引数を渡す.
monitor_cpuが監視用メソッドである.

監視用メソッド

threading.Eventを使って監視用スレッドの管理をする.
threading.Eventの使い方については以下のページが詳しい.
おまいらのthreading.Eventの使い方は間違っている

threading.Eventとはイベントが発生するまでスレッドを待機させ、他のスレッドからイベントを発生させると待機スレッドが再開する、という使い方をする為のクラスです。

上のページでも説明があるように
wait(timeout) : イベントが発生するかtimeout秒経過するまで現在のスレッドを待機させる.
set() : イベントを発生させる.
のふたつを使用する.

def monitor_cpu(initial_time):
    print("START monitor_cpu")
    while not event.wait(1):  # 1秒待機, イベントが発生していなければループ内を実行して再び待機
        elapsed_time = time.time() - initial_time
        cpu_percent = psutil.cpu_percent(percpu=True)
        cpu_percent = '\t'.join(["{:10.4f}".format(v) for v in cpu_percent])
        print("time:", int(elapsed_time), cpu_percent)
    print("END monitor_cpu")  # イベントが発生したらループを抜け実行終了


if __name__=="__main__":
    event = threading.Event()
    initial_time = time.time()
    m = threading.Thread(target=monitor_cpu,args=((initial_time,)))
    m.start()

    # メイン処理

    event.set()  # イベントを発生させる.

monitor_cpu内部では実行間隔を1秒で無限ループを回す. イベントが発生するとループを抜け実行終了する.
ちなみにこの実装はtime.sleep(1)と終了条件を司るフラグを使っても再現できる. こちらのほうがシンプルで良いかもしれない.

def monitor_cpu(initial_time):
    print("START monitor_cpu")
    while flag:
        time.sleep(1)
        elapsed_time = time.time() - initial_time
        cpu_percent = psutil.cpu_percent(percpu=True)
        cpu_percent = '\t'.join(["{:10.4f}".format(v) for v in cpu_percent])
        print("time:", int(elapsed_time), cpu_percent)
    print("END monitor_cpu")

if __name__=="__main__":
    event = threading.Event()
    initial_time = time.time()
    flag = True
    m = threading.Thread(target=monitor_cpu,args=((initial_time,)))
    m.start()
    tmp = 0
    for i in range(100000000):
        tmp = i+i
    flag = False

コード全体

import threading
import time
import psutil

def monitor_cpu(initial_time):
    print("START monitor_cpu")
    while not event.wait(1):
        elapsed_time = time.time() - initial_time
        cpu_percent = psutil.cpu_percent(percpu=True)
        cpu_percent = '\t'.join(["{:10.4f}".format(v) for v in cpu_percent])
        print("time:", int(elapsed_time), cpu_percent)
    print("END monitor_cpu")

if __name__=="__main__":
    event = threading.Event()
    initial_time = time.time()
    m = threading.Thread(target=monitor_cpu,args=((initial_time,)))
    m.start()
    tmp = 0
    for i in range(100000000):
        tmp = i+i
    event.set()

実行結果

左側から時刻(秒), CPU1の使用率(%), CPU2の使用率 ... CPU8の使用率 と並んでいる.

START monitor_cpu
time: 1  52.0     1.0    29.7     1.0    28.7     2.0    30.4     0.0
time: 2  45.0     3.0    36.3     3.0    32.4     1.0    29.4     1.0
time: 3  43.6     2.0    31.0     1.0    33.7     0.0    27.7     2.0
time: 4  45.5     2.0    25.2     1.0    22.8     0.0    19.8     0.0
time: 5  41.6     1.0    26.0     1.0    26.7     1.0    20.8     1.0
time: 6  46.1     5.0    34.7     3.0    38.0     3.0    31.7     4.0
time: 7  63.0    10.0    52.5    10.0    55.4    10.9    55.0    10.0
time: 8  51.5     6.9    36.0     4.9    41.2     5.0    39.6     4.9
time: 9  55.0     0.0    20.6     1.0    26.0     0.0    14.9     0.0
time: 10  49.5    2.9    22.8     1.0    25.2     1.0    23.5     1.0
time: 11  43.6    1.0    32.7     0.0    28.3     0.0    23.8     1.0
time: 12  47.5    2.9    22.8     1.0    20.6     2.0    25.0     2.0
END monitor_cpu