0
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-06-09

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
0
1
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
0
1