Macアクティビティモニタの不満をSwiftUI自作で解消!1画面監視&重いアプリ冬眠ツール開発録
作成日:
更新日:

Macアクティビティモニタの不満をSwiftUI自作で解消!1画面監視&重いアプリ冬眠ツール開発録


🔧 論旨

Windows から Mac へ移行した方や、長年 Mac を複数台運用している方が一度は感じる小さなストレスが、標準搭載の 「アクティビティモニタ」が CPU・メモリ・エネルギー・ディスク・ネットワークと別々のタブに分断されていること です。「今ファンが回っている原因は CPU なのかメモリ圧迫なのか、どのアプリが何コア分・何スレッド抱えているのか」を 1 つの画面で横並びに俯瞰できません。さらに数年前の Intel Mac では、開いたままの裏ブラウザが CPU を食い続けて動作が重くなりがちです。

結論からお伝えすると、C 言語のシステム API(proc_pidinfo)と SwiftUI の Table を組み合わせて Windows タスクマネージャー風の 1 画面横並び監視アプリ『CoreMonitor』 と、子プロセス群を親アプリ単位で合算してワンクリックで一時冬眠(SIGSTOP)させる『MacChiller』 を AI と自作したことで、新旧 2 台の iMac のリソース管理と静音化が劇的に快適になりました。本記事では、macOS の公開 API で「取れる情報・取れない情報の境界線」と、クラッシュやシステム凍結を防ぐ安全な実装ノウハウを公開します。

🔧 合わせて読みたい深まる関連情報
🔧 対象となる方
  • macOS 標準の「アクティビティモニタ」で CPU タブとメモリタブを何度も行ったり来たりするのに不便を感じている 方

  • Intel 世代の iMac や MacBook をサブ機として大切に使っており、使っていない裏アプリの CPU 負荷やファン回転を抑えてサクサク動かしたい方

  • SwiftUI と macOS のシステムコール(libproc / POSIX シグナル)を組み合わせて、自分専用のシステムユーティリティを AI と一緒に作ってみたい 方

なぜMac標準アクティビティモニタは歯痒いのか

macOS 標準の「アクティビティモニタ」は非常に優秀な純正ツールですが、設計思想として「CPU」「メモリ」「エネルギー」が上部のタブで完全に切り離されています。そのため「CPU 使用率が高いアプリが、同時にメモリを何 GB 食っていて、裏で何本のスレッド(並行処理)を走らせているか」を見比べようとすると、その都度タブを切り替えてアプリ名を探し直さなければなりません。

また、macOS(UNIX 系 OS)の CPU 使用率は 「論理 1 コア(1 スレッド)使い切り = 100%」 という基準で表示されます。たとえば 10 コア 20 スレッドの iMac なら最大値は 2,000% になるため、一覧に 250% と出ていても直感的に「いま CPU コアを何個分使っているのか(= 2.5 コア相当)」が頭に入りにくいという弱点もありました。

macOS標準『アクティビティモニタ』と自作2大ツールの役割分担
現場の使い勝手
タブ分離型
macOS標準 アクティビティモニタ
  • CPU・メモリ・スレッドを1画面の横並びテーブルで同時ソートできない
  • Chrome等のレンダラー子プロセスが数十個バラバラに並んで合算しづらい
  • 「アプリを終了せずに一時冬眠させてCPU 0%にする」ボタンはない
1画面俯瞰×冬眠制御
自作『CoreMonitor & MacChiller』
  • Windowsタスクマネージャー風にCPU(%+コア数)・メモリ・スレッドを1画面表示
  • 列ヘッダークリックで重い順へ即座に並び替え可能(1MB級の超軽量ネイティブ動作)
  • 古いMacでは親アプリ単位で子プロセスを一括サスペンド(冬眠)・復帰可能
質問するおじさん

ほんまそれなんよ!Windows のタスクマネージャーみたいに、タブを切り替えんでも「どのアプリが CPU とメモリを両方食っとるか」を 1 枚のエクセル表みたいに横並びで見たいんよね!

解説ナビゲーター

