feat: добавлен коллектор proxvmsystem и обновлена документация

- Добавлен новый коллектор proxvmsystem, объединяющий функциональность system и gpu коллекторов
- Добавлен machine_uid для идентификации VM/контейнеров в Proxmox инфраструктуре
- Обновлена документация по коллекторам и проекту
- Добавлены новые хосты в inventory для тестирования
- Обновлен Makefile для сборки нового коллектора
- Обновлен config.yaml с конфигурацией proxvmsystem коллектора

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
Sergey Antropoff 2025-09-15 14:13:22 +03:00
parent b23d724bea
commit 7d970eada7
9 changed files with 1040 additions and 3 deletions

View File

@ -89,7 +89,8 @@ collectors-linux:
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/kubernetes ./src/collectors/kubernetes && \ CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/kubernetes ./src/collectors/kubernetes && \
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxcluster ./src/collectors/proxcluster && \ CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxcluster ./src/collectors/proxcluster && \
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxnode ./src/collectors/proxnode && \ CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxnode ./src/collectors/proxnode && \
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvms ./src/collectors/proxvms" CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvms ./src/collectors/proxvms && \
CGO_ENABLED=0 go build -trimpath -o ./bin/agent/collectors/proxvmsystem ./src/collectors/proxvmsystem"
collectors-windows: collectors-windows:
# Кросс-сборка коллекторов для Windows # Кросс-сборка коллекторов для Windows

View File

@ -12,6 +12,8 @@ SensusAgent — модульный агент сбора метрик. Аген
- Деплой (Ansible, systemd): `docs/deploy.md` - Деплой (Ansible, systemd): `docs/deploy.md`
- **Kafka SSL поддержка**: `docs/kafka_ssl.md` - **Kafka SSL поддержка**: `docs/kafka_ssl.md`
- **Proxmox кластер**: `docs/collectors/proxcluster.md` - **Proxmox кластер**: `docs/collectors/proxcluster.md`
- **Proxmox VM/контейнеры**: `docs/collectors/proxvms.md`
- **Proxmox системные метрики**: `docs/collectors/proxvmsystem.md`
Быстрый старт: Быстрый старт:
```bash ```bash

View File

@ -118,5 +118,13 @@ collectors:
timeout: "300s" timeout: "300s"
exec: "./collectors/proxvms" exec: "./collectors/proxvms"
platforms: [linux] platforms: [linux]
proxvmsystem:
enabled: true
type: exec
key: proxvmsystem
interval: "300s"
timeout: "60s"
exec: "./collectors/proxvmsystem"
platforms: [linux]

View File

@ -61,6 +61,7 @@ make collectors-darwin # darwin/arm64
- **proxcluster** - информация о Proxmox кластере (ноды, хранилища, кворум) - Linux ⭐ - **proxcluster** - информация о Proxmox кластере (ноды, хранилища, кворум) - Linux ⭐
- **proxnode** - информация о Proxmox ноде (ресурсы, сервисы, диски) - Linux ⭐ - **proxnode** - информация о Proxmox ноде (ресурсы, сервисы, диски) - Linux ⭐
- **proxvms** - информация о виртуальных машинах и контейнерах Proxmox - Linux ⭐ - **proxvms** - информация о виртуальных машинах и контейнерах Proxmox - Linux ⭐
- **proxvmsystem** - системные метрики с machine_uid для Proxmox VM/контейнеров - Linux ⭐
### Документация коллекторов ### Документация коллекторов
@ -68,6 +69,7 @@ make collectors-darwin # darwin/arm64
- [gpu](collectors/gpu.md) - сбор информации о GPU устройствах с агрегированной статистикой - [gpu](collectors/gpu.md) - сбор информации о GPU устройствах с агрегированной статистикой
- [proxnode](collectors/proxnode.md) - сбор информации о Proxmox ноде - [proxnode](collectors/proxnode.md) - сбор информации о Proxmox ноде
- [proxvms](collectors/proxvms.md) - сбор информации о виртуальных машинах и контейнерах Proxmox - [proxvms](collectors/proxvms.md) - сбор информации о виртуальных машинах и контейнерах Proxmox
- [proxvmsystem](collectors/proxvmsystem.md) - системные метрики с machine_uid для Proxmox VM/контейнеров
- [system](collectors/system.md) - сбор системных метрик - [system](collectors/system.md) - сбор системных метрик
- [docker](collectors/docker.md) - сбор информации о Docker контейнерах - [docker](collectors/docker.md) - сбор информации о Docker контейнерах
- [hba](collectors/hba.md) - сбор информации о RAID/HBA контроллерах - [hba](collectors/hba.md) - сбор информации о RAID/HBA контроллерах

