如何获取单个Laravel应用实例的RAM与CPU资源占用情况
问题
我需要获取指定Laravel应用的RAM、CPU资源占用数据,但当前编写的代码仅能展示整个服务器系统的整体资源使用情况,无法定位到单个应用的资源消耗。
需要实现仅统计单个Laravel应用的RAM/CPU资源占用,效果类似Windows任务管理器按程序展示CPU/RAM占用的逻辑,面向Web应用场景,优先Laravel原生方案,通用原生PHP实现方案也可参考。
当前已编写的实现代码如下:
现有实现代码
路由文件 web.php
... Route::get('/details', function () { //RAM usage $free = shell_exec('free'); $free = (string) trim($free); $free_arr = explode("\n", $free); $mem = explode(" ", $free_arr[1]); $mem = array_filter($mem); $mem = array_merge($mem); $usedmem = $mem[2]; $usedmemInGB = number_format($usedmem / 1048576, 2) . ' GB'; $memory1 = $mem[2] / $mem[1] * 100; $memory = round($memory1) . '%'; $fh = fopen('/proc/meminfo', 'r'); $mem = 0; while ($line = fgets($fh)) { $pieces = array(); if (preg_match('/^MemTotal:\s+(\d+)\skB$/', $line, $pieces)) { $mem = $pieces[1]; break; } } fclose($fh); $totalram = number_format($mem / 1048576, 2) . ' GB'; //cpu usage $cpu_load = sys_getloadavg(); $load = $cpu_load[0] . '% / 100%'; return view('details',compact('memory','totalram','usedmemInGB','load')); }); ...
视图文件 details.blade.php
<html> <head> <link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="col-sm-6 col-md-3"> <h2 class="no-margin text-semibold">Current RAM Usage</h2> <div class="progress progress-micro mb-10"> <div class="progress-bar bg-indigo-400" style="width: {{$memory}}"> <span class="sr-only">{{$memory}}</span> </div> </div> <span class="pull-right">{{$usedmemInGB}} / {{$totalram}} ({{$memory}})</span> </div> <div class="col-sm-6 col-md-3"> <h2 class="no-margin text-semibold">Current CPU Usage</h2> <div class="progress progress-micro mb-10"> <div class="progress-bar bg-indigo-400" style="width: {{$load}}"> <span class="sr-only">{{$load}}</span> </div> </div> <span class="pull-right">{{$load}}</span> </div> </body> </html>
解决方案
Laravel没有内置直接统计单应用资源占用的API,本质上单个Web应用的资源占用,是所有归属该应用的PHP进程(FPM工作进程、CLI队列/定时任务进程)的资源总和,用原生PHP读取Linux系统/proc目录下的进程数据就能实现,不需要安装额外扩展。
首先要修正原有代码的逻辑错误:sys_getloadavg()返回的是系统1/5/15分钟的平均负载,不是实时CPU占用率,计算逻辑本身不成立。
实现逻辑
- 确定应用进程匹配规则:优先匹配PHP-FPM专属进程池(推荐给每个Laravel应用单独配置FPM池,识别准确率最高),也可以通过进程命令行是否包含当前应用根路径匹配,覆盖FPM+CLI所有进程
- 读取系统总内存、CPU核心数,用于后续占比换算
- 遍历系统所有运行中的进程,筛选出属于当前应用的进程,累加每个进程的物理内存占用(
VmRSS值,即实际占用的物理内存,不含交换分区) - 间隔100ms两次读取目标进程的CPU时间片,计算实时CPU占用率,避免平均负载的延迟问题
可直接复用的代码
资源统计方法
可以把这个方法放到路由文件、控制器公共文件或者自定义辅助函数里:
function getAppResourceUsage(string $appIdentify, bool $matchFpmPool = false): array { $totalMemoryKb = 0; $totalCpuTickDiff = 0; $processCount = 0; // 获取系统总内存 $totalMemKb = 0; if (is_file('/proc/meminfo')) { foreach (file('/proc/meminfo') as $line) { if (str_starts_with(trim($line), 'MemTotal:')) { preg_match('/(\d+)\skB/', $line, $match); $totalMemKb = (int)$match[1]; break; } } } // 获取CPU核心数 $cpuCores = 1; if (is_file('/proc/cpuinfo')) { $cpuCores = count(array_filter( file('/proc/cpuinfo'), fn($line) => str_starts_with(trim($line), 'processor') )); } // 第一次遍历进程,匹配目标进程、记录内存和第一次CPU时间 $pidCpuTickMap = []; foreach (glob('/proc/[0-9]*') as $procPath) { $pid = basename($procPath); // 跳过无效进程目录 if (!is_file("$procPath/cmdline") || !is_file("$procPath/stat") || !is_file("$procPath/status")) { continue; } // 读取进程命令行做匹配 $cmdline = str_replace("\0", ' ', trim(file_get_contents("$procPath/cmdline"))); $isMatch = $matchFpmPool ? str_contains($cmdline, "php-fpm: pool $appIdentify") : (str_contains($cmdline, 'php') && str_contains($cmdline, $appIdentify)); if (!$isMatch) { continue; } $processCount++; // 累加物理内存占用 foreach (file("$procPath/status") as $statusLine) { if (str_starts_with(trim($statusLine), 'VmRSS:')) { preg_match('/(\d+)\skB/', $statusLine, $memMatch); $totalMemoryKb += (int)$memMatch[1]; break; } } // 记录第一次CPU时间(用户态+内核态) $statArr = explode(' ', trim(file_get_contents("$procPath/stat"))); $pidCpuTickMap[$pid] = (int)$statArr[13] + (int)$statArr[14]; } // 间隔100ms取第二次CPU快照 usleep(100000); // Linux默认时钟频率为100Hz,即每10ms一个时钟滴答,100ms对应10个滴答 $intervalTick = 10; foreach ($pidCpuTickMap as $pid => $firstTick) { $procStatPath = "/proc/$pid/stat"; if (!is_file($procStatPath)) { continue; } $statArr = explode(' ', trim(file_get_contents($procStatPath))); $secondTick = (int)$statArr[13] + (int)$statArr[14]; $totalCpuTickDiff += $secondTick - $firstTick; } // 换算成百分比 $cpuPercent = round(($totalCpuTickDiff / $intervalTick / $cpuCores) * 100, 2); $memPercent = $totalMemKb ? round($totalMemoryKb / $totalMemKb * 100, 2) : 0; return [ 'process_count' => $processCount, 'memory_percent' => $memPercent . '%', 'memory_used' => number_format($totalMemoryKb / 1024 / 1024, 2) . ' GB', 'memory_total' => number_format($totalMemKb / 1024 / 1024, 2) . ' GB', 'cpu_percent' => min($cpuPercent, 100) . '%' ]; }
替换原有路由逻辑
Route::get('/details', function () { // 用应用根路径做匹配标识,自动覆盖FPM+CLI进程 // 如果配置了专属FPM池,第二个参数传true,第一个参数传FPM池名即可 $resource = getAppResourceUsage(base_path()); return view('details', [ 'memory' => $resource['memory_percent'], 'usedmemInGB' => $resource['memory_used'], 'totalram' => $resource['memory_total'], 'load' => $resource['cpu_percent'] ]); });
视图文件不需要做任何修改,就能直接展示单应用的资源占用数据。
注意事项
- 以上代码仅支持Linux环境,Windows环境需要改用
tasklist命令做进程匹配 - 读取
/proc目录需要PHP进程有对应目录的读取权限,如果开启了open_basedir限制需要先放开 - 两次CPU快照的间隔建议控制在100-200ms,太长会拖慢页面响应速度,太短会导致计算误差大
- 用FPM池匹配的准确率高于路径匹配,可以避免统计到其他同名路径下的PHP进程
- 统计结果会自动包含当前应用下运行的队列进程、定时任务进程,不需要额外配置
内容的提问来源于stack exchange,提问作者DistantDigit
相关产品推荐
相关产品推荐

