| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- <template>
- <div style="width: 98%;margin-left: 1%;">
- <el-tabs
- v-model="activeName"
- type="card"
- class="demo-tabs"
-
- @tab-click="handleClick"
- >
- <el-tab-pane label="模型事件" name="first">
- <el-form ref="formRef" :inline="true" :model="queryParams1">
- <el-form-item label="运行事件">
- <el-select
- v-model="queryParams1.event"
- placeholder=""
- clearable
- style="width: 150px"
- >
- <el-option
- v-for="dict in model_run_log_event"
- :key="dict.value"
- :label="dict.label"
- :value="dict.value"
- />
- </el-select>
- </el-form-item>
- <el-form-item label="模型名称">
- <el-input v-model="queryParams1.mdName" style="width: 150px"></el-input>
- </el-form-item>
- <el-form-item label="时间">
- <el-date-picker v-model="queryParams1.time" type="daterange" range-separator="至"
- start-placeholder="开始时间" end-placeholder="结束时间"
- value-format="YYYY-MM-DD">
- </el-date-picker>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="getList1" icon="Search">搜索</el-button>
- </el-form-item>
- </el-form>
- <div style="display: flex;width: 100%;">
- <div style="width: 60%;">
- <el-table
- stripe
- style="height: 68vh;margin-top: 1%;width: 100%;"
- :data="tableDataJian"
- :cell-style="{ padding: '5px' }"
- :row-style="{ fontSize: '1rem', textAlign:'center' }"
- border>
- <el-table-column type="index" align="center" label="序号" width="60">
- <template #default="{ $index }">
- <div style="text-align: center;">{{ $index + 1 }}</div>
- </template>
- </el-table-column>
- <el-table-column show-overflow-tooltip header-align="center" align="left" prop="createBy" label="人员名称"/>
- <el-table-column show-overflow-tooltip header-align="center" align="left" prop="createTime" label="创建时间"/>
- <el-table-column show-overflow-tooltip header-align="center" align="left" prop="mdName" label="模型名称"/>
- <el-table-column show-overflow-tooltip header-align="center" align="left" prop="event" label="运行事件"/>
- </el-table>
- <div style="display: flex;">
- <el-pagination
- small
- background
- style="margin-top: 1%;margin-left: auto;display: flex;margin-right: 1%;"
- layout="prev, pager, next"
- :total="totalJian"
- v-model="pageNumJian"
- @change="fanJian"
- class="mt-4"
- />
- </div>
- </div>
- <div style="width: 49%;margin-left: 1%;margin-top: 1%;">
- <div>
- 过去7天模型吞吐量
- </div>
- <div style="height: 40vh;width: 100%;background-color: ;margin-top: 1%;" id="right1">
- </div>
- <div style="margin-top: 3%;">
- 过去7天模型错误率
- </div>
- <div style="height: 30vh;width: 100%;background-color: ;margin-top: 1%;" id="right2">
- </div>
- </div>
- </div>
-
-
- </el-tab-pane>
- <el-tab-pane label="模型服务监控" name="second" style="height: 100vh;">
- <el-row>
- <!-- 筛选条件 -->
- <el-form ref="formRef" :inline="true" :model="queryParams">
- <el-form-item label="服务名称">
- <el-input v-model="queryParams.name"></el-input>
- </el-form-item>
- <el-form-item label="请求用户">
- <el-input v-model="queryParams.userName"></el-input>
- </el-form-item>
- <el-form-item label="时间">
- <el-date-picker v-model="queryParams.time" type="daterange" unlink-panels range-separator="至"
- start-placeholder="开始时间" end-placeholder="结束时间" :shortcuts="shortcuts"
- format="yyyy-MM-dd">
- </el-date-picker>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="getList" icon="Search">搜索</el-button>
- </el-form-item>
- </el-form>
- </el-row>
- <el-row class="table_box">
- <el-table
- stripe
- style="height: 68vh;"
- :data="tableData"
- :cell-style="{ padding: '5px' }"
- :row-style="{ fontSize: '1rem', textAlign:'center' }"
- border>
- <el-table-column type="index" align="center" label="序号" width="60">
- <template #default="{ $index }">
- <div style="text-align: center;">{{ $index + 1 }}</div>
- </template>
- </el-table-column>
- <el-table-column show-overflow-tooltip header-align="center" align="left" width="240" prop="modelName"
- label="调用模型"/>
- <el-table-column show-overflow-tooltip header-align="center" align="left" width="200" prop="serviceName"
- label="服务名称"/>
- <el-table-column prop="url" show-overflow-tooltip header-align="center" align="left" label="服务地址"/>
- <el-table-column show-overflow-tooltip header-align="center" align="left" width="220" prop="senText"
- label="请求参数" :tooltip-formatter="({ row }) => {
- if( row.senText.length >= 30){
- return row.senText.substring(0, 30) + '...';
- }
- return row.senText;
- }">
- <template #default="scope">
- <span style="cursor: copy;" @click="copyParams(scope.row.senText)">{{ scope.row.senText }}</span>
- </template>
- </el-table-column>
- <el-table-column show-overflow-tooltip align="center" width="180" prop="tm" label="请求时间"></el-table-column>
- <el-table-column show-overflow-tooltip align="center" width="120" prop="execTm" label="请求耗时(ms)"/>
- <el-table-column show-overflow-tooltip align="center" width="100" prop="userName" label="请求用户"/>
- <el-table-column show-overflow-tooltip header-align="center" align="left" width="220" prop="appName"
- label="请求应用"/>
- <el-table-column show-overflow-tooltip align="center" width="100" prop="statusCode" label="请求状态">
- <template #default="scope">
- <el-tag v-if="scope.row.statusCode==200">成功</el-tag>
- <el-tag v-else type="danger">失败</el-tag>
- </template>
- </el-table-column>
- </el-table>
- </el-row>
- <pagination
- v-show="total>0"
- :total="total"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- style="margin-top: 1%;"
- @pagination="getList"
- />
- <el-dialog v-model="open" title="服务日志详情" width="60%" append-to-body>
- </el-dialog>
- </el-tab-pane>
- </el-tabs>
-
- </div>
- </template>
- <script setup>
- import * as echarts from 'echarts';
- import {onMounted, reactive, ref} from "vue";
- import {getServiceLogList,getMdLogList,getMdStatusSum,getMdAllList } from "@/api/service/log.js";
- import useClipboard from 'vue-clipboard3'
- const {toClipboard} = useClipboard()
- const {proxy} = getCurrentInstance();
- const { model_run_log_event } = proxy.useDict("model_run_log_event");
- const tableHeight = ref(window.innerHeight - 400)
- const queryParams1 = ref({
- time:[]
- })
- const queryParams = reactive({
- pageNum: 1,
- pageSize: 20,
- orderBy: "",
- name: "",
- userName: "",
- time: [],
- sttm: "",
- entm: "",
- })
- const totalJian = ref(0)
- const tableDataJian = ref([])
- const pageNumJian = ref(1)
- const activeName = ref('first')
- const tableData = ref([])
- const total = ref(0)
- const shortcuts = ref([
- {
- text: "近一周",
- value: () => {
- const end = new Date();
- const start = new Date();
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
- return [start, end];
- },
- },
- {
- text: "近一个月",
- value: () => {
- const end = new Date();
- const start = new Date();
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
- return [start, end];
- },
- },
- {
- text: "近三个月",
- value: () => {
- const end = new Date();
- const start = new Date();
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
- return [start, end];
- },
- },
- {
- text: "近一年",
- value: () => {
- const end = new Date();
- const start = new Date();
- start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
- return [start, end];
- },
- },
- ])
- async function copyParams(message) {
- toClipboard(message).then(() => {
- proxy.$modal.msgSuccess("复制成功!");
- })
- }
- function fanJian(val){
- pageNumJian.value = val
- getList1()
- }
- function getList1(){
- queryParams1.value.pageSize = 20
- queryParams1.value.pageNum = pageNumJian.value
- var par = {
- pageNum:pageNumJian.value,
- pageSize:20,
- event:queryParams1.value.event,
- mdName:queryParams1.value.mdName,
- params:{
- beginTime:queryParams1.value.time==[]?'':queryParams1.value.time[0],
- endTime:queryParams1.value.time==[]?'':queryParams1.value.time[1],
- }
- }
- getMdLogList(par).then(r=>{
- tableDataJian.value = r.rows;
- totalJian.value = r.total;
- for(var i = 0;i<tableDataJian.value.length;i++){
- for(var i1 = 0;i1<model_run_log_event.value.length;i1++){
- if(tableDataJian.value[i].event===model_run_log_event.value[i1].value){
- tableDataJian.value[i].event = model_run_log_event.value[i1].label
- }
- }
- }
- })
-
- }
- function getLast7DaysRange() {
- const end = new Date();
- const start = new Date();
- end.setDate(start.getDate() - 1);
- start.setDate(end.getDate() - 7);
- function formatDate(date) {
- const year = date.getFullYear();
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
- return `${year}-${month}-${day}`;
- }
- return {
- start: formatDate(start),
- end: formatDate(end)
- };
- }
- function mergeModelData(data) {
- const mergeMap = {};
-
- data.forEach(item => {
- const mdName = item.mdName?.trim(); // 处理可能的空格
- if (!mdName) return;
-
- if (!mergeMap[mdName]) {
- mergeMap[mdName] = {
- mdName: mdName,
- mdId: item.mdId,
- statisNum: 0,
- statisNumErr: 0
- };
- }
-
- const current = mergeMap[mdName];
- const statisNumValue = Number(item.statisNum) || 0;
-
- if (item.statusCode === '200') {
- // statusCode为200,保留statisNum
- current.statisNum = statisNumValue;
- } else if (item.statusCode) {
- // 其他statusCode,累加到statisNumErr
- current.statisNumErr += statisNumValue;
- }
-
- // 保留第一个非空的mdId
- if (!current.mdId && item.mdId) {
- current.mdId = item.mdId;
- }
- });
-
- return Object.values(mergeMap);
- }
- async function drawRight2(){
- var chartDom = document.getElementById('right2');
- var myChart = echarts.init(chartDom);
- var option;
- const last7Days = getLast7DaysRange();
- var par1 = {
- params:{
- beginTime:last7Days.start,
- endTime:last7Days.end,
- }
- }
- var uniqueMdNames
- var mergedData
- var success = []
- var error = []
- await getMdStatusSum(par1).then(res=>{
- uniqueMdNames = [...new Map(res.data.map(item => [item.mdName, item.mdName])).values()].filter(Boolean);
- mergedData = mergeModelData(res.data)
- mergedData.forEach(item=>{
- success.push(item.statisNum)
- error.push(item.statisNumErr)
- })
- })
- option = {
- grid: {
- top: '15%', // 图表距离容器顶部的距离
- right: '5%', // 图表距离容器右侧的距离
- bottom: '15%', // 图表距离容器底部的距离
- left: '10%', // 图表距离容器左侧的距离
- containLabel: true // 确保坐标轴标签在 grid 区域内
- },
- tooltip: {
- trigger: 'axis',
- axisPointer: {
- type: 'shadow'
- }
- },
- legend: {},
- color: ['#67C23A','#F56C6C',],
- xAxis: [
- {
- type: 'category',
- data: uniqueMdNames,
- axisLabel: {
- interval: 0, // 强制显示所有标签
- rotate: -25, // 旋转角度,正值表示顺时针旋转,负值表示逆时针旋转
- // 可以设置文字样式,如字体大小、颜色等
- textStyle: {
- fontSize: 9,
- color: '#333'
- }
- }
- }
- ],
- yAxis: [
- {
- type: 'value'
- }
- ],
- series: [
- {
- name: '成功',
- type: 'bar',
- stack: 'Ad',
- emphasis: {
- focus: 'series'
- },
- data: success,
-
- },
- {
- name: '失败',
- type: 'bar',
- stack: 'Ad',
- emphasis: {
- focus: 'series'
- },
- data: error,
- label: {
- show: true, // 显示标签
- position: 'top', // 标签位置在柱子上方
- // 自定义标签内容,formatter可以是字符串模板,也可以是回调函数
- formatter: function(params) {
- // 计算每个柱子占当天的总比例
- const total = params.data + option.series[0].data[params.dataIndex];
- const percent = ((params.data / total) * 100).toFixed(1);
- if(params.data){
- return params.data + `\n(${percent}%)`;
- }
- },
- // 标签文本样式
- textStyle: {
- color: '#000', // 颜色
- fontSize: 12
- }
- }
- },
- ]
- };
- option && myChart.setOption(option);
- }
- function getList() {
- getServiceLogList(queryParams)
- .then((r) => {
- tableData.value = r.rows;
- total.value = r.total;
- });
- }
- function processModelData(data) {
- const resultMap = {};
-
- data.forEach(item => {
- const mdName = item.mdName?.trim();
- if (!mdName) return;
-
- const statisNum = Number(item.statisNum) || 0;
-
- if (!resultMap[mdName]) {
- // 初始化该模型的数据
- resultMap[mdName] = {
- mdName: mdName,
- mdId: item.mdId || null,
- totalStatisNum: statisNum,
- recordCount: 1,
- firstRecordDate: item.statisTm,
- lastRecordDate: item.statisTm,
- records: [item] // 可选:保留详细记录
- };
- } else {
- // 更新现有模型数据
- resultMap[mdName].totalStatisNum += statisNum;
- resultMap[mdName].recordCount += 1;
-
- // 更新最新记录日期
- if (item.statisTm && (!resultMap[mdName].lastRecordDate || item.statisTm > resultMap[mdName].lastRecordDate)) {
- resultMap[mdName].lastRecordDate = item.statisTm;
- }
-
- resultMap[mdName].records.push(item);
- }
- });
-
- // 转换为数组并排序(按统计数字降序)
- return Object.values(resultMap)
- .sort((a, b) => b.totalStatisNum - a.totalStatisNum);
- }
- function getDateRange(startStr, endStr) {
- const startDate = new Date(startStr);
- const endDate = new Date(endStr);
- const dates = [];
-
- // 验证日期有效性
- if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
- throw new Error('无效的日期格式');
- }
-
- if (startDate > endDate) {
- throw new Error('开始日期不能晚于结束日期');
- }
-
- // 复制开始日期对象,避免修改原日期
- const currentDate = new Date(startDate);
-
- // 循环直到当前日期超过结束日期
- while (currentDate <= endDate) {
- // 格式化为 YYYY-MM-DD
- const year = currentDate.getFullYear();
- const month = String(currentDate.getMonth() + 1).padStart(2, '0');
- const day = String(currentDate.getDate()).padStart(2, '0');
-
- dates.push(`${year}-${month}-${day}`);
-
- // 日期加一天
- currentDate.setDate(currentDate.getDate() + 1);
- }
-
- return dates;
- }
- async function drawRight1(){
- var chartDom = document.getElementById('right1');
- var myChart = echarts.init(chartDom);
- var option;
- const last7Days = getLast7DaysRange();
- var par = {
- params:{
- beginTime:last7Days.start,
- endTime:last7Days.end,
- }
- }
- var x = getDateRange(last7Days.start,last7Days.end)
- var y = []
- await getMdAllList(par).then(res=>{
- res.data.forEach(item=>{
- item.value = []
- item.lineCharts.forEach(item1=>{
- item.value.push(item1.num)
- })
- })
- res.data.forEach(item=>{
- var par = {
- name: item.mdName,
- type: 'line',
- data: item.value,
- smooth: true
- }
- y.push(par)
- })
- console.log(res.data)
- })
- option = {
- grid: {
- top: '10%', // 图表距离容器顶部的距离
- right: '5%', // 图表距离容器右侧的距离
- bottom: '15%', // 图表距离容器底部的距离
- left: '10%', // 图表距离容器左侧的距离
- containLabel: true // 确保坐标轴标签在 grid 区域内
- },
- tooltip: {
- trigger: 'axis',
- show:'false'
- },
- legend: {
- top:'88%'
- },
- xAxis: {
- splitLine: { show: false },
- // type: 'category',
- data: x,
- axisLabel: {
- top:'10%',
- interval: 0, // 强制显示所有标签
- rotate: -25, // 旋转角度,正值表示顺时针旋转,负值表示逆时针旋转
- // 可以设置文字样式,如字体大小、颜色等
- textStyle: {
- fontSize: 9,
- color: '#333'
- }
- }
- },
- yAxis: {
- type: 'value',
- show: true,
- splitLine: { show: false },
- name: '单位:次',
- minInterval: 1,
- axisTick: {
- show: true // 确保显示刻度线
- },
- axisLine: {
- show: true, // 确保显示轴线
- lineStyle: {
- color: '#333', // 可以设置轴线的颜色,例如与文字颜色一致
- // width: 1 // 可以设置轴线宽度,可选
- }
- },
- },
- series:y
- };
-
- option && myChart.setOption(option);
- }
- onMounted(() => {
- getList()
- getList1()
- drawRight2()
- drawRight1()
- });
- watch(() => queryParams.time, value => {
- queryParams.sttm = value[0];
- queryParams.entm = value[1];
- }, {immediate: true})
- </script>
- <style scoped>
- .el-row {
- margin-bottom: 10px;
- }
- .table_box ::v-deep .el-table {
- border: 1px solid #e6e6e6;
- border-right: 1px solid #e6e6e6;
- border-bottom: none;
- border-radius: 5px;
- }
- ::v-deep .el-form-item {
- margin-bottom: 0;
- }
- </style>
|