<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Workerman 进程监控</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
color: #333;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.header h1 {
color: #667eea;
margin-bottom: 10px;
}
.status-bar {
display: flex;
gap: 20px;
align-items: center;
}
.status-indicator {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #22c55e;
animation: pulse 2s infinite;
}
.status-dot.disconnected {
background: #ef4444;
animation: none;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.card h3 {
color: #666;
font-size: 14px;
margin-bottom: 10px;
text-transform: uppercase;
}
.card .value {
font-size: 32px;
font-weight: bold;
color: #667eea;
}
.card .unit {
font-size: 18px;
color: #999;
}
.master-process {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.master-process h2 {
color: #667eea;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.master-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: #f8f9fa;
border-radius: 5px;
}
.info-item label {
display: block;
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.info-item .value {
font-size: 18px;
font-weight: bold;
color: #333;
}
.workers-section {
margin-top: 20px;
}
.worker-group {
background: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.worker-group h3 {
color: #667eea;
margin-bottom: 15px;
}
.process-table {
width: 100%;
border-collapse: collapse;
}
.process-table th {
background: #f8f9fa;
padding: 12px;
text-align: left;
font-weight: 600;
color: #666;
border-bottom: 2px solid #e0e0e0;
}
.process-table td {
padding: 12px;
border-bottom: 1px solid #f0f0f0;
}
.process-table tr:hover {
background: #f8f9fa;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e0e0e0;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, #22c55e 0%, #16a34a 100%);
transition: width 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 12px;
font-weight: bold;
}
.progress-bar-fill.warning {
background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
}
.progress-bar-fill.danger {
background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
}
.status-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
}
.status-badge.running {
background: #dcfce7;
color: #16a34a;
}
.status-badge.sleeping {
background: #dbeafe;
color: #2563eb;
}
.last-update {
text-align: center;
color: white;
margin-top: 20px;
font-size: 14px;
}
.no-data {
text-align: center;
padding: 40px;
color: #999;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>⚙️ Workerman 进程监控</h1>
<div class="status-bar">
<div class="status-indicator">
<div class="status-dot" id="statusDot"></div>
<span id="connectionStatus">连接中...</span>
</div>
<span id="lastUpdate"></span>
</div>
</div>
<div class="summary-cards">
<div class="card">
<h3>Worker 进程数</h3>
<div class="value" id="totalWorkers">0</div>
</div>
<div class="card">
<h3>总 CPU 使用率</h3>
<div class="value" id="totalCpu">0<span class="unit">%</span></div>
</div>
<div class="card">
<h3>总内存使用率</h3>
<div class="value" id="totalMemory">0<span class="unit">%</span></div>
</div>
</div>
<div class="master-process" id="masterProcess">
<h2>🔷 主进程</h2>
<div class="master-info" id="masterInfo">
<div class="no-data">等待数据...</div>
</div>
</div>
<div class="workers-section" id="workersSection">
<div class="no-data">等待数据...</div>
</div>
<div class="last-update" id="footerUpdate"></div>
</div>
<script>
let ws = null;
let reconnectTimer = null;
function connect() {
ws = new WebSocket('ws://localhost:8282');
ws.onopen = function() {
console.log('WebSocket 连接成功');
updateConnectionStatus(true);
// 立即请求一次数据
ws.send(JSON.stringify({ action: 'getProcessInfo' }));
};
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
if (message.action === 'processUpdate') {
updateUI(message.data, message.timestamp);
}
};
ws.onclose = function() {
console.log('WebSocket 连接关闭');
updateConnectionStatus(false);
// 5秒后自动重连
reconnectTimer = setTimeout(connect, 5000);
};
ws.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
}
function updateConnectionStatus(connected) {
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('connectionStatus');
if (connected) {
statusDot.classList.remove('disconnected');
statusText.textContent = '已连接';
} else {
statusDot.classList.add('disconnected');
statusText.textContent = '连接断开';
}
}
function updateUI(data, timestamp) {
// 更新汇总数据
document.getElementById('totalWorkers').textContent = data.summary.totalWorkers;
document.getElementById('totalCpu').innerHTML = `${data.summary.totalCpu}<span class="unit">%</span>`;
document.getElementById('totalMemory').innerHTML = `${data.summary.totalMemory}<span class="unit">%</span>`;
document.getElementById('lastUpdate').textContent = `最后更新: ${timestamp}`;
document.getElementById('footerUpdate').textContent = `最后更新时间: ${timestamp}`;
// 更新主进程信息
if (data.master && data.master.pid) {
const masterInfo = document.getElementById('masterInfo');
masterInfo.innerHTML = `
<div class="info-item">
<label>PID</label>
<div class="value">${data.master.pid}</div>
</div>
<div class="info-item">
<label>CPU</label>
<div class="value">${data.master.cpu}%</div>
</div>
<div class="info-item">
<label>内存</label>
<div class="value">${data.master.memory}%</div>
</div>
<div class="info-item">
<label>内存使用</label>
<div class="value">${data.master.memoryMB} MB</div>
</div>
<div class="info-item">
<label>状态</label>
<div class="value">${getStatusBadge(data.master.status)}</div>
</div>
<div class="info-item">
<label>运行时间</label>
<div class="value">${data.master.uptime}</div>
</div>
`;
}
// 更新 Worker 进程信息
const workersSection = document.getElementById('workersSection');
if (data.workers && data.workers.length > 0) {
let html = '';
data.workers.forEach(worker => {
if (worker.processes && worker.processes.length > 0) {
html += `
<div class="worker-group">
<h3>👷 ${worker.name} (${worker.processes.length} 个进程)</h3>
<table class="process-table">
<thead>
<tr>
<th>PID</th>
<th>Worker ID</th>
<th>状态</th>
<th>CPU</th>
<th>内存</th>
<th>内存使用 (MB)</th>
<th>连接数</th>
<th>运行时间</th>
</tr>
</thead>
<tbody>
`;
worker.processes.forEach(process => {
html += `
<tr>
<td><strong>${process.pid}</strong></td>
<td>${process.workerId}</td>
<td>${getStatusBadge(process.status)}</td>
<td>${renderProgressBar(process.cpu, '%')}</td>
<td>${renderProgressBar(process.memory, '%')}</td>
<td>${process.memoryMB} MB</td>
<td>${process.connections}</td>
<td>${process.uptime}</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
`;
}
});
workersSection.innerHTML = html || '<div class="no-data">暂无 Worker 进程数据</div>';
} else {
workersSection.innerHTML = '<div class="no-data">暂无 Worker 进程数据</div>';
}
}
function getStatusBadge(status) {
const statusMap = {
'R': 'running',
'S': 'sleeping',
'D': 'sleeping',
'Z': 'warning',
'T': 'warning'
};
const statusLabel = {
'R': '运行中',
'S': '睡眠',
'D': '不可中断',
'Z': '僵尸',
'T': '停止'
};
const firstChar = status.charAt(0);
const className = statusMap[firstChar] || 'sleeping';
const label = statusLabel[firstChar] || status;
return `<span class="status-badge ${className}">${label}</span>`;
}
function renderProgressBar(value, unit) {
let className = '';
if (value > 80) className = 'danger';
else if (value > 50) className = 'warning';
return `
<div class="progress-bar">
<div class="progress-bar-fill ${className}" style="width: ${Math.min(value, 100)}%">
${value}${unit}
</div>
</div>
`;
}
// 页面加载时连接 WebSocket
connect();
// 页面关闭时断开连接
window.addEventListener('beforeunload', function() {
if (ws) {
ws.close();
}
if (reconnectTimer) {
clearTimeout(reconnectTimer);
}
});
</script>
</body>
</html>
<?php
/**
* Workerman 进程监控服务
* 通过 WebSocket 实时推送所有 Worker 进程的 CPU 和内存使用情况
*/
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Connection\TcpConnection;
// 创建 WebSocket 服务,用于推送监控数据到前端
$ws_worker = new Worker("websocket://0.0.0.0:8282");
$ws_worker->name = 'ProcessMonitor';
$ws_worker->count = 1;
// 存储所有客户端连接
$ws_worker->connections = [];
// 当 WebSocket 客户端连接时
$ws_worker->onConnect = function(TcpConnection $connection) {
echo "新客户端连接: {$connection->id}\n";
};
// 当收到客户端消息时
$ws_worker->onMessage = function(TcpConnection $connection, $data) {
$message = json_decode($data, true);
if (isset($message['action'])) {
switch ($message['action']) {
case 'ping':
$connection->send(json_encode(['action' => 'pong']));
break;
case 'getProcessInfo':
// 立即发送一次进程信息
$processInfo = collectProcessInfo();
$connection->send(json_encode([
'action' => 'processUpdate',
'data' => $processInfo,
'timestamp' => date('Y-m-d H:i:s')
]));
break;
}
}
};
// 当客户端断开连接时
$ws_worker->onClose = function(TcpConnection $connection) {
echo "客户端断开: {$connection->id}\n";
};
// Worker 启动时的回调
$ws_worker->onWorkerStart = function($worker) {
// 只在第一个进程输出信息
if ($worker->id === 0) {
echo "监控服务启动成功!\n";
echo "WebSocket 地址: ws://0.0.0.0:8282\n";
echo "Web 监控页面: http://localhost:8080/monitor.html\n";
echo "当前进程 PID: " . posix_getpid() . "\n\n";
}
// 每 2 秒收集一次进程信息并推送给所有客户端
Timer::add(2, function() use ($worker) {
// 收集所有进程信息
$processInfo = collectProcessInfo();
// 调试输出
echo "收集到进程数: " . $processInfo['summary']['totalWorkers'] . "\n";
// 推送给所有连接的客户端
$data = json_encode([
'action' => 'processUpdate',
'data' => $processInfo,
'timestamp' => date('Y-m-d H:i:s')
]);
foreach($worker->connections as $connection) {
$connection->send($data);
}
});
};
/**
* 收集所有进程信息
*/
function collectProcessInfo() {
$processInfo = [
'master' => [],
'workers' => [],
'summary' => [
'totalWorkers' => 0,
'totalCpu' => 0,
'totalMemory' => 0
]
];
// 获取当前进程 PID(可能是主进程或子进程)
$currentPid = posix_getpid();
// 查找主进程 PID(通过 ps 命令查找 process_monitor.php 的主进程)
$masterPid = getMasterPid();
if (!$masterPid) {
$masterPid = $currentPid;
}
// 收集主进程信息
$masterInfo = getProcessStats($masterPid);
if ($masterInfo) {
$processInfo['master'] = [
'pid' => $masterPid,
'name' => 'Master Process',
'cpu' => $masterInfo['cpu'],
'memory' => $masterInfo['memory'],
'memoryMB' => $masterInfo['memoryMB'],
'status' => $masterInfo['status'],
'uptime' => $masterInfo['uptime']
];
}
// 获取所有子进程
$childPids = getChildProcesses($masterPid);
// 按 Worker 名称分组
$allWorkers = Worker::getAllWorkers();
$workersByName = [];
foreach($allWorkers as $worker) {
$workersByName[$worker->name] = [
'name' => $worker->name,
'count' => $worker->count,
'processes' => []
];
}
// 收集每个子进程的信息
foreach($childPids as $pid) {
$stats = getProcessStats($pid);
if ($stats) {
// 获取进程的命令行,判断是哪个 Worker
$workerName = getWorkerName($pid);
$processData = [
'pid' => $pid,
'workerId' => $pid,
'cpu' => $stats['cpu'],
'memory' => $stats['memory'],
'memoryMB' => $stats['memoryMB'],
'status' => $stats['status'],
'connections' => 0,
'uptime' => $stats['uptime']
];
if (isset($workersByName[$workerName])) {
$workersByName[$workerName]['processes'][] = $processData;
} else {
// 未知 Worker,创建一个分组
if (!isset($workersByName['Unknown'])) {
$workersByName['Unknown'] = [
'name' => 'Unknown Worker',
'count' => 0,
'processes' => []
];
}
$workersByName['Unknown']['processes'][] = $processData;
}
$processInfo['summary']['totalCpu'] += $stats['cpu'];
$processInfo['summary']['totalMemory'] += $stats['memory'];
$processInfo['summary']['totalWorkers']++;
}
}
$processInfo['workers'] = array_values($workersByName);
// 四舍五入总计
$processInfo['summary']['totalCpu'] = round($processInfo['summary']['totalCpu'], 2);
$processInfo['summary']['totalMemory'] = round($processInfo['summary']['totalMemory'], 2);
return $processInfo;
}
/**
* 获取主进程 PID
*/
function getMasterPid() {
// 查找 process_monitor.php 的主进程
$cmd = "ps aux | grep 'WorkerMan: master process' | grep 'process_monitor.php' | grep -v grep | awk '{print $2}' | head -1";
$output = shell_exec($cmd);
return $output ? (int)trim($output) : null;
}
/**
* 获取指定进程的所有子进程(递归)
*/
function getChildProcesses($parentPid) {
$allPids = [];
// 直接子进程
$cmd = sprintf("ps --ppid %d -o pid --no-headers 2>/dev/null", $parentPid);
$output = shell_exec($cmd);
if (empty($output)) {
return [];
}
$pids = array_filter(array_map('trim', explode("\n", $output)));
$pids = array_map('intval', $pids);
foreach ($pids as $pid) {
if ($pid > 0) {
$allPids[] = $pid;
// 递归获取孙子进程
$grandChildren = getChildProcesses($pid);
$allPids = array_merge($allPids, $grandChildren);
}
}
return $allPids;
}
/**
* 获取进程对应的 Worker 名称
*/
function getWorkerName($pid) {
$cmd = sprintf("ps -p %d -o cmd --no-headers 2>/dev/null", $pid);
$output = shell_exec($cmd);
if (empty($output)) {
return 'Unknown';
}
// 从命令行中提取 Worker 名称
if (preg_match('/\[(\w+)\]/', $output, $matches)) {
return $matches[1];
}
// 根据关键字判断
if (strpos($output, 'ProcessMonitor') !== false) {
return 'ProcessMonitor';
} elseif (strpos($output, 'HttpServer') !== false) {
return 'HttpServer';
}
return 'Worker';
}
/**
* 获取指定进程的统计信息
* @param int $pid 进程 ID
* @return array|null
*/
function getProcessStats($pid) {
// 检查进程是否存在
if (!posix_kill($pid, 0)) {
return null;
}
// 使用 ps 命令获取进程信息
$cmd = sprintf("ps -p %d -o pid,%%cpu,%%mem,rss,stat,etime --no-headers 2>/dev/null", $pid);
$output = shell_exec($cmd);
if (empty($output)) {
return null;
}
$parts = preg_split('/\s+/', trim($output));
if (count($parts) < 6) {
return null;
}
return [
'pid' => (int)$parts[0],
'cpu' => (float)$parts[1],
'memory' => (float)$parts[2],
'memoryMB' => round((float)$parts[3] / 1024, 2),
'status' => $parts[4],
'uptime' => $parts[5]
];
}
// 创建一个简单的 HTTP 服务,用于提供监控页面
$http_worker = new Worker("http://0.0.0.0:8080");
$http_worker->name = 'HttpServer';
$http_worker->count = 1;
$http_worker->onMessage = function(TcpConnection $connection, $request) {
$path = $request->path();
if ($path === '/' || $path === '/monitor.html') {
$htmlFile = __DIR__ . '/monitor.html';
if (file_exists($htmlFile)) {
$connection->send(file_get_contents($htmlFile));
} else {
$connection->send("HTTP/1.1 404 Not Found\r\n\r\nmonitor.html not found");
}
} else {
$connection->send("HTTP/1.1 404 Not Found\r\n\r\n404 Not Found");
}
};
// 运行所有 Worker
Worker::runAll();
解释:
📌 整体功能
通过 WebSocket 实时监控 Workerman 所有 Worker 子进程的 CPU 和内存使用情况,并在浏览器中可视化展示。
🔧 代码结构解析
基础配置(第 7-16 行)
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
WebSocket 服务(第 14-82 行)
$ws_worker = new Worker("websocket://0.0.0.0:8282");
$ws_worker->name = 'ProcessMonitor';
$ws_worker->count = 1;
作用: 创建 WebSocket 服务器,监听 8282 端口
关键回调函数:
核心函数说明
collectProcessInfo() - 收集进程信息(第 87-181 行)
function collectProcessInfo() {
// 返回结构:
// - master: 主进程信息
// - workers: 所有 Worker 子进程信息
// - summary: 总计统计
}
执行流程:
获取当前进程 PID
查找主进程 PID(通过 getMasterPid())
收集主进程的 CPU、内存等信息
查找所有子进程(通过 getChildProcesses())
遍历每个子进程,获取详细信息
按 Worker 名称分组
计算总 CPU、总内存使用率
getMasterPid() - 获取主进程 PID(第 186-191 行)
$cmd = "ps aux | grep 'WorkerMan: master process' | grep 'process_monitor.php' | grep -v grep | awk '{print $2}' | head -1";
作用: 通过 ps 命令查找 Workerman 主进程的 PID
getChildProcesses() - 获取所有子进程(第 196-220 行)
$cmd = sprintf("ps --ppid %d -o pid --no-headers", $parentPid);
作用:
getWorkerName() - 识别 Worker 名称(第 225-246 行)
if (preg_match('/[(\w+)]/', $output, $matches)) {
return $matches[1];
}
作用:
getProcessStats() - 获取进程统计数据(第 253-281 行)
$cmd = sprintf("ps -p %d -o pid,%%cpu,%%mem,rss,stat,etime --no-headers", $pid);
返回数据:
HTTP 服务(第 284-301 行)
$http_worker = new Worker("http://0.0.0.0:8080");
$http_worker->name = 'HttpServer';
作用: 提供 HTTP 服务,访问 http://localhost:8080/monitor.html 返回监控页面
启动服务(第 304 行)
Worker::runAll();
作用: 启动所有定义的 Worker(WebSocket 服务 + HTTP 服务)
🎯 工作流程总结
启动服务 → 创建主进程和 2 个子进程(ProcessMonitor、HttpServer)
定时采集 → 每 2 秒调用 collectProcessInfo() 收集数据
实时推送 → 通过 WebSocket 推送数据给所有连接的浏览器客户端
浏览器展示 → 前端接收数据并实时刷新显示
📊 监控的数据
这就是这个监控系统的完整工作原理!
运行:php process_monitor.php start