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/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
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 контроллерах
|
||||||
|
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
|
#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
|
||||||
|
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