(2025.01.04) コード修正
はじめに
tkinter を使って CPU 使用率グラフを作ってみた、
Mac 標準装備のアクティビティモニタでもいいのだが、欲しい情報を1つの画面に表示したくて今回のものを作った次第である。
こういうものでモニタリングしていると、かなりハードな計算でも4コア程度しか使われておらず、8コア全て使い切るには並列処理などを意識して使っていく必要があることがよくわかる。
環境は以下の通り。
MacBook Pro 14"
Chip Apple M1 Pro (8-core CPU, 14-core GPU)
Memory 16GB
Storage 512GB SSD
macOS Sequoia 15.2
Python 3.13.1
Tcl/Tk 9.0.1
作例
まずは作図事例を示そう。
1枚目の写真はアイドリング時のもの。2枚目の写真は、Python で並列処理をかけて全 CPU を動かした場合のもの。
私が使っている M1 MacBook Pro は 8 コア CPU なので、CPU 稼動率の履歴を示すグラフは8段としている。グラフの上2段が高効率コア、下6段が高性能コア。並びは Mac 標準のアクティビティモニタの表示(CPU history)から類推。これでいいと思う。
概略説明
tkinter の画面全体を2つの部分(Frame)に分けている。
上段 Frame には8個の Canvas を配置、下段 Frame には平均 CPU 稼動率とメモリ使用率を表示するための4個の Label を配置している。
グラフの中には、各 CPU 毎に、コアの番号・最新の CPU 稼動率の数値・CPU 稼動率の履歴グラフを表示している。履歴の横軸は60秒であり1秒ステップで稼動率の値を取得・更新している。
グラフの表示方法については、ネットで検索すると matplotlib で描画しこれを Canvas に貼り付けるものが多くみられるが、ここでは複雑な情報を表示するものでもないので、Canvas に直接描画している。
また、折れ線表示だと視覚的に認識しづらいので、グラフ更新ステップである1秒相当の幅の線を create_line で棒グラフ状に描画している。
グラフの時間ステップでの更新は、x軸として固定の numpy 配列 xx を準備(0から60までの61個の要素)、y軸として CPU 稼動率格納用に61要素の numpy 配列を準備、y軸の数値を1秒毎にずらしながら 0.5*(xx[i]+xx[i+1])
及び 0.5*(yy[i]+yy[i+1])
の位置に棒線を描画していく。この際、線には line
、稼動率数値には sval
というタグをつけ、グラフ更新直前に delete('line')
, delete('sval')
している。
コード
import tkinter as tk
from tkinter import ttk
import numpy as np
import psutil
def update():
global root,cv,xx,yy,mlb,ww,hh,xmin,xmax,ymin,ymax,cpu,fontn
kxi,kxf,kyi,kyf=0,ww,0,hh
lw=int((kxf-kxi)/(xmax-xmin))+1 # line width
r=psutil.cpu_percent(interval=1, percpu=True)
work=yy
yy=np.roll(work,-1,axis=1)
yy[:,-1]=np.array(r)
for k in range(0,len(r)):
cv[k].delete('line')
cv[k].delete('sval')
for i in range(0,len(xx)-1):
xx2=0.5*(xx[i]+xx[i+1])
yy2=0.5*(yy[k,i]+yy[k,i+1])
xx1,yy1=xx2,ymin
x1=kxi+(xx1-xmin)*(kxf-kxi)/(xmax-xmin)
y1=kyf-(yy1-ymin)*(kyf-kyi)/(ymax-ymin)
x2=kxi+(xx2-xmin)*(kxf-kxi)/(xmax-xmin)
y2=kyf-(yy2-ymin)*(kyf-kyi)/(ymax-ymin)
cv[k].create_line(x1,y1,x2,y2,width=lw,fill='#999999',tag='line')
s='{0:3s} {1:6.1f}'.format(cpu[k],r[k])
cv[k].create_text(5,5,text=s,anchor='nw',font=(fontn),fill='#ffff00',tag='sval')
mlb[1]['text']='{0:6.1f}%'.format(sum(r)/len(r))
mlb[3]['text']='{0:6.1f}%'.format(psutil.virtual_memory().percent)
root.after(1000, update)
def main():
global root,cv,xx,yy,mlb,ww,hh,xmin,xmax,ymin,ymax,cpu,fontn
# basin parameter
nc=psutil.cpu_count() # number pf cores
cpu=[]
for i in range(0,nc):
s='P' # performance core
if 0<=i<=1: s='E' # efficiency core
cpu.append('{0}-{1}'.format(i+1,s))
ww,hh=200,50 # size of canvas
tspan=60 # time span of fig
tstr='CPU (ts={0:.0f}s)'.format(tspan)
# initialization of plot data
xmin,xmax,dx=0,tspan,1 # time range
ymin,ymax=0,100 # CPU percentage
xx=np.linspace(xmin,xmax,int((xmax-xmin)/dx)+1)
yy=np.zeros((nc,len(xx)),dtype=np.float64)
# GUI
root =tk.Tk()
root.resizable(False,False)
root.title(tstr)
style=ttk.Style()
style.theme_use('alt')
fontn='Calibri 12 bold'
fontb='Calibri 14 bold'
style.configure('.',font=(fontn))
style.configure('.',background='#393939',foreground='#ffffff')
# arrangement of Canvas
frame0=ttk.Frame(root)
cv=np.empty(nc,dtype=object)
for k in range(0,len(cv)):
cv[k]=tk.Canvas(root,width=ww,height=hh,bg='#0f0f0f')
cv[k].pack()
frame0.pack()
# arrangement of Lavel
frame1=ttk.Frame(root)
nn=4
mlb=np.empty(nn,dtype=object)
txt=['CPU','','MEM','']
for k in range(0,len(mlb)):
mlb[k]=ttk.Label(frame1,text=txt[k],borderwidth=1,relief=tk.SOLID,anchor=tk.CENTER,width=7,font=(fontb))
i,j=k//2,k%2
mlb[k].grid(row=i,column=j)
frame1.pack()
update()
root.mainloop()
if __name__ == '__main__': main()
以 上