268 lines
8.2 KiB
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;
|
|
}
|
|
} |