[PHP]getrusage関数徹底解説 – サーバーリソース監視とパフォーマンス最適化のための秘策

PHP

こんにちは、PHPエンジニアの皆さん!今回は「getrusage()」関数について詳しく解説します。この関数はあまり知られていませんが、アプリケーションのパフォーマンス分析やリソース使用状況の把握に非常に役立つ強力なツールです。

getrusage()とは?

getrusage()は、現在のプロセスやその子プロセスが使用しているリソースに関する情報を取得するPHPの組み込み関数です。この関数はUNIX系OSのシステムコール「getrusage」のラッパーとして機能します。

$usage = getrusage();
print_r($usage);

取得できる情報

getrusage()が返す配列には、以下のような情報が含まれています:

  • ru_utime.tv_sec, ru_utime.tv_usec: ユーザーモードでの実行時間
  • ru_stime.tv_sec, ru_stime.tv_usec: システムモードでの実行時間
  • ru_maxrss: 最大常駐セットサイズ (メモリ使用量)
  • ru_ixrss: 共有メモリサイズ
  • ru_idrss: 非共有データサイズ
  • ru_isrss: 非共有スタックサイズ
  • ru_minflt: ソフトページフォルト(マイナーページフォルト)
  • ru_majflt: ハードページフォルト(メジャーページフォルト)
  • ru_nswap: スワップ回数
  • ru_inblock: ブロック入力操作
  • ru_oublock: ブロック出力操作
  • ru_msgsnd: 送信メッセージ数
  • ru_msgrcv: 受信メッセージ数
  • ru_nsignals: 受信シグナル数
  • ru_nvcsw: 自発的コンテキストスイッチ
  • ru_nivcsw: 非自発的コンテキストスイッチ

引数について

getrusage()関数は、オプションの引数を取ることができます:

getrusage(int $who = 0): array

$whoパラメータは以下の値を取ることができます:

  • 0(デフォルト): 現在のプロセスの情報を取得
  • 1: 終了した子プロセスの情報を取得

実践的な使用例

1. スクリプト実行時間の精密な測定

// スクリプト開始時
$start = getrusage();

// ... 測定したいコード ...

// スクリプト終了時
$end = getrusage();

// ユーザーモードでの実行時間を計算(秒単位)
$user_time = ($end['ru_utime.tv_sec'] - $start['ru_utime.tv_sec']) +
             ($end['ru_utime.tv_usec'] - $start['ru_utime.tv_usec']) / 1000000;

// システムモードでの実行時間を計算(秒単位)
$system_time = ($end['ru_stime.tv_sec'] - $start['ru_stime.tv_sec']) +
               ($end['ru_stime.tv_usec'] - $start['ru_stime.tv_usec']) / 1000000;

echo "ユーザーモード実行時間: {$user_time}秒\n";
echo "システムモード実行時間: {$system_time}秒\n";
echo "合計実行時間: " . ($user_time + $system_time) . "秒\n";

2. メモリ使用状況の監視

function checkMemoryUsage($label = '') {
    $usage = getrusage();
    echo "{$label} - メモリ使用量: " . ($usage['ru_maxrss'] / 1024) . " MB\n";
    echo "マイナーページフォルト: {$usage['ru_minflt']}\n";
    echo "メジャーページフォルト: {$usage['ru_majflt']}\n";
}

checkMemoryUsage('初期状態');

// 大量のメモリを使用
$largeArray = array_fill(0, 1000000, 'data');

checkMemoryUsage('大規模配列作成後');

// メモリ解放
unset($largeArray);
gc_collect_cycles();

checkMemoryUsage('メモリ解放後');

3. パフォーマンスベンチマーキングクラス

class PerformanceMonitor {
    private $startUsage;
    private $checkpoints = [];
    
    public function __construct() {
        $this->startUsage = getrusage();
        $this->addCheckpoint('開始');
    }
    
    public function addCheckpoint($label) {
        $this->checkpoints[$label] = [
            'time' => microtime(true),
            'usage' => getrusage()
        ];
    }
    
    public function compareCheckpoints($label1, $label2) {
        if (!isset($this->checkpoints[$label1]) || !isset($this->checkpoints[$label2])) {
            return "指定されたチェックポイントが存在しません。";
        }
        
        $start = $this->checkpoints[$label1];
        $end = $this->checkpoints[$label2];
        
        $user_time = ($end['usage']['ru_utime.tv_sec'] - $start['usage']['ru_utime.tv_sec']) +
                     ($end['usage']['ru_utime.tv_usec'] - $start['usage']['ru_utime.tv_usec']) / 1000000;
        
        $system_time = ($end['usage']['ru_stime.tv_sec'] - $start['usage']['ru_stime.tv_sec']) +
                       ($end['usage']['ru_stime.tv_usec'] - $start['usage']['ru_stime.tv_usec']) / 1000000;
        
        $report = "=== {$label1} → {$label2} ===\n";
        $report .= "経過時間: " . ($end['time'] - $start['time']) . "秒\n";
        $report .= "CPU時間(ユーザー): {$user_time}秒\n";
        $report .= "CPU時間(システム): {$system_time}秒\n";
        $report .= "CPU時間(合計): " . ($user_time + $system_time) . "秒\n";
        $report .= "ページフォルト増加: " . 
                   ($end['usage']['ru_minflt'] - $start['usage']['ru_minflt']) . "\n";
        $report .= "I/O操作増加(入力): " . 
                   ($end['usage']['ru_inblock'] - $start['usage']['ru_inblock']) . "\n";
        $report .= "I/O操作増加(出力): " . 
                   ($end['usage']['ru_oublock'] - $start['usage']['ru_oublock']) . "\n";
        
        return $report;
    }
    
