
Tichon Uskow, inżynier zespołu integracji Zabbixa
Zabbix to dostosowalna platforma służąca do monitorowania wszelkich danych. Od pierwszych wersji Zabbixa administratorzy monitorowania mieli możliwość uruchamiania różnych skryptów przez Actions w przypadku kontroli na docelowych węzłach sieci. Uruchamianie skryptów wiązało się z wieloma trudnościami, w tym koniecznością wsparcia skryptów, ich dostarczenia do węzłów i proxy, a także wsparcia różnych wersji.
JavaScript dla Zabbixa
W kwietniu 2019 roku zaprezentowano Zabbixa 4.2 z funkcjonalnością wstępnego przetwarzania w JavaScript. Wiele osób zaczęło myśleć o rezygnacji z pisania skryptów, które gdzieś pozyskują dane, przetwarzają je i prezentują w zrozumiałym formacie dla Zabbixa, na rzecz przeprowadzania prostych testów, które będą otrzymywać dane niegotowe do przechowywania i przetwarzania przez Zabbixa, a następnie przetwarzać ten strumień danych przy użyciu narzędzi Zabbixa i JavaScript. W połączeniu z niskopoziomowym wykrywaniem i powiązanymi elementami danych, które pojawiły się w Zabbixie 3.4, powstała достаточно elastyczna koncepcja do sortowania i zarządzania pozyskanymi danymi.
W Zabbixie 4.4, jako logiczne rozwinięcie wstępnego przetwarzania w JavaScript, pojawił się nowy sposób powiadamiania — Webhook, który można wykorzystać do prostej integracji powiadomień Zabbixa z aplikacjami zewnętrznymi.
JavaScript i Duktape
Dlaczego wybrano właśnie JavaScript i Duktape? Rozważano różne opcje języków i silników:
- Lua – Lua 5.1
- Lua – LuaJIT
- JavaScript – Duktape
- JavaScript – JerryScript
- Wbudowany Python
- Wbudowany Perl
Główne kryteria wyboru to powszechność, łatwość integracji silnika w produkt, niskie zużycie zasobów oraz ogólna wydajność silnika, a także bezpieczeństwo wdrażania kodu w tym języku w monitoring. Zwyciężył JavaScript na silniku Duktape.

Kryteria wyboru i testowanie wydajności
Cechy Duktape:
— Standard
— Moduły Zabbixa dla Duktape:
- Zabbix.log() — umożliwia zapisywanie bezpośrednio do logu Zabbix Server komunikatów o różnym poziomie szczegółowości, co pozwala na porównywanie błędów, np. w Webhooku z stanem serwera.
- CurlHttpRequest() — umożliwia wykonywanie zapytań HTTP w sieci, na czym opiera się zastosowanie Webhooku.
- atob() oraz btoa() — umożliwia kodowanie i dekodowanie ciągów w formacie Base64.
UWAGA. Duktape spełnia standardy ACME. W Zabbix używana jest wersja skryptu z 2015 roku. Kolejne zmiany są nieznaczne, więc można je zignorować..
Magia JavaScript
Cała magia JavaScript polega na dynamicznym typowaniu i konwersji typów: łańcuchowych, liczbowych i logicznych.
Oznacza to, że nie trzeba z góry deklarować, jakiego typu wartość ma zmienna.
Podczas operacji matematycznych wartości zwracane przez operatorowe funkcje są przekształcane na liczby. Wyjątkiem w takich operacjach jest dodawanie, ponieważ, jeśli przynajmniej jeden z addendów jest łańcuchem, do wszystkich addendów stosuje się konwersję do łańcucha.
UWAGA. Metody odpowiedzialne za takie przekształcenia są zazwyczaj zrealizowane w prototypach rodzicielskich obiektów, valueOf i toString. valueOf jest wywoływane podczas konwersji do liczby i zawsze przed metodą toString. Metoda valueOf musi zwracać wartości prymitywne, w przeciwnym razie jej wynik jest ignorowany.
Dla obiektu wywoływana jest metoda valueOF. Jeśli nie zostanie znaleziona lub nie zwraca wartości prymitywnej, wywoływana jest metoda toString. Jeśli metoda toString nie zostanie znaleziona, przeszukiwany jest prototyp obiektu i wszystko powtarza się, aż przetworzenie wartości zakończy się, a wszystkie wartości w wyrażeniu zostaną przekształcone do jednego typu. valueOf Jeśli dla obiektu zaimplementowano metodę. , która zwraca wartość prymitywną, to właśnie ona jest używana do konwersji na łańcuch. toStringPrzy czym wynik zastosowania tej metody niekoniecznie musi być łańcuchem. Na przykład, jeśli dla obiektu ‘
obj‘ zdefiniowana jest metoda`var obj = { toString() { return "200" }}` toString,
zwraca dokładnie łańcuch, a przy dodawaniu łańcucha z liczbą uzyskujemy połączony łańcuch: metoda toString `obj + 1 // '2001'``obj + 'a' // ‘200a'`
Ale jeśli przepiszemy, aby metoda zwracała liczbę, przy dodawaniu obiektu będziemy wykonywać operację matematyczną z przekształceniem do liczby, co daje wynik matematycznego dodawania. toString`var obj = { toString() { return 200 }}``obj + 1 // '2001'`
Przy czym, jeśli wykonujemy dodawanie ze łańcuchem, wykonuje się konwersja do łańcucha, co daje nam połączony łańcuch.`obj + 'a' // ‘200a'`
To właśnie w tym tkwi przyczyna wielu błędów początkujących użytkowników JavaScript.Do metody
można wpisać funkcję, która zwiększy bieżącą wartość obiektu o 1. toString Można wpisać funkcję, która zwiększy bieżącą wartość obiektu o 1.

