完善monitor

This commit is contained in:
easonzhu 2025-04-28 16:18:10 +08:00
parent 57baf86fbf
commit 45d58895cf
3 changed files with 171 additions and 62 deletions

View File

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.14/theme-chalk/index.min.css">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="https://cdn.staticfile.org/element-ui/2.15.14/theme-chalk/index.min.css" rel="stylesheet">
<title>Interface Monitor</title>
<script src="https://cdn.staticfile.org/vue/2.7.14/vue.min.js"></script>
<script src="https://cdn.staticfile.org/blueimp-md5/2.19.0/js/md5.min.js"></script>
@ -28,17 +28,17 @@
}
.el-form-item__content {
margin-left: 20px !important;
margin-left: 6px !important;
}
.resu {
margin-left: 30%;
width: 70%;
min-height: 1080px;
margin-left: 360px;
width: 1160px;
min-height: 700px;
}
.line {
width: 1000px;
width: 1100px;
height: 300px;
}
</style>
@ -46,51 +46,70 @@
<body>
<div id="app">
<div style="width:30%;position: fixed;padding: 20px 0 0 50px;">
<div style="width:320px; position: fixed; padding: 20px 0 0 30px;">
<el-form label-width="80px">
<el-form-item label="Host:">
<div style="width: 108%;">
<el-select v-model="query.host" placeholder="请选择">
<el-option v-for="item in data.host" :key="item" :label="item" :value="item"></el-option>
<el-select @change="init()" placeholder="请选择" v-model="query.host">
<el-option :key="item" :label="item" :value="item" v-for="item in data.host"></el-option>
</el-select>
</div>
</el-form-item>
<el-form-item label="开始时间:">
<el-date-picker v-model="query.startTime" type="datetime" placeholder="选择开始时间">
<el-date-picker @change="init()" placeholder="选择开始时间" type="datetime" v-model="query.startTime">
</el-date-picker>
</el-form-item>
<el-form-item label="结束时间:">
<el-date-picker v-model="query.endTime" type="datetime" placeholder="选择结束时间">
<el-date-picker @change="init()" placeholder="选择结束时间" type="datetime" v-model="query.endTime">
</el-date-picker>
</el-form-item>
<el-form-item label="IP:">
<el-select v-model="query.ip" placeholder="请选择">
<el-option v-for="item in data.ip" :key="item" :label="item" :value="item">
<el-select placeholder="请选择" v-model="query.ip">
<el-option :key="item" :label="item" :value="item" @change="init()" v-for="item in data.ip">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="接口:">
<el-select v-model="query.name" placeholder="请选择">
<el-option v-for="item in data.name" :key="item" :label="item" :value="item">
<el-select @change="init(false)" placeholder="请选择" v-model="query.name">
<el-option :key="item" :label="item" :value="item" v-for="item in data.name">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="分组方式:">
<el-radio v-for="item in data.groupBy" v-model="query.groupBy" :label="item">{{ item }}</el-radio>
<el-radio :label="item" @change="groupData()" v-for="item in data.groupBy" v-model="query.groupBy">{{ item }}</el-radio>
</el-form-item>
<el-form-item>
<div style="margin-left: 20%; display: flex;">
<el-button type="primary" @click="listData()">查询曲线</el-button>
<el-button type="primary" @click="groupData()">查询分组</el-button>
<el-button @click="listData()" type="primary">查询曲线</el-button>
<el-button @click="groupData()" type="primary">查询分组</el-button>
</div>
</el-form-item>
</el-form>
</div>
<div class="resu">
<div id="total" class="line"></div>
<div id="average" class="line"></div>
<div id="failure" class="line"></div>
<div id="timeout" class="line"></div>
<div id="line" v-show="data.showLine">
<div class="line" id="total"></div>
<div class="line" id="average"></div>
<div class="line" id="failure"></div>
<div class="line" id="timeout"></div>
</div>
<div id="table" v-show="data.showTable">
<el-table :data="data.groupData" :default-sort = "{prop: 'total', order: 'descending'}" @sort-change="sortChange" height="700" >
<el-table-column label="IP" prop="ip" v-if="query.groupBy === 'ip'" width="120"></el-table-column>
<el-table-column label="接口" prop="interfaceName" v-if="query.groupBy === 'name'" width="230"></el-table-column>
<el-table-column label="数量" prop="total" width="80" sortable='custom'></el-table-column>
<el-table-column label="失败" prop="failure" width="80" sortable='custom'></el-table-column>
<el-table-column label="平均" prop="averageTime" width="80" sortable='custom'></el-table-column>
<el-table-column label="最大" prop="maxTime" width="80" sortable='custom'></el-table-column>
<el-table-column label="最小" prop="minTime" width="80" sortable='custom'></el-table-column>
<el-table-column label="10ms" prop="over10ms" width="80" sortable='custom'></el-table-column>
<el-table-column label="50ms" prop="over50ms" width="80" sortable='custom'></el-table-column>
<el-table-column label="100ms" prop="over100ms" width="88" sortable='custom'></el-table-column>
<el-table-column label="500ms" prop="over500ms" width="88" sortable='custom'></el-table-column>
<el-table-column label="1000ms" prop="over1000ms" width="96" sortable='custom'></el-table-column>
<el-table-column label="5000ms" prop="over5000ms" width="96" sortable='custom'></el-table-column>
</el-table>
</div>
</div>
</div>
@ -176,20 +195,19 @@
total: 0
});
}
current = current.add(5, 'minute');
}
return result;
}
function drawLine(data, div, field, filter) {
function drawLine(data, title, div, field, filter) {
if (data && data.length) {
const xyData = data.map(v => [v.time, v[field]]);
const myChart = echarts.init(document.getElementById(div));
myChart.setOption({
const chart = echarts.init(document.getElementById(div));
chart.setOption({
title: {
text: field
text: title
},
xAxis: {
type: 'time',
@ -206,6 +224,9 @@
type: 'line',
data: xyData,
showSymbol: false,
lineStyle: {
color: '#409eff' // 设置折线的颜色为红色
},
}
]
})
@ -232,38 +253,44 @@
data() {
return {
query: {
host: "http://8.138.144.54:8080",
host: "https://do.tgsys.sztg.com",
// startTime: dayjs().format('YYYY-MM-DD 00:00:00'),
// endTime: dayjs().add(1, 'day').format('YYYY-MM-DD 00:00:00'),
startTime: dayjs().subtract(1, 'day').format('YYYY-MM-DD 00:00:00'),
endTime: dayjs().format('YYYY-MM-DD 00:00:00'),
startTime: dayjs().format('YYYY-MM-DD 00:00:00'),
endTime: dayjs().add(1, 'day').format('YYYY-MM-DD 00:00:00'),
ip: "",
name: "",
groupBy: "name",
orderBy: "",
},
data: {
host: ["http://8.138.144.54:8080"],
host: ["https://do.tgsys.sztg.com", "http://8.138.144.54:8080", "http://127.0.0.1:8080"],
ip: [],
name: [],
groupBy: ["name", "ip"],
groupData: [],
showLine: false,
showTable: false,
},
monitor: [],
interval: null,
};
},
methods: {
async init() {
async init(loadName = true) {
const ip = await API.listIP(this.query.host, this.query)
if(ip){
if (ip) {
ip.unshift("全部")
this.data.ip = ip;
this.query.ip = ip[0];
}
const name = await API.listInterface(this.query.host, this.query)
if(name){
name.unshift("全部")
this.data.name = name;
this.query.name = name[0];
if (loadName) {
const name = await API.listInterface(this.query.host, this.query)
if(name){
name.unshift("全部")
this.data.name = name;
this.query.name = name[0];
}
}
},
async listData() {
@ -271,24 +298,40 @@
// console.log('before', data);
data = fillTimeData(this.query.startTime, this.query.endTime, data);
// console.log('after', data);
drawLine(data, 'total', 'total');
drawLine(data, 'average', 'averageTime');
drawLine(data, 'failure', 'failure');
drawLine(data, 'timeout', 'over5000ms');
drawLine(data, '总流量', 'total', 'total');
drawLine(data, '平均耗时', 'average', 'averageTime');
drawLine(data, '失败流量', 'failure', 'failure');
drawLine(data, '超时流量', 'timeout', 'over5000ms');
this.data.showLine = true;
this.data.showTable = false;
},
async groupData() {
let groupData = await API.groupData(this.query.host, this.query);
// groupData = groupData.filter(data => data.interfaceName && data.interfaceName.indexOf('/admin/monitor') === -1);
this.data.groupData = groupData;
this.data.showLine = false;
this.data.showTable = true;
},
async sortChange({column, prop, order}) {
console.log('sortChange', column, prop, order);
this.query.orderBy = prop;
this.query.order = order;
console.log(this.query);
this.groupData();
}
},
async mounted() {
this.init();
},
watch: {
'query.host': async function (newVal) {
if (this.interval) {
clearTimeout(this.interval)
}
this.interval = setTimeout(async () => {
this.init();
}, 100);
},
// 'query.host': async function (newVal) {
// if (this.interval) {
// clearTimeout(this.interval)
// }
// this.interval = setTimeout(async () => {
// this.init();
// }, 100);
// },
},
});
</script>

View File

@ -19,6 +19,10 @@ public class MonitorQuery {
private String groupBy;
private String orderBy;
private String order;
public LocalDateTime getStartTime() {
return startTime;
}
@ -66,4 +70,20 @@ public class MonitorQuery {
public void setGroupBy(String groupBy) {
this.groupBy = groupBy;
}
public String getOrderBy() {
return orderBy;
}
public void setOrderBy(String orderBy) {
this.orderBy = orderBy;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
}

View File

@ -1,11 +1,14 @@
package com.syzb.monitor.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.hazelcast.core.HazelcastInstance;
import com.syzb.common.handler.BizException;
import com.syzb.common.result.ResponseStatus;
@ -13,16 +16,16 @@ import com.syzb.monitor.entity.InterfaceMonitor;
import com.syzb.monitor.mapper.InterfaceMonitorMapper;
import com.syzb.monitor.query.MonitorQuery;
import com.syzb.monitor.vo.MonitorResult;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
@ -39,6 +42,23 @@ public class MonitorService {
// 记录保存天数
private static final int SAVE_DAY = 15;
// 固定排序字段和列关系防止SQL注入
private static Map<String, String> ORDER_BY_MAP = new HashMap<>();
static {
ORDER_BY_MAP.put("total", "total");
ORDER_BY_MAP.put("averageTime", "average_time");
ORDER_BY_MAP.put("maxTime", "max_time");
ORDER_BY_MAP.put("minTime", "min_time");
ORDER_BY_MAP.put("failure", "failure");
ORDER_BY_MAP.put("over10ms", "over_10ms");
ORDER_BY_MAP.put("over50ms", "over_50ms");
ORDER_BY_MAP.put("over100ms", "over_100ms");
ORDER_BY_MAP.put("over500ms", "over_500ms");
ORDER_BY_MAP.put("over1000ms", "over_1000ms");
ORDER_BY_MAP.put("over5000ms", "over_5000ms");
}
@PostConstruct
private void loadHost() {
host = hazelcastInstance.getCluster().getLocalMember().getAddress().getHost();
@ -54,13 +74,32 @@ public class MonitorService {
this.results.add(new MonitorResult(name, time, code));
}
@Scheduled(cron = "0 * * * * ?")
public void save() {
List<InterfaceMonitor> list = this.statistic();
interfaceMonitorMapper.insertBatchSomeColumn(list);
// 使用自定义线程池+任务实现定时器避免scheduledEnable条件只跑admin服务
@PostConstruct
public void init() {
// 创建一个调度线程池
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// 每分钟 保存日志
scheduler.scheduleAtFixedRate(() -> save(), 1, 1, TimeUnit.MINUTES);
// 每天 清理日子
scheduler.scheduleAtFixedRate(() -> clear(), 0, 1, TimeUnit.DAYS);
}
@Scheduled(cron = "0 1 2 * * ?")
// @Scheduled(cron = "0 * * * * ?")
public void save() {
// 5分钟执行一次
if (LocalDateTime.now().getMinute() % 5 != 0) {
return;
}
List<InterfaceMonitor> list = this.statistic();
if (CollUtil.isNotEmpty(list)) {
interfaceMonitorMapper.insertBatchSomeColumn(list);
}
}
// @Scheduled(cron = "0 1 2 * * ?")
public void clear() {
LocalDateTime time = LocalDate.now().minusDays(SAVE_DAY).atStartOfDay();
LambdaQueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>lambdaQuery()
@ -126,6 +165,11 @@ public class MonitorService {
} else {
throw new BizException(ResponseStatus.OUTSYS_ERROR);
}
String orderBy = query.getOrderBy();
if (StrUtil.isNotEmpty(orderBy)) {
orderBy = ORDER_BY_MAP.get(orderBy);
}
String order = query.getOrder();
QueryWrapper<InterfaceMonitor> wrapper = Wrappers.<InterfaceMonitor>query()
.select(groupColumn,
"sum(total) as total", "avg(average_time) as average_time", "max(max_time) as max_time", "min(min_time) as min_time",
@ -134,7 +178,9 @@ public class MonitorService {
.ge("time", startTime)
.lt("time", endTime)
.groupBy(groupColumn)
.orderByAsc(groupColumn);
.orderByAsc(StrUtil.isEmpty(orderBy) || (!"ascending".equals(order) && !"descending".equals(order)), groupColumn)
.orderByAsc(StrUtil.isNotEmpty(orderBy) && "ascending".equals(order), orderBy)
.orderByDesc(StrUtil.isNotEmpty(orderBy) && "descending".equals(order), orderBy);
return interfaceMonitorMapper.selectList(wrapper);
}