Raspberry Pi CPU周波数、CPU温度、CPU使用率の取得Pythonスクリプト

CPU周波数、CPU温度、CPU使用率

2017年10月、Raspberry PiでCPU周波数、CPU温度、CPU使用率を取得するPythonスクリプトを作成しました。

CPU 周波数、温度、使用率

CPU 周波数、温度、使用率

画面の最終行を例として説明します。

・CPU周波数 : 1200MHz
・CPU温度 : 74.1’C
・CPU使用率 : 76%
・コア別使用率 : 81 , 80 , 72 , 71 %

Raspbian画面に温度計マーク出現

Python版OpenCVによる顔検出を実行したとき、画面右上に「温度計マーク」が表示されました。

Raspberry Pi Python版OpenCVによる顔検出

温度計マーク警告は、CPU使用率が高くなりCPU温度が上昇したのだと想像します。この例では、CPU使用率は76%です。

温度計マーク警告

温度計マーク警告


Raspberry Pi3が搭載している SoC(System-on-a-chip)は、Broadcom BCM2837です。そのCPUコアは、quad-core ARMv8 Cortex-A53でCPUが4個あります。

  • Python版OpenCVの顔検出は、4個のCPUを使用してCPU使用率=76%となっているのでしょうか?
  • それとも、特定のCPUに負荷がかかっているのでしょうか?
  • 温度計マークが出現するときは、CPU温度はどれくらいなのでしょうか?

Raspberry Piの公式フォーラムに「The Raspberry Pi 3 Model B Q&A thread」にCPU温度について書いてありました。

 

Why is there an orange/yellow square in the top right of the display?

This indicates that the Pi 3 is throttling CPU speed due to thermal limitations.
There is a graceful reduction in ARM clockspeeds if the SoC exceeds 80°C and during this time a yellow square icon is rendered on-screen. Idle clockspeeds (600MHz) are forced if the SoC hits 85°C. If this is an issue for your application, buy a heatsink – even a small one will likely prevent throttling.

【要約】
画面の右上にオレンジ色や黄色の四角形が出るんだけど、なぜ?

これは、熱制限によりPi3がCPU動作速度を抑制していることを示しています。
SoCが80度を超え黄色の四角いアイコンが画面上に表示されている場合は、ARMのクロック速度が適切に低減します。
SoCが85度に達すると、アイドルクロックスピード(600MHz)が強制されます。
これがアプリケーションの問題であれば、ヒートシンクを購入してください。小さなものでもスロットリングを防止する可能性があります。

温度計マークではなく黄色四角形を表示すると書いてありますが、この記事が書かれたのは 2016/02/29 なのでデザインが変わったようです。この記述から、80度を超えるとこの温度計マークが出現するようです。

CPUが高温になるとCPUのクロックを下げることも確認したいので、CPU周波数/CPU温度/CPU使用率を調べることにします。

CPU周波数/CPU温度/CPU使用率の取得

Raspberry Piの vcgencmd コマンドで、いろいろなファームウェア情報を取得することができます。

vcgencmd 《カテゴリー》 《項目》

CPU周波数の取得

CPU周波数を取得するコマンドは、「vcgencmd measure_clock arm」です。

$ vcgencmd measure_clock arm
frequency(45)=600000000

CPU周波数は、600GHzでした。1200GHzで動作していると思っていたのですが、アイドル状態では600GHzで動作していました。

CPU温度の取得

CPU温度を取得するコマンドは、「vcgencmd measure_temp」です。

$ vcgencmd measure_temp
temp=43.5'C

CPU温度は、43.5度でした。アイドル状態では、CPU温度はそんなに高くないのですね。

CPU使用率の取得

CPU使用率は、システム統計情報から算出することができます。システム統計情報は、「cat /proc/stat」で参照します。

$ cat /proc/stat | grep cpu
cpu  63016 0 3921 416071 2871 0 1858 0 0 0
cpu0 13712 0 1149 103615 683 0 1856 0 0 0
cpu1 16306 0 866 104437 576 0 0 0 0 0
cpu2 16064 0 929 104190 1071 0 2 0 0 0
cpu3 16934 0 977 103829 541 0 0 0 0 0

左端が「cpu」の行が、CPUが消費した時間統計情報となります。cpu0~cpu3はコア別の時間統計情報で、cpuは全CPUの時間統計情報です。

時間統計情報の数値の意味をマニュアルで調べてみます。

man proc

/proc/stat の項目を検索します。

/proc/stat

kernel/system statistics. Varies with architecture.
Common entries include: cpu 3357 0 4313 1362393

