macOSのメモリ使用状況を常に表示する

## はじめに

### この記事を読むとできること

macOSのそのときどきのメモリの使用状況を、確認することができます。→次に買うMacBookに必要なメモリ搭載量を推定できます。


### ここにいたった背景

MacBookを買い換えようとおもいました。32GB必要か、16GBで済むのか、ここが大きな悩みどころであります。そこで、実際の使用量をリアリタイムで知りたいなあと。もちろんアクティビティモニタを見ればわかりますが、いちいちアプリを切り替えたり、ウィンドウを開くのはいかにもめんどうくさいです。メニューバーに常時表示していれば、目の端でこまめにチェックできます。


## 動きの概要

ツールバーに、使用済みメモリを表示します。クリックすると詳細データをプルダウンで表示します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184128/7db784c8-0829-8eea-b70d-ce6101e53e0d.png" width=80%>

## 詳細


### 1. SwiftBarのインストール

[SwiftBar](https://github.com/swiftbar)は、macOSのメニューバーのカスタマイズツールで、スクリプトの標準出力をメニューバーに出してくれます。

[SwiftBarのリリースページ](https://github.com/swiftbar/SwiftBar/releases)から最新版をzipダウンロードし、任意のフォルダに置き、起動します。プラグインフォルダを指定するように言われるので、適当なフォルダを指定します。わたしはユーザのホーム（`~/`）下にフォルダをおきました。

ご参考：[macOSのメニューバーにお気に入りのニュースを表示 > SwiftBarのインストール - Qiita](https://qiita.com/hann-solo/items/46d2bd25667618c36a5d#1-swiftbarのインストール)

### 2. Pythonのインストール

以前に入れたので、もうやらなくて済みました。

[macOSのメニューバーにお気に入りのニュースを表示 > 2. Pythonのインストール - Qiita](https://qiita.com/hann-solo/items/46d2bd25667618c36a5d#2-pythonのインストール)

### 3. プラグインの作成

プラグインフォルダに`memusage.60s.py`というPythonスクリプトを保存します。`60s`は「60秒ごとに更新」という意味なので、好みの間隔でよいです。

`ps`コマンドと`vm_stat`コマンドからメモリ使用状況を拾っています。

```memusage.60s.py
#!/usr/bin/env python
import subprocess
import re

# Get process info
ps = subprocess.Popen(['ps', '-caxm', '-orss,comm'], stdout=subprocess.PIPE).communicate()[0]
vm = subprocess.Popen(['vm_stat'], stdout=subprocess.PIPE).communicate()[0]

# Iterate processes
processLines = ps.split('\n')
sep = re.compile('[\s]+')
rssTotal = 0 # kB
for row in range(1,len(processLines)):
    rowText = processLines[row].strip()
    rowElements = sep.split(rowText)
    try:
        rss = float(rowElements[0]) * 1024
    except:
        rss = 0 # ignore...
    rssTotal += rss

# Process vm_stat
vmLines = vm.split('\n')
sep = re.compile(':[\s]+')
vmStats = {}
# for row in range(1,len(vmLines)-2):
for row in range(1,len(vmLines)-1):
    rowText = vmLines[row].strip()
    rowElements = sep.split(rowText)
    vmStats[(rowElements[0])] = int(rowElements[1].strip('\.')) * 4096

memApp = ( 
    vmStats["Pages active"] 
    + vmStats["Pages speculative"] + vmStats["Pages throttled"] )
memWird = vmStats["Pages wired down"]
memComp = vmStats["Pages occupied by compressor"]
memUsed = memApp + memWird + memComp
memCash = ( vmStats["File-backed pages"] + vmStats["Pages purgeable"] )
memSwap = ( vmStats["Swapouts"] - vmStats["Swapins"] )
b2gb = 1/1024./1024./1024.

# Close to the value of Activity Monitor
print ':memorychip:%.2f GB'  % ( memUsed * b2gb )
print '---'
print 'App mem:\t%.2f GB'    % ( memApp  * b2gb )
print 'Wired down:\t%.2f GB' % ( memWird * b2gb )
print 'Compressed:\t%.2f GB' % ( memComp * b2gb )
print '---'
print 'Cashed Files:\t%.2f GB' % ( memCash * b2gb )
print 'Swap Used:\t%.2f GB'  % ( memSwap * b2gb )

# keep the value from ps
print '---'
print 'from ps:\t\t%.1f GB'  % ( rssTotal/1024./1024./1024. )

# for debug
print '---'
print 'free:\t\t\t%.2f GB'  % ( vmStats["Pages free"]/1024/1024/1024.0 )
print 'active:\t\t%.2f GB'  % ( vmStats["Pages active"]/1024/1024/1024.0 )
print 'inactive:\t\t%.2f GB'  % ( vmStats["Pages inactive"]/1024/1024/1024.0 )
print 'speculative:\t%.2f GB'  % ( vmStats["Pages speculative"]/1024/1024/1024.0 )
print 'throttoled:\t%.2f GB'  % ( vmStats["Pages throttled"]/1024/1024/1024.0 )
print 'wired down:\t%.2f GB'  % ( vmStats["Pages wired down"]/1024/1024/1024.0 )
print 'purgeable:\t%.2f GB'  % ( vmStats["Pages purgeable"]/1024/1024/1024.0 )
print 'reactivated:\t%.2f GB'  % ( vmStats["Pages reactivated"]/1024/1024/1024.0 )
print 'purged:\t\t%.2f GB'  % ( vmStats["Pages purged"]/1024/1024/1024.0 )
print 'file-backed:\t%.2f GB'  % ( vmStats["File-backed pages"]/1024/1024/1024.0 )
print 'anonymous:\t%.2f GB'  % ( vmStats["Anonymous pages"]/1024/1024/1024.0 )
print 'stored comp:\t%.2f GB'  % ( vmStats["Pages stored in compressor"]/1024/1024/1024.0 )
print 'occpd comp:\t%.2f GB'  % ( vmStats["Pages occupied by compressor"]/1024/1024/1024.0 )
print 'decompress:\t%.2f GB'  % ( vmStats["Decompressions"]/1024/1024/1024.0 )
print 'compress:\t%.2f GB'  % ( vmStats["Compressions"]/1024/1024/1024.0 )
print 'pageins:\t\t%.2f GB'  % ( vmStats["Pageins"]/1024/1024/1024.0 )
print 'pageouts:\t%.2f GB'  % ( vmStats["Pageouts"]/1024/1024/1024.0 )
print 'swapins:\t\t%.2f GB'   % ( vmStats["Swapins"]/1024/1024/1024.0 )
print 'swapouts:\t%.2f GB'  % ( vmStats["Swapouts"]/1024/1024/1024.0 )
```

`# keep the value from ps`以下、および`# for debug`以下はなくてもいいです。

`ps`コマンドで拾っているほうはアクティビティモニタとずいぶんずれるので、参考程度に表示していますが、なくしてもいいかもです。

オリジナルのコードは下記ですが、ほしい数字とちょっと違ったので、改良しました。
[xbar-plugins/memusage.5s.py at main · matryer/xbar-plugins · GitHub](https://github.com/matryer/xbar-plugins/blob/main/System/memusage.5s.py)

## 補足情報

### macOSのメモリ管理

macOSのメモリを、ざっくりと図にするとこんなかんじになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/184128/2fea4b3c-2ce3-c6c9-fe7d-ab14b059e6e7.png" width=50%>

macOS(unix)のメモリ管理のポイントは

- OSは、アプリが要求するメモリ枠を、まず確保してしまう
- 確保枠のなかで、実メモリに割り当てるか、圧縮するか、スワップするかはOSが判断する

ということかなとおもいます。また、ここが混乱しやすいところと思うのですが、仮想メモリという単語には２つの意味があります。

- アプリのためにOSが（仮想的に）確保しておく枠
- 物理メモリで足りなくなったぶんディスク上に（仮想的に）作るメモリ

macOSの場合は前者で、「とりあえず確保しておく枠」のこと。Windowsでは後者で、これはmacOS（unix系）ではスワップと呼ばれます。

参考リンク：

- [Mac初心者に送るMacのメモリ管理について - Qiita](https://qiita.com/zakiyamaaaaa/items/20d4dc4a3d587500e1f1)
- [About the Virtual Memory System](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/AboutMemory.html)
- [【図解】仮想記憶(仮想メモリ)の本質や仕組み、メリット　〜スワップ、MMU、ページングテーブルについて〜 | SEの道標](https://milestone-of-se.nesuke.com/sv-basic/architecture/virtual-memory-and-swap/)

### アクティビティモニタ項目と`vm_stat`項目の対応

ピタっとは数字があわないのですが、だいたいの対応は下記の通りです。

| アクティビティモニタ     |                        | vm_stat                      |
| ------------------------ | ---------------------- | ---------------------------- |
| 使用済みメモリ           | アプリケーションメモリ | Pages active<br>Pages speculative<br>Pages throttled |
|                          | 確保されているメモリ   | Pages wired down             |
|                          | 圧縮                   | Pages occupied by compressor |
| キャッシュされたファイル |                        | File-backed pages<br>Pages purgeable            |
| スワップ使用領域         |                        | Swapouts - Swapins           |


## おわりに

macOS（unix）のメモリ管理の考えかたをふわっとしか理解しておらず、仮想メモリという言葉に違う意味があることを知らず、アクティビティモニタの算出ロジックが見つからなかったので、この三重苦で意外と苦労しました。

アクティビティモニタをコマンドラインで起動できて出力を取れれば簡単だったんですけれど、まあそういうわけにもいかず。

アクティビティモニタと`vm_stat`の対応は、各種記事と`man vm_stat`を調べてあたりをつけ、２週間ぐらい数値を比較して検証しました。間違いがあるとご指摘いただけるとうれしいです。ま、ビシッと一致はしていないのですが、「ふだん使っているメモリ容量を把握しておく」目的にはつかえるぐらいの精度かなとおもっております。

### 環境

ここで書いていることは、下記のバージョンで実施しました。

- SwiftBar 1.4.2(386)
- macOS Monterey 12.1
- MacBook Pro (13-inch, 2020, Four Thunderbolt 3 Ports)