workerman=Workerman 进程监控

http://localhost:8080/monitor.html

<!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>

process_monitor.php

<?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 和内存使用情况,并在浏览器中可视化展示。


🔧 代码结构解析

  1. 基础配置(第 7-16 行)

    require_once __DIR__ . '/vendor/autoload.php';
    use Workerman\Worker;

    • 加载 Workerman 框架
    • 导入必要的类
  2. WebSocket 服务(第 14-82 行)

    $ws_worker = new Worker("websocket://0.0.0.0:8282");
    $ws_worker->name = 'ProcessMonitor';
    $ws_worker->count = 1;

    作用: 创建 WebSocket 服务器,监听 8282 端口

    关键回调函数:

    • onConnect(第 22-24 行):客户端连接时触发
    • onMessage(第 27-46 行):接收客户端消息
    • ping:心跳检测
    • getProcessInfo:立即返回进程信息
    • onClose(第 49-51 行):客户端断开时触发
    • onWorkerStart(第 54-82 行):Worker 启动时执行
    • 输出服务信息
    • 设置定时器,每 2 秒采集并推送进程数据

  3. 核心函数说明

    collectProcessInfo() - 收集进程信息(第 87-181 行)

    function collectProcessInfo() {
    // 返回结构:
    // - master: 主进程信息
    // - workers: 所有 Worker 子进程信息
    // - summary: 总计统计
    }

    执行流程:

  4. 获取当前进程 PID

  5. 查找主进程 PID(通过 getMasterPid())

  6. 收集主进程的 CPU、内存等信息

  7. 查找所有子进程(通过 getChildProcesses())

  8. 遍历每个子进程,获取详细信息

  9. 按 Worker 名称分组

  10. 计算总 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];
    }
    作用:

    • 通过进程命令行识别 Worker 类型
    • 匹配 [ProcessMonitor]、[HttpServer] 等标识
    • 用于分组显示

    getProcessStats() - 获取进程统计数据(第 253-281 行)

    $cmd = sprintf("ps -p %d -o pid,%%cpu,%%mem,rss,stat,etime --no-headers", $pid);
    返回数据:

    • pid: 进程 ID
    • cpu: CPU 使用率(%)
    • memory: 内存使用率(%)
    • memoryMB: 实际内存占用(MB)
    • status: 进程状态(R=运行,S=睡眠,Z=僵尸)
    • uptime: 运行时间

  11. HTTP 服务(第 284-301 行)

    $http_worker = new Worker("http://0.0.0.0:8080");
    $http_worker->name = 'HttpServer';

    作用: 提供 HTTP 服务,访问 http://localhost:8080/monitor.html 返回监控页面


  12. 启动服务(第 304 行)

    Worker::runAll();
    作用: 启动所有定义的 Worker(WebSocket 服务 + HTTP 服务)


    🎯 工作流程总结

  13. 启动服务 → 创建主进程和 2 个子进程(ProcessMonitor、HttpServer)

  14. 定时采集 → 每 2 秒调用 collectProcessInfo() 收集数据

  15. 实时推送 → 通过 WebSocket 推送数据给所有连接的浏览器客户端

  16. 浏览器展示 → 前端接收数据并实时刷新显示


    📊 监控的数据

    • ✅ 主进程 PID、CPU、内存、状态、运行时间
    • ✅ 所有子进程的详细信息(按 Worker 分组)
    • ✅ 总进程数、总 CPU 使用率、总内存使用率

    这就是这个监控系统的完整工作原理!

    运行:php process_monitor.php start

9 0 0
0个评论

贵哥的编程之路

440
积分
0
获赞数
0
粉丝数
2025-07-11 加入
🔝