diff --git a/src/collectors/proxcluster/proxcluster_linux.go b/src/collectors/proxcluster/proxcluster_linux.go index d92e697..c9f52a5 100644 --- a/src/collectors/proxcluster/proxcluster_linux.go +++ b/src/collectors/proxcluster/proxcluster_linux.go @@ -521,14 +521,8 @@ func parseSizeToGB(sizeStr string) (float64, error) { func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID string) ([]map[string]any, error) { var nodes []map[string]any - // Получаем данные из pvecm nodes (имена нод) - nodesData := parsePvecmNodes(ctx) - - // Получаем данные из pvecm status (IP адреса) - statusData := parsePvecmStatus(ctx) - - // Объединяем данные - combinedNodes := combineNodeInfo(nodesData, statusData) + // Получаем данные о нодах через pvesh API + combinedNodes := getNodesFromPvesh(ctx) // Если не удалось получить данные через pvecm, создаем информацию о текущей ноде if len(combinedNodes) == 0 { @@ -605,13 +599,8 @@ func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID stri } } - // Определяем статус ноды - isOnline := true // По умолчанию считаем ноду онлайн - - // Если это не локальная нода, проверяем доступность через ping - if !isLocal { - isOnline = checkNodeOnline(ctx, nodeIP) - } + // Определяем статус ноды из pvesh данных + isOnline := getNodeStatusFromPvesh(ctx, nodeName) // Создаем структуру ноды с правильным порядком полей node := map[string]any{ @@ -704,6 +693,106 @@ func collectDetailedNodesInfo(ctx context.Context, clusterName, clusterUUID stri return nodes, nil } +// getNodesFromPvesh получает информацию о нодах через pvesh API +func getNodesFromPvesh(ctx context.Context) []NodeInfo { + var nodes []NodeInfo + + // Проверяем наличие pvesh + if _, err := exec.LookPath("pvesh"); err != nil { + return nodes + } + + // Получаем список нод + cmd := exec.CommandContext(ctx, "pvesh", "get", "/nodes", "--output-format", "json") + out, err := cmd.Output() + if err != nil { + return nodes + } + + var nodesData []map[string]any + if err := json.Unmarshal(out, &nodesData); err != nil { + return nodes + } + + // Преобразуем данные в структуру NodeInfo + for i, nodeData := range nodesData { + nodeID := i + 1 + name := "" + status := "offline" + + if n, ok := nodeData["node"].(string); ok { + name = n + } + if s, ok := nodeData["status"].(string); ok { + status = s + } + + // Получаем IP адрес ноды + var nodeIP string + if name != "" { + // Получаем детальную информацию о ноде + detailCmd := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/status", name), "--output-format", "json") + detailOut, err := detailCmd.Output() + if err == nil { + var detailData map[string]any + if err := json.Unmarshal(detailOut, &detailData); err == nil { + if ip, ok := detailData["ip"].(string); ok { + nodeIP = ip + } + } + } + } + + // Определяем votes (обычно 1 для каждой ноды) + votes := 1 + if status == "online" { + votes = 1 + } else { + votes = 0 + } + + nodes = append(nodes, NodeInfo{ + NodeID: nodeID, + Votes: votes, + Name: name, + IP: nodeIP, + }) + } + + return nodes +} + +// getNodeStatusFromPvesh получает статус ноды через pvesh API +func getNodeStatusFromPvesh(ctx context.Context, nodeName string) bool { + if nodeName == "" { + return false + } + + // Проверяем наличие pvesh + if _, err := exec.LookPath("pvesh"); err != nil { + return true // Если pvesh недоступен, считаем ноду онлайн + } + + // Получаем статус ноды + cmd := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/status", nodeName), "--output-format", "json") + out, err := cmd.Output() + if err != nil { + return false + } + + var statusData map[string]any + if err := json.Unmarshal(out, &statusData); err != nil { + return false + } + + // Проверяем статус + if status, ok := statusData["status"].(string); ok { + return status == "online" + } + + return false +} + // NodeInfo структура для хранения информации о ноде type NodeInfo struct { NodeID int @@ -1030,58 +1119,93 @@ func getNodeOSInfo(ctx context.Context) (map[string]any, error) { } func getNodeHardwareInfo(ctx context.Context) (map[string]any, error) { - result := map[string]any{} + result := map[string]any{ + "cpu_model": "", + "cpu_cores": 0, + "sockets": 0, + "threads": 0, + "memory_total_mb": 0, + } - // CPU информация - if data, err := os.ReadFile("/proc/cpuinfo"); err == nil { - lines := strings.Split(string(data), "\n") - var cpuModel string - var cores, sockets int - seenModels := make(map[string]bool) - seenSockets := make(map[string]bool) - - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "model name") { - parts := strings.SplitN(line, ":", 2) - if len(parts) == 2 { - model := strings.TrimSpace(parts[1]) - if !seenModels[model] { - cpuModel = model - seenModels[model] = true + // Пробуем получить данные через pvesh API + if hostname, err := os.Hostname(); err == nil { + if _, err := exec.LookPath("pvesh"); err == nil { + // Получаем информацию о ноде через pvesh + cmd := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/status", hostname), "--output-format", "json") + out, err := cmd.Output() + if err == nil { + var statusData map[string]any + if err := json.Unmarshal(out, &statusData); err == nil { + // CPU cores + if maxcpu, ok := statusData["maxcpu"].(float64); ok { + result["cpu_cores"] = int(maxcpu) + result["threads"] = int(maxcpu) } - } - } - if strings.HasPrefix(line, "processor") { - cores++ - } - if strings.HasPrefix(line, "physical id") { - parts := strings.SplitN(line, ":", 2) - if len(parts) == 2 { - socket := strings.TrimSpace(parts[1]) - if !seenSockets[socket] { - sockets++ - seenSockets[socket] = true + + // Memory total + if maxmem, ok := statusData["maxmem"].(float64); ok { + result["memory_total_mb"] = int(maxmem) } } } } - - result["cpu_model"] = cpuModel - result["cpu_cores"] = cores - result["sockets"] = sockets - result["threads"] = cores // В упрощенном виде } - // Memory - if data, err := os.ReadFile("/proc/meminfo"); err == nil { - lines := strings.Split(string(data), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "MemTotal:") { - fields := strings.Fields(line) - if len(fields) >= 2 { - if kb, err := strconv.ParseUint(fields[1], 10, 64); err == nil { - result["memory_total_mb"] = int(kb / 1024) + // Fallback: получаем данные из /proc если pvesh недоступен + if result["cpu_cores"].(int) == 0 { + // CPU информация из /proc/cpuinfo + if data, err := os.ReadFile("/proc/cpuinfo"); err == nil { + lines := strings.Split(string(data), "\n") + var cpuModel string + var cores, sockets int + seenModels := make(map[string]bool) + seenSockets := make(map[string]bool) + + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "model name") { + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + model := strings.TrimSpace(parts[1]) + if !seenModels[model] { + cpuModel = model + seenModels[model] = true + } + } + } + if strings.HasPrefix(line, "processor") { + cores++ + } + if strings.HasPrefix(line, "physical id") { + parts := strings.SplitN(line, ":", 2) + if len(parts) == 2 { + socket := strings.TrimSpace(parts[1]) + if !seenSockets[socket] { + sockets++ + seenSockets[socket] = true + } + } + } + } + + result["cpu_model"] = cpuModel + result["cpu_cores"] = cores + result["sockets"] = sockets + result["threads"] = cores + } + } + + if result["memory_total_mb"].(int) == 0 { + // Memory информация из /proc/meminfo + if data, err := os.ReadFile("/proc/meminfo"); err == nil { + lines := strings.Split(string(data), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "MemTotal:") { + fields := strings.Fields(line) + if len(fields) >= 2 { + if kb, err := strconv.ParseUint(fields[1], 10, 64); err == nil { + result["memory_total_mb"] = int(kb / 1024) + } } } } @@ -1092,50 +1216,118 @@ func getNodeHardwareInfo(ctx context.Context) (map[string]any, error) { } func getNodeResources(ctx context.Context) (map[string]any, error) { - result := map[string]any{} + result := map[string]any{ + "cpu_usage_percent": 0.0, + "memory_used_mb": 0, + "swap_used_mb": 0, + "loadavg": []float64{0, 0, 0}, + } - // CPU usage (упрощенная версия) - result["cpu_usage_percent"] = 0.0 - - // Memory usage - if data, err := os.ReadFile("/proc/meminfo"); err == nil { - lines := strings.Split(string(data), "\n") - var total, free, buffers, cached uint64 - for _, line := range lines { - fields := strings.Fields(line) - if len(fields) >= 2 { - if val, err := strconv.ParseUint(fields[1], 10, 64); err == nil { - switch fields[0] { - case "MemTotal:": - total = val - case "MemFree:": - free = val - case "Buffers:": - buffers = val - case "Cached:": - cached = val + // Пробуем получить данные через pvesh API + if hostname, err := os.Hostname(); err == nil { + if _, err := exec.LookPath("pvesh"); err == nil { + // Получаем ресурсы через pvesh + cmd := exec.CommandContext(ctx, "pvesh", "get", fmt.Sprintf("/nodes/%s/status", hostname), "--output-format", "json") + out, err := cmd.Output() + if err == nil { + var statusData map[string]any + if err := json.Unmarshal(out, &statusData); err == nil { + // CPU usage + if cpu, ok := statusData["cpu"].(float64); ok { + result["cpu_usage_percent"] = cpu * 100 + } + + // Memory usage + if mem, ok := statusData["memory"].(float64); ok { + result["memory_used_mb"] = int(mem) + } + + // Load average + if loadavg, ok := statusData["loadavg"].([]interface{}); ok && len(loadavg) >= 3 { + var load []float64 + for i := 0; i < 3; i++ { + if val, ok := loadavg[i].(float64); ok { + load = append(load, val) + } else { + load = append(load, 0) + } + } + result["loadavg"] = load } } } } - used := total - free - buffers - cached - result["memory_used_mb"] = int(used / 1024) } - // Swap - result["swap_used_mb"] = 0 - - // Load average - if data, err := os.ReadFile("/proc/loadavg"); err == nil { - fields := strings.Fields(string(data)) - if len(fields) >= 3 { - var loadavg []float64 - for i := 0; i < 3; i++ { - if val, err := strconv.ParseFloat(fields[i], 64); err == nil { - loadavg = append(loadavg, val) + // Fallback: получаем данные из /proc если pvesh недоступен + if result["cpu_usage_percent"].(float64) == 0.0 { + // CPU usage из /proc/stat + if data, err := os.ReadFile("/proc/stat"); err == nil { + lines := strings.Split(string(data), "\n") + if len(lines) > 0 { + fields := strings.Fields(lines[0]) + if len(fields) >= 8 { + // Простой расчет CPU usage + var total, idle uint64 + for i := 1; i < len(fields); i++ { + if val, err := strconv.ParseUint(fields[i], 10, 64); err == nil { + total += val + if i == 4 { // idle time + idle = val + } + } + } + if total > 0 { + usage := float64(total-idle) / float64(total) * 100 + result["cpu_usage_percent"] = usage + } } } - result["loadavg"] = loadavg + } + } + + if result["memory_used_mb"].(int) == 0 { + // Memory usage из /proc/meminfo + if data, err := os.ReadFile("/proc/meminfo"); err == nil { + lines := strings.Split(string(data), "\n") + var total, free, buffers, cached uint64 + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) >= 2 { + if val, err := strconv.ParseUint(fields[1], 10, 64); err == nil { + switch fields[0] { + case "MemTotal:": + total = val + case "MemFree:": + free = val + case "Buffers:": + buffers = val + case "Cached:": + cached = val + } + } + } + } + used := total - free - buffers - cached + result["memory_used_mb"] = int(used / 1024) + } + } + + if len(result["loadavg"].([]float64)) == 0 || result["loadavg"].([]float64)[0] == 0 { + // Load average из /proc/loadavg + if data, err := os.ReadFile("/proc/loadavg"); err == nil { + fields := strings.Fields(string(data)) + if len(fields) >= 3 { + var loadavg []float64 + for i := 0; i < 3; i++ { + if val, err := strconv.ParseFloat(fields[i], 64); err == nil { + loadavg = append(loadavg, val) + } else { + loadavg = append(loadavg, 0) + } + } + result["loadavg"] = loadavg + } } }