View File

@ -0,0 +1,302 @@
# Коллектор proxvmsystem
**Автор:** Сергей Антропов
**Сайт:** https://devops.org.ru
**Платформа:** Linux
**Тип:** exec (Go-бинарник)
## Описание
Коллектор `proxvmsystem` объединяет функциональность коллекторов `system` и `gpu`, добавляя уникальный идентификатор `machine_uid` для идентификации виртуальной машины или контейнера в Proxmox инфраструктуре.
## Назначение
- Сбор системных метрик (CPU, RAM, сеть, диски, время синхронизации, обновления)
- Сбор информации о GPU устройствах (NVIDIA/AMD)
- Генерация уникального `machine_uid` на основе `/etc/machine-id` или `/var/lib/dbus/machine-id`
- Предоставление единого интерфейса для мониторинга VM/контейнеров с привязкой к уникальному идентификатору
## Конфигурация
```yaml
proxvmsystem:
enabled: true
type: exec
key: proxvmsystem
interval: "300s"
timeout: "60s"
exec: "./collectors/proxvmsystem"
platforms: [linux]
```
## Выходные данные
### Структура JSON
```json
{
"collector_name": "proxvmsystem",
"machine_uid": "e7b3e8ed05a8d0c9",
"cpu": {
"usage_percent": 15.2,
"cores": 4,
"load_avg": [0.8, 1.2, 1.5]
},
"ram": {
"total_gb": 16.0,
"used_gb": 8.5,
"free_gb": 7.5,
"usage_percent": 53.1
},
"swap": {
"total_gb": 2.0,
"used_gb": 0.1,
"free_gb": 1.9,
"usage_percent": 5.0
},
"network": {
"interfaces": [
{
"name": "eth0",
"speed_mbps": 1000,
"mac": "00:11:22:33:44:55",
"ips": ["10.14.246.75/24"],
"rx_bytes": 1024000,
"tx_bytes": 2048000
}
]
},
"disks": [
{
"device": "/dev/sda",
"mountpoint": "/",
"total_gb": 50.0,
"used_gb": 25.0,
"free_gb": 25.0,
"usage_percent": 50.0
}
],
"time_sync": {
"ntp_sync": true,
"ntp_offset_ms": 0.5,
"system_time": "2024-01-15T10:30:00Z"
},
"updates": {
"available": 5,
"security": 2,
"last_check": "2024-01-15T09:00:00Z"
},
"gpu": [
{
"id": "0",
"name": "NVIDIA GeForce RTX 3080",
"driver_version": "470.86",
"memory_total_mb": 10240,
"memory_used_mb": 2048,
"memory_free_mb": 8192,
"utilization_percent": 25.0,
"temperature_c": 65,
"power_usage_w": 180
}
],
"gpu_summary": {
"total_gpus": 1,
"total_memory_mb": 10240,
"total_utilization_percent": 25.0,
"total_power_usage_w": 180
},
"execution_time_ms": 1250,
"execution_time_seconds": 1.25
}
```
### Поля данных
#### Основные поля
- **`collector_name`** (string) - имя коллектора: "proxvmsystem"
- **`machine_uid`** (string) - уникальный идентификатор машины (SHA256 хэш от machine-id)
#### Системные метрики
- **`cpu`** (object) - информация о процессоре
- `usage_percent` (float) - загрузка CPU в процентах
- `cores` (int) - количество ядер
- `load_avg` (array) - средняя загрузка за 1, 5, 15 минут
- **`ram`** (object) - информация об оперативной памяти
- `total_gb` (float) - общий объем RAM в GB
- `used_gb` (float) - используемый объем RAM в GB
- `free_gb` (float) - свободный объем RAM в GB
- `usage_percent` (float) - процент использования RAM
- **`swap`** (object) - информация о swap
- `total_gb` (float) - общий объем swap в GB
- `used_gb` (float) - используемый объем swap в GB
- `free_gb` (float) - свободный объем swap в GB
- `usage_percent` (float) - процент использования swap
- **`network`** (object) - информация о сетевых интерфейсах
- `interfaces` (array) - массив сетевых интерфейсов
- `name` (string) - имя интерфейса
- `speed_mbps` (int) - скорость в Mbps
- `mac` (string) - MAC адрес
- `ips` (array) - IP адреса
- `rx_bytes` (int) - получено байт
- `tx_bytes` (int) - отправлено байт
- **`disks`** (array) - информация о дисках
- `device` (string) - устройство
- `mountpoint` (string) - точка монтирования
- `total_gb` (float) - общий размер в GB
- `used_gb` (float) - используемый размер в GB
- `free_gb` (float) - свободный размер в GB
- `usage_percent` (float) - процент использования
- **`time_sync`** (object) - информация о синхронизации времени
- `ntp_sync` (bool) - синхронизация с NTP
- `ntp_offset_ms` (float) - смещение от NTP в миллисекундах
- `system_time` (string) - системное время
- **`updates`** (object) - информация об обновлениях
- `available` (int) - количество доступных обновлений
- `security` (int) - количество обновлений безопасности
- `last_check` (string) - время последней проверки
#### GPU метрики
- **`gpu`** (array) - информация о GPU устройствах
- `id` (string) - ID GPU
- `name` (string) - название GPU
- `driver_version` (string) - версия драйвера
- `memory_total_mb` (int) - общая память в MB
- `memory_used_mb` (int) - используемая память в MB
- `memory_free_mb` (int) - свободная память в MB
- `utilization_percent` (float) - загрузка GPU в процентах
- `temperature_c` (int) - температура в градусах Цельсия
- `power_usage_w` (int) - потребление энергии в ваттах
- **`gpu_summary`** (object) - агрегированная информация о GPU
- `total_gpus` (int) - общее количество GPU
- `total_memory_mb` (int) - общая память всех GPU в MB
- `total_utilization_percent` (float) - средняя загрузка GPU
- `total_power_usage_w` (int) - общее потребление энергии
#### Метаданные
- **`execution_time_ms`** (int) - время выполнения коллектора в миллисекундах
- **`execution_time_seconds`** (float) - время выполнения коллектора в секундах
## Генерация machine_uid
Коллектор генерирует уникальный идентификатор `machine_uid` на основе machine-id системы:
1. **Чтение machine-id**: Пытается прочитать из `/etc/machine-id` или `/var/lib/dbus/machine-id`
2. **Генерация хэша**: Создает SHA256 хэш от machine-id
3. **Усечение**: Берет первые 16 символов хэша для компактности
```go
func getMachineIDFromHost() string {
paths := []string{
"/etc/machine-id",
"/var/lib/dbus/machine-id",
}
for _, path := range paths {
if data, err := os.ReadFile(path); err == nil {
machineID := strings.TrimSpace(string(data))
if machineID != "" {
hash := sha256.Sum256([]byte(machineID))
return hex.EncodeToString(hash[:])[:16]
}
}
}
return ""
}
```
## Зависимости
### Системные команды
- **`df`** - информация о дисках
- **`ntpq`** - информация о NTP синхронизации
- **`timedatectl`** - информация о времени системы
- **`apt`** - информация об обновлениях (Ubuntu/Debian)
- **`yum`** - информация об обновлениях (CentOS/RHEL)
### GPU команды
- **`nvidia-smi`** - информация о NVIDIA GPU
- **`rocm-smi`** - информация об AMD GPU
### Файлы системы
- **`/proc/stat`** - статистика CPU
- **`/proc/meminfo`** - информация о памяти
- **`/proc/loadavg`** - средняя загрузка системы
- **`/sys/class/net/<iface>/speed`** - скорость сетевых интерфейсов
- **`/etc/machine-id`** - уникальный идентификатор машины
- **`/var/lib/dbus/machine-id`** - альтернативный путь к machine-id
## Использование
### Запуск коллектора
```bash
# Прямой запуск
./collectors/proxvmsystem
# С переменными окружения
COLLECTOR_TIMEOUT=60s ./collectors/proxvmsystem
```
### Интеграция с агентом
Коллектор автоматически запускается агентом согласно конфигурации в `config.yaml`.
## Ограничения
- Работает только на Linux системах
- Требует права на чтение системных файлов
- GPU метрики доступны только при наличии соответствующих драйверов
- Время выполнения может варьироваться в зависимости от количества GPU и системных ресурсов
## Логирование
Коллектор выводит отладочную информацию в stderr для диагностики проблем:
- Ошибки чтения системных файлов
- Проблемы с выполнением команд
- Ошибки парсинга данных
## Примеры использования
### Мониторинг VM в Proxmox
```bash
# Получение machine_uid VM
ssh vm-host "./collectors/proxvmsystem" | jq '.machine_uid'
# Сравнение с данными от proxvms коллектора на Proxmox ноде
ssh proxmox-node "./collectors/proxvms" | jq '.vms[] | select(.machine_uid == "e7b3e8ed05a8d0c9")'
```
### Мониторинг производительности
```bash
# Загрузка CPU и GPU
./collectors/proxvmsystem | jq '{cpu: .cpu.usage_percent, gpu: .gpu_summary.total_utilization_percent}'
# Использование памяти
./collectors/proxvmsystem | jq '{ram: .ram.usage_percent, swap: .swap.usage_percent}'
```
## Связанные коллекторы
- **`system`** - базовые системные метрики
- **`gpu`** - информация о GPU устройствах
- **`proxvms`** - информация о VM/контейнерах Proxmox
- **`proxnode`** - информация о Proxmox нодах
- **`proxcluster`** - информация о Proxmox кластере

