package api import ( "fmt" "net/http" "strings" "github.com/pulse-lets-go/internal/models" ) // handleZabbix — GET /api/monitoring/zabbix — возвращает JSON для Zabbix LLD + items. func (a *API) handleZabbix(w http.ResponseWriter, r *http.Request) { nodes := a.engine.GetAllNodes() data := make([]models.ZabbixNode, 0, len(nodes)) for _, n := range nodes { data = append(data, models.ZabbixNode{ NodeID: n.NodeID, Status: n.Status, ActiveCalls: n.ActiveCalls, MaxCalls: n.MaxCalls, IdleCPU: n.IdleCPU, LoadAvg: n.LoadAvg, CallFailureRate: n.CallFailureRate, Score: n.Score, Disabled: n.Disabled, IsStale: n.IsStale, SecondsAgo: n.SecondsAgo, }) } stats := a.engine.GetHealthStats() writeJSON(w, http.StatusOK, models.ZabbixResponse{ Data: data, RouteRequests: stats.RouteRequests, RouteFallbacks: stats.RouteFallbacks, UptimeSeconds: stats.UptimeSeconds, }) } // handlePrometheus — GET /api/monitoring/prometheus — text/plain метрики для Prometheus. func (a *API) handlePrometheus(w http.ResponseWriter, r *http.Request) { nodes := a.engine.GetAllNodes() var sb strings.Builder // Общие метрики (counter gauge) stats := a.engine.GetHealthStats() fmt.Fprintf(&sb, "# HELP pulse_route_requests_total Total number of route requests.\n") fmt.Fprintf(&sb, "# TYPE pulse_route_requests_total counter\n") fmt.Fprintf(&sb, "pulse_route_requests_total %d\n", stats.RouteRequests) fmt.Fprintf(&sb, "# HELP pulse_nodes_total Total nodes registered.\n") fmt.Fprintf(&sb, "# TYPE pulse_nodes_total gauge\n") fmt.Fprintf(&sb, "pulse_nodes_total %d\n", stats.TotalNodes) fmt.Fprintf(&sb, "# HELP pulse_nodes_healthy Healthy nodes count.\n") fmt.Fprintf(&sb, "# TYPE pulse_nodes_healthy gauge\n") fmt.Fprintf(&sb, "pulse_nodes_healthy %d\n", stats.HealthyNodes) fmt.Fprintf(&sb, "# HELP pulse_route_fallbacks_total Total number of fallback route requests.\n") fmt.Fprintf(&sb, "# TYPE pulse_route_fallbacks_total counter\n") fmt.Fprintf(&sb, "pulse_route_fallbacks_total %d\n", stats.RouteFallbacks) fmt.Fprintf(&sb, "# HELP pulse_uptime_seconds Uptime in seconds.\n") fmt.Fprintf(&sb, "# TYPE pulse_uptime_seconds gauge\n") fmt.Fprintf(&sb, "pulse_uptime_seconds %d\n", stats.UptimeSeconds) fmt.Fprintf(&sb, "\n") for _, n := range nodes { id := sanitizePromLabel(n.NodeID) fmt.Fprintf(&sb, "pulse_node_active_calls{node=\"%s\"} %d\n", id, n.ActiveCalls) fmt.Fprintf(&sb, "pulse_node_max_calls{node=\"%s\"} %d\n", id, n.MaxCalls) fmt.Fprintf(&sb, "pulse_node_idle_cpu{node=\"%s\"} %.2f\n", id, n.IdleCPU) fmt.Fprintf(&sb, "pulse_node_load_avg{node=\"%s\"} %.2f\n", id, n.LoadAvg) fmt.Fprintf(&sb, "pulse_node_call_failure_rate{node=\"%s\"} %.2f\n", id, n.CallFailureRate) fmt.Fprintf(&sb, "pulse_node_score{node=\"%s\"} %.2f\n", id, n.Score) fmt.Fprintf(&sb, "pulse_node_seconds_ago{node=\"%s\"} %.2f\n", id, n.SecondsAgo) disabledVal := 0 staleVal := 0 statusVal := 1 // ok if n.Disabled { disabledVal = 1 } if n.IsStale { staleVal = 1 } if n.Status != "ok" { statusVal = 0 } fmt.Fprintf(&sb, "pulse_node_disabled{node=\"%s\"} %d\n", id, disabledVal) fmt.Fprintf(&sb, "pulse_node_stale{node=\"%s\"} %d\n", id, staleVal) fmt.Fprintf(&sb, "pulse_node_status_ok{node=\"%s\"} %d\n", id, statusVal) } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write([]byte(sb.String())) } // sanitizePromLabel заменяет символы, недопустимые в Prometheus-лейблах. func sanitizePromLabel(s string) string { return strings.ReplaceAll(strings.ReplaceAll(s, ".", "_"), "-", "_") }