2026-02-28 16:18:52 +08:00
< ? php
namespace app\controller ;
use support\Request ;
use support\Response ;
/**
* 指标
* 提供Prometheus格式的监控指标
*/
class MetricsController extends Base
{
/**
* 无需登录及鉴权的方法
* @var array
*/
protected $noNeedLogin = [ 'index' ];
/**
* 指标数据存储
*/
protected static $metrics = [];
/**
* 指标类型定义
*/
const TYPE_COUNTER = 'counter' ;
const TYPE_GAUGE = 'gauge' ;
const TYPE_HISTOGRAM = 'histogram' ;
const TYPE_SUMMARY = 'summary' ;
/**
* 获取指标数据
*
* @param Request $request
* @return Response
*/
public function index ( Request $request ) : Response
{
$metrics = [];
// 系统基础指标
$metrics [] = $this -> getSystemMetrics ();
// PHP运行指标
$metrics [] = $this -> getPhpMetrics ();
// 应用业务指标
$metrics [] = $this -> getAppMetrics ();
// 数据库指标
$metrics [] = $this -> getDatabaseMetrics ();
// Redis指标
$metrics [] = $this -> getRedisMetrics ();
2026-02-28 19:24:05 +08:00
// MongoDB指标
$metrics [] = $this -> getMongoDBMetrics ();
2026-02-28 16:18:52 +08:00
// 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 ();
2026-02-28 19:24:05 +08:00
// 1. 全局状态指标
$status = $db -> query ( 'SHOW GLOBAL STATUS' );
2026-02-28 16:18:52 +08:00
$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' ]);
}
2026-02-28 19:24:05 +08:00
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' ]);
}
2026-02-28 16:18:52 +08:00
} catch ( \Exception $e ) {
// 数据库连接失败,记录错误
$metrics [] = $this -> formatGauge ( 'webman_db_up' , 'Database connection status' , [], 0 );
}
return implode ( " \n " , $metrics );
}
/**
* 获取Redis指标
*/
protected function getRedisMetrics () : string
{
$metrics = [];
try {
2026-03-06 02:27:52 +08:00
$redis = \support\think\Cache :: handler ();
2026-02-28 16:18:52 +08:00
$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' ]);
}
2026-02-28 19:24:05 +08:00
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' ]);
}
2026-02-28 16:18:52 +08:00
// 连接数
if ( isset ( $info [ 'connected_clients' ])) {
$metrics [] = $this -> formatGauge ( 'webman_redis_connected_clients' , 'Number of connected clients' , [], ( int ) $info [ 'connected_clients' ]);
}
2026-02-28 19:24:05 +08:00
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' ]);
}
2026-02-28 16:18:52 +08:00
// 命令统计
if ( isset ( $info [ 'total_commands_processed' ])) {
$metrics [] = $this -> formatCounter ( 'webman_redis_commands_processed_total' , 'Total commands processed' , [], ( int ) $info [ 'total_commands_processed' ]);
}
2026-02-28 19:24:05 +08:00
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' ]);
}
2026-02-28 16:18:52 +08:00
// 键数量
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' ]);
}
2026-02-28 19:24:05 +08:00
// 过期键
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 );
}
2026-02-28 16:18:52 +08:00
} catch ( \Exception $e ) {
$metrics [] = $this -> formatGauge ( 'webman_redis_up' , 'Redis connection status' , [], 0 );
}
return implode ( " \n " , $metrics );
}
2026-02-28 19:24:05 +08:00
/**
* 获取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 );
}
2026-02-28 16:18:52 +08:00
/**
* 获取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 );
}
}