毎日見る計器盤だからこそ、ワンアクション減るだけで快適さがまるで違いますよね!SwiftUI には macOS 12 以降で標準の表コンポーネント(Table)が用意されているので、実は驚くほどスッキリ作れるんです。

【中古・整備済品】Apple iMac 27インチ Retina 5K (2020年モデル / Intel最終世代・メモリ自己増設対応)

【中古・整備済品】Apple iMac 27インチ Retina 5K (2020年モデル / Intel最終世代・メモリ自己増設対応)


Windows風1画面監視『CoreMonitor』の実装

proc_pidinfoでコア数とスレッドを一括取得

1 つ目に作成した CoreMonitor は、現在起動しているアプリケーションごとに 「アプリ名(アイコン付き)」「PID(プロセス ID)」「CPU 使用率(% および消費コア数換算)」「メモリ使用量(MB / GB)」「Threads(同時稼働スレッド数)」 の 5 列を 1 つのテーブルに横並び表示し、どの列見出しをクリックしても昇順・降順ソートできるダッシュボードです。

外部コマンドの ps や top を毎秒サブプロセス起動すると、監視アプリ自身が CPU を食ってしまいます。そこで macOS カーネルの C 言語ライブラリ(<libproc.h> の proc_pidinfo に PROC_PIDTASKINFO を指定)を Swift から直接呼び出し、ナノ秒単位の累積 CPU 時間・物理メモリ常駐サイズ(pti_resident_size)・稼働スレッド数(pti_threadnum)をインプロセスで高速取得する設計にしました。

ここでプログラムの堅牢性として絶対に外せない 境界値バグ対策 があります。proc_pidinfo が返す累積 CPU 時間(pti_total_user + pti_total_system)は UInt64(符号なし 64 ビット整数)ですが、更新タイミングの狭間でプロセスが終了・PID 再利用されたりした場合に万が一 今回の値 < 前回の値 となると、Swift の整数減算は アンダーフロートラップ(EXC_BAD_INSTRUCTION)を起こしてアプリが即死クラッシュ します。必ず current >= previous ? current - previous : 0 の三項ガードを挟んで差分を計算しましょう。

// ProcessMonitor.swift(抜粋:proc_pidinfo直接呼び出し&UInt64アンダーフロー防止ガード)
import Foundation
import AppKit
import Darwin

struct AppResourceRow: Identifiable {
    let pid: pid_t
    let name: String
    let icon: NSImage?
    let cpuPercent: Double
    var coresUsed: Double { cpuPercent / 100.0 }
    let memoryMB: Double
    let threadCount: Int
    var id: pid_t { pid }
}

final class ProcessMonitor {
    private var prevCpuNs: [pid_t: UInt64] = [:]
    private var prevTimestampNs: UInt64 = DispatchTime.now().uptimeNanoseconds
    private let timebaseInfo: mach_timebase_info_data_t = {
        var info = mach_timebase_info_data_t()
        mach_timebase_info(&info)
        return info
    }()

    func sampleRunningApps() -> [AppResourceRow] {
        let nowNs = DispatchTime.now().uptimeNanoseconds
        let elapsedNs = nowNs > prevTimestampNs ? (nowNs - prevTimestampNs) : 1
        prevTimestampNs = nowNs

        var nextCpuMap: [pid_t: UInt64] = [:]
        var rows: [AppResourceRow] = []

        for app in NSWorkspace.shared.runningApplications {
            let pid = app.processIdentifier
            guard pid > 0 else { continue }

            var taskInfo = proc_taskinfo()
            let size = Int32(MemoryLayout<proc_taskinfo>.stride)
            let ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &taskInfo, size)
            guard ret == size else { continue }

            // Mach absolute time をナノ秒へ変換(オーバーフロー回避のためUInt128相当の順序で計算)
            let rawTotal = taskInfo.pti_total_user &+ taskInfo.pti_total_system
            let numer = UInt64(timebaseInfo.numer)
            let denom = max(UInt64(timebaseInfo.denom), 1)
            let totalCpuNs = (rawTotal / denom) * numer

            nextCpuMap[pid] = totalCpuNs
            let prevNs = prevCpuNs[pid] ?? totalCpuNs
            // PID再利用や異常値で totalCpuNs < prevNs となっても絶対にクラッシュさせない安全減算
            let deltaCpuNs = totalCpuNs >= prevNs ? (totalCpuNs - prevNs) : 0

            let cpuPct = min((Double(deltaCpuNs) / Double(elapsedNs)) * 100.0, 6400.0)
            let memMB = Double(taskInfo.pti_resident_size) / (1024.0 * 1024.0)
            let threads = Int(max(taskInfo.pti_threadnum, 0))

            rows.append(AppResourceRow(
                pid: pid,
                name: app.localizedName ?? "PID \(pid)",
                icon: app.icon,
                cpuPercent: cpuPct.isFinite ? cpuPct : 0,
                memoryMB: memMB.isFinite ? memMB : 0,
                threadCount: threads
            ))
        }
        prevCpuNs = nextCpuMap
        return rows
    }
}

