
Тихон Усков, инженер на екипа за интеграция Zabbix
Zabbix — персонализирана платформа, която се използва за мониторинг на всякакви данни. От самото начало на версиите на Zabbix администраторите по мониторинг са имали възможността да стартират различни скриптове чрез Действия за проверки на целевите мрежови възли. При това стартирането на скриптовете е предизвиквало редица трудности, включително необходимостта от поддръжка на скриптовете, тяхното доставяне до възлите за свързване и прокси, както и поддръжка на различни версии.
JavaScript за Zabbix
През април 2019 година беше представен Zabbix 4.2 с функция за предварителна обработка на JavaScript. Много хора се запалиха по идеята да откажат написването на скриптове, които взимат данни отнякъде, обработват ги и ги представят в разбираем за Zabbix формат, и вместо това да извършват прости проверки, които ще получават недоизготовени данни за съхранение и обработка от Zabbix и след това да обработват този поток данни с помощта на средствата на Zabbix и JavaScript. В комбинация с нискоуровневото откритие и зависимите елементи от данни, които се появиха в Zabbix 3.4, се получи достатъчно гъвкава концепция за сортиране и управление на получените данни.
В Zabbix 4.4, като логично продължение на предварителната обработка на JavaScript, се появи нов начин за известяване — Webhook, който може да се използва за лесна интеграция на известията от Zabbix със странични приложения.
JavaScript и Duktape
Защо точно JavaScript и Duktape? Бяха разгледани различни опции за езици и движещи механизми:
- Lua – Lua 5.1
- Lua – LuaJIT
- JavaScript – Duktape
- JavaScript – JerryScript
- Вграден Python
- Вграден Perl
Основните критерии за избор бяха разпространеност, простота на интеграция на механизма в продукта, ниско потребление на ресурси и обща производителност на механизма, както и сигурност на внедряване на код на този език в мониторинга. На базата на тези показатели, JavaScript с Duktape спечели.

Критерии за избор и тестване на производителността
Особености на Duktape:
— Стандарт
— Модули на Zabbix за Duktape:
- Zabbix.log() — позволява да бъдат записвани директно в логовете на Zabbix Server съобщения с различни нива на детайлизация, което осигурява възможност за съпоставяне на грешки, например в Webhook, със състоянието на сървъра.
- CurlHttpRequest() — позволява да се извършват HTTP заявки в мрежата, на базата на което е основано използването на Webhook.
- atob() и btoa() — позволява кодиране и декодиране на низове в Base64 формат.
ЗАБЕЛЕЖКА. Duktape отговаря на стандартите на ACME. В Zabbix се използва версия на скрипта от 2015 година. Следващите промени са незначителни, затова могат да бъдат игнорирани..
Магията на JavaScript
Цялата магия на JavaScript се състои в динамичното типизиране и преобразуването на типове: стрингови, числови и логически.
Това означава, че не е необходимо предварително да се обявява какъв тип трябва да върне променливата.
При математически операции стойностите, които се връщат от операторите-функции, се преобразуват в числа. Изключение от такива операции е събирането, тъй като, ако поне едно от събираемите е стринг, към всички събираеми се прилага стрингово преобразуване.
ЗАБЕЛЕЖКА. Методите, отговарящи за такива преобразования, обикновено са реализирани в родителските прототипи на обектите, valueOf и toString. valueOf се извиква при числово преобразуване и винаги преди метода toString. Методът valueOf трябва да връща примитивни стойности, иначе резултатът му се игнорира.
За обекта се извиква методът valueOF. Ако не бъде намерен или не връща примитивна стойност, се извиква методът toString. Ако методът toString не бъде намерен, се търси valueOf в прототипа на обекта, и всичко се повтаря до приключване на обработката на стойността и преобразуването на всички стойности в израза в един тип.. Ако за обекта е реализиран метод toString, който връща примитивна стойност, то именно той се използва за стрингово преобразуване. При това резултатът от прилагането на този метод не е задължително стринг.
Например, ако за обекта ‘obj‘ се определя метод toString,
`var obj = { toString() { return "200" }}` метод toString върта именно стринг, и при събиране на стринг с число получаваме събрана стринг:
`obj + 1 \/\/ '2001'`
`obj + 'a' \/\/ ‘200a'`Но ако пренапишем toString, така че методът да връща число, при събиране на обекта ще се извърши математическа операция с числово преобразуване и ще получим резултат от математическото събиране.
`var obj = { toString() { return 200 }}`
`obj + 1 \/\/ '2001'`При това, ако извършваме събиране със стринг, се извършва стрингово преобразуване и получаваме събрана стринг.
`obj + 'a' \/\/ ‘200a'`Точно в това се крие причината за многото грешки на начинаещите потребители на JavaScript.
В метода toString може да се впише функция, която да увеличава текущите стойности на обекта с 1.

