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 @@ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ {notempty name='$Request.param.backTagId'} +
返回
+ {/notempty} + + +
+ +
+
\ 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 @@ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ {notempty name='$Request.param.backTagId'} +
返回
+ {/notempty} + + +
+ +
+
\ 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 @@ +
+
+
+
+
+
+

#{$row.id} {$title}

+
ID: {$row.id}
+
+
+ + +
+
+
+
+
+ +
+
+
+
节点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} +
+
+ +
+
+ +
+

基础信息

+
+
+
ID
+
{$row.id}
+
+
+
入库时间戳
+
+ {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