diff --git a/app/admin/controller/xhprof/Run.php b/app/admin/controller/xhprof/Run.php
new file mode 100644
index 0000000..583b63a
--- /dev/null
+++ b/app/admin/controller/xhprof/Run.php
@@ -0,0 +1,81 @@
+ 'desc',
+ ];
+
+ public function __construct(App $app)
+ {
+ parent::__construct($app);
+
+ $this->model = new \app\admin\model\XhprofRun();
+ }
+
+ /**
+ * @NodeAnotation(title="列表")
+ */
+ public function index()
+ {
+ if ($this->request->isAjax()) {
+ if (input('selectFields')) {
+ return $this->selectList();
+ }
+ // wall_time 搜索下限:前端输入毫秒,换算为微秒后再构建查询条件
+ // 注意:Request::withGet 是整体替换,必须先取全量 GET 再合并写回
+ $filter = (string) $this->request->get('filter', '');
+ if ($filter !== '') {
+ $filterArr = json_decode($filter, true) ?: [];
+ if (isset($filterArr['wall_time']) && $filterArr['wall_time'] !== '') {
+ $filterArr['wall_time'] = (string) ((float) $filterArr['wall_time'] * 1000);
+ $this->request->withGet(array_merge($this->request->get(), [
+ 'filter' => json_encode($filterArr, JSON_UNESCAPED_UNICODE),
+ ]));
+ }
+ }
+ list($page, $limit, $where, $excludes, $request_options, $group) = $this->buildTableParames();
+ $count = $this->model
+ ->where($where)
+ ->group($group)
+ ->count();
+ $list = $this->model
+ ->where($where)
+ ->page($page, $limit)
+ ->order($this->sort)
+ ->group($group)
+ ->select();
+ $data = [
+ 'code' => 0,
+ 'msg' => '',
+ 'count' => $count,
+ 'data' => $list,
+ ];
+
+ return json($data);
+ }
+
+ // 节点下拉选项(多节点环境区分数据来源)
+ $nodeIds = $this->model->distinct(true)->column('node_id');
+ $nodeSelect = array_combine($nodeIds, $nodeIds) ?: [];
+ $this->assign('select_list_node_id', $nodeSelect, true);
+
+ return $this->fetch();
+ }
+}
\ No newline at end of file
diff --git a/app/admin/model/XhprofRun.php b/app/admin/model/XhprofRun.php
index 0bcf37f..330ac78 100644
--- a/app/admin/model/XhprofRun.php
+++ b/app/admin/model/XhprofRun.php
@@ -5,16 +5,16 @@ namespace app\admin\model;
use app\common\model\TimeModel;
/**
- * @property int $id 主键ID
- * @property string $node_id 节点ID(区分不同采集节点)
- * @property string $url 请求完整 URL(超长截断)
- * @property string $simple_url 规范化 URL(去 query、数字段替换,聚合用)
- * @property string $method HTTP 方法
- * @property int $wall_time 总耗时(微秒,main() 的 wt)
- * @property int $cpu_time CPU 耗时(微秒,main() 的 cpu)
- * @property int $memory_peak 内存峰值(字节,main() 的 pmu)
- * @property string $request_time 请求开始时间(REQUEST_TIME_FLOAT 浮点秒)
- * @property int $create_time 入库时间戳
+ * @property int $id
+ * @property string $node_id 节点ID(区分不同采集节点)
+ * @property string $url 请求完整 URL(超长截断)
+ * @property string $simple_url 规范化 URL(去 query、数字段替换,聚合用)
+ * @property string $method HTTP 方法
+ * @property int $wall_time 总耗时(微秒,main 的 wt)
+ * @property int $cpu_time CPU 耗时(微秒,main 的 cpu)
+ * @property int $memory_peak 内存峰值(字节,main 的 pmu)
+ * @property float|double $request_time 请求开始时间(REQUEST_TIME_FLOAT 浮点秒)
+ * @property int $create_time 入库时间戳
*/
class XhprofRun extends TimeModel
{
@@ -23,4 +23,8 @@ class XhprofRun extends TimeModel
protected $deleteTime = false;
+
+
+
+
}
diff --git a/app/admin/view/xhprof/run/_common.js b/app/admin/view/xhprof/run/_common.js
new file mode 100644
index 0000000..f4ca003
--- /dev/null
+++ b/app/admin/view/xhprof/run/_common.js
@@ -0,0 +1,12 @@
+var init = {
+ tableElem: '#currentTable',
+ tableRenderId: 'currentTableRenderId',
+ indexUrl: 'xhprof.run/index',
+ addUrl: '',
+ editUrl: '',
+ readUrl: '',
+ deleteUrl: '',
+ exportUrl: '',
+ modifyUrl: '',
+ detailUrl: 'xhprof.run/detail',
+};
diff --git a/app/admin/view/xhprof/run/add.html b/app/admin/view/xhprof/run/add.html
new file mode 100644
index 0000000..d70f578
--- /dev/null
+++ b/app/admin/view/xhprof/run/add.html
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/app/admin/view/xhprof/run/add.js b/app/admin/view/xhprof/run/add.js
new file mode 100644
index 0000000..4a445e0
--- /dev/null
+++ b/app/admin/view/xhprof/run/add.js
@@ -0,0 +1,3 @@
+$(function(){
+ ua.listen();
+})
\ No newline at end of file
diff --git a/app/admin/view/xhprof/run/edit.html b/app/admin/view/xhprof/run/edit.html
new file mode 100644
index 0000000..af7b9c1
--- /dev/null
+++ b/app/admin/view/xhprof/run/edit.html
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/app/admin/view/xhprof/run/edit.js b/app/admin/view/xhprof/run/edit.js
new file mode 100644
index 0000000..4a445e0
--- /dev/null
+++ b/app/admin/view/xhprof/run/edit.js
@@ -0,0 +1,3 @@
+$(function(){
+ ua.listen();
+})
\ No newline at end of file
diff --git a/app/admin/view/xhprof/run/index.html b/app/admin/view/xhprof/run/index.html
new file mode 100644
index 0000000..d9b8e18
--- /dev/null
+++ b/app/admin/view/xhprof/run/index.html
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/app/admin/view/xhprof/run/index.js b/app/admin/view/xhprof/run/index.js
new file mode 100644
index 0000000..7bae649
--- /dev/null
+++ b/app/admin/view/xhprof/run/index.js
@@ -0,0 +1,92 @@
+$(function () {
+ // request_time 为浮点秒(decimal(13,3)),渲染为日期时间
+ var formatRequestTime = function (d) {
+ var t = parseFloat(d.request_time);
+ if (!t || isNaN(t)) {
+ return '-';
+ }
+ var dt = new Date(t * 1000);
+ var p = function (n) {
+ return n < 10 ? '0' + n : '' + n;
+ };
+ return dt.getFullYear() + '-' + p(dt.getMonth() + 1) + '-' + p(dt.getDate())
+ + ' ' + p(dt.getHours()) + ':' + p(dt.getMinutes()) + ':' + p(dt.getSeconds());
+ };
+
+ ua.table.render({
+ init: init,
+ toolbar: [],
+ defaultToolbar: ['filter'],
+ cols: [[
+ {field: 'request_time', title: '请求时间', width: 170, sort: true, search: 'range', templet: formatRequestTime},
+ {
+ field: 'method', title: '方法', width: 90, search: 'select', selectList: {
+ GET: 'GET', POST: 'POST', PUT: 'PUT', DELETE: 'DELETE', HEAD: 'HEAD', OPTIONS: 'OPTIONS', PATCH: 'PATCH'
+ }, templet: function (d) {
+ var cls = {
+ GET: 'layui-bg-blue',
+ POST: 'layui-bg-green',
+ PUT: 'layui-bg-orange',
+ DELETE: 'layui-bg-red',
+ HEAD: 'layui-bg-gray',
+ OPTIONS: 'layui-bg-gray',
+ PATCH: 'layui-bg-cyan'
+ }[d.method];
+ if (d.method && cls) {
+ return '' + d.method + '';
+ }
+ return d.method || '-';
+ }
+ },
+ {
+ field: 'url', title: 'URL', minWidth: 220, searchOp: '%*%', templet: function (d) {
+ var u = d.url || '';
+ var short = u.length > 50 ? u.substring(0, 50) + '…' : u;
+ return '' + (short === '' ? '/' : short) + '';
+ }
+ },
+ {field: 'node_id', title: '节点', width: 160, search: 'select', searchOp: '=', selectList: ua.getDataBrage('select_list_node_id') || {}},
+ {
+ field: 'wall_time', title: '总耗时', width: 110, sort: true, searchOp: 'min', searchTip: '总耗时下限(ms)', templet: function (d) {
+ var ms = parseInt(d.wall_time, 10) / 1000;
+ var txt = ms.toFixed(1) + 'ms';
+ if (ms > 1000) {
+ return '' + txt + '';
+ }
+ return txt;
+ }
+ },
+ {
+ field: 'cpu_time', title: 'CPU耗时', width: 110, search: false, templet: function (d) {
+ var ms = parseInt(d.cpu_time, 10) / 1000;
+ var txt = ms.toFixed(1) + 'ms';
+ if (ms > 1000) {
+ return '' + txt + '';
+ }
+ return txt;
+ }
+ },
+ {
+ field: 'memory_peak', title: '内存峰值', width: 110, sort: true, search: false, templet: function (d) {
+ return (parseInt(d.memory_peak, 10) / 1048576).toFixed(2) + 'MB';
+ }
+ },
+ {
+ width: 100, title: '操作', fixed: 'right', templet: ua.table.tool, operat: [
+ [{
+ class: 'layui-btn layui-btn-primary layui-btn-xs',
+ method: 'open',
+ field: 'id',
+ text: '详情',
+ title: '采样详情(函数级分析)',
+ auth: 'detail',
+ url: init.detailUrl,
+ extend: 'data-full="true"'
+ }]
+ ]
+ }
+ ]]
+ });
+
+ ua.listen();
+});
diff --git a/app/admin/view/xhprof/run/read.html b/app/admin/view/xhprof/run/read.html
new file mode 100644
index 0000000..14d0f75
--- /dev/null
+++ b/app/admin/view/xhprof/run/read.html
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
节点ID(区分不同采集节点)
+
+ {notempty name="row.node_id"}
+ {$row.node_id}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
请求完整 URL(超长截断)
+
+ {notempty name="row.url"}
+ {$row.url}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
规范化 URL(去 query、数字段替换,聚合用)
+
+ {notempty name="row.simple_url"}
+ {$row.simple_url}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
HTTP 方法
+
+ {notempty name="row.method"}
+ {$row.method}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
总耗时(微秒,main 的 wt)
+
+ {notempty name="row.wall_time"}
+ {$row.wall_time}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
CPU 耗时(微秒,main 的 cpu)
+
+ {notempty name="row.cpu_time"}
+ {$row.cpu_time}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
内存峰值(字节,main 的 pmu)
+
+ {notempty name="row.memory_peak"}
+ {$row.memory_peak}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
请求开始时间(REQUEST_TIME_FLOAT 浮点秒)
+
+ {notempty name="row.request_time"}
+ {$row.request_time}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
+
+
+
+
基础信息
+
+
+
+
入库时间戳
+
+ {notempty name="row.create_time"}
+ {$row.create_time|date="Y-m-d H:i:s"}
+ {else/}
+ 暂无数据
+ {/notempty}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/admin/view/xhprof/run/read.js b/app/admin/view/xhprof/run/read.js
new file mode 100644
index 0000000..f5671a7
--- /dev/null
+++ b/app/admin/view/xhprof/run/read.js
@@ -0,0 +1,22 @@
+$(function(){
+ // 删除数据
+ window.deleteData = function(id) {
+ layer.confirm('确定要删除这条数据吗?', {
+ icon: 3,
+ title: '提示'
+ }, function(index) {
+ $.post('{{:url("delete")}}', {id: id}, function(res) {
+ if (res.code == 0) {
+ layer.msg('删除成功', {icon: 1}, function() {
+ location.href = '{{:url("index")}}';
+ });
+ } else {
+ layer.msg(res.msg, {icon: 2});
+ }
+ }, 'json');
+ layer.close(index);
+ });
+ };
+
+ ua.listen();
+})
\ No newline at end of file