feat(admin): XHProf 运行详情页函数级分析

This commit is contained in:
augushong
2026-08-15 07:20:29 +08:00
parent c980ebdff0
commit f611ab385c
3 changed files with 336 additions and 0 deletions

View File

@@ -0,0 +1,74 @@
<div class="layuimini-container">
<div class="layuimini-main" style="padding: 10px 15px;">
<!-- 顶部摘要卡片(服务端渲染) -->
<div class="layui-row layui-col-space15">
<div class="layui-col-md3 layui-col-sm6">
<div class="layui-card xhprof-summary-card">
<div class="layui-card-header">总耗时</div>
<div class="layui-card-body">
<span class="xhprof-num">{$summary.wall_time_ms}</span><span class="xhprof-unit">ms</span>
<div class="xhprof-sub">CPU {$summary.cpu_time_ms}ms</div>
</div>
</div>
</div>
<div class="layui-col-md3 layui-col-sm6">
<div class="layui-card xhprof-summary-card">
<div class="layui-card-header">SQL 占比</div>
<div class="layui-card-body">
<span class="xhprof-num" id="summary-sql-pct">--</span><span class="xhprof-unit">%</span>
<div class="xhprof-sub">think\db* 独占耗时 / 总耗时</div>
</div>
</div>
</div>
<div class="layui-col-md3 layui-col-sm6">
<div class="layui-card xhprof-summary-card">
<div class="layui-card-header">内存峰值</div>
<div class="layui-card-body">
<span class="xhprof-num">{$summary.memory_peak_mb}</span><span class="xhprof-unit">MB</span>
<div class="xhprof-sub">函数总数 <span id="summary-fn-total">-</span></div>
</div>
</div>
</div>
<div class="layui-col-md3 layui-col-sm6">
<div class="layui-card xhprof-summary-card">
<div class="layui-card-header">请求信息</div>
<div class="layui-card-body xhprof-request-card">
<div>
<span class="layui-badge layui-bg-blue">{$run.method|default='-'}</span>
<span class="xhprof-url" title="{$summary.url_display}">{$summary.url_display}</span>
</div>
<div class="xhprof-sub">{$summary.request_time_text} · 节点 {$summary.node_display}</div>
</div>
</div>
</div>
</div>
<!-- SQL 摘要条JS 以接口模式取数填充) -->
<div class="layui-card">
<div class="layui-card-header">SQL 耗时摘要think\db 前缀函数独占耗时合计)</div>
<div class="layui-card-body">
<div class="xhprof-bar-track"><div class="xhprof-bar-fill" id="sql-bar" style="width: 0;"></div></div>
<div class="xhprof-bar-text" id="sql-bar-text">数据加载中...</div>
</div>
</div>
<!-- 函数级分析表JS 以接口模式取数后本地渲染) -->
<div class="layui-card">
<div class="layui-card-header">函数级分析(按独占耗时降序 Top 100</div>
<div class="layui-card-body" style="padding: 5px 10px;">
<table id="funcTable" class="layui-table" data-id="{$run.id}" lay-filter="funcTable"></table>
</div>
</div>
</div>
</div>
<style>
.xhprof-summary-card .layui-card-body { padding: 12px 15px; }
.xhprof-num { font-size: 26px; font-weight: 600; color: #16baaa; }
.xhprof-num.is-warn { color: #ff5722; }
.xhprof-unit { margin-left: 4px; color: #999; }
.xhprof-sub { margin-top: 4px; color: #999; font-size: 12px; }
.xhprof-request-card .xhprof-url { margin-left: 6px; font-weight: 600; word-break: break-all; }
.xhprof-bar-track { height: 18px; background: #f2f2f2; border-radius: 9px; overflow: hidden; }
.xhprof-bar-fill { height: 100%; background: linear-gradient(90deg, #16b777, #16baaa); border-radius: 9px; transition: width .4s ease; min-width: 0; }
.xhprof-bar-text { margin-top: 6px; color: #666; font-size: 12px; }
</style>

View File

@@ -0,0 +1,104 @@
$(function () {
var $table = $('#funcTable');
var runId = parseInt($table.data('id'), 10);
if (!runId || isNaN(runId)) {
$('#sql-bar-text').text('缺少采样记录 id');
return;
}
var escapeHtml = function (s) {
return String(s == null ? '' : s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
// 微秒 → 可读耗时
var fmtUs = function (us) {
us = parseInt(us, 10) || 0;
if (us >= 1000000) {
return (us / 1000000).toFixed(2) + 's';
}
return (us / 1000).toFixed(2) + 'ms';
};
var fmtBytes = function (b) {
b = parseInt(b, 10) || 0;
if (b >= 1048576) {
return (b / 1048576).toFixed(2) + 'MB';
}
if (b >= 1024) {
return (b / 1024).toFixed(2) + 'KB';
}
return b + 'B';
};
// 接口模式请求同一路由Accept: application/json 触发页面/接口同体的接口分支)
$.ajax({
url: 'xhprof.run/detail',
type: 'get',
data: {id: runId},
headers: {Accept: 'application/json'},
dataType: 'json',
success: function (res) {
if (!res || res.code !== 0 || !res.data) {
var msg = (res && res.msg) ? res.msg : '数据加载失败';
$('#sql-bar-text').text(msg);
layer.msg(msg, {icon: 2});
return;
}
var d = res.data;
var sql = d.sql_summary || {};
var pct = parseFloat(sql.wt_pct) || 0;
// 顶部 SQL 占比卡片 + SQL 摘要条
$('#summary-sql-pct').text(pct.toFixed(2));
$('#sql-bar').css('width', Math.min(100, pct) + '%');
$('#sql-bar-text').text(
'SQL 独占耗时 ' + fmtUs(sql.wt || 0)
+ '(占总耗时 ' + pct.toFixed(2) + '%),命中函数 ' + (sql.fn_count || 0)
+ ' 个,调用 ' + (sql.ct || 0) + ' 次'
);
$('#summary-fn-total').text(d.summary && d.summary.function_total ? d.summary.function_total : '-');
// 函数表:本地数据渲染(服务端已按独占耗时降序取 Top 100
layui.use(['table'], function () {
var table = layui.table;
table.render({
elem: '#funcTable',
data: d.functions || [],
even: true,
page: false,
limit: 1000,
defaultToolbar: ['filter'],
cols: [[
{
field: 'fn', title: '函数', minWidth: 380, templet: function (o) {
return '<span title="' + escapeHtml(o.fn) + '">' + escapeHtml(o.fn) + '</span>';
}
},
{field: 'ct', title: '调用次数', width: 100, sort: true},
{
field: 'excl_wt', title: '独占耗时', width: 130, sort: true, templet: function (o) {
return fmtUs(o.excl_wt);
}
},
{
field: 'incl_wt', title: '包含耗时', width: 130, sort: true, templet: function (o) {
return fmtUs(o.incl_wt);
}
},
{
field: 'mu', title: '内存增量', width: 120, sort: true, templet: function (o) {
return fmtBytes(o.mu);
}
}
]]
});
});
},
error: function () {
$('#sql-bar-text').text('请求失败,请刷新重试');
layer.msg('请求失败', {icon: 2});
}
});
});