Pコア判別やGPU取得ができないmacOSの壁

開発の途中で「せっかくなら CPU・メモリだけでなく、高性能コア(P コア)と高効率コア(E コア)のどちらを使っているかや、アプリごとの GPU 使用率・通信量も全部同じ表に出せないか?」と検証しました。結論から言うと、一般権限で動く自作アプリでは「CPU・メモリ・スレッド数」の 3 点に絞るのが最も合理的 です。

取得したい指標一般権限の公開APIでの可否技術的な理由・無理に取得しようとした場合の副作用
CPU使用率(消費コア数)◎ 取得可能(超軽量)proc_pidinfo(PROC_PIDTASKINFO)でミリ秒未満で取得可能
実メモリ使用量・スレッド数◎ 取得可能(超軽量)同じく pti_resident_size と pti_threadnum から一括取得可能
Pコア / Eコア別の稼働内訳✕ 一般権限では不可sudo powermetrics 等の管理者(root)権限と専用サンプリングが必要
プロセス別のGPU使用率✕ 公開APIなし純正アクティビティモニタは Apple 非公開フレームワーク(SkyLight / IOGPU 等)を使用。ioreg 総当たりで代用すると監視ツール自身が激重になる
プロセス別の通信量△ 管理者権限が必要nettop 相当のソケット統計は一般ユーザー権限だけでは制限が多い

「無理に裏技コマンド(ioreg 毎秒スキャン等)を回してモニターアプリ自身が CPU を 20% 食う本末転倒」を避け、公開 API で軽く取れる 5 列(アプリ名・PID・CPU コア数・メモリ・スレッド数)に割り切ったことで、常駐させても負荷ほぼゼロの軽快な計器盤に仕上がりました。


古いMacを冷やす一括冬眠ツール『MacChiller』

なぜ古いMacで最新ブラウザや動画が重くなるか

メイン機の iMac 20,1(2020 年モデル・10 コア CPU・メモリ 64GB)ではどんな作業も快適ですが、サブ機として動かしている先代の iMac 15,1(Late 2014 モデル) では「数年前まではサクサクだったのに、OS やブラウザが新しくなるにつれて YouTube 再生やタブ切り替えが重くなった」と感じる場面が増えていました。

ハードウェアが壊れたわけではないのに古い Mac が苦しくなる主因は、主に 2 つのソフトウェア肥大化 にあります。

🔧 🐢 古いIntel MacのCPUが悲鳴を上げる2大原因
  1. 高圧縮動画コーデック(VP9 / AV1)の CPU 力業デコード : 昔の Web 動画は軽い H.264 形式が主流でしたが、現在の YouTube 等は高画質な VP9 や AV1 形式を優先配信します。近年のチップは専用回路(ハードウェアデコーダ)で涼しい顔をして再生しますが、2014 年世代の GPU は非対応のため CPU の力業(ソフトウェアデコード)で毎秒展開する羽目になり、一気に CPU 使用率とファン回転数が跳ね上がります。

  2. モダンブラウザの大量マルチプロセス化 : 現在の Google Chrome 等のブラウザはセキュリティ分離のため、タブや拡張機能ごとに Google Chrome Helper (Renderer) といった独立した子プロセスを 15〜30 個も立ち上げます。見ていない裏ウィンドウの広告スクリプトやバックグラウンド通信がチリツモで CPU コアを奪い続けます。