    public function generateFullReport() {
        $report = "=== パフォーマンスレポート ===\n";
        
        $checkpointLabels = array_keys($this->checkpoints);
        $firstLabel = $checkpointLabels[0];
        $lastLabel = end($checkpointLabels);
        
        $report .= $this->compareCheckpoints($firstLabel, $lastLabel);
        $report .= "\n=== 詳細チェックポイント分析 ===\n";
        
        for ($i = 0; $i < count($checkpointLabels) - 1; $i++) {
            $report .= $this->compareCheckpoints($checkpointLabels[$i], $checkpointLabels[$i + 1]);
            $report .= "\n";
        }
        
        return $report;
    }
}

使用例:

$monitor = new PerformanceMonitor();

// データベース操作
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
$monitor->addCheckpoint('DB接続');

$result = $db->query('SELECT * FROM large_table');
$data = $result->fetchAll(PDO::FETCH_ASSOC);
$monitor->addCheckpoint('データ取得');

// データ処理
$processed = array_map(function($row) {
    // 何らかの処理
    return $row;
}, $data);
$monitor->addCheckpoint('データ処理');

// レポート生成
echo $monitor->generateFullReport();

プラットフォーム依存性と注意点

getrusage()関数はUNIX系OSに依存しているため、以下の点に注意が必要です:

  1. Windows非対応: Windows環境では、この関数は利用できないか、制限された情報しか返さない場合があります。
  2. プラットフォームごとの違い: Linux、macOS、FreeBSDなど、異なるUNIX系OSでは返される情報やその精度に違いがある場合があります。
  3. 値の単位: 一部の値(特にru_maxrss)は、プラットフォームによってキロバイト、バイト、ページ単位など異なる単位で報告されることがあります。

Linuxでの例:

// Linuxでは通常ru_maxrssはキロバイト単位
$usage = getrusage();
echo "メモリ使用量: " . ($usage['ru_maxrss']) . " KB\n";

macOSでの例:

// macOSでは通常ru_maxrssはバイト単位
$usage = getrusage();
echo "メモリ使用量: " . ($usage['ru_maxrss'] / 1024 / 1024) . " MB\n";

応用:アプリケーションパフォーマンスの監視システム

大規模なアプリケーションでは、getrusage()を使って様々な操作のパフォーマンスを継続的に監視することができます:

class ApplicationPerformanceMonitor {
    private static $instance;
    private $operations = [];
    private $thresholds = [
        'execution_time' => 1.0, // 1秒以上は警告
        'memory_usage' => 10 * 1024 * 1024, // 10MB以上は警告
    ];
    