View File

@ -4,5 +4,8 @@
#kube_ansible ansible_host=10.14.246.9 ansible_user=devops #kube_ansible ansible_host=10.14.246.9 ansible_user=devops
#videotest7 ansible_host=10.13.37.186 ansible_user=devops #videotest7 ansible_host=10.13.37.186 ansible_user=devops
#videotest8 ansible_host=10.13.37.187 ansible_user=devops #videotest8 ansible_host=10.13.37.187 ansible_user=devops
pnode02 ansible_host=10.14.253.12 ansible_user=devops #pnode02 ansible_host=10.14.253.12 ansible_user=devops
#dbrain01 ansible_host=10.14.246.75 ansible_user=devops #pnode06 ansible_host=10.14.253.16 ansible_user=devops
pnode10 ansible_host=10.14.253.20 ansible_user=devops
kube-dbrain-node01 ansible_host=10.14.246.75 ansible_user=devops
kube-data ansible_host=10.14.246.150 ansible_user=devops

View File

@ -0,0 +1,55 @@
package main
// Автор: Сергей Антропов, сайт: https://devops.org.ru
// Коллектор proxvmsystem - объединяет функциональность system и gpu коллекторов
// с добавлением machine_uid для идентификации VM/контейнера
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
// collectProxVMSystem реализуется в файлах с билд-тегами под конкретные ОС.
func main() {
// Засекаем время начала выполнения
startTime := time.Now()
// Таймаут можно переопределить окружением COLLECTOR_TIMEOUT
timeout := parseDurationOr("COLLECTOR_TIMEOUT", 15*time.Second)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
data, err := collectProxVMSystem(ctx)
if err != nil || data == nil {
fmt.Println("{}")
return
}
// Вычисляем время выполнения
executionTime := time.Since(startTime)
// Добавляем время выполнения в результат
data["execution_time_ms"] = executionTime.Milliseconds()
data["execution_time_seconds"] = executionTime.Seconds()
enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
_ = enc.Encode(data)
}
func parseDurationOr(env string, def time.Duration) time.Duration {
v := strings.TrimSpace(os.Getenv(env))
if v == "" {
return def
}
d, err := time.ParseDuration(v)
if err != nil {
return def
}
return d
}