Wykonanie skryptu pod warunkiem, że zmienna jest równa 3, a jednocześnie równa 4.
Podczas porównania z rzutowaniem typów (==) za każdym razem wywoływana jest metoda toString z funkcją zwiększania wartości. W związku z tym, przy każdym kolejnym porównaniu wartość wzrasta. Można temu zapobiec, stosując porównanie bez rzutowania typów (===).

Porównanie bez rzutowania typów
UWAGA. Nie używaj porównania z rzutowaniem typów bez potrzeby.
Dla złożonych skryptów, na przykład Webhook z złożoną logiką, w których konieczne jest porównanie z rzutowaniem typów, zaleca się wcześniejsze skonstruowanie kontroli dla wartości, które zwracają zmienne, oraz obsługiwanie niezgodności i błędów.
Webhook Media
Pod koniec 2019 roku i na początku 2020 roku zespół integracji Zabbix intensywnie rozwijał Webhooki oraz integracje „z pudełka”, które są dostarczane w dystrybucji Zabbix.

Link do
Preprocessing
- Pojawienie się wstępnego przetwarzania w JavaScript pozwoliło zrezygnować z większości zewnętrznych skryptów, a obecnie w Zabbix można uzyskać dowolną wartość i przekształcić ją w zupełnie inną.
- Wstępne przetwarzanie w Zabbix implementowane jest kodem w JavaScript, który podczas kompilacji do bajt-kodu przekształca się w funkcję przyjmującą pojedynczą wartość jako parametr value w postaci napisu (w napisie mogą znajdować się zarówno cyfry, jak i litery).
- Ponieważ na wyjściu otrzymujemy funkcję, na końcu skryptu wymagany jest return.
- Możliwe jest użycie makr użytkownika w kodzie.
- Zasoby można ograniczyć nie tylko na poziomie systemu operacyjnego, ale również programowo. Dla kroku wstępnego przetwarzania przydzielane jest maksymalnie 10 megabajtów pamięci operacyjnej oraz limit czasu wykonywania wynoszący 10 sekund.

UWAGA. Wartości limitu czasowego wynoszące 10 sekund są dość dużą wartością, ponieważ zbieranie kilkudziesięciu tysięcy elementów danych w 1 sekundę w dość „ciężkim” scenariuszu wstępnego przetwarzania może spowolnić działanie Zabbix. Dlatego nie zaleca się używania wstępnego przetwarzania do wykonywania pełnowartościowych skryptów w JavaScript przez tzw. fikcyjne elementy danych (dummy items), które są uruchamiane wyłącznie w celu wykonania wstępnego przetwarzania..
Swój kod można sprawdzić za pomocą testu wstępnego przetwarzania lub przy użyciu narzędzia zabbix_js:
`zabbix_js -s *script-file -p *input-param* [-l log-level] [-t timeout]`
`zabbix_js -s script-file -i input-file [-l log-level] [-t timeout]`
`zabbix_js -h`
`zabbix_js -V`
Praktyczne zadania
Zadanie 1
Zamienić obliczany element danych poprzez wstępną obróbkę.
Warunek: otrzymujemy z czujnika temperaturę w stopniach Fahrenheita do przechowania w stopniach Celsjusza.
Wcześniej stworzylibyśmy element danych, który zbiera temperaturę w stopniach Fahrenheita. Następnie jeszcze jeden element danych (obliczany), który przekształcałby stopnie Fahrenheita na stopnie Celsjusza według wzoru.
Problemy:
- Należy dublować elementy danych i przechowywać wszystkie wartości w bazie.
- Należy skoordynować interwały dla „rodzica” elementu danych, który jest obliczany i używany w formule, oraz dla obliczanego elementu danych. W przeciwnym razie obliczany element danych może przejść w stan nieobsługiwany lub policzyć poprzednią wartość, co wpłynie na niezawodność wyników monitorowania.
Jednym z rozwiązań było zrezygnowanie z elastycznych interwałów sprawdzeń na rzecz stałych interwałów, aby gwarantować obliczenie obliczanego elementu danych po elemencie danych zbierającym dane (w naszym przypadku - temperaturze w stopniach Fahrenheita).
Ale jeśli, na przykład, szablon używamy do sprawdzania dużej liczby urządzeń, a sprawdzenie odbywa się co 30 sekund, przez 29 sekund Zabbix „nie działa”, a w ostatnią sekundę zaczyna sprawdzenia i obliczenia. To prowadzi do powstawania kolejki i wpływa na wydajność. Dlatego zaleca się stosowanie stałych interwałów tylko wtedy, gdy jest to naprawdę konieczne.
W tej sytuacji optymalne rozwiązanie to wstępna obróbka z jednej linii w JavaScript, która konwertuje stopnie Fahrenheita na stopnie Celsjusza:
`return (value - 32) * 5 / 9;`To szybkie i proste, nie ma potrzeby tworzenia zbędnych elementów danych i przechowywania historii, a także można używać elastycznych interwałów do sprawdzeń.

