MSI Afterburner и Zabbix

Есть популярная утилита мониторинга видеокарты и FPS в играх — MSI Afterburner. С её помощью удобно не только разгонять видеокарту, но и мониторить параметры как видеокарты, так и системы. И можно даже писать данные мониторинга в лог. И здесь раскрывается хорошая возможность передавать данные из Afterburner в Zabbix.

Изначальные условия таковы: ОС Windows 10, Zabbix 6.0, Zabbix agent 2 v.7 (windows), MSI Afterburner 4.6.5

Формат файла лога — csv, имеет следующую структуру

00, <дата>, Hardware monitoring log v1.6
01, <дата>, NVIDIA GeForce GTX 1070 Ti
02, <дата>, GPU temperature ,GPU usage, ...и другие параметры, которые можно включить на вкладке Мониторинг.
80, <дата>, 53.000 ,4.000 , ...остальные параметры

Ранее, я делал простой способ парсинга этих данных — в заббиксе для итема использовал встроенную функцию log() и в аргументах указывал регулярку для поиска нужного значения, то есть что-то типа

log[{$AB_FILE_DATA},".{46}(.{6}).*",,,,\1]
, где $AB_FILE_DATA путь к файлу лога, указывается в макросах шаблона

оно всё находило нужное, но если я в MSI Afterburner выберу другие данные системы для мониторинга, то придётся все регулярки переписывать заново. Неудобно. Поэтому пока я настроил и забыл.

Однако пришли нейронки и появилась идея спросить у них, в данном случае — Qwen.

В процессе борьбы с глюками поиска нейронка сначала выдала вполне работающий скрипт на PowerShell, а вот с шаблонами для заббикса пошли косяки — квен упорно выдавал то шаблоны с ненужными UID, то с лишними полями, видимо подразумевая, что у меня 7 версия заббикс-сервера, даже если ему прямо сказать версию. В итоге плюнул, создал свой базовый шаблон, подсунул квену, и тот что-то «понял».

По итогу решено было делать мониторинг через внешний скрипт на PowerShell. Плюсы — можно в Afterburner менять данные и парсинг не поломается, минусы — работает внешний скрипт, парсит много данных, время на ответ больше, требуется увеличение таймаутов в заббиксе. Так-то не критично, но неприятно.

Для этого необходимо подготовить систему к запуску скрипта сервисом заббикс агента.

Подготовка системы

1. Добавьте в конфигурацию заббикс-агента следующие строки

UserParameter=afterburner.metric[*],powershell -nologo -noprofile -ExecutionPolicy Bypass -File "C:\zabbix\ab_parse.ps1" "$1" "$2"
, где -nologo -noprofile немного ускоряет запуск оболочки PowerShell,
ExecutionPolicy Bypass - разрешает выполнение скриптов, не изменяя настройки политики запуска в Windows.

2. Перезапустите сервис агента

3. Положите в удобное место скрипт ab_parse.ps1 со следующим содержимым:

# ab_parse.ps1
# Usage: .\ab_parse.ps1 "C:\log.txt" "gpu_temp"
param(
[Parameter(Mandatory = $true)][string]$LogPath,
[Parameter(Mandatory = $true)][string]$Metric
)

$lookup = @{
gpu_temp = 'GPU temperature'
gpu_usage = 'GPU usage'
gpu_mem_usage = 'Memory usage' # VRAM
gpu_core_clock = 'Core clock'
fan_speed = 'Fan speed'
cpu_temp = 'CPU temperature'
cpu_usage = 'CPU usage'
cpu_clock = 'CPU clock'
cpu_power = 'CPU power'
ram_usage = 'RAM usage'
}

$headerTarget = $lookup[$Metric]
if (-not $headerTarget) {
[Console]::Error.WriteLine("ERR: Unknown metric '$Metric'")
exit 1
}

if (-not (Test-Path -LiteralPath $LogPath)) {
[Console]::Error.WriteLine("ERR: File not found: $LogPath")
exit 1
}