Изпълнение на скрипт при условие, че променливата е равна на 3 и същевременно е равна на 4.
При сравнение с приведени типове (==) всеки път се изпълнява метод toString с функция за увеличаване на стойността. Съответно, при всяко следващо сравнение стойността се увеличава. Това може да бъде избегнато чрез използване на сравнение без приведени типове (===).

Сравнение без приведени типове
ЗАБЕЛЕЖКА. Не използвайте сравнение с приведени типове без необходимост.
За сложни скриптове, например, Webhook с комплексна логика, в които е необходимо сравнение с приведени типове, се препоръчва предварително да напишете проверки за стойностите, които връщат променливите и да обработите несъответствията и грешките.
Webhook Media
В края на 2019 и началото на 2020 година екипът по интеграция на Zabbix активно разработваше Webhooks и интеграции "извън кутията", които се предлагат в дистрибуцията на Zabbix.

Линк към
Предобработка
- Появата на предобработка на JavaScript позволи да се откажем от повечето външни скриптове и в момента в Zabbix можем да получим всяка стойност и да я преобразуваме в напълно различна стойност.
- Предобработката в Zabbix е реализирана с код на JavaScript, който при компилиране в байт-код се преобразува във функция, приемаща единствена стойност под формата на параметър value под формата на низ (в низ може да има и цифра, и число).
- Тъй като на изхода се получава функция, в края на скрипта е задължително return.
- Възможно е използване на потребителски макроси в кода.
- Ресурсите могат да бъдат ограничени не само на ниво операционна система, но и програмно. За стъпката на предобработка се отделят максимум 10 мегабайта оперативна памет и лимит на времето за изпълнение от 10 секунди.

