Files
YwxAppThink/ywxapp/service/AddonPerformanceMonitor.php

268 lines
8.2 KiB
PHP

<?php
// +----------------------------------------------------------------------
// | YwxApp [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Author: ywxapp<admin@ywxapp.cn>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace ywxapp\service;
use think\facade\Cache;
/**
* 插件性能监控服务
*
* 功能特性:
* - 监控插件执行时间
* - 监控插件内存使用
* - 记录插件调用次数
* - 检测性能异常
*/
class AddonPerformanceMonitor
{
/** 监控数据缓存前缀 */
const CACHE_PREFIX = 'addon_perf_';
/** 监控数据缓存时间(秒) */
const CACHE_EXPIRE = 300;
/** 性能阈值(秒) */
const PERFORMANCE_THRESHOLD = 2.0;
/** 内存阈值(字节) */
const MEMORY_THRESHOLD = 10485760; // 10MB
/**
* 监控插件执行性能
*
* @param string $addon 插件名称
* @param string $action 操作类型
* @param callable $callback 要执行的回调
* @return mixed
*/
public static function measure(string $addon, string $action, callable $callback)
{
$startTime = microtime(true);
$startMemory = memory_get_usage();
$success = true;
$error = null;
try {
$result = $callback();
return $result;
} catch (\Exception $e) {
$success = false;
$error = $e->getMessage();
throw $e;
} finally {
$endTime = microtime(true);
$endMemory = memory_get_usage();
$performanceData = [
'addon' => $addon,
'action' => $action,
'execution_time' => $endTime - $startTime,
'memory_usage' => $endMemory - $startMemory,
'peak_memory' => memory_get_peak_usage(),
'timestamp' => time(),
'success' => $success,
'error' => $error
];
// 保存性能数据
self::savePerformanceData($addon, $performanceData);
// 性能异常检测
self::checkPerformanceAnomaly($addon, $performanceData);
}
}
/**
* 保存性能数据
*
* @param string $addon 插件名称
* @param array $data 性能数据
*/
private static function savePerformanceData(string $addon, array $data): void
{
try {
$cacheKey = self::CACHE_PREFIX . $addon;
$existingData = Cache::get($cacheKey, []);
// 只保留最近100条记录
$existingData[] = $data;
if (count($existingData) > 100) {
$existingData = array_slice($existingData, -100);
}
Cache::set($cacheKey, $existingData, self::CACHE_EXPIRE);
} catch (\Throwable $e) {
// 监控落库失败绝不影响业务响应
\think\facade\Log::warning('插件性能数据写入失败:' . $e->getMessage());
}
}
/**
* 检测性能异常
*
* @param string $addon 插件名称
* @param array $data 性能数据
*/
private static function checkPerformanceAnomaly(string $addon, array $data): void
{
$anomalies = [];
// 执行时间检测
if ($data['execution_time'] > self::PERFORMANCE_THRESHOLD) {
$anomalies[] = sprintf(
"插件 %s 的 %s 操作执行时间过长:%.3f秒",
$addon,
$data['action'],
$data['execution_time']
);
}
// 内存使用检测
if ($data['memory_usage'] > self::MEMORY_THRESHOLD) {
$anomalies[] = sprintf(
"插件 %s 的 %s 操作内存使用过多:%.2fMB",
$addon,
$data['action'],
$data['memory_usage'] / 1048576
);
}
// 记录异常
if (!empty($anomalies)) {
foreach ($anomalies as $anomaly) {
\think\facade\Log::warning('插件性能异常:' . $anomaly);
}
}
}
/**
* 获取插件性能统计
*
* @param string $addon 插件名称
* @return array
*/
public static function getPerformanceStats(string $addon): array
{
$cacheKey = self::CACHE_PREFIX . $addon;
$data = Cache::get($cacheKey, []);
if (empty($data)) {
return [
'total_calls' => 0,
'avg_execution_time' => 0,
'max_execution_time' => 0,
'avg_memory_usage' => 0,
'max_memory_usage' => 0,
'success_rate' => 0,
'recent_performance' => []
];
}
$totalCalls = count($data);
$totalExecutionTime = array_sum(array_column($data, 'execution_time'));
$maxExecutionTime = max(array_column($data, 'execution_time'));
$totalMemoryUsage = array_sum(array_column($data, 'memory_usage'));
$maxMemoryUsage = max(array_column($data, 'memory_usage'));
$successCount = count(array_filter($data, function($item) {
return $item['success'] === true;
}));
// 最近10次性能数据
$recentPerformance = array_slice($data, -10);
return [
'total_calls' => $totalCalls,
'avg_execution_time' => $totalCalls > 0 ? $totalExecutionTime / $totalCalls : 0,
'max_execution_time' => $maxExecutionTime,
'avg_memory_usage' => $totalCalls > 0 ? $totalMemoryUsage / $totalCalls : 0,
'max_memory_usage' => $maxMemoryUsage,
'success_rate' => $totalCalls > 0 ? ($successCount / $totalCalls) * 100 : 0,
'recent_performance' => $recentPerformance
];
}
/**
* 获取所有插件性能概览
*
* @return array
*/
public static function getAllPerformanceOverview(): array
{
$overview = [];
$addon = \think\facade\Config::get('addon', []);
foreach ($addon as $addon) {
if (isset($addon['name'])) {
$overview[$addon['name']] = self::getPerformanceStats($addon['name']);
}
}
return $overview;
}
/**
* 清除性能监控数据
*
* @param string|null $addon 插件名称,为null时清除所有
*/
public static function clearPerformanceData(?string $addon = null): void
{
if ($addon === null) {
// 清除所有插件的性能数据
$addon = \think\facade\Config::get('addon', []);
foreach ($addon as $addonInfo) {
if (isset($addonInfo['name'])) {
Cache::delete(self::CACHE_PREFIX . $addonInfo['name']);
}
}
} else {
Cache::delete(self::CACHE_PREFIX . $addon);
}
}
/**
* 获取性能问题插件列表
*
* @return array
*/
public static function getProblematicaddon(): array
{
$problematic = [];
$overview = self::getAllPerformanceOverview();
foreach ($overview as $addon => $stats) {
$issues = [];
if ($stats['avg_execution_time'] > self::PERFORMANCE_THRESHOLD) {
$issues[] = '平均执行时间过长';
}
if ($stats['avg_memory_usage'] > self::MEMORY_THRESHOLD) {
$issues[] = '平均内存使用过多';
}
// 仅在存在采样数据时判定成功率,避免“无数据=0%”的假阳性
if ($stats['total_calls'] > 0 && $stats['success_rate'] < 90) {
$issues[] = '成功率过低';
}
if (!empty($issues)) {
$problematic[$addon] = [
'issues' => $issues,
'stats' => $stats
];
}
}
return $problematic;
}
}