From 873c7cf9c2734bad0247554eda2d8e43ab8a970d Mon Sep 17 00:00:00 2001 From: commie Date: Sat, 28 Feb 2026 19:24:05 +0800 Subject: [PATCH] 14 --- app/controller/MetricsController.php | 207 ++++++++++++++++++++++++++- app/middleware/MetricsMiddleware.php | 2 +- 2 files changed, 207 insertions(+), 2 deletions(-) diff --git a/app/controller/MetricsController.php b/app/controller/MetricsController.php index 3c65380..e6287dc 100644 --- a/app/controller/MetricsController.php +++ b/app/controller/MetricsController.php @@ -55,6 +55,9 @@ class MetricsController extends Base // Redis指标 $metrics[] = $this->getRedisMetrics(); + // MongoDB指标 + $metrics[] = $this->getMongoDBMetrics(); + // HTTP请求指标 $metrics[] = $this->getHttpMetrics(); @@ -183,7 +186,9 @@ class MetricsController extends Base // 尝试获取数据库状态 $db = \think\facade\Db::connect(); - $status = $db->query('SHOW STATUS'); + + // 1. 全局状态指标 + $status = $db->query('SHOW GLOBAL STATUS'); $statusMap = []; foreach ($status as $item) { $statusMap[$item['Variable_name']] = $item['Value']; @@ -205,6 +210,60 @@ class MetricsController extends Base if (isset($statusMap['Uptime'])) { $metrics[] = $this->formatCounter('webman_db_uptime_seconds', 'Database uptime in seconds', [], (int)$statusMap['Uptime']); } + if (isset($statusMap['Innodb_buffer_pool_pages_data'])) { + $metrics[] = $this->formatGauge('webman_db_innodb_buffer_pool_pages_data', 'Number of pages containing data', [], (int)$statusMap['Innodb_buffer_pool_pages_data']); + } + if (isset($statusMap['Innodb_buffer_pool_pages_free'])) { + $metrics[] = $this->formatGauge('webman_db_innodb_buffer_pool_pages_free', 'Number of free pages', [], (int)$statusMap['Innodb_buffer_pool_pages_free']); + } + if (isset($statusMap['Innodb_buffer_pool_pages_total'])) { + $metrics[] = $this->formatGauge('webman_db_innodb_buffer_pool_pages_total', 'Total number of pages', [], (int)$statusMap['Innodb_buffer_pool_pages_total']); + } + if (isset($statusMap['Innodb_row_reads'])) { + $metrics[] = $this->formatCounter('webman_db_innodb_row_reads_total', 'Number of rows read', [], (int)$statusMap['Innodb_row_reads']); + } + if (isset($statusMap['Innodb_row_writes'])) { + $metrics[] = $this->formatCounter('webman_db_innodb_row_writes_total', 'Number of rows written', [], (int)$statusMap['Innodb_row_writes']); + } + if (isset($statusMap['Com_select'])) { + $metrics[] = $this->formatCounter('webman_db_com_select_total', 'Number of SELECT statements', [], (int)$statusMap['Com_select']); + } + if (isset($statusMap['Com_insert'])) { + $metrics[] = $this->formatCounter('webman_db_com_insert_total', 'Number of INSERT statements', [], (int)$statusMap['Com_insert']); + } + if (isset($statusMap['Com_update'])) { + $metrics[] = $this->formatCounter('webman_db_com_update_total', 'Number of UPDATE statements', [], (int)$statusMap['Com_update']); + } + if (isset($statusMap['Com_delete'])) { + $metrics[] = $this->formatCounter('webman_db_com_delete_total', 'Number of DELETE statements', [], (int)$statusMap['Com_delete']); + } + + // 2. 数据库大小 + $databases = $db->query('SHOW DATABASES WHERE `Database` NOT IN ("information_schema", "mysql", "performance_schema", "sys")'); + foreach ($databases as $dbInfo) { + $dbName = $dbInfo['Database']; + $sizeResult = $db->query("SELECT table_schema AS 'database', SUM(data_length + index_length) AS 'size_bytes' FROM information_schema.tables WHERE table_schema = '{$dbName}' GROUP BY table_schema"); + if (isset($sizeResult[0]['size_bytes'])) { + $metrics[] = $this->formatGauge('webman_db_database_size_bytes', 'Database size in bytes', [ + 'database' => $dbName + ], (int)$sizeResult[0]['size_bytes']); + } + } + + // 3. 表状态 + $tables = $db->query('SHOW TABLE STATUS FROM `' . ($dbConfig['database'] ?? 'test') . '`'); + foreach ($tables as $table) { + $tableName = $table['Name']; + $metrics[] = $this->formatGauge('webman_db_table_rows', 'Number of rows in table', [ + 'table' => $tableName + ], (int)$table['Rows']); + $metrics[] = $this->formatGauge('webman_db_table_data_length_bytes', 'Data length of table', [ + 'table' => $tableName + ], (int)$table['Data_length']); + $metrics[] = $this->formatGauge('webman_db_table_index_length_bytes', 'Index length of table', [ + 'table' => $tableName + ], (int)$table['Index_length']); + } } catch (\Exception $e) { // 数据库连接失败,记录错误 @@ -242,16 +301,31 @@ class MetricsController extends Base if (isset($info['used_memory_peak'])) { $metrics[] = $this->formatGauge('webman_redis_memory_peak_bytes', 'Redis peak memory used in bytes', [], (int)$info['used_memory_peak']); } + if (isset($info['used_memory_rss'])) { + $metrics[] = $this->formatGauge('webman_redis_memory_rss_bytes', 'Redis RSS memory used in bytes', [], (int)$info['used_memory_rss']); + } + if (isset($info['mem_fragmentation_ratio'])) { + $metrics[] = $this->formatGauge('webman_redis_memory_fragmentation_ratio', 'Redis memory fragmentation ratio', [], (float)$info['mem_fragmentation_ratio']); + } // 连接数 if (isset($info['connected_clients'])) { $metrics[] = $this->formatGauge('webman_redis_connected_clients', 'Number of connected clients', [], (int)$info['connected_clients']); } + if (isset($info['client_recent_max_input_buffer'])) { + $metrics[] = $this->formatGauge('webman_redis_client_max_input_buffer_bytes', 'Maximum input buffer size', [], (int)$info['client_recent_max_input_buffer']); + } + if (isset($info['client_recent_max_output_buffer'])) { + $metrics[] = $this->formatGauge('webman_redis_client_max_output_buffer_bytes', 'Maximum output buffer size', [], (int)$info['client_recent_max_output_buffer']); + } // 命令统计 if (isset($info['total_commands_processed'])) { $metrics[] = $this->formatCounter('webman_redis_commands_processed_total', 'Total commands processed', [], (int)$info['total_commands_processed']); } + if (isset($info['instantaneous_ops_per_sec'])) { + $metrics[] = $this->formatGauge('webman_redis_instantaneous_ops_per_sec', 'Instantaneous operations per second', [], (int)$info['instantaneous_ops_per_sec']); + } // 键数量 if (isset($info['db0'])) { @@ -266,6 +340,29 @@ class MetricsController extends Base $metrics[] = $this->formatCounter('webman_redis_uptime_seconds', 'Redis uptime in seconds', [], (int)$info['uptime_in_seconds']); } + // 过期键 + if (isset($info['expired_keys'])) { + $metrics[] = $this->formatCounter('webman_redis_expired_keys_total', 'Total number of expired keys', [], (int)$info['expired_keys']); + } + if (isset($info['evicted_keys'])) { + $metrics[] = $this->formatCounter('webman_redis_evicted_keys_total', 'Total number of evicted keys', [], (int)$info['evicted_keys']); + } + + // 命中和未命中 + if (isset($info['keyspace_hits'])) { + $metrics[] = $this->formatCounter('webman_redis_keyspace_hits_total', 'Total number of keyspace hits', [], (int)$info['keyspace_hits']); + } + if (isset($info['keyspace_misses'])) { + $metrics[] = $this->formatCounter('webman_redis_keyspace_misses_total', 'Total number of keyspace misses', [], (int)$info['keyspace_misses']); + } + + // 复制状态 + if (isset($info['role'])) { + $metrics[] = $this->formatGauge('webman_redis_role', 'Redis role (master=1, slave=2)', [ + 'role' => $info['role'] + ], $info['role'] === 'master' ? 1 : 2); + } + } catch (\Exception $e) { $metrics[] = $this->formatGauge('webman_redis_up', 'Redis connection status', [], 0); } @@ -273,6 +370,114 @@ class MetricsController extends Base return implode("\n", $metrics); } + /** + * 获取MongoDB指标 + */ + protected function getMongoDBMetrics(): string + { + $metrics = []; + + try { + // 检查MongoDB扩展是否安装 + if (!class_exists('MongoDB\\Client')) { + $metrics[] = $this->formatGauge('webman_mongodb_up', 'MongoDB connection status', [], 0); + $metrics[] = $this->formatGauge('webman_mongodb_error', 'MongoDB error', [ + 'error' => 'extension_not_installed' + ], 1); + return implode("\n", $metrics); + } + + // 尝试连接MongoDB + $mongoClient = new \MongoDB\Client('mongodb://localhost:27017'); + + // MongoDB连接状态 + $metrics[] = $this->formatGauge('webman_mongodb_up', 'MongoDB connection status', [], 1); + + // 获取服务器状态 + $serverStatus = $mongoClient->selectDatabase('admin')->command(['serverStatus' => 1]); + $status = $serverStatus->toArray()[0]; + + // 版本信息 + if (isset($status['version'])) { + $metrics[] = $this->formatGauge('webman_mongodb_version_info', 'MongoDB version information', [ + 'version' => $status['version'] + ], 1); + } + + // 连接数 + if (isset($status['connections'])) { + $metrics[] = $this->formatGauge('webman_mongodb_connections_current', 'Current number of connections', [], (int)$status['connections']['current']); + $metrics[] = $this->formatGauge('webman_mongodb_connections_available', 'Available number of connections', [], (int)$status['connections']['available']); + } + + // 内存使用 + if (isset($status['mem'])) { + if (isset($status['mem']['resident'])) { + $metrics[] = $this->formatGauge('webman_mongodb_memory_resident_bytes', 'Resident memory usage in bytes', [], (int)$status['mem']['resident'] * 1024 * 1024); + } + if (isset($status['mem']['virtual'])) { + $metrics[] = $this->formatGauge('webman_mongodb_memory_virtual_bytes', 'Virtual memory usage in bytes', [], (int)$status['mem']['virtual'] * 1024 * 1024); + } + } + + // 操作统计 + if (isset($status['opcounters'])) { + if (isset($status['opcounters']['insert'])) { + $metrics[] = $this->formatCounter('webman_mongodb_ops_insert_total', 'Total insert operations', [], (int)$status['opcounters']['insert']); + } + if (isset($status['opcounters']['query'])) { + $metrics[] = $this->formatCounter('webman_mongodb_ops_query_total', 'Total query operations', [], (int)$status['opcounters']['query']); + } + if (isset($status['opcounters']['update'])) { + $metrics[] = $this->formatCounter('webman_mongodb_ops_update_total', 'Total update operations', [], (int)$status['opcounters']['update']); + } + if (isset($status['opcounters']['delete'])) { + $metrics[] = $this->formatCounter('webman_mongodb_ops_delete_total', 'Total delete operations', [], (int)$status['opcounters']['delete']); + } + } + + // 集合和数据库统计 + $databases = $mongoClient->listDatabases(); + foreach ($databases as $dbInfo) { + $dbName = $dbInfo->getName(); + if (!in_array($dbName, ['admin', 'local', 'config'])) { + $db = $mongoClient->selectDatabase($dbName); + $collections = $db->listCollections(); + $collectionCount = 0; + foreach ($collections as $collection) { + $collectionCount++; + $collectionName = $collection->getName(); + $stats = $db->command(['collStats' => $collectionName]); + $collStats = $stats->toArray()[0]; + if (isset($collStats['count'])) { + $metrics[] = $this->formatGauge('webman_mongodb_collection_documents', 'Number of documents in collection', [ + 'database' => $dbName, + 'collection' => $collectionName + ], (int)$collStats['count']); + } + if (isset($collStats['size'])) { + $metrics[] = $this->formatGauge('webman_mongodb_collection_size_bytes', 'Size of collection in bytes', [ + 'database' => $dbName, + 'collection' => $collectionName + ], (int)$collStats['size']); + } + } + $metrics[] = $this->formatGauge('webman_mongodb_database_collections', 'Number of collections in database', [ + 'database' => $dbName + ], $collectionCount); + } + } + + } catch (\Exception $e) { + $metrics[] = $this->formatGauge('webman_mongodb_up', 'MongoDB connection status', [], 0); + $metrics[] = $this->formatGauge('webman_mongodb_error', 'MongoDB error', [ + 'error' => 'connection_failed' + ], 1); + } + + return implode("\n", $metrics); + } + /** * 获取HTTP请求指标 */ diff --git a/app/middleware/MetricsMiddleware.php b/app/middleware/MetricsMiddleware.php index 1cf47be..5e27291 100644 --- a/app/middleware/MetricsMiddleware.php +++ b/app/middleware/MetricsMiddleware.php @@ -4,7 +4,7 @@ namespace app\middleware; use app\controller\MetricsController; use support\Request; -use support\Response; +use Webman\Http\Response; /** * 指标收集中间件