【Intel Mac増設用】Crucial iMac 27インチ(2019/2020)・ノートPC用 メモリ DDR4-2666 SO-DIMM (16GB×2 / 32GB×2)

【Intel Mac増設用】Crucial iMac 27インチ(2019/2020)・ノートPC用 メモリ DDR4-2666 SO-DIMM (16GB×2 / 32GB×2)

親アプリ合算とSIGSTOP一括冬眠の安全境界

「タブを全部閉じてアプリを終了させれば軽くなるけれど、調べ物途中のウィンドウ配置は消したくない…」という悩みを解決するために作った 2 つ目のツールが MacChiller(v2) です。

散らばった 15 個の Chrome Helper プロセスを親プロセスの階層(ppid やバンドルグループ)で 「Google Chrome(計 15 プロセス:合計 CPU 120% / メモリ 2.5GB)」 のように 1 行へまとめて合算表示し、横にある [一括 Suspend(冬眠)] ボタンを押すだけで、親プロセスと配下の子プロセス全員に UNIX 標準の一時停止シグナル(SIGSTOP)を送って メモリ上の状態を保ったまま CPU 消費をピタッと 0% に凍結 します。作業を再開したいときは[Resume(復帰)]ボタンを押せば SIGCONT が送られ、0.1 秒で元の画面のまま動き出します。

『MacChiller v2』による親子プロセス一括冬眠(SIGSTOP)と自動解凍の安全設計
Step 1
親子プロセスのグループ合算

メインアプリのPIDに紐づくHelper子プロセス群を束ね、合計CPUコア数とメモリ使用量を算出します。

Step 2
一括サスペンド(冬眠)

システム必須プロセスを除外した上で、対象アプリと子プロセス群を一時停止しCPU消費を0%にします。

Step 3
ワンクリック復帰&終了時保護

Resume押下時だけでなく、MacChiller自身の終了時(applicationWillTerminate)にも全停止中PIDへ自動でSIGCONTを送って凍結残りを防ぎます。

アプリ終了時の自動SIGCONT復元とシステム重要プロセスの除外ガードを必ずセットで実装します

なお、SIGSTOP は強力ゆえに 絶対に守るべき 3 つの安全ルール があります。

🔧 ⚠️ プロセス冬眠(SIGSTOP)を使う際の3大安全ガード
  1. システム中枢プロセスと自分自身は絶対に停止対象から除外する : WindowServer、Finder、Dock、loginwindow、kernel_task、coreaudiod、および MacChiller 自身の PID(getpid())や他ユーザー権限のプロセスを止めると、画面描画やマウス操作ごと Mac が固まります。必ず一般アプリ(activationPolicy == .regular かつ安全除外リスト外)のみにボタンを表示してください。

  2. アプリ終了時の「自動解凍(SIGCONT)」フェイルセーフを入れる : もし対象アプリを冬眠させたまま MacChiller 自体をうっかり Cmd+Q で閉じてしまうと、冬眠中のアプリが「応答なし(虹色カーソル)」のまま取り残されます。停止させた PID 一覧を保持し、NSApplicationDelegate の終了フックで漏れなく kill(pid, SIGCONT) を送る後始末処理が必須です。

  3. iCloud 同期や通話・書き出し中のアプリには使わない : 通信ソケットを張っている最中に長時間 SIGSTOP するとサーバー側からタイムアウト切断されます。「しばらく見ない裏ブラウザや待機中の重いエディタを一時的に黙らせる用途」に絞って使うのが快適運用のコツです。

ノルウェージャンフォレストキャット

使っていない裏のブラウザさんに「ちょっとお昼寝しててね〜」って毛布をかけてあげるみたいで賢いでちね!これなら古い iMac さんも涼しい顔で動けるでち!

解説ナビゲーター

さらに熱対策を万全にしたい Intel Mac ユーザーの方は、定番のファン回転数制御ツール『Macs Fan Control』で温度が上がりきる前に緩やかにファンを回してあげると、サーマルスロットリング(熱による速度低下)も防げて一石二鳥ですよ!