The amount of time, measured in units of USER_HZ (1/100ths of a second on most architectures, use sysconf(_SC_CLK_TCK) to obtain the right value), that the system spent in various states:

== 項目 == 説明
user (01) Time spent in user mode.
nice (02) Time spent in user mode with low priority (nice).
system (03) Time spent in system mode.
idle (04) Time spent in the idle task.
iowait (05) Time waiting for I/O to complete.
irq (06) Time servicing interrupts.
softirq (07) Time servicing softirqs.
steal (08) Stolen time, which is the time spent in other operating systems when running in a virtualized environment.
guest (09) Time spent running a virtual CPU for guest operating systems under the control of the Linux kernel.
guest_nice (10) Time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel).

【要約】

カーネル/システム統計。 アーキテクチャによって異なります。
一般的なエントリーは次のとおりです。: cpu 3357 0 4313 1362393

システムがさまざまな状態で費やした、USER_HZ単位で測定される時間(ほとんどのアーキテクチャで1/100秒)は、sysconf(_SC_CLK_TCK)を使用して適切な値を取得します。

== 項目 == 説明
user (01) ユーザーモードで費やされた時間
nice (02) ユーザーモードで低優先度(nice)で過ごした時間
system (03) システムモードで費やされた時間
idle (04) アイドルタスクで費やされた時間
iowait (05) I/Oが完了するのを待つ時間
irq (06) 割り込みを処理する時間
softirq (07) ソフト割り込みを処理する時間
steal (08) 仮想化された環境で動作しているときに他のオペレーティングシステムで費やされた時間
guest (09) Linuxカーネルの制御下でゲストOS用に仮想CPUを実行するのに費やされた時間
guest_nice (10) Linuxカーネルの制御下にあるゲストOS用の仮想CPUの実行に費やされた時間

定期的に /proc/stat から時間統計情報を取得します。そして、前回値との差分が、近々の時間統計情報となります。たとえば1秒間隔で取得すれば、過去1秒間の時間統計情報を取得できます。

Idle = Time(04)                       // idle
Busy = Time(01) + Time(02) + Time(03) // user + nice + system
All = Busy + Idle
Rate = Busy×100÷All

Busy×100÷All がCPU使用率となります。

Pythonスクリプト

PythonとOpenCVのバージョンを確認します。

  • Python : 2.7.13
  • OpenCV : 3.3.0
$ python
Python 2.7.13 (default, Jan 19 2017, 14:48:08)
[GCC 6.3.0 20170124] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> print cv2.__version__
3.3.0
>>> quit()

CPU周波数/CPU温度/CPU使用率を取得するPythonスクリプトを下記に示します。

$ cat cpustat.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
import time 
import subprocess
import sys

def GetCpuFreq():
    Cmd = 'vcgencmd measure_clock arm'
    result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    Rstdout ,Rstderr = result.communicate()
    CpuFreq = Rstdout.split('=')
    return int(CpuFreq[1])

def GetCpuTemp():
    Cmd = 'vcgencmd measure_temp'
    result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    Rstdout ,Rstderr = result.communicate()
    CpuTemp = Rstdout.split()
    return CpuTemp[0]


def GetCpuStat():
    Cmd = 'cat /proc/stat | grep cpu'
    result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    Rstdout ,Rstderr = result.communicate()
    LineList = Rstdout.splitlines()
    #
    TckList = []
    for Line in LineList:
        ItemList = Line.split()
        TckIdle = int(ItemList[4])
        TckBusy = int(ItemList[1])+int(ItemList[2])+int(ItemList[3])
        TckAll  = TckBusy + TckIdle
        TckList.append( [ TckBusy ,TckAll ] )
    return  TckList


class CpuUsage:
    def __init__(self):
        self._TckList    = GetCpuStat()

    def get(self):
        TckListPre       = self._TckList
        TckListNow       = GetCpuStat()
        self._TckList    = TckListNow
        CpuRateList = []
        for (TckNow, TckPre) in zip(TckListNow, TckListPre):
            TckDiff = [ Now - Pre for (Now , Pre) in zip(TckNow, TckPre) ]
            TckBusy = TckDiff[0]
            TckAll  = TckDiff[1]
            CpuRate = int(TckBusy*100/TckAll)
            CpuRateList.append( CpuRate )
        return CpuRateList

#
if __name__=='__main__':
    
    gCpuUsage = CpuUsage()       # 初期化
    for ix in range(10000):
        time.sleep(1)
        CpuRateList = gCpuUsage.get()
        CpuRate     = CpuRateList[0]
        CpuRate_str = "  CPU:%3d" % CpuRate
        del CpuRateList[0]
        CpuTemp     = GetCpuTemp()
        CpuFreq     = int(GetCpuFreq()/1000000)
        CpuFreq_str = "ARM %4dMHz  " % CpuFreq
        Info_str = CpuFreq_str + CpuTemp + CpuRate_str + "%"
        print Info_str ,CpuRateList

