274 lines
8.5 KiB
Go
274 lines
8.5 KiB
Go
package controller
|
||
|
||
import (
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"ezops/model"
|
||
snmppkg "ezops/pkg/snmp"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// snmpChartableNames 适合折线图展示的标量指标 // Logan.Li
|
||
var snmpChartableNames = []string{
|
||
"h3c_cpu_usage", "hw_cpu_usage", "cisco_cpu_5min", "ruijie_cpu_usage", "inspur_cpu_usage",
|
||
"h3c_mem_usage", "hw_mem_usage", "ruijie_mem_usage", "inspur_mem_usage",
|
||
"h3c_temperature", "hw_temperature", "ruijie_temperature", "dell_temperature", "inspur_temperature",
|
||
"h3c_fan_speed", "hw_fan_speed", "ruijie_fan_speed", "dell_fan_speed", "inspur_fan_speed",
|
||
"cisco_sensor_value", "ent_sensor_value",
|
||
"if_in_octets", "if_out_octets",
|
||
}
|
||
|
||
// snmpCounterMetrics 累计型 counter,历史查询需差分换算速率 // Logan.Li
|
||
var snmpCounterMetrics = map[string]bool{
|
||
"if_in_octets": true,
|
||
"if_out_octets": true,
|
||
}
|
||
|
||
type snmpMetricSeries struct {
|
||
Name string `json:"name"`
|
||
Index string `json:"index,omitempty"`
|
||
Unit string `json:"unit,omitempty"`
|
||
Label string `json:"label"`
|
||
LatestValue float64 `json:"latest_value"`
|
||
}
|
||
|
||
type snmpHistoryPoint struct {
|
||
Timestamp int64 `json:"timestamp"`
|
||
Value float64 `json:"value"`
|
||
Unit string `json:"unit,omitempty"`
|
||
}
|
||
|
||
type snmpOverviewItem struct {
|
||
DeviceID uint `json:"device_id"`
|
||
Name string `json:"name"`
|
||
IP string `json:"ip"`
|
||
Status string `json:"status"`
|
||
CPU float64 `json:"cpu,omitempty"`
|
||
Mem float64 `json:"mem,omitempty"`
|
||
Temp float64 `json:"temp,omitempty"`
|
||
}
|
||
|
||
// ListMetricSeries GET /snmp/devices/:id/metrics/series — 可绘图指标序列 // Logan.Li
|
||
func (h *SNMPHandler) ListMetricSeries(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
var device model.SNMPDevice
|
||
if err := h.db.First(&device, id).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "设备不存在"})
|
||
return
|
||
}
|
||
|
||
var latestTS int64
|
||
h.db.Model(&model.SNMPMetric{}).Where("device_id = ?", id).
|
||
Select("COALESCE(MAX(timestamp), 0)").Scan(&latestTS)
|
||
if latestTS == 0 {
|
||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{"list": []snmpMetricSeries{}, "total": 0}})
|
||
return
|
||
}
|
||
|
||
type row struct {
|
||
Name string
|
||
Index string
|
||
Unit string
|
||
LatestValue float64
|
||
}
|
||
var rows []row
|
||
h.db.Model(&model.SNMPMetric{}).
|
||
Select("name, `index`, unit, MAX(value) as latest_value").
|
||
Where("device_id = ? AND timestamp = ? AND name IN ?", id, latestTS, snmpChartableNames).
|
||
Group("name, `index`, unit").
|
||
Order("name ASC, `index` ASC").
|
||
Scan(&rows)
|
||
|
||
series := make([]snmpMetricSeries, 0, len(rows))
|
||
for _, r := range rows {
|
||
label := snmpMetricLabel(r.Name, r.Index)
|
||
series = append(series, snmpMetricSeries{
|
||
Name: r.Name, Index: r.Index, Unit: r.Unit,
|
||
Label: label, LatestValue: r.LatestValue,
|
||
})
|
||
}
|
||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{"list": series, "total": len(series)}})
|
||
}
|
||
|
||
// QueryMetricHistory GET /snmp/devices/:id/metrics/history — 指标时序 // Logan.Li
|
||
func (h *SNMPHandler) QueryMetricHistory(c *gin.Context) {
|
||
id, _ := strconv.Atoi(c.Param("id"))
|
||
metricName := c.Query("metric_name")
|
||
if metricName == "" {
|
||
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "metric_name 不能为空"})
|
||
return
|
||
}
|
||
index := c.Query("index")
|
||
|
||
hours, _ := strconv.Atoi(c.DefaultQuery("hours", "168"))
|
||
if hours <= 0 {
|
||
hours = 168
|
||
}
|
||
if hours > snmppkg.MetricsRetentionDays*24 {
|
||
hours = snmppkg.MetricsRetentionDays * 24
|
||
}
|
||
fromTS := time.Now().Add(-time.Duration(hours) * time.Hour).Unix()
|
||
|
||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "500"))
|
||
if limit <= 0 || limit > 2000 {
|
||
limit = 500
|
||
}
|
||
|
||
q := h.db.Model(&model.SNMPMetric{}).
|
||
Where("device_id = ? AND name = ? AND timestamp >= ?", id, metricName, fromTS)
|
||
if index != "" {
|
||
q = q.Where("`index` = ?", index)
|
||
}
|
||
|
||
var points []snmpHistoryPoint
|
||
if index != "" {
|
||
var metrics []model.SNMPMetric
|
||
q.Order("timestamp ASC").Limit(limit).Find(&metrics)
|
||
points = make([]snmpHistoryPoint, 0, len(metrics))
|
||
for _, m := range metrics {
|
||
points = append(points, snmpHistoryPoint{
|
||
Timestamp: m.Timestamp, Value: m.Value, Unit: m.Unit,
|
||
})
|
||
}
|
||
} else {
|
||
type aggRow struct {
|
||
Timestamp int64
|
||
Value float64
|
||
Unit string
|
||
}
|
||
var rows []aggRow
|
||
h.db.Model(&model.SNMPMetric{}).
|
||
Select("timestamp, AVG(value) as value, MAX(unit) as unit").
|
||
Where("device_id = ? AND name = ? AND timestamp >= ?", id, metricName, fromTS).
|
||
Group("timestamp").
|
||
Order("timestamp ASC").
|
||
Limit(limit).
|
||
Scan(&rows)
|
||
points = make([]snmpHistoryPoint, 0, len(rows))
|
||
for _, r := range rows {
|
||
points = append(points, snmpHistoryPoint{
|
||
Timestamp: r.Timestamp, Value: r.Value, Unit: r.Unit,
|
||
})
|
||
}
|
||
}
|
||
|
||
if points == nil {
|
||
points = []snmpHistoryPoint{}
|
||
}
|
||
// counter 指标差分换算为 bps(bits per second)// Logan.Li
|
||
rateUnit := ""
|
||
if snmpCounterMetrics[metricName] && len(points) > 1 {
|
||
points = snmpCounterToRate(points)
|
||
rateUnit = "bps"
|
||
}
|
||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{
|
||
"list": points, "total": len(points),
|
||
"metric_name": metricName, "index": index, "hours": hours,
|
||
"rate_unit": rateUnit,
|
||
}})
|
||
}
|
||
|
||
// snmpCounterToRate 将累计字节 counter 差分为比特速率 // Logan.Li
|
||
func snmpCounterToRate(points []snmpHistoryPoint) []snmpHistoryPoint {
|
||
if len(points) < 2 {
|
||
return points
|
||
}
|
||
out := make([]snmpHistoryPoint, 0, len(points)-1)
|
||
for i := 1; i < len(points); i++ {
|
||
prev, cur := points[i-1], points[i]
|
||
dt := float64(cur.Timestamp - prev.Timestamp)
|
||
if dt <= 0 {
|
||
continue
|
||
}
|
||
delta := cur.Value - prev.Value
|
||
if delta < 0 {
|
||
// counter 回绕,跳过该点 // Logan.Li
|
||
continue
|
||
}
|
||
bps := (delta * 8) / dt
|
||
out = append(out, snmpHistoryPoint{
|
||
Timestamp: cur.Timestamp,
|
||
Value: bps,
|
||
Unit: "bps",
|
||
})
|
||
}
|
||
if len(out) == 0 {
|
||
return []snmpHistoryPoint{}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// GetOverviewMetrics GET /snmp/overview/metrics — 各设备最新关键指标(图表概览) // Logan.Li
|
||
func (h *SNMPHandler) GetOverviewMetrics(c *gin.Context) {
|
||
var devices []model.SNMPDevice
|
||
h.db.Where("template_id > 0").Order("name ASC").Find(&devices)
|
||
|
||
cpuNames := map[string]bool{
|
||
"h3c_cpu_usage": true, "hw_cpu_usage": true, "cisco_cpu_5min": true,
|
||
"ruijie_cpu_usage": true, "inspur_cpu_usage": true,
|
||
}
|
||
memNames := map[string]bool{
|
||
"h3c_mem_usage": true, "hw_mem_usage": true, "ruijie_mem_usage": true, "inspur_mem_usage": true,
|
||
}
|
||
tempNames := map[string]bool{
|
||
"h3c_temperature": true, "hw_temperature": true, "ruijie_temperature": true,
|
||
"dell_temperature": true, "inspur_temperature": true, "cisco_sensor_value": true,
|
||
}
|
||
|
||
items := make([]snmpOverviewItem, 0, len(devices))
|
||
for _, d := range devices {
|
||
item := snmpOverviewItem{
|
||
DeviceID: d.ID, Name: d.Name, IP: d.IP, Status: string(d.Status),
|
||
}
|
||
var latestTS int64
|
||
h.db.Model(&model.SNMPMetric{}).Where("device_id = ?", d.ID).
|
||
Select("COALESCE(MAX(timestamp), 0)").Scan(&latestTS)
|
||
if latestTS == 0 {
|
||
items = append(items, item)
|
||
continue
|
||
}
|
||
var metrics []model.SNMPMetric
|
||
h.db.Where("device_id = ? AND timestamp = ? AND name IN ?", d.ID, latestTS, snmpChartableNames).
|
||
Find(&metrics)
|
||
for _, m := range metrics {
|
||
switch {
|
||
case cpuNames[m.Name] && m.Value > item.CPU:
|
||
item.CPU = m.Value
|
||
case memNames[m.Name] && m.Value > item.Mem:
|
||
item.Mem = m.Value
|
||
case tempNames[m.Name] && m.Value > item.Temp:
|
||
item.Temp = m.Value
|
||
}
|
||
}
|
||
items = append(items, item)
|
||
}
|
||
c.JSON(http.StatusOK, model.Response{Code: 200, Data: gin.H{"list": items, "total": len(items)}})
|
||
}
|
||
|
||
func snmpMetricLabel(name, index string) string {
|
||
labels := map[string]string{
|
||
"h3c_cpu_usage": "H3C CPU", "hw_cpu_usage": "华为 CPU", "cisco_cpu_5min": "Cisco CPU",
|
||
"ruijie_cpu_usage": "锐捷 CPU", "inspur_cpu_usage": "浪潮 CPU",
|
||
"h3c_mem_usage": "H3C 内存", "hw_mem_usage": "华为 内存",
|
||
"ruijie_mem_usage": "锐捷 内存", "inspur_mem_usage": "浪潮 内存",
|
||
"h3c_temperature": "H3C 温度", "hw_temperature": "华为 温度",
|
||
"ruijie_temperature": "锐捷 温度", "dell_temperature": "Dell 温度",
|
||
"inspur_temperature": "浪潮 温度", "cisco_sensor_value": "Cisco 传感器",
|
||
"h3c_fan_speed": "H3C 风扇", "hw_fan_speed": "华为 风扇",
|
||
"ruijie_fan_speed": "锐捷 风扇", "dell_fan_speed": "Dell 风扇", "inspur_fan_speed": "浪潮 风扇",
|
||
"ent_sensor_value": "实体传感器",
|
||
"if_in_octets": "入方向流量", "if_out_octets": "出方向流量",
|
||
}
|
||
label := labels[name]
|
||
if label == "" {
|
||
label = name
|
||
}
|
||
if index != "" {
|
||
label += " #" + index
|
||
}
|
||
return label
|
||
}
|