ЗАБЕЛЕЖКА. Стойностите на таймаута от 10 секунди са достатъчно много, защото събирането на хиляди условни данни за 1 секунда при доста „тежък“ сценарий на предобработка може да забави работата на Zabbix. Затова не се препоръчва използването на предобработка за изпълнение на пълноценни скриптове на JavaScript чрез т.нар. тъмни елементи данни (dummy items), които се стартират само за изпълнение на предобработката..
Кодът може да бъде проверен чрез тест за предобработка или с помощта на утилита. 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`
Практически задачи
Задача 1
Да се замени изчисляемият елемент на данни с предобработка.
Условие: получаваме от сензора температурата в градуси по Фаренхайт, за да я съхраняваме в градуси по Целзий.
По-рано бихме създали елемент на данни, който събира температура в градуси по Фаренхайт. След това — още един елемент на данни (изчисляем), който по формула преобразуваше градусите по Фаренхайт в градуси по Целзий.
Проблеми:
- Необходимо е да се дублират елементите на данни и да се съхраняват всички стойности в базата.
- Трябва да се съгласуват интервалите за ‘родителския’ елемент на данни, който се изчислява и използва във формулата, и за изчисляемия елемент на данни. В противен случай, изчисляемият елемент на данни може да премине в неподдържано състояние или да вземе предишна стойност, което ще повлияе на надеждността на резултатите от мониторинга.
Едно от решенията беше отказ от гъвкави интервали на проверките в полза на фиксирани интервали, за да се гарантира, че изчисляемият елемент на данни ще се изчислява след елемента на данни, който получава данни (в нашия случай — температура в градуси по Фаренхайт).
Но ако, например, шаблонът се използва за проверка на голям брой устройства и проверките се извършват на всеки 30 секунди, 29 секунди Zabbix ‘халтури’, а в последната секунда започва проверки и изчисления. Това води до създаване на опашка и влияе на производителността. Поради това се препоръчва да се използват фиксирани интервали, само ако е наистина необходимо.
В тази задача оптималното решение е предобработка от един ред на JavaScript, която конвертира градусите по Фаренхайт в градуси по Целзий:
`return (value - 32) * 5 / 9;`Това е бързо и просто, не е необходимо да се създават излишни елементи на данни и да се съхранява история по тях, а също така може да се използва за проверки с гъвкави интервали.

`return (parseInt(value) + parseInt("{$EXAMPLE.MACRO}"));`Но, ако в хипотетична ситуация е необходимо полученият елемент на данни да се събере, например, с някаква константа, определена в макроса, трябва да се вземе предвид, че параметърът value се разкрива в низ. При операция на събиране на низове, двете низове просто се комбинират в един.

`return (value + "{$EXAMPLE.MACRO}");`За да получите резултат от математическо действие, е необходимо да преобразувате типовете на получените стойности в числов формат. За целта можете да използвате функцията parseInt(), която връща цяло число, функцията parseFloat(), която връща десетично число, или функция номер, която връща цяло число или десетично число.
Задача 2
Да получите времето в секунди до изтичането на сертификата.
Условие: даден сервис предоставя дата на изтичане на сертификата във формат "Feb 12 12:33:56 2022 GMT".
В ECMAScript5 Date.parse() приема дата в ISO 8601 формат (YYYY-MM-DDTHH:mm:ss.sssZ). Необходимо е да преобразувате низът във формат MMM DD YYYY HH:mm:ss ZZ
Проблема: стойността на месеца е изразена с текст, а не с число. Данни в такъв формат не се приемат от Duktape.
Пример за решение:
Първо се обявява променлива, която приема стойност (целият скрипт — обявление на променливи, които са изброени с запетая).
В първия ред получаваме датата в параметъра value и я разделяме на пробели с метода split. Така получаваме масив, където на всеки елемент от масива, започваща от индекс 0, отговаря един елемент от датата преди и след пробела. split(0) — месец, split(1) — число, split(2) — низ с времето и т.н. След това до всеки елемент от датата можем да се обръщаме по индекс в масива.
`var split = value.split(' '),`- На всеки месец (в хронологичен ред) съответства индексът на неговото положение в масива (от 0 до 11). За да конвертирате текстовата стойност в числова, към индекса на месеца се прибавя единица (понеже номерацията на месеците започва от 1). При това изразът с прибавяне на единица е взет в скоби, защото в противен случай ще получите низ, а не число. Накрая изпълняваме slice() — изрезка на масива от края, за да оставим само два символа (което е важно за месеците с двуцифрен номер).
`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),`- Сформираме от получените стойности низ във формат ISO с обикновено събиране на низове в съответния ред.
`ISOdate = split[3] + '-' + month_index + '-' + split[1] + 'T' + split[2],`Данните в получения формат — броя на секундите от 1970 година до конкретен момент в бъдещето. Да използвате данните в получения формат в тригери е практически невъзможно, тъй като Zabbix позволява работа само с макроси {Date} и {Time}, които връщат дата и час в разбираем за потребителя формат.
- След това можем да получим текущата дата в JavaScript в формат Unix Timestamp и да извадим това от получената стойност на датата на изтичане на сертификата, за да получим броя на милисекундите от момента сега до момента на изтичане на сертификата.
`now = Date.now();`- Разделяме получената стойност на хиляда, за да получим секунди в Zabbix.
`return parseInt((Date.parse(ISOdate) - now) / 1000);`В триггера можем да посочим израз ‘last<‘ и набор от числа, които отговарят на броя секунди в периода, на който трябва да реагираме, например в седмици. По този начин, тригерът ще уведомява, че срокът на сертификата изтича след седмица.
ЗАБЕЛЕЖКА. Обърнете внимание на използването на parseInt() в функция return, за да конвертираме дробно число, получено в резултат на делението на милисекунди, в цяло число. Може също да се използва parseFloat() и да се съхраняват дробни данни.
Източник: habr.com
