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 }