We solve practical problems in Zabbix using JavaScript

We solve practical problems in Zabbix using JavaScript
Tikhon Uskov, Zabbix integration team engineer

Zabbix is ​​a customizable platform that is used to monitor any kind of data. Since the earliest versions of Zabbix, monitoring administrators have 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. Many people got excited about the idea of ​​abandoning writing scripts that take data somewhere, digest it and provide it in a format that Zabbix understands, and 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 and JavaScript tools. In conjunction with low-level discovery and dependent items that appeared in Zabbix 3.4, we got a fairly flexible concept for sorting and managing the received data.

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

JavaScript and Duktapes

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. Based on the combination of indicators, JavaScript won on the Duktape engine.

We solve practical problems in Zabbix using JavaScript

Selection criteria and performance testing

Features of Duktape:

- Standard ECMAScript E5/E5.1
— Zabbix modules for Duktape:

  • Zabbix.log() - allows you to write messages with different levels of detail directly into the Zabbix Server log, which makes it possible to correlate errors, for example, in a Webhook, with the server state.
  • CurlHttpRequest() - allows you to make HTTP requests to the network, on which the use of Webhook is based.
  • atob() and btoa() - allows you to encode and decode strings in Base64 format.

NOTE. Duktape complies with ACME standards. Zabbix uses the 2015 version of the script. Subsequent changes are minor, so they can be ignored..

JavaScript magic

All the magic of JavaScript lies in dynamic typing and type casting: string, numeric, and boolean.

This means that it is not necessary to declare in advance what type the variable should return a value.

In mathematical operations, the values ​​returned by function operators are converted to numbers. The exception to such operations is addition, because if at least one of the terms is a string, string conversion is applied to all terms.

NOTE. The methods responsible for such transformations are usually implemented in the object's parent prototypes, valueOf и toString. valueOf called during numerical conversion and always before the method toString. Method valueOf must return primitive values, otherwise its result is ignored.

A method is called on an 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 prototype of the object, and everything is repeated until the processing of the value is completed and all values ​​in the expression are cast to the same type. If the object implements a method toString, which returns a primitive value, then it is it that is used for string conversion. However, the result of applying 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 toString, so that the method returns a number, when the object is added, a mathematical operation with a numeric conversion will be performed and the result of mathematical addition will be obtained.

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

`obj + 1 // '2001'`

In this case, if we perform addition with a string, a string conversion is performed, and we get a glued string.

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

This is the reason for a large number of mistakes by novice JavaScript users.

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

We solve practical problems in Zabbix using JavaScript
Execution of the script, provided that the variable is equal to 3, and it is also equal to 4.

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

We solve practical problems in Zabbix using JavaScript
Comparison without type casting

NOTE. Don't Use Cast Comparison Unnecessarily.

For complex scripts, such as Webhooks with complex logic, that require comparison with type casting, it is recommended to pre-write checks for the values ​​that return variables and handle inconsistencies and errors.

Webhook Media

In late 2019 and early 2020, the Zabbix integration team has been actively developing Webhooks and out-of-the-box integrations that come with the Zabbix distribution.

We solve practical problems in Zabbix using JavaScript
Link to documentation

pre-processing

  • The advent of preprocessing in JavaScript made it possible to abandon most external scripts, and currently in Zabbix you can get any value and convert it to a completely different value.
  • Preprocessing 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 (a string can contain both a digit and a number).
  • Since the output is a function, at the end of the script 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 run time limit of 10 seconds.

We solve practical problems in Zabbix using JavaScript

NOTE. The timeout value of 10 seconds is quite a lot, because collecting conditional thousands of data items in 1 second according to a rather “heavy” preprocessing scenario can slow down Zabbix. Therefore, it is not recommended to use preprocessing to execute full-fledged JavaScript scripts through the so-called shadow data elements (dummy items), which are run 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

Task 1

Replace calculated item with preprocessing.

Condition: Get the temperature in Fahrenheit from the sensor to store in Celsius.

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

Problems:

  • It is necessary to duplicate data elements and store all values ​​in the database.
  • You must agree on the intervals for the "parent" data item that is calculated and used in the formula, and for the calculated data item. Otherwise, the computed item may go into an unsupported state or calculate a previous value, which will affect 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 computed item is evaluated after the item that receives the data (in our case, the temperature in degrees Fahrenheit).

But if, for example, we use the template to check a large number of devices, and the check is performed once every 30 seconds, Zabbix "hacks" for 29 seconds, and at the last second it starts checking and calculating. This creates a queue and affects performance. Therefore, it is recommended to use fixed intervals only if it is really necessary.

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

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

It's fast and easy, you don't need to create unnecessary data items and keep a history on them, and you can also use flexible intervals for checks.

We solve practical problems in Zabbix using JavaScript

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

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

We solve practical problems in Zabbix using JavaScript

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

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

Task 2

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

Condition: a service issues a certificate expiration date 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). It is necessary to cast a string to it in the format MMM DD YYYY HH:mm:ss ZZ

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

Solution example:

  • First of all, a variable is declared that takes a value (the entire script is a declaration of variables that 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 at index 0, corresponds to one date element before and after a space. split(0) - month, split(1) - number, split(2) - a string with time, etc. After that, each element of the date can be accessed by 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 taken in brackets, because otherwise a string will be obtained, not a number. At the end we do slice () - cut the array from the end to leave only two characters (which is important for months with a two-digit number).

`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 a string in ISO format from the obtained values ​​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 allows you to operate only with macros {Date} и {Time}, which return the date and time in a user-friendly format.

  • We can then get the current date in JavaScript in Unix Timestamp format and subtract it from the resulting certificate expiration date to get the number of milliseconds from now until the certificate expires.

`now = Date.now();`

  • We divide the received value by a thousand to get seconds in Zabbix.

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

In the trigger, you can specify the expression 'last' followed by a set of digits that corresponds to the number of seconds in the period to which you want to respond, for example, in weeks. Thus, the trigger will notify that the certificate expires in a week.

NOTE. Pay attention to the use parseInt () in function returnto convert the fractional number resulting from division of milliseconds to an integer. You can also use parseFloat () and store fractional data.

Watch report

Source: habr.com

Add a comment