スクリプトの説明

コマンドの実行方法

Pythonでコマンドを実行する方法はいくつかあります。そのなかで、Subprocessモジュールを使用することを公式推奨していますので subprocessを使用します。

import subprocess
import sys

Cmd = 'コマンド'
result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
Rstdout ,Rstderr = result.communicate()

Rstdout は、コマンドの標準出力
Rstderr は、コマンドのエラー出力

コマンドを実行するために、subprocess.Popenオブジェクトを作成します。第1引数にコマンドを指定します。

  • 引数「shell=True」は、シェル経由での実行を指定します。第1引数のコマンドは、シェルに与えるコマンド文字列として与えます。
  • 引数「stdout=○」と「stderr=○」は、標準ストリームに対するパイプ(subprocess.PIPE)を開くことを指定します。
  • 引数「universal_newlines=True」は、ファイルオブジェクト stdout, stderr をテキストモード指定します。デフォルトは、Falseでバイナリーモード指定です。

コマンドの実行結果は、communicate()メソッドで取得することができます。最初の戻り値が標準出力で、次の戻り値がエラー出力です。

CPU周波数の取得

def GetCpuFreq():
    Cmd = 'vcgencmd measure_clock arm'
    result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    Rstdout ,Rstderr = result.communicate()
    CpuFreq = Rstdout.split('=')
    return int(CpuFreq[1])

関数 GetCpuFreq() で、CPU周波数を取得します。

subprocessで、「vcgencmd measure_clock arm」を実行します。

frequency(45)=600000000

コマンドの標準出力結果を「=」で分解して、右側を整数に変換して戻り値にします。

CPU温度の取得

def GetCpuTemp():
    Cmd = 'vcgencmd measure_temp'
    result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    Rstdout ,Rstderr = result.communicate()
    CpuTemp = Rstdout.split()
    return CpuTemp[0]

関数 GetCpuTemp() で、CPU温度を取得します。

subprocessで、「vcgencmd measure_temp」を実行します。

temp=43.5’C

コマンドの標準出力結果に改行が含まれているのでsplit()で分解して、改行の手前を戻り値にします。

CPU使用率の取得

