394 lines
17 KiB
HTML
394 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>插件性能监控</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" href="/assets/layui/css/layui.css">
|
|
<style>
|
|
body { background: #f2f3f5; padding: 15px; }
|
|
.stat-card {
|
|
background: #fff;
|
|
border-radius: 4px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
.stat-card .value {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #1e9fff;
|
|
margin: 10px 0;
|
|
}
|
|
.stat-card .label {
|
|
color: #666;
|
|
font-size: 14px;
|
|
}
|
|
.stat-card.warning .value { color: #ffb800; }
|
|
.stat-card.danger .value { color: #ff5722; }
|
|
.stat-card.success .value { color: #5fb878; }
|
|
|
|
.chart-container {
|
|
background: #fff;
|
|
border-radius: 4px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
min-height: 400px;
|
|
}
|
|
|
|
.addon-list-item {
|
|
background: #fff;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
margin-bottom: 10px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.addon-status {
|
|
display: inline-block;
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
margin-right: 8px;
|
|
}
|
|
.addon-status.healthy { background: #5fb878; }
|
|
.addon-status.warning { background: #ffb800; }
|
|
.addon-status.danger { background: #ff5722; }
|
|
|
|
.performance-bar {
|
|
height: 8px;
|
|
background: #f0f0f0;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
margin-top: 5px;
|
|
}
|
|
.performance-bar-fill {
|
|
height: 100%;
|
|
background: #1e9fff;
|
|
transition: width 0.3s;
|
|
}
|
|
.performance-bar-fill.warning { background: #ffb800; }
|
|
.performance-bar-fill.danger { background: #ff5722; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="layui-fluid">
|
|
<!-- 统计卡片 -->
|
|
<div class="layui-row layui-col-space15">
|
|
<div class="layui-col-md3">
|
|
<div class="stat-card">
|
|
<div class="label">总插件数</div>
|
|
<div class="value" id="totaladdon">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="layui-col-md3">
|
|
<div class="stat-card success">
|
|
<div class="label">健康插件</div>
|
|
<div class="value" id="healthyaddon">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="layui-col-md3">
|
|
<div class="stat-card warning">
|
|
<div class="label">问题插件</div>
|
|
<div class="value" id="problematicaddon">-</div>
|
|
</div>
|
|
</div>
|
|
<div class="layui-col-md3">
|
|
<div class="stat-card">
|
|
<div class="label">平均执行时间</div>
|
|
<div class="value"><span id="avgExecutionTime">-</span> ms</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 性能图表 -->
|
|
<div class="layui-row layui-col-space15" style="margin-top: 15px;">
|
|
<div class="layui-col-md6">
|
|
<div class="chart-container">
|
|
<div style="margin-bottom: 15px;">
|
|
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="execution_time">执行时间</button>
|
|
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="memory_usage">内存使用</button>
|
|
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="success_rate">成功率</button>
|
|
<button class="layui-btn layui-btn-sm layui-btn-primary" data-type="call_count">调用次数</button>
|
|
</div>
|
|
<div id="performanceChart" style="height: 350px;"></div>
|
|
</div>
|
|
</div>
|
|
<div class="layui-col-md6">
|
|
<div class="chart-container">
|
|
<div style="margin-bottom: 15px;">
|
|
<span style="font-weight: bold;">插件性能列表</span>
|
|
<button class="layui-btn layui-btn-sm layui-btn-normal" id="refreshBtn" style="float: right;">
|
|
<i class="layui-icon layui-icon-refresh"></i> 刷新
|
|
</button>
|
|
</div>
|
|
<div id="addonList" style="height: 350px; overflow-y: auto;">
|
|
<div style="text-align: center; color: #999; padding-top: 100px;">
|
|
加载中...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 热重载统计 -->
|
|
<div class="layui-row layui-col-space15" style="margin-top: 15px;">
|
|
<div class="layui-col-md12">
|
|
<div class="stat-card">
|
|
<div style="margin-bottom: 15px;">
|
|
<span style="font-weight: bold;">热重载统计</span>
|
|
<button class="layui-btn layui-btn-sm layui-btn-danger" id="clearPerfBtn" style="float: right;">
|
|
<i class="layui-icon layui-icon-delete"></i> 清除性能数据
|
|
</button>
|
|
</div>
|
|
<div id="reloadStats">
|
|
<div style="text-align: center; color: #999; padding: 20px;">
|
|
加载中...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/assets/layui/layui.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
|
<script>
|
|
layui.use(['jquery', 'layer', 'http'], function() {
|
|
var $ = layui.jquery;
|
|
var layer = layui.layer;
|
|
var http = layui.http;
|
|
var performanceChart = null;
|
|
var currentChartType = 'execution_time';
|
|
|
|
// 初始化图表
|
|
function initChart() {
|
|
performanceChart = echarts.init(document.getElementById('performanceChart'));
|
|
loadChartData(currentChartType);
|
|
}
|
|
|
|
// 加载图表数据
|
|
function loadChartData(type) {
|
|
currentChartType = type;
|
|
http.get('/backend/addon_monitor/chart', { type: type }).then(function(res) {
|
|
if (res.code === 0) {
|
|
updateChart(res.data.chart_data, type);
|
|
} else {
|
|
layer.msg('加载图表数据失败', {icon: 2});
|
|
}
|
|
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
|
|
}
|
|
|
|
// 更新图表
|
|
function updateChart(data, type) {
|
|
var names = data.map(item => item.name);
|
|
var values = data.map(item => item.value);
|
|
var title = '';
|
|
var unit = '';
|
|
|
|
switch(type) {
|
|
case 'execution_time':
|
|
title = '执行时间 (ms)';
|
|
unit = 'ms';
|
|
break;
|
|
case 'memory_usage':
|
|
title = '内存使用 (KB)';
|
|
unit = 'KB';
|
|
break;
|
|
case 'success_rate':
|
|
title = '成功率 (%)';
|
|
unit = '%';
|
|
break;
|
|
case 'call_count':
|
|
title = '调用次数';
|
|
unit = '次';
|
|
break;
|
|
}
|
|
|
|
var option = {
|
|
title: {
|
|
text: title,
|
|
left: 'center'
|
|
},
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
formatter: function(params) {
|
|
return params[0].name + ': ' + params[0].value + ' ' + unit;
|
|
}
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: names,
|
|
axisLabel: {
|
|
rotate: 45,
|
|
interval: 0
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'value',
|
|
name: unit
|
|
},
|
|
series: [{
|
|
data: values,
|
|
type: 'bar',
|
|
itemStyle: {
|
|
color: '#1e9fff'
|
|
}
|
|
}]
|
|
};
|
|
|
|
performanceChart.setOption(option);
|
|
}
|
|
|
|
// 加载概览数据
|
|
function loadOverview() {
|
|
http.get('/backend/addon_monitor', { action: 'overview' }).then(function(res) {
|
|
if (res.code === 0) {
|
|
var data = res.data;
|
|
$('#totaladdon').text(data.total_addon);
|
|
$('#healthyaddon').text(data.healthy_addon);
|
|
$('#problematicaddon').text(data.problematic_addon);
|
|
$('#avgExecutionTime').text(data.avg_execution_time);
|
|
|
|
// 更新重载统计
|
|
updateReloadStats(data.reload_stats);
|
|
} else {
|
|
layer.msg('加载概览数据失败', {icon: 2});
|
|
}
|
|
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
|
|
}
|
|
|
|
// 更新重载统计
|
|
function updateReloadStats(stats) {
|
|
if (!stats || stats.total_reloads === 0) {
|
|
$('#reloadStats').html('<div style="text-align: center; color: #999; padding: 20px;">暂无重载数据</div>');
|
|
return;
|
|
}
|
|
|
|
var html = '<div class="layui-row">';
|
|
html += '<div class="layui-col-md3"><div class="label">总重载次数</div><div class="value">' + stats.total_reloads + '</div></div>';
|
|
html += '<div class="layui-col-md3"><div class="label">成功次数</div><div class="value success">' + stats.successful_reloads + '</div></div>';
|
|
html += '<div class="layui-col-md3"><div class="label">失败次数</div><div class="value danger">' + stats.failed_reloads + '</div></div>';
|
|
html += '<div class="layui-col-md3"><div class="label">成功率</div><div class="value">' + (stats.total_reloads > 0 ? ((stats.successful_reloads / stats.total_reloads) * 100).toFixed(2) : 0) + '%</div></div>';
|
|
html += '</div>';
|
|
|
|
if (stats.addon && Object.keys(stats.addon).length > 0) {
|
|
html += '<div style="margin-top: 20px;"><strong>各插件重载状态:</strong></div>';
|
|
html += '<div style="margin-top: 10px;">';
|
|
|
|
for (var addon in stats.addon) {
|
|
var status = stats.addon[addon];
|
|
var statusClass = status.status === 'success' ? 'success' : 'danger';
|
|
var statusText = status.status === 'success' ? '成功' : '失败';
|
|
var lastReload = status.reload_time ? new Date(status.reload_time * 1000).toLocaleString() : '未重载';
|
|
|
|
html += '<div style="padding: 8px 0; border-bottom: 1px solid #f0f0f0;">';
|
|
html += '<span class="layui-badge layui-bg-' + statusClass + '">' + addon + '</span>';
|
|
html += ' <span style="color: #666;">' + lastReload + '</span>';
|
|
html += ' <span style="color: ' + (status.status === 'success' ? '#5fb878' : '#ff5722') + ';">' + statusText + '</span>';
|
|
html += '</div>';
|
|
}
|
|
|
|
html += '</div>';
|
|
}
|
|
|
|
$('#reloadStats').html(html);
|
|
}
|
|
|
|
// 加载插件列表
|
|
function loadAddonList() {
|
|
http.get('/backend/addon_monitor', { action: 'performance' }).then(function(res) {
|
|
if (res.code === 0) {
|
|
renderAddonList(res.data);
|
|
} else {
|
|
layer.msg('加载插件列表失败', {icon: 2});
|
|
}
|
|
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
|
|
}
|
|
|
|
// 渲染插件列表
|
|
function renderAddonList(addon) {
|
|
if (!addon || Object.keys(addon).length === 0) {
|
|
$('#addonList').html('<div style="text-align: center; color: #999; padding-top: 100px;">暂无插件数据</div>');
|
|
return;
|
|
}
|
|
|
|
var html = '';
|
|
for (var addon in addon) {
|
|
var stats = addon[addon];
|
|
var statusClass = 'healthy';
|
|
var statusText = '健康';
|
|
|
|
if (stats.success_rate < 90 || stats.avg_execution_time > 1.0) {
|
|
statusClass = 'danger';
|
|
statusText = '异常';
|
|
} else if (stats.avg_execution_time > 0.5 || stats.success_rate < 95) {
|
|
statusClass = 'warning';
|
|
statusText = '警告';
|
|
}
|
|
|
|
var execTimePercent = Math.min((stats.avg_execution_time / 2.0) * 100, 100);
|
|
var execTimeClass = stats.avg_execution_time > 1.0 ? 'danger' : (stats.avg_execution_time > 0.5 ? 'warning' : '');
|
|
|
|
html += '<div class="addon-list-item">';
|
|
html += '<div style="display: flex; justify-content: space-between; align-items: center;">';
|
|
html += '<div><span class="addon-status ' + statusClass + '"></span><strong>' + addon + '</strong></div>';
|
|
html += '<span style="font-size: 12px; color: #666;">' + statusText + '</span>';
|
|
html += '</div>';
|
|
html += '<div style="margin-top: 10px; font-size: 12px; color: #666;">';
|
|
html += '<div>调用次数: ' + stats.total_calls + ' | 成功率: ' + stats.success_rate.toFixed(2) + '%</div>';
|
|
html += '<div style="margin-top: 5px;">平均执行时间: ' + (stats.avg_execution_time * 1000).toFixed(2) + ' ms</div>';
|
|
html += '<div class="performance-bar">';
|
|
html += '<div class="performance-bar-fill ' + execTimeClass + '" style="width: ' + execTimePercent + '%;"></div>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
html += '</div>';
|
|
}
|
|
|
|
$('#addonList').html(html);
|
|
}
|
|
|
|
// 绑定事件
|
|
$('button[data-type]').on('click', function() {
|
|
var type = $(this).data('type');
|
|
$('button[data-type]').removeClass('layui-btn-normal').addClass('layui-btn-primary');
|
|
$(this).removeClass('layui-btn-primary').addClass('layui-btn-normal');
|
|
loadChartData(type);
|
|
});
|
|
|
|
$('#refreshBtn').on('click', function() {
|
|
loadOverview();
|
|
loadAddonList();
|
|
loadChartData(currentChartType);
|
|
layer.msg('数据已刷新', {icon: 1});
|
|
});
|
|
|
|
$('#clearPerfBtn').on('click', function() {
|
|
layer.confirm('确定要清除所有性能数据吗?', function(index) {
|
|
http.get('/backend/addon_monitor', { action: 'clear_performance' }).then(function(res) {
|
|
layer.close(index);
|
|
if (res.code === 0) {
|
|
layer.msg('性能数据已清除', {icon: 1});
|
|
loadOverview();
|
|
loadAddonList();
|
|
} else {
|
|
layer.msg('清除失败', {icon: 2});
|
|
}
|
|
}).catch(function() { layer.msg('请求失败', {icon: 2}); });
|
|
});
|
|
});
|
|
|
|
// 初始化
|
|
initChart();
|
|
loadOverview();
|
|
loadAddonList();
|
|
|
|
// 定时刷新
|
|
setInterval(function() {
|
|
loadOverview();
|
|
}, 30000); // 30秒刷新一次
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |