Solving practical tasks in Zabbix using JavaScript

8 min



Tikhon Uskov, Zabbix Integration Team Engineer

Zabbix is ​​a customizable platform that is used to monitor any kind of data. From the earliest versions of Zabbix, monitoring administrators had the ability to run various scripts via Actions for checks on target network nodes. At the same time, the launch of scripts led to a number of difficulties, including such as the need to support scripts, their delivery to communication nodes and proxies, as well as support for different versions.

JavaScript for Zabbix

In April 2019, Zabbix 4.2 was introduced with JavaScript preprocessing functionality. Many got the idea to give up writing scripts that take data somewhere, digest it and provide it in a format that Zabbix understands, but perform simple checks that will receive data that is not ready for storage and processing by Zabbix, and then process this data stream using Zabbix tools and JavaScript. In conjunction with the low-level discovery and dependent items that appeared in Zabbix 3.4, we got a rather flexible concept for sorting and managing the received data.

In Zabbix 4.4, as a logical continuation of JavaScript preprocessing, a new notification method has appeared – Webhook, which can be used to easily integrate Zabbix notifications with third-party applications.

JavaScript and Duktape

Why were JavaScript and Duktape chosen? Various options for languages ​​and engines were considered:

  • Lua – Lua 5.1
  • Lua – LuaJIT
  • Javascript – Duktape
  • Javascript – JerryScript
  • Embedded Python
  • Embedded Perl

The main selection criteria were prevalence, ease of integration of the engine into the product, low resource consumption and overall performance of the engine, and the safety of introducing code in this language into monitoring. On the whole, JavaScript on the Duktape engine won.

Selection criteria and performance testing

Duktape features:

– Standard ECMAScript E5 / E5.1

– Zabbix modules for Duktape:

  • Zabbix.log () – allows you to write messages with different levels of detail directly to the Zabbix Server log, which makes it possible to compare errors, for example, in Webhook, with the server state.
  • CurlHttpRequest () – allows you to make HTTP requests to the network, which is what Webhook is based on.
  • atob () and btoa () – Encodes and decodes strings in Base64 format.

NOTEDuktape is ACME compliant. Zabbix uses the 2015 version of the script. Subsequent changes are minor and can be ignored

The magic of JavaScript

All the magic of JavaScript lies in dynamic typing and typecasting: strings, numbers and booleans.

This means that you do not need to declare in advance what type of variable should return a value.

Mathematical operations convert the values ​​returned by function operators to numbers. An exception to such operations is addition, because if at least one of the terms is a string, a string transformation is applied to all the terms.

NOTEThe methods responsible for such transformations are usually implemented in the parent object prototypes, valueOf and toStringvalueOf called on numeric conversion and always before the method toString… Method valueOf must return primitive values, otherwise its result is ignored.

The method is called for the object valueOF… If it is not found or does not return a primitive value, the method is called toString… If the method toString not found, searching valueOf in the object’s prototype, and everything is repeated until the value is processed and all values ​​in the expression are cast to the same typeIf the method is implemented for the object toString, which returns a primitive value, then it is he who is used for string conversion. However, the result of this method is not necessarily a string.

For example, if for for object ‘obj‘method is defined toString,

`var obj = { toString() { return "200" } }` 

method toString returns exactly a string, and when adding a string with a number, we get a glued string:

`obj + 1 // '2001'` 

`obj + 'a' // ‘200a'`

But if you rewrite toStringso that the method returns a number, adding the object will perform a mathematical operation with a numeric conversion and produce the result of the mathematical addition.

`var obj = { toString() { return 200 }` 

`obj + 1 // '2001'`

That said, if we do an addition with a string, a string conversion is performed, and we get a glued string.

`obj + 'a' // ‘200a'`

This is the reason for the large number of mistakes beginner JavaScript users make.

In method toString you can write a function that will increase the current value of the object by 1.


Executing the script, provided that the variable is equal to 3, and it is also equal to 4.

When compared with typecasting (==), the method is executed each time toString with the function of increasing the value. Accordingly, with each subsequent comparison, the value increases. This can be avoided by using non-cast comparison (===).


Comparison without type casting

NOTEDon’t use cast comparisons unnecessarily

For complex scripts, for example, Webhook with complex logic, in which comparison with type casting is necessary, it is recommended to write checks for values ​​that return variables and handle inconsistencies and errors.

Webhook Media

In late 2019 and early 2020, the Zabbix integration team was actively developing Webhooks and out-of-the-box integrations that are shipped with the Zabbix distribution.


Link to documentation

Preprocessing

  • The advent of JavaScript preprocessing made it possible to abandon most external scripts, and now in Zabbix you can get any value and convert it to any completely different value.
  • Pre-processing in Zabbix is ​​implemented by JavaScript code, which, when compiled into bytecode, is converted into a function that takes a single value as a parameter value as a string (the string can contain both a digit and a number).
  • Since the output is a function, at the end of the script it is required return
  • It is possible to use custom macros in the code.
  • Resources can be limited not only at the operating system level, but also programmatically. The preprocessing step is allocated a maximum of 10 megabytes of RAM and a runtime limit of 10 seconds.

