18
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リアルタイムなシステムモニターをPythonで作ってみよう!

Posted at

はじめに

コンピュータのリソース使用状況をリアルタイムで可視化する、オリジナルのシステムモニターを作ってみませんか?この記事では、Pythonを使ってデスクトップアプリケーションとして動作する、美しいシステムモニターを実装していきます。

image.png

完成イメージ

image.png

  • CPU・メモリ使用率をリアルタイムでグラフ表示
  • ダークモードのモダンなUI
  • 更新間隔のカスタマイズ機能
  • 開始/停止機能付き

使用する技術とライブラリ

  • tkinter: PythonのGUIライブラリ
  • psutil: システム情報取得ライブラリ
  • datetime: 時刻表示用
  • math: 計算処理用

実装のポイント

1. モダンなUIデザイン

  • ダークモードをベースにした配色
  • フラットデザインのボタン
  • 適切な余白とパディング
  • 洗練されたグリッドライン

2. スマートなデータ管理

  • 直近60秒分のデータのみを保持し、メモリ使用を最適化
  • 例外処理によるロバストな実装
  • 柔軟な更新間隔の設定

3. 美しいグラフ描画

  • スムージング効果による滑らかな線
  • 適切なパディングとスケーリング
  • 動的なキャンバスサイズ対応
  • グリッドラインによる視認性向上

実装コード

import tkinter as tk
from tkinter import ttk
import psutil
import time
from datetime import datetime
import math