try {
# === Шаг 1: Получаем заголовок (первая строка или 02,...)
$headerLine = $null
$stream = New-Object System.IO.FileStream($LogPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
$reader = New-Object System.IO.StreamReader($stream)
try {
$firstLine = $reader.ReadLine()
if ($firstLine -match '^02,\s*\d{2}-\d{2}-\d{4}') {
$headerLine = $firstLine
} elseif ($firstLine -like '*GPU temperature*') {
$headerLine = $firstLine
} else {
# fallback: читаем до 02 построчно (редко)
while (-not $reader.EndOfStream) {
$line = $reader.ReadLine()
if ($line -match '^02,\s*\d{2}-\d{2}-\d{4}') {
$headerLine = $line
break
}
}
}
} finally {
$reader.Dispose()
$stream.Dispose()
}

if (-not $headerLine) {
$headerLine = Get-Content $LogPath -TotalCount 1
}

# === Шаг 2: Ищем ПОСЛЕДНЮЮ строку данных (80,...), читая с конца
$dataLine = $null

# Открываем заново, чтобы искать с конца
$stream = New-Object System.IO.FileStream($LogPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
$bufferSize = 4096
$buffer = New-Object byte[] $bufferSize
$encoding = [System.Text.Encoding]::UTF8 # или Default, если лог в ANSI

$length = $stream.Length
$pos = $length
$linesFromEnd = @()

# Читаем блоки с конца до тех пор, пока не найдём строку 80,...
while ($pos -gt 0 -and $null -eq $dataLine) {
$readSize = [Math]::Min($bufferSize, $pos)
$pos -= $readSize
$stream.Seek($pos, [System.IO.SeekOrigin]::Begin) | Out-Null
$stream.Read($buffer, 0, $readSize) | Out-Null

$chunk = $encoding.GetString($buffer, 0, $readSize)
# Разбиваем на строки (включая потенциально "разорванные" строки)
$chunkLines = $chunk -split "`n" | ForEach-Object { $_.TrimEnd("`r") }

# Добавляем к накопленным строкам — но в обратном порядке блоков!
$linesFromEnd = $chunkLines + $linesFromEnd

# Ищем в новых строках первую (с конца) подходящую 80,...
foreach ($line in $linesFromEnd) {
if ([string]::IsNullOrWhiteSpace($line)) { continue }
if ($line -match '^80,\s*\d{2}-\d{2}-\d{4}') {
$dataLine = $line
break
}
}
}
} finally {
$stream.Dispose()
}

# Fallback на Get-Content -Tail, если ничего не нашли (редко)
if (-not $dataLine) {
$fallbackLines = Get-Content $LogPath -Tail 20 | Where-Object { $_ -match '^80,\s*\d{2}-\d{2}-\d{4}' }
if ($fallbackLines) {
$dataLine = $fallbackLines[-1] # последняя из подходящих
}
}

# Если всё ещё нет — выдаём 0
if (-not $dataLine) {
Write-Host "0"
exit 0
}

# === Шаг 3: Парсим
$headers = $headerLine.Split(',') | ForEach-Object { $_.Trim() }
$data = $dataLine.Split(',') | ForEach-Object { $_.Trim() }

$idx = -1
for ($i = 0; $i -lt $headers.Count; $i++) {
if ($headers[$i] -like "*$headerTarget*") {
$idx = $i
break
}
}

if ($idx -lt 0 -or $idx -ge $data.Count) {
Write-Host "0"
exit 0
}

$rawVal = $data[$idx]
$cleanVal = [System.Text.RegularExpressions.Regex]::Replace($rawVal, '[^\d\.\-]', '')

if ([string]::IsNullOrWhiteSpace($cleanVal) -or -not ($cleanVal -match '^-?\d+(\.\d+)?$')) {
Write-Host "0"
exit 0
}

Write-Host $cleanVal

} catch {
[Console]::Error.WriteLine("ERR: $($_.Exception.Message.Substring(0, [Math]::Min(100, $_.Exception.Message.Length)))")
Write-Host "0"
exit 0
}

Изначально при генерации этого скрипта не было двух строк, из-за которых чтение лога сопровождалось, видимо, исключением в Afterburner и отключением логирования. Опять же квен помог выявить проблему. Чтение теперь происходит не в режиме

[System.IO.File]::OpenText($LogPath) а в режиме New-Object System.IO.FileStream

что не мешает Afterburner писать логи в файл.

4. Импортируйте шаблон (ссылка будет ниже) или создайте новый примерно такого содержания

Name: CPU Clock
Type: Zabbix agent (active)
Key: afterburner.metric[{$AB_LOG_PATH},cpu_clock]
Type of information: Numeric (float)

Теперь всё готово. Цепляем шаблон к хосту, проверяем с заббикс сервера доступность

zabbix_get -s GPU-Host -k "afterburner.metric[C:\Program Files (x86)\MSI Afterburner\HardwareMonitoring.csv,cpu_temp]"

и если ответ получился через полсекунды, то всё настроено верно.

На этом всё. О наёденных ошибках, огрехах и неточностях сообщайте.

Подписаться
Уведомить о
guest

0 комментариев
Новые
Старые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии