getSystemMetrics(); // PHP运行指标 $metrics[] = $this->getPhpMetrics(); // 应用业务指标 $metrics[] = $this->getAppMetrics(); // 数据库指标 $metrics[] = $this->getDatabaseMetrics(); // Redis指标 $metrics[] = $this->getRedisMetrics(); // MongoDB指标 $metrics[] = $this->getMongoDBMetrics(); // HTTP请求指标 $metrics[] = $this->getHttpMetrics(); $content = implode("\n", array_filter($metrics)); return response($content, 200, [ 'Content-Type' => 'text/plain; charset=utf-8' ]); } /** * 获取系统指标 */ protected function getSystemMetrics(): string { $metrics = []; // 内存使用 $memoryUsage = memory_get_usage(true); $memoryPeak = memory_get_peak_usage(true); $memoryLimit = $this->getBytes(ini_get('memory_limit')); $metrics[] = $this->formatGauge('webman_memory_usage_bytes', 'Current memory usage in bytes', [], $memoryUsage); $metrics[] = $this->formatGauge('webman_memory_peak_bytes', 'Peak memory usage in bytes', [], $memoryPeak); $metrics[] = $this->formatGauge('webman_memory_limit_bytes', 'Memory limit in bytes', [], $memoryLimit); // CPU负载 if (function_exists('sys_getloadavg')) { $load = sys_getloadavg(); $metrics[] = $this->formatGauge('webman_system_load1', 'System load average over 1 minute', [], $load[0]); $metrics[] = $this->formatGauge('webman_system_load5', 'System load average over 5 minutes', [], $load[1]); $metrics[] = $this->formatGauge('webman_system_load15', 'System load average over 15 minutes', [], $load[2]); } // 磁盘使用 $diskTotal = disk_total_space('/'); $diskFree = disk_free_space('/'); $diskUsed = $diskTotal - $diskFree; $metrics[] = $this->formatGauge('webman_disk_total_bytes', 'Total disk space in bytes', [], $diskTotal); $metrics[] = $this->formatGauge('webman_disk_free_bytes', 'Free disk space in bytes', [], $diskFree); $metrics[] = $this->formatGauge('webman_disk_used_bytes', 'Used disk space in bytes', [], $diskUsed); return implode("\n", $metrics); } /** * 获取PHP指标 */ protected function getPhpMetrics(): string { $metrics = []; // PHP版本信息 $metrics[] = $this->formatGauge('webman_php_version_info', 'PHP version information', [ 'version' => PHP_VERSION, 'sapi' => PHP_SAPI ], 1); // OPcache指标 if (function_exists('opcache_get_status')) { $opcache = opcache_get_status(false); if ($opcache) { $metrics[] = $this->formatGauge('webman_opcache_enabled', 'OPcache enabled status', [], $opcache['opcache_enabled'] ? 1 : 0); $metrics[] = $this->formatGauge('webman_opcache_hit_rate', 'OPcache hit rate', [], $opcache['opcache_statistics']['opcache_hit_rate'] ?? 0); $metrics[] = $this->formatCounter('webman_opcache_hits_total', 'Total OPcache hits', [], $opcache['opcache_statistics']['hits'] ?? 0); $metrics[] = $this->formatCounter('webman_opcache_misses_total', 'Total OPcache misses', [], $opcache['opcache_statistics']['misses'] ?? 0); } } // 运行时间 if (function_exists('posix_times')) { $times = posix_times(); $metrics[] = $this->formatCounter('webman_cpu_user_seconds_total', 'Total user CPU time', [], $times['utime'] ?? 0); $metrics[] = $this->formatCounter('webman_cpu_system_seconds_total', 'Total system CPU time', [], $times['stime'] ?? 0); } return implode("\n", $metrics); } /** * 获取应用业务指标 */ protected function getAppMetrics(): string { $metrics = []; // 应用信息 $metrics[] = $this->formatGauge('webman_app_info', 'Application information', [ 'name' => config('app.name', 'webman'), 'version' => config('app.version', '1.0.0'), 'env' => config('app.debug') ? 'development' : 'production' ], 1); // 启动时间 $metrics[] = $this->formatGauge('webman_start_time_seconds', 'Application start time', [], defined('WEBMAN_START_TIME') ? WEBMAN_START_TIME : time()); // 运行时长 $startTime = defined('WEBMAN_START_TIME') ? WEBMAN_START_TIME : time(); $uptime = time() - $startTime; $metrics[] = $this->formatCounter('webman_uptime_seconds_total', 'Total uptime in seconds', [], $uptime); return implode("\n", $metrics); } /** * 获取数据库指标 */ protected function getDatabaseMetrics(): string { $metrics = []; try { // 数据库连接信息 $dbConfig = config('database.connections.mysql'); if ($dbConfig) { $metrics[] = $this->formatGauge('webman_db_config_info', 'Database configuration', [ 'host' => $dbConfig['hostname'] ?? 'unknown', 'database' => $dbConfig['database'] ?? 'unknown', 'charset' => $dbConfig['charset'] ?? 'utf8' ], 1); } // 数据库连接状态 - 连接成功 $metrics[] = $this->formatGauge('webman_db_up', 'Database connection status', [], 1); // 尝试获取数据库状态 $db = \think\facade\Db::connect(); // 1. 全局状态指标 $status = $db->query('SHOW GLOBAL STATUS'); $statusMap = []; foreach ($status as $item) { $statusMap[$item['Variable_name']] = $item['Value']; } // 关键指标 if (isset($statusMap['Threads_connected'])) { $metrics[] = $this->formatGauge('webman_db_threads_connected', 'Number of currently connected threads', [], (int)$statusMap['Threads_connected']); } if (isset($statusMap['Threads_running'])) { $metrics[] = $this->formatGauge('webman_db_threads_running', 'Number of threads running', [], (int)$statusMap['Threads_running']); } if (isset($statusMap['Queries'])) { $metrics[] = $this->formatCounter('webman_db_queries_total', 'Total number of queries', [], (int)$statusMap['Queries']); } if (isset($statusMap['Slow_queries'])) { $metrics[] = $this->formatCounter('webman_db_slow_queries_total', 'Total number of slow queries', [], (int)$statusMap['Slow_queries']); } 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) { // 数据库连接失败,记录错误 $metrics[] = $this->formatGauge('webman_db_up', 'Database connection status', [], 0); } return implode("\n", $metrics); } /** * 获取Redis指标 */ protected function getRedisMetrics(): string { $metrics = []; try { $redis = \Bilulanlv\ThinkCache\facade\ThinkCache::handler(); $info = $redis->info(); // Redis连接状态 $metrics[] = $this->formatGauge('webman_redis_up', 'Redis connection status', [], 1); // Redis版本 if (isset($info['redis_version'])) { $metrics[] = $this->formatGauge('webman_redis_version_info', 'Redis version information', [ 'version' => $info['redis_version'] ], 1); } // 内存使用 if (isset($info['used_memory'])) { $metrics[] = $this->formatGauge('webman_redis_memory_used_bytes', 'Redis memory used in bytes', [], (int)$info['used_memory']); } 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'])) { preg_match('/keys=(\d+)/', $info['db0'], $matches); if (isset($matches[1])) { $metrics[] = $this->formatGauge('webman_redis_keys_total', 'Total number of keys', [], (int)$matches[1]); } } // 运行时间 if (isset($info['uptime_in_seconds'])) { $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); } 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请求指标 */ protected function getHttpMetrics(): string { $metrics = []; // 请求计数器(这里需要从中间件或日志中统计,示例使用静态数据) $metrics[] = $this->formatCounter('webman_http_requests_total', 'Total HTTP requests', [ 'method' => 'GET', 'status' => '200' ], 0); $metrics[] = $this->formatCounter('webman_http_requests_total', 'Total HTTP requests', [ 'method' => 'POST', 'status' => '200' ], 0); // 请求处理时间(使用Gauge类型) $metrics[] = $this->formatGauge('webman_http_request_duration_seconds', 'HTTP request duration in seconds', [ 'method' => 'GET', 'path' => '/metrics' ], 0); // 响应大小 $metrics[] = $this->formatCounter('webman_http_response_size_bytes_total', 'Total HTTP response size in bytes', [], 0); // 请求大小 $metrics[] = $this->formatCounter('webman_http_request_size_bytes_total', 'Total HTTP request size in bytes', [], 0); return implode("\n", $metrics); } /** * 格式化Counter类型指标 */ protected function formatCounter(string $name, string $help, array $labels, float $value): string { $output = []; $output[] = "# HELP {$name} {$help}"; $output[] = "# TYPE {$name} counter"; $labelStr = $this->formatLabels($labels); $output[] = $name . $labelStr . ' ' . $value; return implode("\n", $output); } /** * 格式化Gauge类型指标 */ protected function formatGauge(string $name, string $help, array $labels, float $value): string { $output = []; $output[] = "# HELP {$name} {$help}"; $output[] = "# TYPE {$name} gauge"; $labelStr = $this->formatLabels($labels); $output[] = $name . $labelStr . ' ' . $value; return implode("\n", $output); } /** * 格式化Histogram类型指标 */ protected function formatHistogram(string $name, string $help, array $labels, array $buckets): string { $output = []; $output[] = "# HELP {$name} {$help}"; $output[] = "# TYPE {$name} histogram"; // 如果没有提供桶数据,返回空直方图 if (empty($buckets)) { $labelStr = $this->formatLabels($labels); $output[] = $name . '_bucket' . $labelStr . ',le="+Inf" 0'; $output[] = $name . '_sum' . $labelStr . ' 0'; $output[] = $name . '_count' . $labelStr . ' 0'; } return implode("\n", $output); } /** * 格式化标签 */ protected function formatLabels(array $labels): string { if (empty($labels)) { return ''; } $pairs = []; foreach ($labels as $key => $value) { $pairs[] = $key . '="' . $this->escapeLabel($value) . '"'; } return '{' . implode(',', $pairs) . '}'; } /** * 转义标签值 */ protected function escapeLabel(string $value): string { return str_replace(['\\', '"', "\n"], ['\\\\', '\\"', '\\n'], $value); } /** * 将PHP内存限制字符串转换为字节数 */ protected function getBytes(string $val): int { $val = trim($val); $last = strtolower($val[strlen($val) - 1]); $val = (int) $val; switch ($last) { case 'g': $val *= 1024; case 'm': $val *= 1024; case 'k': $val *= 1024; } return $val; } /** * 增加计数器值(供其他控制器调用) * * @param string $name * @param array $labels * @param float $value */ public static function incCounter(string $name, array $labels = [], float $value = 1): void { $key = $name . json_encode($labels); if (!isset(self::$metrics[$key])) { self::$metrics[$key] = [ 'type' => 'counter', 'name' => $name, 'labels' => $labels, 'value' => 0 ]; } self::$metrics[$key]['value'] += $value; } /** * 设置Gauge值(供其他控制器调用) * * @param string $name * @param float $value * @param array $labels */ public static function setGauge(string $name, float $value, array $labels = []): void { $key = $name . json_encode($labels); self::$metrics[$key] = [ 'type' => 'gauge', 'name' => $name, 'labels' => $labels, 'value' => $value ]; } /** * 记录HTTP请求指标(供中间件调用) * * @param string $method * @param string $path * @param int $status * @param float $duration * @param int $responseSize */ public static function recordHttpRequest(string $method, string $path, int $status, float $duration, int $responseSize = 0): void { $labels = [ 'method' => $method, 'path' => $path, 'status' => (string)$status ]; // 请求总数 self::incCounter('webman_http_requests_total', $labels); // 响应大小 self::incCounter('webman_http_response_size_bytes_total', $labels, $responseSize); } }