View File

@ -0,0 +1,646 @@
//go:build linux
package main
// Автор: Сергей Антропов, сайт: https://devops.org.ru
// Коллектор proxvmsystem для Linux - объединяет system и gpu с machine_uid
import (
"bufio"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
)
// collectProxVMSystem собирает системные данные (CPU, RAM, GPU, сеть, диски)
// с добавлением machine_uid для идентификации VM/контейнера
func collectProxVMSystem(ctx context.Context) (map[string]any, error) {
result := map[string]any{
"collector_name": "proxvmsystem",
}
// Получаем machine_uid
machineUID := getMachineUID()
if machineUID != "" {
result["machine_uid"] = machineUID
}
// Собираем системные данные (из system коллектора)
cpu, err := collectCPU(ctx)
if err == nil {
result["cpu"] = cpu
}
ram, swap, err := collectMem(ctx)
if err == nil {
result["ram"] = ram
result["swap"] = swap
}
netIfs, err := collectNet(ctx)
if err == nil {
result["network"] = netIfs
}
disks, err := collectDisks(ctx)
if err == nil {
result["disks"] = disks
}
ts, err := collectTimeSync(ctx)
if err == nil {
result["time_sync"] = ts
}
updates, err := collectUpdates(ctx)
if err == nil {
result["updates"] = updates
}
// Собираем GPU данные (из gpu коллектора)
gpuData, err := collectGPUData(ctx)
if err == nil && gpuData != nil {
if gpuArray, ok := gpuData["gpu"].([]map[string]any); ok && len(gpuArray) > 0 {
result["gpu"] = gpuArray
}
if summary, ok := gpuData["summary"].(map[string]any); ok {
result["gpu_summary"] = summary
}
}
// Если нет данных, возвращаем ошибку
if len(result) <= 2 { // только collector_name и возможно machine_uid
return nil, errors.New("no data collected")
}
return result, nil
}
// getMachineUID получает machine_uid для текущей системы
func getMachineUID() string {
// Пытаемся получить UUID из DMI (для QEMU VM)
if uuid := getDMISystemUUID(); uuid != "" {
hash := sha256.Sum256([]byte(uuid))
return hex.EncodeToString(hash[:])[:16]
}
// Пытаемся получить machine-id (для LXC контейнеров)
if machineID := getSystemMachineID(); machineID != "" {
hash := sha256.Sum256([]byte(machineID))
return hex.EncodeToString(hash[:])[:16]
}
return ""
}
// getDMISystemUUID получает UUID из DMI (для QEMU VM)
func getDMISystemUUID() string {
// Пробуем /sys/class/dmi/id/product_uuid
if data, err := os.ReadFile("/sys/class/dmi/id/product_uuid"); err == nil {
uuid := strings.TrimSpace(string(data))
if uuid != "" && uuid != "00000000-0000-0000-0000-000000000000" {
return uuid
}
}
// Fallback на dmidecode
if exists("dmidecode") {
cmd := exec.Command("dmidecode", "-s", "system-uuid")
if output, err := cmd.Output(); err == nil {
uuid := strings.TrimSpace(string(output))
if uuid != "" && uuid != "00000000-0000-0000-0000-000000000000" {
return uuid
}
}
}
return ""
}
// getSystemMachineID получает machine-id системы (для LXC контейнеров)
func getSystemMachineID() string {
// Пробуем /etc/machine-id
if data, err := os.ReadFile("/etc/machine-id"); err == nil {
machineID := strings.TrimSpace(string(data))
if machineID != "" {
return machineID
}
}
// Fallback на /var/lib/dbus/machine-id
if data, err := os.ReadFile("/var/lib/dbus/machine-id"); err == nil {
machineID := strings.TrimSpace(string(data))
if machineID != "" {
return machineID
}
}
return ""
}
// collectGPUData собирает данные о GPU (адаптировано из gpu коллектора)
func collectGPUData(ctx context.Context) (map[string]any, error) {
var gpuArray []map[string]any
// Сначала пробуем NVIDIA
if exists("nvidia-smi") {
if arr := collectNvidia(ctx); len(arr) > 0 {
gpuArray = arr
}
}
// Затем AMD ROCm
if exists("rocm-smi") && len(gpuArray) == 0 {
if arr := collectRocm(ctx); len(arr) > 0 {
gpuArray = arr
}
}
result := map[string]any{
"gpu": gpuArray,
}
// Добавляем агрегированную статистику
if len(gpuArray) > 0 {
result["summary"] = calculateGPUSummary(gpuArray)
}
return result, nil
}
// collectNvidia собирает данные о NVIDIA GPU
func collectNvidia(ctx context.Context) []map[string]any {
cmd := exec.CommandContext(ctx, "nvidia-smi", "--query-gpu=index,name,driver_version,memory.total,memory.used,memory.free,temperature.gpu,utilization.gpu,utilization.memory,power.draw,power.limit", "--format=csv,noheader,nounits")
output, err := cmd.Output()
if err != nil {
return nil
}
var gpus []map[string]any
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
for _, line := range lines {
if line == "" {
continue
}
fields := strings.Split(line, ", ")
if len(fields) < 11 {
continue
}
gpu := map[string]any{
"index": parseFloat(fields[0]),
"name": strings.TrimSpace(fields[1]),
"driver_version": strings.TrimSpace(fields[2]),
"memory_total_mb": parseFloat(fields[3]),
"memory_used_mb": parseFloat(fields[4]),
"memory_free_mb": parseFloat(fields[5]),
"temperature_c": parseFloat(fields[6]),
"utilization_gpu": parseFloat(fields[7]),
"utilization_mem": parseFloat(fields[8]),
"power_draw_w": parseFloat(fields[9]),
"power_limit_w": parseFloat(fields[10]),
}
gpus = append(gpus, gpu)
}
return gpus
}
// collectRocm собирает данные о AMD GPU
func collectRocm(ctx context.Context) []map[string]any {
cmd := exec.CommandContext(ctx, "rocm-smi", "--showid", "--showtemp", "--showuse", "--showmemuse", "--showmeminfo", "vram", "--showpower")
output, err := cmd.Output()
if err != nil {
return nil
}
var gpus []map[string]any
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
for _, line := range lines {
if line == "" || !strings.Contains(line, "card") {
continue
}
// Парсим строку вида "card 0: AMD Radeon RX 580"
parts := strings.Split(line, ":")
if len(parts) < 2 {
continue
}
cardPart := strings.TrimSpace(parts[0])
namePart := strings.TrimSpace(parts[1])
// Извлекаем индекс карты
cardIndex := strings.TrimPrefix(cardPart, "card ")
gpu := map[string]any{
"index": parseFloat(cardIndex),
"name": namePart,
"driver_version": "ROCm",
"memory_total_mb": 0.0, // ROCm не предоставляет эту информацию
"memory_used_mb": 0.0,
"memory_free_mb": 0.0,
"temperature_c": 0.0,
"utilization_gpu": 0.0,
"utilization_mem": 0.0,
"power_draw_w": 0.0,
"power_limit_w": 0.0,
}
gpus = append(gpus, gpu)
}
return gpus
}
// calculateGPUSummary вычисляет агрегированную статистику по GPU
func calculateGPUSummary(gpus []map[string]any) map[string]any {
if len(gpus) == 0 {
return map[string]any{}
}
totalMemory := 0.0
usedMemory := 0.0
totalPower := 0.0
avgTemp := 0.0
avgUtil := 0.0
for _, gpu := range gpus {
if mem, ok := gpu["memory_total_mb"].(float64); ok {
totalMemory += mem
}
if mem, ok := gpu["memory_used_mb"].(float64); ok {
usedMemory += mem
}
if power, ok := gpu["power_draw_w"].(float64); ok {
totalPower += power
}
if temp, ok := gpu["temperature_c"].(float64); ok {
avgTemp += temp
}
if util, ok := gpu["utilization_gpu"].(float64); ok {
avgUtil += util
}
}
count := float64(len(gpus))
return map[string]any{
"gpu_count": len(gpus),
"total_memory_mb": totalMemory,
"used_memory_mb": usedMemory,
"total_power_w": totalPower,
"avg_temperature": avgTemp / count,
"avg_utilization": avgUtil / count,
}
}
// parseFloat безопасно парсит строку в float64
func parseFloat(s string) float64 {
if s == "" || s == "N/A" {
return 0.0
}
f, _ := strconv.ParseFloat(strings.TrimSpace(s), 64)
return f
}
// exists проверяет существование команды в PATH
func exists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
// --- CPU функции из system коллектора ---
// cpuTimes — срез счётчиков CPU из /proc/stat для вычисления загрузки CPU.
type cpuTimes struct{ user, nice, system, idle, iowait, irq, softirq, steal, guest, guestNice uint64 }
// readProcStat читает строку "cpu " из /proc/stat и возвращает агрегированные
// счётчики времени CPU для последующих дифф-вычислений загрузки.
func readProcStat() (cpuTimes, error) {
f, err := os.Open("/proc/stat")
if err != nil { return cpuTimes{}, err }
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
line := s.Text()
if !strings.HasPrefix(line, "cpu ") { continue }
fields := strings.Fields(line)
if len(fields) < 5 { break }
parse := func(i int) uint64 { if i < len(fields) { v,_ := strconv.ParseUint(fields[i],10,64); return v }; return 0 }
return cpuTimes{
user: parse(1), nice: parse(2), system: parse(3), idle: parse(4),
iowait: parse(5), irq: parse(6), softirq: parse(7), steal: parse(8), guest: parse(9), guestNice: parse(10),
}, nil
}
return cpuTimes{}, errors.New("no cpu line")
}
// cpuUsagePercent возвращает среднюю загрузку CPU в процентах за окно sampleMs.
func cpuUsagePercent(sampleMs int) (float64, error) {
a, err := readProcStat()
if err != nil { return 0, err }
time.Sleep(time.Duration(sampleMs) * time.Millisecond)
b, err := readProcStat()
if err != nil { return 0, err }
totalA := a.user+a.nice+a.system+a.idle+a.iowait+a.irq+a.softirq+a.steal
totalB := b.user+b.nice+b.system+b.idle+b.iowait+b.irq+b.softirq+b.steal
delta := float64(totalB-totalA)
busy := float64((b.user-a.user)+(b.nice-a.nice)+(b.system-a.system)+(b.irq-a.irq)+(b.softirq-a.softirq)+(b.steal-a.steal))
if delta <= 0 { return 0, nil }
return (busy/delta)*100.0, nil
}
// loadAvg читает 1/5/15-минутную среднюю загрузку системы из /proc/loadavg.
func loadAvg() (float64,float64,float64,error) {
b, err := os.ReadFile("/proc/loadavg")
if err != nil { return 0,0,0,err }
fields := strings.Fields(string(b))
if len(fields) < 3 { return 0,0,0,errors.New("bad loadavg") }
l1,_ := strconv.ParseFloat(fields[0],64)
l5,_ := strconv.ParseFloat(fields[1],64)
l15,_ := strconv.ParseFloat(fields[2],64)
return l1,l5,l15,nil
}
// cpuCores возвращает число доступных логических CPU.
func cpuCores() int {
return runtimeNumCPU()
}
// runtimeNumCPU — обёртка для получения числа CPU без CGO.
func runtimeNumCPU() int {
return int(numCPU())
}
// numCPU возвращает количество процессоров из /proc/cpuinfo (без вызова CGO).
func numCPU() uint64 {
f, err := os.Open("/proc/cpuinfo")
if err != nil { return 1 }
defer f.Close()
s := bufio.NewScanner(f)
var cnt uint64
for s.Scan() {
if strings.HasPrefix(s.Text(), "processor") { cnt++ }
}
if cnt == 0 { cnt = 1 }
return cnt
}
// collectCPU собирает базовые CPU-метрики: cores, usage_pct и load1/5/15.
func collectCPU(ctx context.Context) (map[string]any, error) {
usage, _ := cpuUsagePercent(300)
l1,l5,l15,_ := loadAvg()
cores := cpuCores()
return map[string]any{
"cores": cores,
"usage_pct": usage,
"load1": l1,
"load5": l5,
"load15": l15,
}, nil
}
// --- MEM функции из system коллектора ---
// collectMem собирает метрики RAM и Swap из /proc/meminfo (байты и usage_pct).
func collectMem(ctx context.Context) (map[string]any, map[string]any, error) {
b, err := os.ReadFile("/proc/meminfo")
if err != nil { return nil, nil, err }
vals := map[string]uint64{}
s := bufio.NewScanner(strings.NewReader(string(b)))
for s.Scan() {
line := s.Text()
parts := strings.Split(line, ":")
if len(parts) < 2 { continue }
key := strings.TrimSpace(parts[0])
num := strings.Fields(parts[1])
if len(num) == 0 { continue }
v, _ := strconv.ParseUint(num[0], 10, 64)
vals[key] = v * 1024 // kB -> bytes
}
total := vals["MemTotal"]
free := vals["MemFree"] + vals["Buffers"] + vals["Cached"]
used := total - free
usage := pct(used, total)
// swap
sTotal := vals["SwapTotal"]
sFree := vals["SwapFree"]
sUsed := sTotal - sFree
sUsage := pct(sUsed, sTotal)
return map[string]any{
"total": total,
"used": used,
"usage_pct": usage,
}, map[string]any{
"total": sTotal,
"used": sUsed,
"usage_pct": sUsage,
}, nil
}
// pct вычисляет процент от общего значения
func pct(part, total uint64) float64 {
if total == 0 { return 0 }
return float64(part) / float64(total) * 100.0
}
// --- NET функции из system коллектора ---
// netCounters хранит суммарные байты интерфейса (из /proc/net/dev)
type netCounters struct{ rxBytes, txBytes uint64 }
// readNetCounters читает /proc/net/dev и возвращает суммарные rx/tx байты по интерфейсам.
func readNetCounters() (map[string]netCounters, error) {
f, err := os.Open("/proc/net/dev")
if err != nil { return nil, err }
defer f.Close()
res := map[string]netCounters{}
s := bufio.NewScanner(f)
for i := 0; s.Scan(); i++ {
if i < 2 { continue }
line := strings.TrimSpace(s.Text())
parts := strings.Split(line, ":")
if len(parts) != 2 { continue }
name := strings.TrimSpace(parts[0])
fields := strings.Fields(parts[1])
if len(fields) < 16 { continue }
rx,_ := strconv.ParseUint(fields[0],10,64)
tx,_ := strconv.ParseUint(fields[8],10,64)
res[name] = netCounters{rxBytes: rx, txBytes: tx}
}
return res, nil
}
// ifaceSpeedMbps возвращает объявленную скорость интерфейса
func ifaceSpeedMbps(name string) int {
p := filepath.Join("/sys/class/net", name, "speed")
b, err := os.ReadFile(p)
if err != nil { return 0 }
n, _ := strconv.Atoi(strings.TrimSpace(string(b)))
return n
}
// ifaceMAC возвращает MAC-адрес интерфейса.
func ifaceMAC(name string) string {
iface, err := net.InterfaceByName(name)
if err != nil { return "" }
return iface.HardwareAddr.String()
}
// ifaceIPs возвращает список IPv4/IPv6 адресов интерфейса
func ifaceIPs(name string) []string {
iface, err := net.InterfaceByName(name)
if err != nil { return nil }
addrs, _ := iface.Addrs()
ips := []string{}
for _, a := range addrs {
ip, _, _ := net.ParseCIDR(a.String())
if ip == nil { continue }
if ip.IsLoopback() { continue }
if v4 := ip.To4(); v4 != nil {
if v4[0] == 169 && v4[1] == 254 { continue }
ips = append(ips, v4.String())
continue
}
if ip.IsLinkLocalUnicast() { continue }
ips = append(ips, ip.String())
}
return ips
}
// collectNet собирает метрики сетевых интерфейсов
func collectNet(ctx context.Context) (map[string]any, error) {
counters, err := readNetCounters()
if err != nil { return nil, err }
interfaces := map[string]any{}
for name, cnt := range counters {
if strings.HasPrefix(name, "lo") { continue }
speed := ifaceSpeedMbps(name)
mac := ifaceMAC(name)
ips := ifaceIPs(name)
interfaces[name] = map[string]any{
"rx_bytes": cnt.rxBytes,
"tx_bytes": cnt.txBytes,
"speed_mbps": speed,
"mac": mac,
"ips": ips,
}
}
return interfaces, nil
}
// --- DISK функции из system коллектора ---
// collectDisks собирает метрики дисков
func collectDisks(ctx context.Context) (map[string]any, error) {
cmd := exec.CommandContext(ctx, "df", "-B1", "--output=source,target,fstype,size,used,avail,pcent")
output, err := cmd.Output()
if err != nil { return nil, err }
disks := map[string]any{}
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
for i, line := range lines {
if i == 0 { continue } // пропускаем заголовок
fields := strings.Fields(line)
if len(fields) < 7 { continue }
device := fields[0]
mount := fields[1]
fstype := fields[2]
size, _ := strconv.ParseUint(fields[3], 10, 64)
used, _ := strconv.ParseUint(fields[4], 10, 64)
avail, _ := strconv.ParseUint(fields[5], 10, 64)
pcent := strings.TrimSuffix(fields[6], "%")
usage, _ := strconv.ParseFloat(pcent, 64)
disks[device] = map[string]any{
"mount": mount,
"fstype": fstype,
"size_bytes": size,
"used_bytes": used,
"avail_bytes": avail,
"usage_pct": usage,
}
}
return disks, nil
}
// --- TIME SYNC функции из system коллектора ---
// collectTimeSync собирает информацию о синхронизации времени
func collectTimeSync(ctx context.Context) (map[string]any, error) {
result := map[string]any{}
// Проверяем NTP
if exists("ntpq") {
cmd := exec.CommandContext(ctx, "ntpq", "-p")
output, err := cmd.Output()
if err == nil {
result["ntp_peers"] = strings.TrimSpace(string(output))
}
}
// Проверяем systemd-timesyncd
if exists("timedatectl") {
cmd := exec.CommandContext(ctx, "timedatectl", "status")
output, err := cmd.Output()
if err == nil {
result["timedatectl"] = strings.TrimSpace(string(output))
}
}
return result, nil
}
// --- UPDATES функции из system коллектора ---
// collectUpdates собирает информацию об обновлениях
func collectUpdates(ctx context.Context) (map[string]any, error) {
result := map[string]any{}
// Проверяем apt (Debian/Ubuntu)
if exists("apt") {
cmd := exec.CommandContext(ctx, "apt", "list", "--upgradable")
output, err := cmd.Output()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
updates := 0
for _, line := range lines {
if strings.Contains(line, "upgradable") {
updates++
}
}
result["apt_updates"] = updates
}
}
// Проверяем yum (RHEL/CentOS)
if exists("yum") {
cmd := exec.CommandContext(ctx, "yum", "check-update", "--quiet")
output, err := cmd.Output()
if err == nil {
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
result["yum_updates"] = len(lines)
}
}
return result, nil
}

View File

@ -0,0 +1,18 @@
//go:build !linux
package main
// Автор: Сергей Антропов, сайт: https://devops.org.ru
// Коллектор proxvmsystem для неподдерживаемых платформ
import (
"context"
)
// collectProxVMSystem возвращает пустой результат для неподдерживаемых платформ
func collectProxVMSystem(ctx context.Context) (map[string]any, error) {
return map[string]any{
"collector_name": "proxvmsystem",
"error": "unsupported platform",
}, nil
}