feat: добавлен коллектор proxvmsystem и обновлена документация
- Добавлен новый коллектор proxvmsystem, объединяющий функциональность system и gpu коллекторов - Добавлен machine_uid для идентификации VM/контейнеров в Proxmox инфраструктуре - Обновлена документация по коллекторам и проекту - Добавлены новые хосты в inventory для тестирования - Обновлен Makefile для сборки нового коллектора - Обновлен config.yaml с конфигурацией proxvmsystem коллектора Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
parent
b23d724bea
commit
7d970eada7
3
Makefile
3
Makefile
@ -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/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/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:
|
||||
# Кросс-сборка коллекторов для Windows
|
||||
|
@ -12,6 +12,8 @@ SensusAgent — модульный агент сбора метрик. Аген
|
||||
- Деплой (Ansible, systemd): `docs/deploy.md`
|
||||
- **Kafka SSL поддержка**: `docs/kafka_ssl.md` ⭐
|
||||
- **Proxmox кластер**: `docs/collectors/proxcluster.md` ⭐
|
||||
- **Proxmox VM/контейнеры**: `docs/collectors/proxvms.md` ⭐
|
||||
- **Proxmox системные метрики**: `docs/collectors/proxvmsystem.md` ⭐
|
||||
|
||||
Быстрый старт:
|
||||
```bash
|
||||
|
@ -118,5 +118,13 @@ collectors:
|
||||
timeout: "300s"
|
||||
exec: "./collectors/proxvms"
|
||||
platforms: [linux]
|
||||
proxvmsystem:
|
||||
enabled: true
|
||||
type: exec
|
||||
key: proxvmsystem
|
||||
interval: "300s"
|
||||
timeout: "60s"
|
||||
exec: "./collectors/proxvmsystem"
|
||||
platforms: [linux]
|
||||
|
||||
|
||||
|
@ -61,6 +61,7 @@ make collectors-darwin # darwin/arm64
|
||||
- **proxcluster** - информация о Proxmox кластере (ноды, хранилища, кворум) - Linux ⭐
|
||||
- **proxnode** - информация о 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 устройствах с агрегированной статистикой
|
||||
- [proxnode](collectors/proxnode.md) - сбор информации о Proxmox ноде
|
||||
- [proxvms](collectors/proxvms.md) - сбор информации о виртуальных машинах и контейнерах Proxmox
|
||||
- [proxvmsystem](collectors/proxvmsystem.md) - системные метрики с machine_uid для Proxmox VM/контейнеров
|
||||
- [system](collectors/system.md) - сбор системных метрик
|
||||
- [docker](collectors/docker.md) - сбор информации о Docker контейнерах
|
||||
- [hba](collectors/hba.md) - сбор информации о RAID/HBA контроллерах
|
||||
|
302
docs/collectors/proxvmsystem.md
Normal file
302
docs/collectors/proxvmsystem.md
Normal 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 кластере
|
@ -4,5 +4,8 @@
|
||||
#kube_ansible ansible_host=10.14.246.9 ansible_user=devops
|
||||
#videotest7 ansible_host=10.13.37.186 ansible_user=devops
|
||||
#videotest8 ansible_host=10.13.37.187 ansible_user=devops
|
||||
pnode02 ansible_host=10.14.253.12 ansible_user=devops
|
||||
#dbrain01 ansible_host=10.14.246.75 ansible_user=devops
|
||||
#pnode02 ansible_host=10.14.253.12 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
|
||||
|
55
src/collectors/proxvmsystem/main.go
Normal file
55
src/collectors/proxvmsystem/main.go
Normal 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
|
||||
}
|
646
src/collectors/proxvmsystem/proxvmsystem_linux.go
Normal file
646
src/collectors/proxvmsystem/proxvmsystem_linux.go
Normal 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
|
||||
}
|
18
src/collectors/proxvmsystem/proxvmsystem_unsupported.go
Normal file
18
src/collectors/proxvmsystem/proxvmsystem_unsupported.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user