    private function __construct() {
        // シングルトンパターン
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function startOperation($name) {
        $this->operations[$name]['start'] = [
            'time' => microtime(true),
            'usage' => getrusage()
        ];
    }
    
    public function endOperation($name) {
        if (!isset($this->operations[$name]['start'])) {
            return false;
        }
        
        $start = $this->operations[$name]['start'];
        $end = [
            'time' => microtime(true),
            'usage' => getrusage()
        ];
        
        // 計算
        $execution_time = $end['time'] - $start['time'];
        $user_time = ($end['usage']['ru_utime.tv_sec'] - $start['usage']['ru_utime.tv_sec']) +
                     ($end['usage']['ru_utime.tv_usec'] - $start['usage']['ru_utime.tv_usec']) / 1000000;
        $system_time = ($end['usage']['ru_stime.tv_sec'] - $start['usage']['ru_stime.tv_sec']) +
                       ($end['usage']['ru_stime.tv_usec'] - $start['usage']['ru_stime.tv_usec']) / 1000000;
        
        // 結果格納
        $this->operations[$name]['metrics'] = [
            'execution_time' => $execution_time,
            'user_time' => $user_time,
            'system_time' => $system_time,
            'total_cpu_time' => $user_time + $system_time,
            'cpu_utilization' => ($user_time + $system_time) / $execution_time * 100,
            'memory_delta' => $end['usage']['ru_maxrss'] - $start['usage']['ru_maxrss'],
            'io_operations' => [
                'in' => $end['usage']['ru_inblock'] - $start['usage']['ru_inblock'],
                'out' => $end['usage']['ru_oublock'] - $start['usage']['ru_oublock']
            ]
        ];
        
        // 閾値チェック
        if ($execution_time > $this->thresholds['execution_time']) {
            error_log("パフォーマンス警告: 操作 '{$name}' の実行時間が {$execution_time} 秒でした");
        }
        
        return $this->operations[$name]['metrics'];
    }
    
    public function getMetrics($name = null) {
        if ($name !== null) {
            return isset($this->operations[$name]['metrics']) ? $this->operations[$name]['metrics'] : null;
        }
        
        $result = [];
        foreach ($this->operations as $opName => $data) {
            if (isset($data['metrics'])) {
                $result[$opName] = $data['metrics'];
            }
        }
        return $result;
    }
    
    public function logMetricsToFile($filename) {
        $metrics = $this->getMetrics();
        $data = date('Y-m-d H:i:s') . " - パフォーマンスログ\n";
        
        foreach ($metrics as $operation => $values) {
            $data .= "操作: {$operation}\n";
            $data .= "  実行時間: {$values['execution_time']} 秒\n";
            $data .= "  CPU時間: {$values['total_cpu_time']} 秒 (使用率: {$values['cpu_utilization']}%)\n";
            $data .= "  メモリ変化: {$values['memory_delta']} KB\n";
            $data .= "  I/O操作: 入力 {$values['io_operations']['in']}, 出力 {$values['io_operations']['out']}\n";
        }
        
        file_put_contents($filename, $data, FILE_APPEND);
    }
}

使用例:

// グローバルモニターを取得
$monitor = ApplicationPerformanceMonitor::getInstance();

// データベース操作の監視
$monitor->startOperation('database_query');
// データベース操作...
$monitor->endOperation('database_query');

// ファイル処理の監視
$monitor->startOperation('file_processing');
// ファイル処理...
$monitor->endOperation('file_processing');

// APIリクエストの監視
$monitor->startOperation('api_request');
// APIリクエスト...
$monitor->endOperation('api_request');

// ログに記録
$monitor->logMetricsToFile('/var/log/app_performance.log');

// メトリクスを取得して表示
$metrics = $monitor->getMetrics();
foreach ($metrics as $operation => $data) {
    echo "{$operation}: {$data['execution_time']}秒 (CPU: {$data['cpu_utilization']}%)\n";
}

バージョン間の違いと代替手段

PHPバージョン対応

getrusage()関数は、PHP 4.0.0以降で利用可能です。ただし、すべてのプラットフォームでサポートされているわけではありません。

代替手段

getrusage()が利用できない環境や、より詳細な情報が必要な場合は、以下の代替手段を検討できます:

  1. memory_get_usage() / memory_get_peak_usage(): メモリ使用量の監視に特化
$start_memory = memory_get_usage();
// コード実行
$end_memory = memory_get_usage();
echo "メモリ使用増加: " . ($end_memory - $start_memory) . " バイト\n";
  1. microtime(): より精度の高い時間測定
$start_time = microtime(true);
// コード実行
$end_time = microtime(true);
echo "実行時間: " . ($end_time - $start_time) . " 秒\n";
  1. proc_open(): より詳細なプロセス情報の取得(Linuxのみ)
function getProcStatus($pid) {
    $status = file_get_contents("/proc/{$pid}/status");
    $lines = explode("\n", $status);
    $result = [];
    
    foreach ($lines as $line) {
        if (preg_match('/^([^:]+):\s+(.+)$/', $line, $matches)) {
            $result[$matches[1]] = $matches[2];
        }
    }
    
    return $result;
}

$pid = getmypid();
$status = getProcStatus($pid);
echo "VmRSS (実メモリ使用量): {$status['VmRSS']}\n";
echo "VmSize (仮想メモリサイズ): {$status['VmSize']}\n";

まとめ

getrusage()関数は、PHPアプリケーションのパフォーマンスとリソース使用状況を詳細に監視するための強力なツールです。特に以下のような場面で役立ちます:

  1. パフォーマンスボトルネックの特定: CPU使用時間やI/O操作数を測定して、どの処理が遅いのかを特定
  2. メモリリークの検出: 定期的なメモリ使用量のチェックによる異常検出
  3. リソース使用の最適化: 様々な実装アプローチのベンチマーキング
  4. システム負荷の監視: 長時間実行されるプロセスやデーモンの監視

ただし、プラットフォーム依存性があるため、クロスプラットフォームのアプリケーションでは、適切な代替手段と組み合わせて使用することをお勧めします。

getrusage()を活用することで、アプリケーションのパフォーマンスと信頼性を向上させ、ユーザー体験を最適化することができます。皆さんのプロジェクトでぜひ試してみてください!

タイトルとURLをコピーしました