[PHP]sys_getloadavg完全解説|システムの負荷状況を取得してサーバー監視に活用する方法

PHP

はじめに

サーバーの健全性を判断する重要な指標のひとつが**システムロードアベレージ(load average)**です。「直近1分・5分・15分の平均的な処理待ちプロセス数」を表すこの値を、PHPから直接取得できるのが sys_getloadavg() です。

ヘルスチェックエンドポイント・負荷に応じた処理の調整(スロットリング)・監視ダッシュボードなど、サーバーサイドアプリケーションの安定運用に役立つ関数です。ただしWindowsでは使えないなどプラットフォーム依存の制約があるため、本記事ではその点も含めて詳しく解説します。


関数の基本情報

項目内容
関数名sys_getloadavg()
利用可能バージョンPHP 5.1.3以降
所属システム関数(Misc Functions)
戻り値array|false(3要素の配列、取得失敗時はfalse
拡張機能不要(コア関数)
対応OSLinux・macOS・BSD系など Unix系OSのみ(Windowsは未対応

シグネチャ

sys_getloadavg(): array|false

パラメータ

なし。

戻り値の形式

[
    0 => 1.23,  // 直近1分の平均ロード
    1 => 0.98,  // 直近5分の平均ロード
    2 => 0.75,  // 直近15分の平均ロード
]
インデックス意味
[0]直近1分間の平均ロード
[1]直近5分間の平均ロード
[2]直近15分間の平均ロード

ロードアベレージとは: CPUを使用中、または使用待ち(I/O待ちを含む)のプロセス数の平均値です。値がCPUコア数を超えると、システムに処理待ちが発生していることを示します。


Windowsでの動作

$load = sys_getloadavg();

if ($load === false) {
    echo "この環境ではロードアベレージを取得できません(Windowsなど)\n";
}

Windowsには「ロードアベレージ」という概念自体が存在しないため、Windows環境では常に false が返ります。クロスプラットフォーム対応のコードでは必ずこの判定が必要です。


実践サンプル集(PHP 8.x対応)

サンプル1:基本的な取得と表示

<?php
declare(strict_types=1);

$load = sys_getloadavg();

if ($load === false) {
    echo "ロードアベレージを取得できません(この環境では非対応)\n";
} else {
    [$load1, $load5, $load15] = $load;

    printf("1分平均:  %.2f\n", $load1);
    printf("5分平均:  %.2f\n", $load5);
    printf("15分平均: %.2f\n", $load15);

    // CPUコア数と比較
    $cpuCount = (int) shell_exec('nproc 2>/dev/null') ?: 1;
    echo "\nCPUコア数: {$cpuCount}\n";

    $ratio = round($load1 / $cpuCount, 2);
    echo "コアあたりの負荷率(1分): {$ratio}\n";

    if ($ratio > 1.0) {
        echo "⚠️  CPUコア数を超える負荷がかかっています\n";
    } else {
        echo "✅ 負荷は正常範囲内です\n";
    }
}

実行結果(Linux環境の例):

1分平均:  0.45
5分平均:  0.62
15分平均: 0.71

CPUコア数: 4
コアあたりの負荷率(1分): 0.11
✅ 負荷は正常範囲内です

解説: ロードアベレージは絶対値だけでは判断しづらいため、CPUコア数で割った「コアあたりの負荷率」に変換すると解釈しやすくなります。一般的に1.0を超えると処理待ちが発生している状態です。


サンプル2:ヘルスチェックエンドポイントへの組み込み

Webアプリケーションの /health エンドポイントでロード状況を返すパターンです。

<?php
declare(strict_types=1);

class HealthChecker
{
    public function __construct(
        private readonly float $warningThreshold  = 0.8,
        private readonly float $criticalThreshold = 1.5,
    ) {}

    public function check(): array
    {
        $load = sys_getloadavg();

        if ($load === false) {
            return [
                'status'  => 'unknown',
                'message' => 'この環境ではロードアベレージを取得できません',
            ];
        }

        [$load1, $load5, $load15] = $load;
        $cpuCount = $this->getCpuCount();
        $ratio1   = $cpuCount > 0 ? $load1 / $cpuCount : 0;

        $status = match (true) {
            $ratio1 >= $this->criticalThreshold => 'critical',
            $ratio1 >= $this->warningThreshold   => 'warning',
            default                              => 'healthy',
        };

        return [
            'status'      => $status,
            'load'        => [
                '1min'  => round($load1, 2),
                '5min'  => round($load5, 2),
                '15min' => round($load15, 2),
            ],
            'cpu_count'   => $cpuCount,
            'load_ratio'  => round($ratio1, 2),
            'timestamp'   => date('c'),
        ];
    }

    private function getCpuCount(): int
    {
        if (is_readable('/proc/cpuinfo')) {
            $cpuinfo = file_get_contents('/proc/cpuinfo');
            return max(1, substr_count($cpuinfo ?: '', 'processor'));
        }

        $nproc = trim(shell_exec('nproc 2>/dev/null') ?? '');
        return $nproc !== '' ? (int)$nproc : 1;
    }

    public function toHttpResponse(): array
    {
        $result = $this->check();

        $httpStatus = match ($result['status']) {
            'critical' => 503, // Service Unavailable
            'warning'  => 200, // 警告だが稼働中
            'healthy'  => 200,
            default    => 200,
        };

        return ['http_status' => $httpStatus, 'body' => $result];
    }
}

// --- 使用例 ---
$checker = new HealthChecker(warningThreshold: 0.7, criticalThreshold: 1.2);
$response = $checker->toHttpResponse();

echo "HTTP Status: {$response['http_status']}\n";
echo json_encode($response['body'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";

実行結果(例):

HTTP Status: 200
{
    "status": "healthy",
    "load": {
        "1min": 0.45,
        "5min": 0.62,
        "15min": 0.71
    },
    "cpu_count": 4,
    "load_ratio": 0.11,
    "timestamp": "2026-06-24T10:30:00+09:00"
}

解説: critical 状態のときに HTTP 503 を返すことで、ロードバランサーやオーケストレーター(Kubernetesなど)に「このインスタンスへのリクエストを一時的に減らすべき」と伝えられます。


サンプル3:負荷に応じた処理のスロットリング

高負荷時に重い処理(バッチ・レポート生成など)を自動的に制限するパターンです。

<?php
declare(strict_types=1);

class LoadAwareThrottler
{
    public function __construct(
        private readonly float $maxRatio = 1.0,
        private readonly int $cpuCount = 0,
    ) {}

    /**
     * 現在の負荷状況に基づいて処理を実行してよいか判定
     */
    public function shouldThrottle(): bool
    {
        $load = sys_getloadavg();
        if ($load === false) {
            return false; // 判定できない場合はスロットリングしない
        }

        $cpuCount = $this->cpuCount > 0 ? $this->cpuCount : $this->detectCpuCount();
        $ratio    = $load[0] / max(1, $cpuCount);

        return $ratio >= $this->maxRatio;
    }

    /**
     * 負荷状況に応じて並列度を動的に決定
     */
    public function recommendedConcurrency(int $maxConcurrency): int
    {
        $load = sys_getloadavg();
        if ($load === false) {
            return $maxConcurrency;
        }

        $cpuCount = $this->cpuCount > 0 ? $this->cpuCount : $this->detectCpuCount();
        $ratio    = $load[0] / max(1, $cpuCount);

        // 負荷が高いほど並列度を下げる
        $factor = match (true) {
            $ratio >= 1.5 => 0.25,
            $ratio >= 1.0 => 0.5,
            $ratio >= 0.7 => 0.75,
            default       => 1.0,
        };

        return max(1, (int) floor($maxConcurrency * $factor));
    }

    private function detectCpuCount(): int
    {
        $nproc = trim(shell_exec('nproc 2>/dev/null') ?? '');
        return $nproc !== '' ? (int)$nproc : 1;
    }
}

// --- 使用例:バッチ処理の実行可否判定 ---
$throttler = new LoadAwareThrottler(maxRatio: 0.8);

function runHeavyBatchJob(LoadAwareThrottler $throttler, array $items): void
{
    if ($throttler->shouldThrottle()) {
        echo "⚠️  サーバー負荷が高いため、バッチ処理を延期します\n";
        return;
    }

    $concurrency = $throttler->recommendedConcurrency(maxConcurrency: 10);
    echo "バッチ処理を実行: 並列度={$concurrency} / 件数=" . count($items) . "\n";

    foreach (array_chunk($items, $concurrency) as $chunkIndex => $chunk) {
        echo "  チャンク{$chunkIndex}: " . count($chunk) . "件処理\n";
    }
}

$items = range(1, 50);
runHeavyBatchJob($throttler, $items);

実行結果(負荷が低い場合の例):

バッチ処理を実行: 並列度=10 / 件数=50
  チャンク0: 10件処理
  チャンク1: 10件処理
  チャンク2: 10件処理
  チャンク3: 10件処理
  チャンク4: 10件処理

解説: ロードアベレージを参照して並列度を動的に調整することで、負荷の高いタイミングでバッチ処理がサーバーに追加負荷をかけることを防げます。CI/CDのワーカーやキュー処理システムでの応用が考えられます。


サンプル4:定期的な負荷ロギングシステム

ロードアベレージを時系列でログに記録し、後で分析できるようにするパターンです。

<?php
declare(strict_types=1);

class LoadLogger
{
    public function __construct(
        private readonly string $logFile
    ) {}

    /**
     * 現在の負荷状況をログに1行追記する
     */
    public function record(): bool
    {
        $load = sys_getloadavg();
        if ($load === false) {
            return false;
        }

        $entry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'load_1'    => round($load[0], 3),
            'load_5'    => round($load[1], 3),
            'load_15'   => round($load[2], 3),
            'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 1),
        ];

        $line = implode(',', $entry) . "\n";
        return file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX) !== false;
    }

    /**
     * ログファイルを解析して統計を出す
     */
    public function analyze(): array
    {
        if (!file_exists($this->logFile)) {
            return ['error' => 'ログファイルが存在しません'];
        }

        $lines  = file($this->logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        $load1s = [];

        foreach ($lines as $line) {
            $parts = explode(',', $line);
            if (count($parts) >= 2 && is_numeric($parts[1])) {
                $load1s[] = (float)$parts[1];
            }
        }

        if (empty($load1s)) {
            return ['error' => '有効なデータがありません'];
        }

        return [
            'count'   => count($load1s),
            'min'     => round(min($load1s), 3),
            'max'     => round(max($load1s), 3),
            'avg'     => round(array_sum($load1s) / count($load1s), 3),
            'latest'  => round(end($load1s), 3),
        ];
    }
}

// --- 使用例 ---
$logFile = sys_get_temp_dir() . '/load_average.log';
$logger  = new LoadLogger($logFile);

// 定期実行を想定(実際にはcronやワーカーループ内で呼ぶ)
for ($i = 0; $i < 5; $i++) {
    $success = $logger->record();
    echo "記録{$i}: " . ($success ? 'OK' : 'NG') . "\n";
    usleep(100_000); // デモ用の短い待機
}

echo "\n--- 分析結果 ---\n";
print_r($logger->analyze());

// クリーンアップ(デモ用)
unlink($logFile);

実行結果:

記録0: OK
記録1: OK
記録2: OK
記録3: OK
記録4: OK

--- 分析結果 ---
Array
(
    [count] => 5
    [min] => 0.42
    [max] => 0.48
    [avg] => 0.451
    [latest] => 0.44
)

解説: CSVライクな形式でログに記録し、後から min/max/avg を計算できます。cron で定期実行すれば、簡易的な負荷モニタリングシステムとして機能します。本格的な監視にはPrometheus等の専用ツールを推奨しますが、軽量な自前実装としては十分実用的です。


サンプル5:クロスプラットフォーム対応のラッパー

Windowsでも動作する(疑似的な代替値を返す)ラッパークラスです。

<?php
declare(strict_types=1);

class PortableLoadAverage
{
    /**
     * @return array{available: bool, load1: float|null, load5: float|null, load15: float|null, platform: string}
     */
    public static function get(): array
    {
        $platform = PHP_OS_FAMILY; // 'Linux', 'Darwin', 'Windows', 'BSD' など

        $load = sys_getloadavg();

        if ($load !== false) {
            return [
                'available' => true,
                'load1'     => $load[0],
                'load5'     => $load[1],
                'load15'    => $load[2],
                'platform'  => $platform,
            ];
        }

        // Windows向けフォールバック:CPU使用率で近似(typeperf等を利用する例)
        if ($platform === 'Windows') {
            $cpuUsage = self::getWindowsCpuUsage();
            return [
                'available' => $cpuUsage !== null,
                'load1'     => $cpuUsage,
                'load5'     => null, // Windowsでは時間平均の取得が難しい
                'load15'    => null,
                'platform'  => $platform,
            ];
        }

        return [
            'available' => false,
            'load1'     => null,
            'load5'     => null,
            'load15'    => null,
            'platform'  => $platform,
        ];
    }

    /**
     * Windows環境でのCPU使用率取得(wmicを利用、利用可否は環境依存)
     */
    private static function getWindowsCpuUsage(): ?float
    {
        if (!function_exists('shell_exec')) {
            return null;
        }

        $output = @shell_exec('wmic cpu get loadpercentage /value');
        if ($output === null) {
            return null;
        }

        if (preg_match('/LoadPercentage=(\d+)/', $output, $matches)) {
            // 0-100の値を0-1スケールに変換(ロードアベレージのイメージに近づける)
            return ((float)$matches[1]) / 100;
        }

        return null;
    }

    public static function describe(): string
    {
        $info = self::get();

        if (!$info['available']) {
            return "負荷情報を取得できません(プラットフォーム: {$info['platform']})";
        }

        if ($info['load5'] === null) {
            // Windowsの簡易CPU使用率
            return sprintf(
                "CPU使用率: %.1f%% (プラットフォーム: %s、簡易値)",
                $info['load1'] * 100,
                $info['platform']
            );
        }

        return sprintf(
            "ロードアベレージ: 1分=%.2f / 5分=%.2f / 15分=%.2f (プラットフォーム: %s)",
            $info['load1'],
            $info['load5'],
            $info['load15'],
            $info['platform']
        );
    }
}

// --- 使用例 ---
echo PortableLoadAverage::describe() . "\n";

print_r(PortableLoadAverage::get());

実行結果(Linux環境の例):

ロードアベレージ: 1分=0.45 / 5分=0.62 / 15分=0.71 (プラットフォーム: Linux)
Array
(
    [available] => 1
    [load1] => 0.45
    [load5] => 0.62
    [load15] => 0.71
    [platform] => Linux
)

解説: PHP_OS_FAMILY でプラットフォームを判定し、Unix系では sys_getloadavg()、Windowsでは wmic コマンドによる簡易的なCPU使用率取得にフォールバックします。完全な互換性はありませんが、クロスプラットフォームなツールで「何らかの負荷指標」を提供したい場合に有効です。


サンプル6:複数サーバーの負荷を集約するダッシュボード用API

複数のアプリケーションサーバーから収集したロードアベレージを集約するパターンです(単一サーバー内のデモ実装)。

<?php
declare(strict_types=1);

class ClusterLoadDashboard
{
    /** @var array<string, array> サーバー名 → 負荷情報 */
    private array $servers = [];

    /**
     * 自サーバーの負荷情報を登録
     */
    public function registerSelf(string $serverName): void
    {
        $load = sys_getloadavg();

        $this->servers[$serverName] = [
            'load'      => $load !== false ? $load : null,
            'hostname'  => gethostname() ?: 'unknown',
            'timestamp' => time(),
        ];
    }

    /**
     * (デモ用)外部サーバーのデータを手動で追加
     * 実際には他サーバーのヘルスチェックAPIをHTTPで叩いて取得する想定
     */
    public function addExternalServer(string $name, array $loadData): void
    {
        $this->servers[$name] = [
            'load'      => $loadData,
            'hostname'  => $name,
            'timestamp' => time(),
        ];
    }

    /**
     * クラスタ全体の統計を計算
     */
    public function clusterStats(): array
    {
        $load1Values = [];

        foreach ($this->servers as $info) {
            if ($info['load'] !== null) {
                $load1Values[] = $info['load'][0];
            }
        }

        if (empty($load1Values)) {
            return ['error' => 'データがありません'];
        }

        return [
            'server_count'    => count($this->servers),
            'reporting_count' => count($load1Values),
            'avg_load1'       => round(array_sum($load1Values) / count($load1Values), 3),
            'max_load1'       => round(max($load1Values), 3),
            'min_load1'       => round(min($load1Values), 3),
            'busiest_server'  => $this->findBusiest(),
        ];
    }

    private function findBusiest(): ?string
    {
        $maxLoad  = -1;
        $busiest  = null;

        foreach ($this->servers as $name => $info) {
            if ($info['load'] !== null && $info['load'][0] > $maxLoad) {
                $maxLoad = $info['load'][0];
                $busiest = $name;
            }
        }

        return $busiest;
    }

    public function render(): void
    {
        echo "=== クラスタ負荷ダッシュボード ===\n";
        foreach ($this->servers as $name => $info) {
            if ($info['load'] === null) {
                echo "  {$name}: データなし\n";
                continue;
            }
            printf(
                "  %-15s 1分=%.2f 5分=%.2f 15分=%.2f\n",
                $name,
                $info['load'][0],
                $info['load'][1],
                $info['load'][2]
            );
        }

        echo "\n--- 集約統計 ---\n";
        print_r($this->clusterStats());
    }
}

// --- 使用例 ---
$dashboard = new ClusterLoadDashboard();

// 自サーバーの実データを登録
$dashboard->registerSelf('web-server-01');

// 他サーバーのデータ(実際はAPI経由で取得)をシミュレート
$dashboard->addExternalServer('web-server-02', [0.85, 0.92, 0.78]);
$dashboard->addExternalServer('web-server-03', [1.45, 1.32, 1.10]); // 高負荷サーバー
$dashboard->addExternalServer('worker-01',     [2.10, 1.95, 1.60]); // 最も高負荷

$dashboard->render();

実行結果(例):

=== クラスタ負荷ダッシュボード ===
  web-server-01   1分=0.45 5分=0.62 15分=0.71
  web-server-02   1分=0.85 5分=0.92 15分=0.78
  web-server-03   1分=1.45 5分=1.32 15分=1.10
  worker-01       1分=2.10 5分=1.95 15分=1.60

--- 集約統計 ---
Array
(
    [server_count] => 4
    [reporting_count] => 4
    [avg_load1] => 1.213
    [busiest_server] => worker-01
    [max_load1] => 2.1
    [min_load1] => 0.45
)

解説: 実際の運用では、各サーバーが自身のヘルスチェックAPI(サンプル2のようなもの)を提供し、ダッシュボードサーバーがweb_fetch等で巡回収集する構成になります。sys_getloadavg() 自体は単一サーバーの値しか返さないため、クラスタ監視には集約レイヤーが必要です。


よくある落とし穴

① Windowsでは常にfalse

// ❌ 戻り値を無条件に配列として扱う
$load = sys_getloadavg();
echo $load[0]; // Windowsでは警告+null

// ✅ false チェックを必ず行う
$load = sys_getloadavg();
if ($load === false) {
    // Windowsまたは取得不可な環境
} else {
    echo $load[0];
}

② コンテナ内では実際のホストの負荷と異なる場合がある

// Dockerコンテナ内の sys_getloadavg() は、
// コンテナ自身のリソース制限ではなく「ホストマシン全体」の負荷を返すことがある
// → コンテナ内のCPU使用率は別途 /sys/fs/cgroup などから取得する必要がある場合がある

③ 値の即時性に過度な期待をしない

// ロードアベレージは「平均値」なので急激な変化には反応が遅れる
// 1分間隔のリクエストスパイクなどはload_avgだけでは検出しづらい
// → 即時性が必要な監視には別の指標(CPU使用率の瞬間値など)を併用する

④ CPUコア数の取得方法に頼り過ぎない

// shell_exec('nproc') はセーフモードや disable_functions で無効化されている場合がある
$cpuCount = shell_exec('nproc 2>/dev/null');
if ($cpuCount === null) {
    // フォールバック:/proc/cpuinfo を読む、または固定値を使う
}

まとめ

ポイント内容
主な用途サーバーの負荷状況の取得・ヘルスチェック・スロットリング判断
戻り値[1分, 5分, 15分]の3要素配列、または取得不可時はfalse
対応OSUnix系のみ(Windowsは常にfalse
解釈の指針CPUコア数で割った比率(負荷率)で判断するのが実用的
活用パターンヘルスチェックAPI・処理のスロットリング・定期ロギング・クラスタ監視
注意点コンテナ内ではホスト全体の値を返す場合がある/即時性には限界がある

sys_getloadavg() は単純な関数ですが、サーバーの状態を素早く把握するための重要な手がかりを提供します。Windows非対応という制約を踏まえつつ、ヘルスチェックや負荷ベースの動的制御に組み込むことで、より安定したアプリケーション運用が実現できます。


PHP 8.x / 執筆時点の最新安定版にて動作確認済み

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