MacOS に free コマンドがないので、ほぼ等価な Python3 での実装

### MacOS に free コマンドがないので、ほぼ等価な Python3 での実装

以下のとおり、sysctl と vm_stat 組み合わせて、極力近づけてみました。厳密には shard
ライブラリのメモリローディングとかありそうだけど、常駐しているわけでもないので、、、

```
chmod a+x free.py
ln -s free.py free
```

しておけば、そのままコマンドとして振る舞います。もちろん引数はサポートしてません :-)

単発プログラムでライブラリ等の依存もないので、Python 3 実行環境だけあれば、PYTHONPATH
とか気にせずに実行できます。

```
#!/usr/bin/python3

"""
free.py - free コマンドの等価プログラム

MacOS 用に free コマンドがないので、等価に動作する Python 3 プログラム 
"""

# インポート
import          sys
from            subprocess          import  run, PIPE, CompletedProcess
from            typing              import List

#
# free() - free コマンドの等価プログラムの主処理
#
def         free() -> int :
    
    """
    free コマンドの主処理
    """
    
    # メモリの使用状況を得る
    try :
        
        # 搭載メモリサイズを得るため、sysctl コマンドの出力を得る
        result      : CompletedProcess      = doCmd( [ 'sysctl', 'hw.memsize', ], )

        # - 行から搭載メモリサイズを得る - 例 : hw.memsize: 17179869184
        line        = result.stdout.decode( 'utf-8' ).splitlines()[ 0 ]
        m_total     = int( line.split()[ 1 ] )
        
        # メモリ使用状況を得るため、vm_stat コマンドの出力を得る
        result      : CompletedProcess      = doCmd( [ 'vm_stat', ], )

        # - メモリ情報すべてのキーと値の組を得る
        vmstat      = {}
        
        # - 先頭行を飛ばして、次の行からキーと値の組を得る  - 例 : Pages active: 683617.
        # - :. などの末尾にある記号は削除する。また、値はページ (4096 バイト単位) なのでバイトに変換する
        for line in result.stdout.decode( 'utf-8' ).splitlines()[ 1 : ] :
            words           = line.split( ':' )
            key             = words[ 0 ]
            vmstat[ key ]   = int( words[ 1 ][ : -1 ] ) * 4096
            
        # - 空き領域を得る (ただし実際使える領域はもっと大きい）
        m_free      = vmstat[ 'Pages free' ]
        
        # - キャッシュ領域を得る
        m_cached    = vmstat[ 'Pages purgeable' ] + vmstat[ 'File-backed pages' ]
        
        # - 使用量を計算する - 使用領域からキャッシュを引いたものが実際の使用量
        m_used      = (
                vmstat[ 'Pages wired down' ] + vmstat[ 'Pages active' ] + vmstat[ 'Pages inactive' ]
                                + vmstat[ 'Pages speculative' ] + vmstat[ 'Pages occupied by compressor' ]
                                                - m_cached
        )

        # - 実際のお空き領域は実使用量を引いたもの
        m_avail     = m_total - m_used

        # スワップ使用状況を得るため、sysctl コマンドの出力を得る
        result      : CompletedProcess      = doCmd( [ 'sysctl', 'vm.swapusage', ], )

        # - 行からスワップ使用状況を得る - 例 : vm.swapusage: total = 2048.00M  used = 1272.50M  free = 775.50M
        # - 値は MB (1024 * 1024) 単位なのでバイトに変換する
        line        = result.stdout.decode( 'utf-8' ).splitlines()[ 0 ]
        words       = line.split()
        s_total     = int( float( words[ 3 ][ : -1 ] ) * 1024 * 1024 )
        s_used      = int( float( words[ 6 ][ : -1 ] ) * 1024 * 1024 )
        s_free      = int( float( words[ 9 ][ : -1 ] ) * 1024 * 1024 )

        # 全体の実行結果を free コマンドのフォーマットで出力する
        print( '            total        used        free      shared  buff/cache   available', )
        
        print( '{:5s}{:12d}{:12d}{:12d}{:>12s}{:12d}{:12d}'
                        .format( "Mem:", m_total, m_used, m_free, '-', m_cached, m_avail, ), )
        print( '{:5s}{:12d}{:12d}{:12d}'
                        .format( "Swap:", s_total, s_used, s_free,), )

    # エラーの時の処理 - 例外メッセージ（複数行）を出力する
    except Exception as e :
        
        # すべてのエラーメッセージを出力する
        for line in str( e ).split( '\n' ) :
            print( line, file = sys.stderr )
            
        # エラーコードを返す
        return -1
    
#
# doCmd() - コマンド実行
#
def             doCmd( cmd : List[ str ] ) -> CompletedProcess :
    """
    コマンドを実行する
    
    :param cmd:         コマンド名および引数
    :return:            実行結果。リターンコードおよび stdout, stderr の内容を返す
    """
    
    # コマンドを引数とともに実行する
    try :
        result      : CompletedProcess      = run( cmd, stdout = PIPE, stderr = PIPE, )
    
    # - コマンドが実行できなかった時はメッセージを設定してエラー
    except Exception :
        raise Exception( "cannot exec command {}.".format( " ".join( cmd ) ) )
    
    # - 実行結果がエラーなら stderr メッセージを設定してエラー
    if result.returncode :
        raise Exception( result.stderr.decode( 'utf-8' ) )
    
    return result

#
# エントリポイント - メインを呼び出す
#
if __name__ == '__main__' :

    # 主処理を呼び出す
    exit( free() )
```

実行結果

```
% ./free
            total        used        free      shared  buff/cache   available
Mem:  17179869184 14519988224   262684672           -  2396454912  2659880960
Swap:  3221225472  1577844736  1643380736
```