def GetCpuStat():
    Cmd = 'cat /proc/stat | grep cpu'
    result = subprocess.Popen(Cmd, shell=True,  stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    Rstdout ,Rstderr = result.communicate()
    LineList = Rstdout.splitlines()
    #
    TckList = []
    for Line in LineList:
        ItemList = Line.split()
        TckIdle = int(ItemList[4])
        TckBusy = int(ItemList[1])+int(ItemList[2])+int(ItemList[3])
        TckAll  = TckBusy + TckIdle
        TckList.append( [ TckBusy ,TckAll ] )
    return  TckList

関数 GetCpuStat() で、CPU時間統計を取得します。

subprocessで、「cat /proc/stat | grep cpu」を実行します。

cpu 63016 0 3921 416071 2871 0 1858 0 0 0
cpu0 13712 0 1149 103615 683 0 1856 0 0 0
cpu1 16306 0 866 104437 576 0 0 0 0 0
cpu2 16064 0 929 104190 1071 0 2 0 0 0
cpu3 16934 0 977 103829 541 0 0 0 0 0

コマンドの標準出力結果は、複数行です。

LineList = Rstdout.splitlines()

行に分解して「for Line in LineList:」で1行ごとに処理します。

cpu 63016 0 3921 416071 2871 0 1858 0 0 0

ItemList = Line.split()

項目ごとに分解したリストを生成します。

[‘cpu’, ‘63016’, ‘0’, ‘3921’, ‘416071’, ‘2871’, ‘0’, ‘1858’, ‘0’, ‘0’, ‘0’]

TckIdle = int(ItemList[4])
TckBusy = int(ItemList[1])+int(ItemList[2])+int(ItemList[3])
TckAll = TckBusy + TckIdl

TckBusy ← ビジー時間 ( user + nice + system )
TckAll ← 全体の時間 (idle + user + nice + system)

TckList.append( [ TckBusy ,TckAll ] )

CPU時間情報をTckListリストに追加します。

この関数の戻り値は、以下のような形になります。

[ [66937, 483008], [14861, 118476], [17172, 121609], [16993, 121183], [17911, 121740] ]


 

class CpuUsage:
    def __init__(self):
        self._TckList    = GetCpuStat()

    def get(self):
        TckListPre       = self._TckList
        TckListNow       = GetCpuStat()
        self._TckList    = TckListNow
        CpuRateList = []
        for (TckNow, TckPre) in zip(TckListNow, TckListPre):
            TckDiff = [ Now - Pre for (Now , Pre) in zip(TckNow, TckPre) ]
            TckBusy = TckDiff[0]
            TckAll  = TckDiff[1]
            CpuRate = int(TckBusy*100/TckAll)
            CpuRateList.append( CpuRate )
        return CpuRateList

クラス CpuUsage のget()で、近々のCPU使用率を取得します。

TckListPre に前回の時間統計、TckListNow に現在の時間統計を代入します。

for (TckNow, TckPre) in zip(TckListNow, TckListPre):

両者のリスト要素を順々に取り出します。

TckNow は、[354778, 842834]
TckPre は、[354426, 842409]

TckDiff = [ Now – Pre for (Now , Pre) in zip(TckNow, TckPre) ]

リストの要素ごとに差分を算出します。

TckDiff は、[352 ,425]

TckBusy = TckDiff[0]
TckAll = TckDiff[1]
CpuRate = int(TckBusy*100/TckAll)

CpuRate を TckBusy×100÷TckAll で算出します。


gCpuUsage = CpuUsage()       # 初期化
for ix in range(10000):
    time.sleep(1)
    CpuRateList = gCpuUsage.get()
    CpuRate     = CpuRateList[0]

最初に「gCpuUsage = CpuUsage()」でインスタンスを生成して初期化します。
定期的(1秒ごと)に「CpuRateList = gCpuUsage.get()」でCPU使用率を取得します。

CpuRateListは、[ CPU ,CPU0 ,CPU1 ,CPU2 ,CPU3 ] のリスト形式です。

[80, 77, 76, 87, 82]

リストの先頭がCPU使用率で、その次からコア別のCPU使用率となります。

CPU周波数/CPU温度/CPU使用率の取得スクリプト実行

Linuxコンソールから、CPU周波数/CPU温度/CPU使用率の取得スクリプトを実行します。

$ ./cpustat.py

CPU温度は、47度ぐらいです。
CPU周波数は、デフォルトの600MHzです。
CPU使用率は、2%程度でほとんど使用していません。

CPU状態 アイドル

CPU状態 アイドル

 


さて、顔検出を開始すると、CPU周波数/CPU温度/CPU使用率はどのように変化するでしょうか。

画面にOpenCVのlena.jpg サンプル画像を表示して、そのモニター画像をカメラで撮影して顔を検出します。

CPU状態 テスト

CPU状態 テスト

画面左下にlena画像を表示し、左手前の白いボックスのUSBカメラでlenaを撮影します。右下に顔検出結果が表示されています。

CPU状態 テスト

CPU状態 テスト


顔検出を開始すると、以下のように変化しました。

CPU温度は、47度 → 66度 に上昇しました。
CPU周波数は、600MHz → 1200MHz に上がりました。
CPU使用率は、2% → 77% に上がりました。

CPU状態 顔検出開始

CPU状態 顔検出開始


顔検出を開始してから数分で、以下のように変化しました。

CPU温度は、66度 → 80度 に上昇しました。
CPU周波数は、1200MHz → 1172MHz と下がりました。
CPU使用率は、77% → 76% とほとんど変化なしです。

CPU状態 CPU温度上昇

CPU状態 CPU温度上昇

scrotコマンドによる画面キャプチャーでは、温度計マークは写りませんでした。実際には、温度計マークが表示されています。

CPU状態 CPU温度警告

CPU状態 CPU温度警告


顔検出が終了しました。

CPU温度は、80度 → 70度 に下降しました。
CPU周波数は、1172MHz → 600MHz と下がりアイドル状態になりました。
CPU使用率は、76% → 2% と低下しました。

CPU状態 顔検出終了

CPU状態 顔検出終了

まとめ

CPU周波数/CPU温度/CPU使用率を取得するPythonスクリプトを実行して、疑問が解決しました。

  • CPU周波数は、アイドル状態で 600MHz で動作します。そして、CPU負荷に応じて動的に動作周波数を変更します。顔検出を開始すると 1200MHz にCPU周波数を上げます。
  • Python版OpenCVの顔検出は、4個のCPUで処理分散して実行しています。
  • CPU使用率が高い状態が続くと、CPU温度が上昇します。
  • 温度計マークは、CPU温度が80度を超過すると出現します。
  • CPU温度が高いと、CPU周波数を適切に低減します。
  • 顔検出が終了しCPU使用率が低下すると、CPU周波数がアイドル状態の 600MHz になり、CPU温度も下降します。