O nástroji pro monitorování serveru v reálném čase jsem již psal v článku o netdata. Dnes budu popisovat, jak si můžete naprogramovat vlastní plugin, který Vám bude v netdata vykreslovat například teplotu a vlhkost z čidla, které máte připojeno k Raspberry.

Graf napětí a teloty

Plugin je možné naprogramovat v jakémkoliv jazyce, pokud výsledný program dokáže vypisovat na standardní výstup (stdout). Dále je možné využít modulární pluginy pro BASH, Python a Node.js, které mají unadnit nastavení a vykreslování grafů.

API netdata je popsáno na wiki projektu a myslím si, že je popsáno ne úplně šťastným způsobem. Při programování mého pluginu jsem tedy hlavně zkoumal, co do netdata posílají ostatní pluginy, které jsou umístěné v:

  • Standardní - /usr/libexec/netdata/plugins.d/
  • Bash - /usr/libexec/netdata/charts.d/
  • Python - /usr/libexec/netdata/python.d/
  • Node.js - /usr/libexec/netdata/node.d/

Pro svůj plugin jsem využil standardní API netdata a programovací jazyk C++. Budu tedy popisovat hlavně mé zkušenosti s touto kombinací.

API netdata

Standardní pluginy, které využívají API netdata, fungují tak, že netdata při startu spouštějí všechny spustitelné soubory, které se nacházejí v /usr/libexec/netdata/plugins.d/ a předávají jim jeden parametr, který říká, po kolika sekundách má plugin aktualizovat data. Je tedy také nutné počítat s tím, že se pluginy spouští pod uživatelem netdata, který nemusí mít všechna potřebná práva. Plugin po spuštění nastaví grafy, do kterých chce posílat data a poté už v pravidelných intervalech odesílá naměřené hodnoty.

Komunikace pluginu s netdata probíhá přes standardní výstup stdout. Zpráva se skládá z úvodního klíčového slova (CHART, DIMENSION, SET, ...), dále následují parametry a zpráva je ukončena znakem nového řádku \n.

Plugin může vypisovat chyby na chybový výstup stderr, který je přesměrován do logu netdata, pokud je logování povoleno.

Parametry je nutno uvádět do jednoduchých uvozovek 'parametr', pokud obsají nepovolené znaky (mezera) a pokud je parametr vynechán, tak se odešlou jen prázdné uvozovky ''. Parametry mohou obsahovat znaky s diakritikou, ale v netdata se nezobrazí správně.

Nastavení grafu

Nejprve se musí vytvořit graf, do kterého se poté vykreslují data. K tomu slouží zpráva CHART, která má následující formát:

CHART type.id name title units [family [context [charttype [priority [update_every]]]]]

type.id
Unikátní název grafu, na který se dále odkazuje z dalších příkazů. type označuje kategorii v menu.

name
Je použito pro komunikaci s uživatelem jako název grafu. Pokud není vyplněno, tak se použije id.

title
Popis, který se zobrazuje nad grafem.

units
Jednotky hodnot, které jsou vykresleny v grafu.

family
Slouží pro seskupování grafů pod submenu v grafu. Všechny grafy, které vykreslují teplotu by měly mít family nastaveno například na 'Teplota'.

context
Seskupuje grafy, které mají stejný context, aby měly stejný vizuální styl.

charttype
Nastavuje typ grafu. Možné hodnoty jsou line, area, stacked. Pokud chybí, tak je použit typ line.

priority
Udává pozici v menu. Menší číslo znamená větší prioritu a grafy se tak zobrazí výše na stránce. 10000 nastavuje prioritu tak, že se grafy zobrazí pod grafy využití procesoru.

update_every
Přepisuje periodu aktualizace, která je nastavena serverem. Měla by odpovídat periodě, s kterou se doopravdy odesílají data.

Příklad

Nastavení grafů, pro zobrazení venkonví i vnitřní teploty a vlhkosti by mohlo vypadat následovně:

CHART Meteostanice.temperature_outside 'Venkovni teplota' '°C' teploty  '' line 10000 1
CHART Meteostanice.temperature_inside  'Vnitrni teplota'  '°C' teploty  '' line 10001 1
CHART Meteostanice.humidity            'Vlhkost'          '%'  vlhkosti '' line 10002 1

Nastavení dimenzí

Pro každý graf se musí nastavit dimenze (data), které se v něm zobrazují a graf jich může mít více. K tomu slouží zpráva DIMENSION s formátem:

DIMENSION id [name [algorithm [multiplier [divisor [hidden]]]]]

Nastavení dimenzí musí proběhnout hned po nadefinování konkrétního grafu, do kterého mají nastavované dimenze patřit.

id
Jméno dimenze, na které se odkazuje při odesílání dat. Nemělo by obsahovat znak ..

name
Jméno dimenze, které se zobrazuje uživateli.

algorithm
Nastavuje algoritmus pro zpracování přijatých dat. Může být například absolute, kdy se data zobrazují tak, jak byla přijata, nebo například incremental, kdy se k zobrazované hodnotě přičítají přijatá data.