class SystemMonitor:
    def __init__(self, root):
        self.root = root
        self.root.title("System Monitor")
        self.root.geometry("800x600")
        self.root.configure(bg='#2b2b2b')

        # スタイル設定
        style = ttk.Style()
        style.theme_use('default')
        style.configure('TButton', 
                       padding=6, 
                       relief="flat",
                       background="#4CAF50",
                       foreground="white")
        style.configure('TLabel', 
                       background='#2b2b2b',
                       foreground='white')
        
        # コントロールフレーム
        control_frame = tk.Frame(root, bg='#2b2b2b')
        control_frame.pack(fill='x', padx=10, pady=5)
        
        # 更新間隔の設定
        ttk.Label(control_frame, 
                 text="Update Interval:",
                 style='TLabel').pack(side='left', padx=5)
        
        self.interval_var = tk.StringVar(value='1')
        interval_menu = ttk.OptionMenu(control_frame, 
                                     self.interval_var,
                                     '1',
                                     '1', '2', '5', '10',
                                     command=self.change_interval)
        interval_menu.pack(side='left', padx=5)
        
        ttk.Label(control_frame, 
                 text="seconds",
                 style='TLabel').pack(side='left')
        
        # 開始/停止ボタン
        self.running = True
        self.toggle_button = ttk.Button(control_frame,
                                      text="Stop",
                                      command=self.toggle_update)
        self.toggle_button.pack(side='left', padx=10)
        
        # グラフキャンバス
        self.canvas = tk.Canvas(root,
                              bg='#1E1E1E',
                              width=750,
                              height=500,
                              highlightthickness=0)
        self.canvas.pack(padx=10, pady=5, fill='both', expand=True)
        
        # ステータスバー
        self.status_var = tk.StringVar()
        status_label = ttk.Label(root,
                               textvariable=self.status_var,
                               style='TLabel')
        status_label.pack(pady=5)
        
        # データ初期化
        self.cpu_history = []
        self.memory_history = []
        self.max_points = 60  # 60秒分のデータを保持
        
        # 初回更新
        self.update_data()
        
    def update_data(self):
        """データの更新とグラフの描画"""
        if not self.running:
            return
        
        try:
            # データ収集
            cpu_percent = psutil.cpu_percent()
            memory_percent = psutil.virtual_memory().percent
            
            # 履歴の更新
            self.cpu_history.append(cpu_percent)
            self.memory_history.append(memory_percent)
            
            # 履歴を制限
            if len(self.cpu_history) > self.max_points:
                self.cpu_history.pop(0)
                self.memory_history.pop(0)
            
            # グラフの描画
            self.draw_graph()
            
            # ステータスの更新
            self.status_var.set(f"Last update: {datetime.now().strftime('%H:%M:%S')}")
            
            # 次の更新をスケジュール
            interval = int(self.interval_var.get()) * 1000
            self.root.after(interval, self.update_data)
            
        except Exception as e:
            self.status_var.set(f"Error: {str(e)}")
            self.root.after(1000, self.update_data)
    
    def draw_graph(self):
        """グラフの描画"""
        self.canvas.delete('all')  # キャンバスをクリア
        
        # キャンバスのサイズ取得
        width = self.canvas.winfo_width()
        height = self.canvas.winfo_height()
        padding = 40
        
        # グリッドの描画
        self.draw_grid(width, height, padding)
        
        # データが存在する場合のみグラフを描画
        if self.cpu_history:
            # CPU使用率のライン
            self.draw_line(self.cpu_history, '#4CAF50', width, height, padding)
            # メモリ使用率のライン
            self.draw_line(self.memory_history, '#2196F3', width, height, padding)
        
        # 現在値の表示
        current_cpu = self.cpu_history[-1] if self.cpu_history else 0
        current_memory = self.memory_history[-1] if self.memory_history else 0
        
        self.canvas.create_text(10, 20,
                              text=f"CPU: {current_cpu:.1f}%",
                              fill='#4CAF50',
                              anchor='w')
        self.canvas.create_text(120, 20,
                              text=f"Memory: {current_memory:.1f}%",
                              fill='#2196F3',
                              anchor='w')
    
    def draw_grid(self, width, height, padding):
        """グリッドの描画"""
        # Y軸の目盛りと数値
        for i in range(11):  # 0%から100%まで10%刻み
            y = padding + (height - 2 * padding) * (10 - i) / 10
            # グリッド線
            self.canvas.create_line(padding, y,
                                  width-padding, y,
                                  fill='#333333',
                                  dash=(2, 4))
            # Y軸の数値
            self.canvas.create_text(padding - 5, y,
                                  text=f"{i * 10}%",
                                  fill='white',
                                  anchor='e')
        
        # X軸の目盛りと数値(60秒を6分割)
        for i in range(7):  # 0秒から60秒まで10秒刻み
            x = padding + (width - 2 * padding) * i / 6
            # グリッド線
            self.canvas.create_line(x, padding,
                                  x, height-padding,
                                  fill='#333333',
                                  dash=(2, 4))
            # X軸の数値
            self.canvas.create_text(x, height-padding+5,
                                  text=f"-{60-i*10}s",
                                  fill='white',
                                  anchor='n')
    
    def draw_line(self, data, color, width, height, padding):
        """データラインの描画"""
        if not data:
            return
            
        points = []
        chart_width = width - 2 * padding
        chart_height = height - 2 * padding
        
        x_step = chart_width / (len(data) - 1) if len(data) > 1 else 0
        
        for i, value in enumerate(data):
            x = padding + (i * x_step)
            y = height - padding - (value / 100 * chart_height)
            points.extend([x, y])
        
        if len(points) >= 4:
            self.canvas.create_line(points,
                                  fill=color,
                                  width=2,
                                  smooth=True)
    
    def change_interval(self, _):
        """更新間隔の変更"""
        if self.running:
            self.toggle_update()  # 一旦停止
            self.toggle_update()  # 新しい間隔で再開
    
    def toggle_update(self):
        """更新の開始/停止"""
        self.running = not self.running
        if self.running:
            self.toggle_button.configure(text="Stop")
            self.update_data()
        else:
            self.toggle_button.configure(text="Start")

def main():
    root = tk.Tk()
    app = SystemMonitor(root)
    root.mainloop()

if __name__ == "__main__":
    main()

発展的な使い方

このベースコードを元に、以下のような機能拡張が可能です:

  1. ディスクI/O監視の追加

    • psutilのdisk_io_counters()を使用
    • 読み書き速度のリアルタイム表示
  2. ネットワーク使用率の可視化

    • net_io_counters()によるネットワークトラフィック監視
    • 送受信速度のグラフ表示
  3. プロセス別の使用率表示

    • Process()クラスを使用した詳細情報の取得
    • リソースを多く使用するプロセスのランキング表示
  4. アラート機能の実装

    • 閾値を超えた場合の通知
    • ログファイルへの記録

まとめ

Pythonを使用することで、実用的かつ見た目の良いシステムモニターを比較的少ないコードで実装できました。tkinterは一見古く感じるかもしれませんが、適切なスタイリングと設計によって、モダンなアプリケーションを作ることが可能です。

このコードをベースに、自分の用途に合わせたカスタマイズを加えていくことで、より実用的なツールへと発展させることができます。

補足:実行時の注意点

  • psutilのインストールが必要です:pip install psutil
  • Windows/Mac/Linuxのすべてのプラットフォームで動作します
  • Python 3.6以上を推奨します

参考リンク

設計

クラス図

シーケンス図

18
20
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
18
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?