こんにちは、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に依存しているため、以下の点に注意が必要です:
- Windows非対応: Windows環境では、この関数は利用できないか、制限された情報しか返さない場合があります。
- プラットフォームごとの違い: Linux、macOS、FreeBSDなど、異なるUNIX系OSでは返される情報やその精度に違いがある場合があります。
- 値の単位: 一部の値(特に
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()
が利用できない環境や、より詳細な情報が必要な場合は、以下の代替手段を検討できます:
- memory_get_usage() / memory_get_peak_usage(): メモリ使用量の監視に特化
$start_memory = memory_get_usage();
// コード実行
$end_memory = memory_get_usage();
echo "メモリ使用増加: " . ($end_memory - $start_memory) . " バイト\n";
- microtime(): より精度の高い時間測定
$start_time = microtime(true);
// コード実行
$end_time = microtime(true);
echo "実行時間: " . ($end_time - $start_time) . " 秒\n";
- 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アプリケーションのパフォーマンスとリソース使用状況を詳細に監視するための強力なツールです。特に以下のような場面で役立ちます:
- パフォーマンスボトルネックの特定: CPU使用時間やI/O操作数を測定して、どの処理が遅いのかを特定
- メモリリークの検出: 定期的なメモリ使用量のチェックによる異常検出
- リソース使用の最適化: 様々な実装アプローチのベンチマーキング
- システム負荷の監視: 長時間実行されるプロセスやデーモンの監視
ただし、プラットフォーム依存性があるため、クロスプラットフォームのアプリケーションでは、適切な代替手段と組み合わせて使用することをお勧めします。
getrusage()
を活用することで、アプリケーションのパフォーマンスと信頼性を向上させ、ユーザー体験を最適化することができます。皆さんのプロジェクトでぜひ試してみてください!