Astropad Luna Display (USB-Cモデル) Mac/iPadを5Kサブモニター化するハードウェアドングル

Astropad Luna Display (USB-Cモデル) Mac/iPadを5Kサブモニター化するハードウェアドングル


まとめ:新旧Macを長く快適に使い倒す自作計器盤

OS 標準ツールに「あと一歩、こう見えたらいいのに」という不満があるとき、今の時代は AI を相棒にすれば わずか数 MB でサクサク動く自分専用のネイティブ計器盤 を 1 時間足らずで手に入れることができます。「メイン機では『CoreMonitor』で CPU コア・メモリ・スレッドを 1 画面で気持ちよく俯瞰し、古いサブ機では『MacChiller』で裏プロセスを賢く冬眠させて延命する」——この組み合わせは、手元の愛機を最後まで快適に使い倒す最高の処方箋になります。

ご自身の Mac でも Windows タスクマネージャー風の 1 画面統合モニターを作ってみたい方は、以下のコピペ用プロンプトを最上位 AI モデルへ渡してビルドしてみてください。

🔧 🤖 『Windows風1画面監視アプリ CoreMonitor』をAIに作らせるコピペ用プロンプト

※C 言語 API(proc_pidinfo)のメモリレイアウト整合性や UInt64 アンダーフロー防止ガードを正確に書かせるため、必ず 『Gemini 4 Argon』(または同等以上の最上位推論モデル)を選択して実行してください。

以下の記事の設計を参考に、Swift Package Manager(Package.swift / macOS 13以降対象)で動く、Windowsタスクマネージャー風の1画面リソース監視Macアプリ『CoreMonitor』を作成してください。
参照URL: https://lab.chihouzakki.com/blog/review/swiftui-coremonitor-macchiller-process-manager-diy/

【実装ルールと安全要件(厳守)】
1. 外部コマンド(psやtop)のサブプロセス起動は使わず、Darwinの <libproc.h>(proc_pidinfo / PROC_PIDTASKINFO)と NSWorkspace.shared.runningApplications を用いて、2秒周期で「アプリ名+アイコン」「PID」「CPU使用率(%表示および ◯.◯コア相当 の併記)」「実メモリ使用量(MB/GB)」「スレッド数(pti_threadnum)」の5項目を取得してください。
2. SwiftUIの Table と KeyPathComparator を用い、5つの列すべてで見出しクリックによる昇順・降順ソートが可能な1画面ダッシュボードUIにしてください(画面上部にシステム全体の合計稼働アプリ数と更新間隔切替、タイトルバーまたは下部にリビジョン番号を表示)。
3. 累積CPUナノ秒(UInt64)の差分計算時は、PID再利用等で現在値が前回値を下回っても絶対にアンダーフロートラップ(EXC_BAD_INSTRUCTION)を起こさないよう `current >= prev ? current - prev : 0` と `isFinite` ガードを徹底してください。
4. App.init() に NSApplication.shared.setActivationPolicy(.regular) と activate(ignoringOtherApps: true) を実装し、RAMDiskがある環境では /Volumes/RAMDisk/$USER/swift-build/CoreMonitor(無い場合は /tmp/swift-build-$USER/CoreMonitor)を --scratch-path に使う起動スクリプトも添えてください。
5. (任意オプション)もし選択した一般アプリを一時サスペンド(SIGSTOP / SIGCONT)するボタンを追加する場合は、Finder・Dock・WindowServer・loginwindow・自身PID等のシステム重要プロセスを厳格に除外し、アプリ終了時(applicationWillTerminate)に停止中プロセスへ必ず SIGCONT を送って解凍するフェイルセーフを必須実装してください。

お気に入りの Mac の体調を一目で把握して、静かで軽快なデスク環境を楽しんでいきましょう!

涼しいお部屋でスヤスヤと気持ちよさそうに冬眠するノルウェージャンフォレストキャット

▲ 使わない裏アプリはスヤスヤお昼寝させて、Macも涼しく快適運転!