NOTEThe timeout value of 10 seconds is quite a lot, because collecting conditional thousands of data items in 1 second in a rather “heavy” preprocessing scenario can slow down Zabbix. Therefore, it is not recommended to use preprocessing for executing full-fledged JavaScript scripts through the so-called dummy items, which are launched only to perform preprocessing.

You can check your code through the preprocessing test or using the utility 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`

Practical tasks

A task 1

Replace a calculated item with preprocessing.

Condition: Get the temperature in Fahrenheit from the sensor for storage in Celsius.

In the past, we would create a data item that collects temperature in degrees Fahrenheit. After that, there is another data item (calculated) that would convert Fahrenheit to Celsius using the formula.

Problems:

  • It is necessary to duplicate data items and store all values ​​in the database.
  • You must agree on the spacing for the “parent” item that is calculated and used in the formula, and for the calculated item. Otherwise, the calculated item may go to an unsupported state or calculate a previous value, affecting the reliability of the monitoring results.

One solution was to move away from flexible check intervals in favor of fixed intervals to ensure that the calculated item is calculated after the item receiving the data (in our case, temperature in degrees Fahrenheit).

But if, for example, we use a template to check a large number of devices, and the check is performed once every 30 seconds, Zabbix “hacks” for 29 seconds, and starts checks and calculations at the last second. This creates a queue and affects performance. Therefore, it is recommended that you use fixed intervals only if you really need to.

For this problem, the optimal solution is a one-line JavaScript preprocessing that converts Fahrenheit degrees to Celsius degrees:

`return (value - 32) * 5 / 9;`

It’s quick and easy, you don’t need to create unnecessary data items and store history for them, and you can also use flexible intervals for checks.

`return (parseInt(value) + parseInt("{$EXAMPLE.MACRO}"));`

But, if in a hypothetical situation it is necessary to add the resulting data element, for example, with any constant defined in the macro, it must be borne in mind that the parameter value expands to a string. In a string addition operation, two strings are simply concatenated into one.

`return (value + "{$EXAMPLE.MACRO}");`

To obtain the result of a mathematical operation, it is necessary to bring the types of the obtained values ​​into a numeric format. To do this, you can use the function parseInt ()which outputs an integer, the function parseFloat (), which produces a decimal, or a function number, which gives an integer or decimal.

Problem 2

Get the time in seconds until the end of the certificate.

Condition: some service issues the expiration date of the certificate in the format “Feb 12 12:33:56 2022 GMT”.

In ECMAScript5 Date.parse () accepts a date in ISO 8601 format (YYYY-MM-DDTHH: mm: ss.sssZ). You must convert a string to it in the format MMM DD YYYY HH: mm: ss ZZ

Problem: The month is expressed as text, not a number. Data in this format will not be accepted by Duktape.

Solution example:

  • First of all, a variable is declared that takes a value (the entire script is a declaration of variables, which are listed separated by commas).

  • In the first line, we get the date in the parameter value and separate it with spaces using the method split… Thus, we get an array, where each element of the array, starting from index 0, corresponds to one date element before and after the space. split (0) – month, split (1) – number, split (2) – string with time, etc. After that, each element of the date can be accessed by its index in the array.

`var split = value.split(' '),`

  • Each month (in chronological order) corresponds to the index of its position in the array (from 0 to 11). To convert a text value to a numeric value, one is added to the month index (because months are numbered starting at 1). In this case, the expression with the addition of one is enclosed in brackets, because otherwise a string will be obtained, not a number. At the end we execute slice () – slice the array from the end to leave only two characters (which is important for two-digit months).

`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),`

  • We form from the obtained values ​​a string in ISO format by the usual addition of strings in the appropriate order.

`ISOdate = split[3] + '-' + month_index + '-' + split[1] + 'T' + split[2],`

The data in the resulting format is the number of seconds from 1970 to some point in the future. It is almost impossible to use data in the received format in triggers, because Zabbix only allows you to operate with macros {Date} and {Time}that return date and time in a user-friendly format.

  • After that, we can get the current date in JavaScript in Unix Timestamp format and subtract it from the obtained certificate expiration date value to get the number of milliseconds from now until the certificate expires.

`now = Date.now();`

  • Divide the resulting value by one thousand to get seconds in Zabbix.

`return parseInt((Date.parse(ISOdate) - now) / 1000);`

In the trigger, you can specify the expression ‘last‘and a set of numbers that corresponds to the number of seconds in the period to which you want to react, for example, in weeks. Thus, the trigger will notify that the certificate expires in a week.

NOTEPay attention to the use parseInt () in function returnto convert the fractional number obtained by dividing the milliseconds to an integer. You can also use parseFloat () and store fractional data

Watch the report


0 Comments

Leave a Reply