multiplier
Hodnota (celé číslo), kterou se násobí přijatá data.

divisor
Hodnota (celé číslo), kterým se dělí přijatá data.

hidden
Klíčové slovo hidden schová dimenzi, ale ta bude stále využita při výpočtech ostatních.

Příklad

Grafy teploty mají jen jednu dimenzi, graf vlhkosti má dimenze dvě.

DIMENSION temperature_outside Teplota absolute 1 100
DIMENSION temperature_inside  Teplota absolute 1 100
DIMENSION humidity_SHT15      SHT15   absolute 1 100
DIMENSION humidity_SHT25      SHT25   absolute 1 100

Odesílání dat

Pro odesílání naměřených dat slouží trojice zpráv BEGIN, SET a END.

Začátek dat označuje BEGIN s formátem:

BEGIN type.id [microseconds]

type.id
Udává, do kterého grafu patří odesílaná data.

microseconds
Udává počet mikrosekund, který uplynul od poslední aktualizace grafu. Je to nepovinná hodnota, která zpřesní vykreslení dat, pokud by na velmi vytíženém systému docházelo ke zpomalování přenosu dat.

Jednotlivá data jsou uvozena zprávou SET s formátem:

SET id = value

id
ID dimenze, do které patří data, která udává value. Netdata přijímají jen celá čísla a pokud se mají zobrazovat desetinná, tak je potřeba data před odesláním vynásobit a nastavit divisor na odpovídající hodnotu.

Konec dat je singnalizován zprávou END, která nemá žádné parametry.

Příklad

Odeslání naměřených teplot a vlhkostí.

BEGIN Meteostanice.temperature_outside
SET temperature_outside = 2055
END

BEGIN Meteostanice.temperature_inside
SET temperature_inside = 2178
END

BEGIN Meteostanice.humidity
SET humidity_SHT15 = 8012
SET humidity_SHT25 = 7982
END

Shrnutí

Pro nastavení grafů meteostanice a následné odesílání dat slouží následující příkazy, v daném pořadí, které náš plugin vypisuje na stdout.

#Inicializace grafů
CHART Meteostanice.temperature_outside 'Venkovni teplota' '°C' teploty '' line 1000 1
DIMENSION temperature_outside Teplota absolute 1 100

CHART Meteostanice.temperature_inside 'Vnitrni teplota' '°C' teploty '' line 1001 1
DIMENSION temperature_inside Teplota absolute 1 100

CHART Meteostanice.humidity 'Vlhkost' '%' vlhkosti '' line 1002 1
DIMENSION humidity_SHT15 SHT15 absolute 1 100
DIMENSION humidity_SHT25 SHT25 absolute 1 100

#Odesílání dat
BEGIN Meteostanice.temperature_outside
SET temperature_outside = 2055
END

BEGIN Meteostanice.temperature_inside
SET temperature_inside = 2178
END

BEGIN Meteostanice.humidity
SET humidity_SHT15 = 8012
SET humidity_SHT25 = 7982
END

Výledek bude vypadat takto:

Grafy meteostanice

Debugování

Pro debugování pluginu ho stačí pouze spustit a jelikož vypisuje na standardní výstup, tak je kontrola velmi snadná.

Pro kontrolu, že plugin běží správně, když je spuštěn přes netdata, lze použít následující postup:

#Získání PID procesu, pod kterým plugin běží
ps ax | grep muj.plugin

10537 ?        R      0:27 /usr/libexec/netdata/plugins.d/muj.plugin 2

#Připojení se na stdout tohoto procesoru
sudo strace -p10537 -s9999 -e write

C++ problém s std::cout

Když jsem programoval svůj plugin, tak jsem narazil na problém, kdy se v grafech odesílané hodnoty chvíly zobrazovaly a chvíli se naopak nezobrazovaly, ale při testování mi plugin korektně každou 1s vypisoval odesílání dat. Problém jsem vyřešil poté, co jsem se podíval na výstup pluginu, když byl spuštěn přes netdata. Zde jsem viděl, že se data neodesílají každou vteřinu, ale odeslalo se jich naráz několik za několik sekund. Problém byl v tom, že data vypisuji následovně:

cout << "BEGIN Stojan.temperatures" << '\n';
cout << "SET RPi = " << temperatures.RPi*100 << '\n';
cout << "SET Interface = " << temperatures.interface*100 << '\n';
cout << "END" << '\n';

A výstup na cout se provádí z bufferu a není zaručeno, že se výpis provede hned. Jedno řešení je místo \n používat std::endl, který pošle znak nového řádku a vyprázdní buffer. Nevýhodou tohoto řešení je, že vyprazdňování bufferu zpomaluje program. Druhou možností, kterou jsem využil, je zavolání std::cout << std::flush; na konci odesílání dat, čímž dojde k vyprázdnění bufferu a okamžitému výpisu dat na obrazovku.

Dejte nám vědět!

Naprogramovali jste si vlastní plugin pro netdata? Pochlubte se na na našem fórum.