`return (parseInt(value) + parseInt("{$EXAMPLE.MACRO}"));`Jednak, jeśli w hipotetycznej sytuacji należy uzyskany element danych zsumować, na przykład z jakąś stałą określoną w makrosie, należy pamiętać, że parametr value rozszerza się do ciągu. Przy operacji dodawania ciągów, dwa ciągi po prostu łączą się w jeden.

`return (value + "{$EXAMPLE.MACRO}");`Aby uzyskać wynik działania matematycznego, należy przekształcić typy uzyskanych wartości na format numeryczny. Można to zrobić za pomocą funkcji parseInt(), która zwraca liczbę całkowitą, funkcji parseFloat(), która zwraca liczbę dziesiętną, lub funkcji number, która zwraca liczbę całkowitą lub dziesiętną.
Zadanie 2
Uzyskaj czas w sekundach do wygaśnięcia certyfikatu.
Warunek: pewna usługa zwraca datę wygaśnięcia certyfikatu w formacie "Feb 12 12:33:56 2022 GMT".
W ECMAScript5 Date.parse() przyjmuje datę w formacie ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ). Należy przekształcić do niego ciąg w formacie MMM DD YYYY HH:mm:ss ZZ
Problem: wartość miesiąca jest wyrażona tekstem, a nie liczbą. Dane w takim formacie nie są akceptowane przez Duktape.
Przykład rozwiązania:
Na początku deklarowana jest zmienna, która przyjmuje wartość (cały skrypt to deklaracje zmiennych, które są wymienione oddzielnie).
W pierwszej linii uzyskujemy datę w parametrze value i dzielimy ją na części za pomocą metody split. W ten sposób uzyskujemy tablicę, w której każdemu elementowi tablicy, zaczynając od indeksu 0, odpowiada jeden element daty przed i po spacjach. split(0) — miesiąc, split(1) — dzień, split(2) — ciąg z czasem itd. Następnie do każdego elementu daty można odwoływać się za pomocą indeksu w tablicy.
`var split = value.split(' '),`- Każdemu miesiącowi (w porządku chronologicznym) przypisany jest indeks jego położenia w tablicy (od 0 do 11). Aby przekonwertować wartość tekstową na numeryczną, do indeksu miesiąca dodaje się 1 (ponieważ numeracja miesięcy zaczyna się od 1). W tym przypadku wyrażenie z dodawaniem 1 jest umieszczone w nawiasach, ponieważ w przeciwnym razie uzyska się ciąg, a nie liczbę. Na końcu wykonujemy slice() — wycinanie tablicy od końca, aby pozostawić tylko dwa znaki (co jest istotne dla miesięcy z dwucyfrowym numerem).
`MONTHS_LIST = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],`
`month_index = ('0' + (MONTHS_LIST.indexOf(split[0]) + 1)).slice(-2),`- Formujemy z uzyskanych wartości ciąg w formacie ISO przez zwykłe dodawanie ciągów w odpowiedniej kolejności.
`ISOdate = split[3] + '-' + month_index + '-' + split[1] + 'T' + split[2],`Dane w uzyskanym formacie — liczba sekund od 1970 roku do określonego momentu w przyszłości. Używanie danych w uzyskanym formacie w wyzwalaczach jest praktycznie niemożliwe, ponieważ Zabbix pozwala operować tylko na makrosach {Date} i {Time}, które zwracają datę i czas w zrozumiałym dla użytkownika formacie.
- Następnie możemy uzyskać w JavaScript bieżącą datę w formacie Unix Timestamp i odjąć ją od uzyskanej daty wygaśnięcia certyfikatu, aby uzyskać liczbę milisekund od teraz do momentu wygaśnięcia certyfikatu.
`now = Date.now();`- Dzielimy uzysaną wartość przez tysiąc, aby uzyskać sekundy w Zabbix.
`return parseInt((Date.parse(ISOdate) - now) / 1000);`W wyzwalaczu można ustawić wyrażenie ‘last<‘ i zestaw cyfr, który odpowiada liczbie sekund w okresie, na który należy zareagować, na przykład w tygodniach. W ten sposób wyzwalacz będzie powiadamiać o tym, że ważność certyfikatu kończy się za tydzień.
UWAGA. Zwróć uwagę na użycie parseInt() w funkcji return, aby skonwertować liczbę zmiennoprzecinkową uzyskaną w wyniku dzielenia milisekund na liczbę całkowitą. Można również użyć parseFloat() i przechowywać dane zmiennoprzecinkowe.
Źródło: habr.com
