/* ** Copyright (C) 2001-2025 Zabbix SIA ** ** This program is free software: you can redistribute it and/or modify it under the terms of ** the GNU Affero General Public License as published by the Free Software Foundation, version 3. ** ** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; ** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ** See the GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License along with this program. ** If not, see <https://www.gnu.org/licenses/>. **/ #include "zbxalgo.h" #include "zbxnum.h" #define ZBX_MATH_EPSILON (1e-6) #define ZBX_IS_NAN(x) ((x) != (x)) #define ZBX_VALID_MATRIX(m) (0 < (m)->rows && 0 < (m)->columns && NULL != (m)->elements) #define ZBX_MATRIX_EL(m, row, col) ((m)->elements[(row) * (m)->columns + (col)]) #define ZBX_MATRIX_ROW(m, row) ((m)->elements + (row) * (m)->columns) typedef struct { int rows; int columns; double *elements; } zbx_matrix_t; static void zbx_matrix_struct_alloc(zbx_matrix_t **pm) { *pm = (zbx_matrix_t *)zbx_malloc(*pm, sizeof(zbx_matrix_t)); (*pm)->rows = 0; (*pm)->columns = 0; (*pm)->elements = NULL; } static int zbx_matrix_alloc(zbx_matrix_t *m, int rows, int columns) { if (0 >= rows || 0 >= columns) goto error; m->rows = rows; m->columns = columns; m->elements = (double *)zbx_malloc(m->elements, sizeof(double) * rows * columns); return SUCCEED; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static void zbx_matrix_free(zbx_matrix_t *m) { if (NULL != m) zbx_free(m->elements); zbx_free(m); } static int zbx_matrix_copy(zbx_matrix_t *dest, zbx_matrix_t *src) { if (!ZBX_VALID_MATRIX(src)) goto error; if (SUCCEED != zbx_matrix_alloc(dest, src->rows, src->columns)) return FAIL; memcpy(dest->elements, src->elements, sizeof(double) * src->rows * src->columns); return SUCCEED; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static int zbx_identity_matrix(zbx_matrix_t *m, int n) { if (SUCCEED != zbx_matrix_alloc(m, n, n)) return FAIL; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) ZBX_MATRIX_EL(m, i, j) = (i == j ? 1.0 : 0.0); return SUCCEED; } static int zbx_transpose_matrix(zbx_matrix_t *m, zbx_matrix_t *r) { if (!ZBX_VALID_MATRIX(m)) goto error; if (SUCCEED != zbx_matrix_alloc(r, m->columns, m->rows)) return FAIL; for (int i = 0; i < r->rows; i++) for (int j = 0; j < r->columns; j++) ZBX_MATRIX_EL(r, i, j) = ZBX_MATRIX_EL(m, j, i); return SUCCEED; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static void zbx_matrix_swap_rows(zbx_matrix_t *m, int r1, int r2) { double tmp; for (int i = 0; i < m->columns; i++) { tmp = ZBX_MATRIX_EL(m, r1, i); ZBX_MATRIX_EL(m, r1, i) = ZBX_MATRIX_EL(m, r2, i); ZBX_MATRIX_EL(m, r2, i) = tmp; } } static void zbx_matrix_divide_row_by(zbx_matrix_t *m, int row, double denominator) { for (int i = 0; i < m->columns; i++) ZBX_MATRIX_EL(m, row, i) /= denominator; } static void zbx_matrix_add_rows_with_factor(zbx_matrix_t *m, int dest, int src, double factor) { for (int i = 0; i < m->columns; i++) ZBX_MATRIX_EL(m, dest, i) += ZBX_MATRIX_EL(m, src, i) * factor; } static int zbx_inverse_matrix(zbx_matrix_t *m, zbx_matrix_t *r) { zbx_matrix_t *l = NULL; double pivot, factor, det; int i, j, k, n, res; if (!ZBX_VALID_MATRIX(m) || m->rows != m->columns) goto error; n = m->rows; if (1 == n) { if (SUCCEED != zbx_matrix_alloc(r, 1, 1)) return FAIL; if (0.0 == ZBX_MATRIX_EL(m, 0, 0)) { zabbix_log(LOG_LEVEL_DEBUG, "matrix is singular"); res = FAIL; goto out; } ZBX_MATRIX_EL(r, 0, 0) = 1.0 / ZBX_MATRIX_EL(m, 0, 0); return SUCCEED; } if (2 == n) { if (SUCCEED != zbx_matrix_alloc(r, 2, 2)) return FAIL; if (0.0 == (det = ZBX_MATRIX_EL(m, 0, 0) * ZBX_MATRIX_EL(m, 1, 1) - ZBX_MATRIX_EL(m, 0, 1) * ZBX_MATRIX_EL(m, 1, 0))) { zabbix_log(LOG_LEVEL_DEBUG, "matrix is singular"); res = FAIL; goto out; } ZBX_MATRIX_EL(r, 0, 0) = ZBX_MATRIX_EL(m, 1, 1) / det; ZBX_MATRIX_EL(r, 0, 1) = -ZBX_MATRIX_EL(m, 0, 1) / det; ZBX_MATRIX_EL(r, 1, 0) = -ZBX_MATRIX_EL(m, 1, 0) / det; ZBX_MATRIX_EL(r, 1, 1) = ZBX_MATRIX_EL(m, 0, 0) / det; return SUCCEED; } if (SUCCEED != zbx_identity_matrix(r, n)) return FAIL; zbx_matrix_struct_alloc(&l); if (SUCCEED != (res = zbx_matrix_copy(l, m))) goto out; /* Gauss-Jordan elimination with partial (row) pivoting */ for (i = 0; i < n; i++) { k = i; pivot = ZBX_MATRIX_EL(l, i, i); for (j = i; j < n; j++) { if (fabs(ZBX_MATRIX_EL(l, j, i)) > fabs(pivot)) { k = j; pivot = ZBX_MATRIX_EL(l, j, i); } } if (0.0 == pivot) { zabbix_log(LOG_LEVEL_DEBUG, "matrix is singular"); res = FAIL; goto out; } if (k != i) { zbx_matrix_swap_rows(l, i, k); zbx_matrix_swap_rows(r, i, k); } for (j = i + 1; j < n; j++) { if (0.0 != (factor = -ZBX_MATRIX_EL(l, j, i) / ZBX_MATRIX_EL(l, i, i))) { zbx_matrix_add_rows_with_factor(l, j, i, factor); zbx_matrix_add_rows_with_factor(r, j, i, factor); } } } for (i = n - 1; i > 0; i--) { for (j = 0; j < i; j++) { if (0.0 != (factor = -ZBX_MATRIX_EL(l, j, i) / ZBX_MATRIX_EL(l, i, i))) { zbx_matrix_add_rows_with_factor(l, j, i, factor); zbx_matrix_add_rows_with_factor(r, j, i, factor); } } } for (i = 0; i < n; i++) zbx_matrix_divide_row_by(r, i, ZBX_MATRIX_EL(l, i, i)); res = SUCCEED; out: zbx_matrix_free(l); return res; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static int zbx_matrix_mult(zbx_matrix_t *left, zbx_matrix_t *right, zbx_matrix_t *result) { double element; if (!ZBX_VALID_MATRIX(left) || !ZBX_VALID_MATRIX(right) || left->columns != right->rows) goto error; if (SUCCEED != zbx_matrix_alloc(result, left->rows, right->columns)) return FAIL; for (int i = 0; i < result->rows; i++) { for (int j = 0; j < result->columns; j++) { element = 0; for (int k = 0; k < left->columns; k++) element += ZBX_MATRIX_EL(left, i, k) * ZBX_MATRIX_EL(right, k, j); ZBX_MATRIX_EL(result, i, j) = element; } } return SUCCEED; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static int zbx_least_squares(zbx_matrix_t *independent, zbx_matrix_t *dependent, zbx_matrix_t *coefficients) { /* |<----------to_be_inverted---------->| */ /* coefficients = inverse( transpose( independent ) * independent ) * transpose( independent ) * dependent */ /* |<------------------left_part------------------>| |<-----------right_part----------->| */ /* we change order of matrix multiplication to reduce operation count and memory usage */ zbx_matrix_t *independent_transposed = NULL, *to_be_inverted = NULL, *left_part = NULL, *right_part = NULL; int res; zbx_matrix_struct_alloc(&independent_transposed); zbx_matrix_struct_alloc(&to_be_inverted); zbx_matrix_struct_alloc(&left_part); zbx_matrix_struct_alloc(&right_part); if (SUCCEED != (res = zbx_transpose_matrix(independent, independent_transposed))) goto out; if (SUCCEED != (res = zbx_matrix_mult(independent_transposed, independent, to_be_inverted))) goto out; if (SUCCEED != (res = zbx_inverse_matrix(to_be_inverted, left_part))) goto out; if (SUCCEED != (res = zbx_matrix_mult(independent_transposed, dependent, right_part))) goto out; if (SUCCEED != (res = zbx_matrix_mult(left_part, right_part, coefficients))) goto out; out: zbx_matrix_free(independent_transposed); zbx_matrix_free(to_be_inverted); zbx_matrix_free(left_part); zbx_matrix_free(right_part); return res; } static int zbx_fill_dependent(double *x, int n, zbx_fit_t fit, zbx_matrix_t *m) { if (FIT_LINEAR == fit || FIT_POLYNOMIAL == fit || FIT_LOGARITHMIC == fit) { if (SUCCEED != zbx_matrix_alloc(m, n, 1)) return FAIL; for (int i = 0; i < n; i++) ZBX_MATRIX_EL(m, i, 0) = x[i]; } else if (FIT_EXPONENTIAL == fit || FIT_POWER == fit) { if (SUCCEED != zbx_matrix_alloc(m, n, 1)) return FAIL; for (int i = 0; i < n; i++) { if (0.0 >= x[i]) { zabbix_log(LOG_LEVEL_DEBUG, "data contains negative or zero values"); return FAIL; } ZBX_MATRIX_EL(m, i, 0) = log(x[i]); } } return SUCCEED; } static int zbx_fill_independent(double *t, int n, zbx_fit_t fit, int k, zbx_matrix_t *m) { double element; if (FIT_LINEAR == fit || FIT_EXPONENTIAL == fit) { if (SUCCEED != zbx_matrix_alloc(m, n, 2)) return FAIL; for (int i = 0; i < n; i++) { ZBX_MATRIX_EL(m, i, 0) = 1.0; ZBX_MATRIX_EL(m, i, 1) = t[i]; } } else if (FIT_LOGARITHMIC == fit || FIT_POWER == fit) { if (SUCCEED != zbx_matrix_alloc(m, n, 2)) return FAIL; for (int i = 0; i < n; i++) { ZBX_MATRIX_EL(m, i, 0) = 1.0; ZBX_MATRIX_EL(m, i, 1) = log(t[i]); } } else if (FIT_POLYNOMIAL == fit) { if (k > n - 1) k = n - 1; if (SUCCEED != zbx_matrix_alloc(m, n, k+1)) return FAIL; for (int i = 0; i < n; i++) { element = 1.0; for (int j = 0; j < k; j++) { ZBX_MATRIX_EL(m, i, j) = element; element *= t[i]; } ZBX_MATRIX_EL(m, i, k) = element; } } return SUCCEED; } static int zbx_regression(double *t, double *x, int n, zbx_fit_t fit, int k, zbx_matrix_t *coefficients) { zbx_matrix_t *independent = NULL, *dependent = NULL; int res; zbx_matrix_struct_alloc(&independent); zbx_matrix_struct_alloc(&dependent); if (SUCCEED != (res = zbx_fill_independent(t, n, fit, k, independent))) goto out; if (SUCCEED != (res = zbx_fill_dependent(x, n, fit, dependent))) goto out; if (SUCCEED != (res = zbx_least_squares(independent, dependent, coefficients))) goto out; out: zbx_matrix_free(independent); zbx_matrix_free(dependent); return res; } static double zbx_polynomial_value(double t, zbx_matrix_t *coefficients) { double pow = 1.0, res = 0.0; for (int i = 0; i < coefficients->rows; i++, pow *= t) res += ZBX_MATRIX_EL(coefficients, i, 0) * pow; return res; } static double zbx_polynomial_antiderivative(double t, zbx_matrix_t *coefficients) { double pow = t, res = 0.0; for (int i = 0; i < coefficients->rows; i++, pow *= t) res += ZBX_MATRIX_EL(coefficients, i, 0) * pow / (i + 1); return res; } static int zbx_derive_polynomial(zbx_matrix_t *polynomial, zbx_matrix_t *derivative) { int i; if (!ZBX_VALID_MATRIX(polynomial)) goto error; if (SUCCEED != zbx_matrix_alloc(derivative, (polynomial->rows > 1 ? polynomial->rows - 1 : 1), 1)) return FAIL; for (i = 1; i < polynomial->rows; i++) ZBX_MATRIX_EL(derivative, i - 1, 0) = ZBX_MATRIX_EL(polynomial, i, 0) * i; if (1 == i) ZBX_MATRIX_EL(derivative, 0, 0) = 0.0; return SUCCEED; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static int zbx_polynomial_roots(zbx_matrix_t *coefficients, zbx_matrix_t *roots) { #define Re(z) (z)[0] #define Im(z) (z)[1] #define ZBX_COMPLEX_MULT(z1, z2, tmp) \ do \ { \ Re(tmp) = Re(z1) * Re(z2) - Im(z1) * Im(z2); \ Im(tmp) = Re(z1) * Im(z2) + Im(z1) * Re(z2); \ Re(z1) = Re(tmp); \ Im(z1) = Im(tmp); \ } \ while(0) #define ZBX_MAX_ITERATIONS 200 zbx_matrix_t *denominator_multiplicands = NULL, *updates = NULL; double z[2], mult[2], denominator[2], zpower[2], polynomial[2], highest_degree_coefficient, lower_bound, upper_bound, radius, max_update, min_distance, residual, temp; int i, j, degree, first_nonzero, res, iteration = 0, roots_ok = 0, root_init = 0; if (!ZBX_VALID_MATRIX(coefficients)) goto error; degree = coefficients->rows - 1; highest_degree_coefficient = ZBX_MATRIX_EL(coefficients, degree, 0); while (0.0 == highest_degree_coefficient && 0 < degree) highest_degree_coefficient = ZBX_MATRIX_EL(coefficients, --degree, 0); if (0 == degree) { /* please check explicitly for an attempt to solve equation 0 == 0 */ if (0.0 == highest_degree_coefficient) goto error; return SUCCEED; } if (1 == degree) { if (SUCCEED != zbx_matrix_alloc(roots, 1, 2)) return FAIL; Re(ZBX_MATRIX_ROW(roots, 0)) = -ZBX_MATRIX_EL(coefficients, 0, 0) / ZBX_MATRIX_EL(coefficients, 1, 0); Im(ZBX_MATRIX_ROW(roots, 0)) = 0.0; return SUCCEED; } if (2 == degree) { if (SUCCEED != zbx_matrix_alloc(roots, 2, 2)) return FAIL; if (0.0 < (temp = ZBX_MATRIX_EL(coefficients, 1, 0) * ZBX_MATRIX_EL(coefficients, 1, 0) - 4 * ZBX_MATRIX_EL(coefficients, 2, 0) * ZBX_MATRIX_EL(coefficients, 0, 0))) { temp = (0 < ZBX_MATRIX_EL(coefficients, 1, 0) ? -ZBX_MATRIX_EL(coefficients, 1, 0) - sqrt(temp) : -ZBX_MATRIX_EL(coefficients, 1, 0) + sqrt(temp)); Re(ZBX_MATRIX_ROW(roots, 0)) = 0.5 * temp / ZBX_MATRIX_EL(coefficients, 2, 0); Re(ZBX_MATRIX_ROW(roots, 1)) = 2.0 * ZBX_MATRIX_EL(coefficients, 0, 0) / temp; Im(ZBX_MATRIX_ROW(roots, 0)) = Im(ZBX_MATRIX_ROW(roots, 1)) = 0.0; } else { Re(ZBX_MATRIX_ROW(roots, 0)) = Re(ZBX_MATRIX_ROW(roots, 1)) = -0.5 * ZBX_MATRIX_EL(coefficients, 1, 0) / ZBX_MATRIX_EL(coefficients, 2, 0); Im(ZBX_MATRIX_ROW(roots, 0)) = -(Im(ZBX_MATRIX_ROW(roots, 1)) = 0.5 * sqrt(-temp)) / ZBX_MATRIX_EL(coefficients, 2, 0); } return SUCCEED; } zbx_matrix_struct_alloc(&denominator_multiplicands); zbx_matrix_struct_alloc(&updates); if (SUCCEED != zbx_matrix_alloc(roots, degree, 2) || SUCCEED != zbx_matrix_alloc(denominator_multiplicands, degree, 2) || SUCCEED != zbx_matrix_alloc(updates, degree, 2)) { res = FAIL; goto out; } /* if n lower coefficients are zeros, zero is a root of multiplicity n */ for (first_nonzero = 0; 0.0 == ZBX_MATRIX_EL(coefficients, first_nonzero, 0); first_nonzero++) Re(ZBX_MATRIX_ROW(roots, first_nonzero)) = Im(ZBX_MATRIX_ROW(roots, first_nonzero)) = 0.0; /* compute bounds for the roots */ upper_bound = lower_bound = 1.0; for (i = first_nonzero; i < degree; i++) { if (upper_bound < fabs(ZBX_MATRIX_EL(coefficients, i, 0) / highest_degree_coefficient)) upper_bound = fabs(ZBX_MATRIX_EL(coefficients, i, 0) / highest_degree_coefficient); if (lower_bound < fabs(ZBX_MATRIX_EL(coefficients, i + 1, 0) / ZBX_MATRIX_EL(coefficients, first_nonzero, 0))) lower_bound = fabs(ZBX_MATRIX_EL(coefficients, i + 1, 0) / ZBX_MATRIX_EL(coefficients, first_nonzero, 0)); } radius = 1.0 / lower_bound; /* Weierstrass (Durand-Kerner) method */ while (ZBX_MAX_ITERATIONS >= ++iteration && !roots_ok) { if (0 == root_init) { if (radius <= upper_bound) { for (i = 0; i < degree - first_nonzero; i++) { Re(ZBX_MATRIX_ROW(roots, i)) = radius * cos((2.0 * M_PI * (i + 0.25)) / (degree - first_nonzero)); Im(ZBX_MATRIX_ROW(roots, i)) = radius * sin((2.0 * M_PI * (i + 0.25)) / (degree - first_nonzero)); } radius *= 2.0; } else root_init = 1; } roots_ok = 1; max_update = 0.0; min_distance = HUGE_VAL; for (i = first_nonzero; i < degree; i++) { Re(z) = Re(ZBX_MATRIX_ROW(roots, i)); Im(z) = Im(ZBX_MATRIX_ROW(roots, i)); /* subtract from z every one of denominator_multiplicands and multiply them */ Re(denominator) = highest_degree_coefficient; Im(denominator) = 0.0; for (j = first_nonzero; j < degree; j++) { if (j == i) continue; temp = (ZBX_MATRIX_EL(roots, i, 0) - ZBX_MATRIX_EL(roots, j, 0)) * (ZBX_MATRIX_EL(roots, i, 0) - ZBX_MATRIX_EL(roots, j, 0)) + (ZBX_MATRIX_EL(roots, i, 1) - ZBX_MATRIX_EL(roots, j, 1)) * (ZBX_MATRIX_EL(roots, i, 1) - ZBX_MATRIX_EL(roots, j, 1)); if (temp < min_distance) min_distance = temp; Re(ZBX_MATRIX_ROW(denominator_multiplicands, j)) = Re(z) - Re(ZBX_MATRIX_ROW(roots, j)); Im(ZBX_MATRIX_ROW(denominator_multiplicands, j)) = Im(z) - Im(ZBX_MATRIX_ROW(roots, j)); ZBX_COMPLEX_MULT(denominator, ZBX_MATRIX_ROW(denominator_multiplicands, j), mult); } /* calculate complex value of polynomial for z */ Re(zpower) = 1.0; Im(zpower) = 0.0; Re(polynomial) = ZBX_MATRIX_EL(coefficients, first_nonzero, 0); Im(polynomial) = 0.0; for (j = first_nonzero + 1; j <= degree; j++) { ZBX_COMPLEX_MULT(zpower, z, mult); Re(polynomial) += Re(zpower) * ZBX_MATRIX_EL(coefficients, j, 0); Im(polynomial) += Im(zpower) * ZBX_MATRIX_EL(coefficients, j, 0); } /* check how good root approximation is */ residual = fabs(Re(polynomial)) + fabs(Im(polynomial)); roots_ok = roots_ok && (ZBX_MATH_EPSILON > residual); /* divide polynomial value by denominator */ if (0.0 != (temp = Re(denominator) * Re(denominator) + Im(denominator) * Im(denominator))) { Re(ZBX_MATRIX_ROW(updates, i)) = (Re(polynomial) * Re(denominator) + Im(polynomial) * Im(denominator)) / temp; Im(ZBX_MATRIX_ROW(updates, i)) = (Im(polynomial) * Re(denominator) - Re(polynomial) * Im(denominator)) / temp; } else /* Denominator is zero if two or more root approximations are equal. */ /* Since root approximations are initially different their equality means that they */ /* converged to a multiple root (hopefully) and no updates are required in this case. */ { Re(ZBX_MATRIX_ROW(updates, i)) = Im(ZBX_MATRIX_ROW(updates, i)) = 0.0; } temp = ZBX_MATRIX_EL(updates, i, 0) * ZBX_MATRIX_EL(updates, i, 0) + ZBX_MATRIX_EL(updates, i, 1) * ZBX_MATRIX_EL(updates, i, 1); if (temp > max_update) max_update = temp; } if (max_update > radius * radius && 0 == root_init) continue; else root_init = 1; for (i = first_nonzero; i < degree; i++) { Re(ZBX_MATRIX_ROW(roots, i)) -= Re(ZBX_MATRIX_ROW(updates, i)); Im(ZBX_MATRIX_ROW(roots, i)) -= Im(ZBX_MATRIX_ROW(updates, i)); } } if (0 == roots_ok) { zabbix_log(LOG_LEVEL_DEBUG, "polynomial root finding problem is ill-defined"); res = FAIL; } else res = SUCCEED; out: zbx_matrix_free(denominator_multiplicands); zbx_matrix_free(updates); return res; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; #undef ZBX_MAX_ITERATIONS #undef Re #undef Im } static int zbx_polynomial_minmax(double now, double time, zbx_mode_t mode, zbx_matrix_t *coefficients, double *result) { zbx_matrix_t *derivative = NULL, *derivative_roots = NULL; double min, max, tmp; int i, res; if (!ZBX_VALID_MATRIX(coefficients)) goto error; zbx_matrix_struct_alloc(&derivative); zbx_matrix_struct_alloc(&derivative_roots); if (SUCCEED != (res = zbx_derive_polynomial(coefficients, derivative))) goto out; if (SUCCEED != (res = zbx_polynomial_roots(derivative, derivative_roots))) goto out; /* Choose min and max among now, now + time and derivative roots in between (these are potential local */ /* extrema). We ignore imaginary part of roots. This means that more calculations will be made, but result */ /* will not be affected and we won't need a boundary on minimal imaginary part that differs from zero. */ min = zbx_polynomial_value(now, coefficients); tmp = zbx_polynomial_value(now + time, coefficients); if (tmp < min) { max = min; min = tmp; } else max = tmp; for (i = 0; i < derivative_roots->rows; i++) { tmp = ZBX_MATRIX_EL(derivative_roots, i, 0); if (tmp < now || tmp > now + time) continue; tmp = zbx_polynomial_value(tmp, coefficients); if (tmp < min) min = tmp; else if (tmp > max) max = tmp; } if (MODE_MAX == mode) *result = max; else if (MODE_MIN == mode) *result = min; else if (MODE_DELTA == mode) *result = max - min; else THIS_SHOULD_NEVER_HAPPEN; out: zbx_matrix_free(derivative); zbx_matrix_free(derivative_roots); return res; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static int zbx_polynomial_timeleft(double now, double threshold, zbx_matrix_t *coefficients, double *result) { zbx_matrix_t *shifted_coefficients = NULL, *roots = NULL; double tmp; int res, no_root = 1; if (!ZBX_VALID_MATRIX(coefficients)) goto error; zbx_matrix_struct_alloc(&shifted_coefficients); zbx_matrix_struct_alloc(&roots); if (SUCCEED != (res = zbx_matrix_copy(shifted_coefficients, coefficients))) goto out; ZBX_MATRIX_EL(shifted_coefficients, 0, 0) -= threshold; if (SUCCEED != (res = zbx_polynomial_roots(shifted_coefficients, roots))) goto out; /* choose the closest root right from now or set result to -1 otherwise */ /* if zbx_polynomial_value(tmp) is not close enough to zero it must be a complex root and must be skipped */ for (int i = 0; i < roots->rows; i++) { tmp = ZBX_MATRIX_EL(roots, i, 0); if (no_root) { if (tmp > now && ZBX_MATH_EPSILON > fabs(zbx_polynomial_value(tmp, shifted_coefficients))) { no_root = 0; *result = tmp; } } else if (now < tmp && tmp < *result && ZBX_MATH_EPSILON > fabs(zbx_polynomial_value(tmp, shifted_coefficients))) { *result = tmp; } } if (no_root) *result = DBL_MAX; else *result -= now; out: zbx_matrix_free(shifted_coefficients); zbx_matrix_free(roots); return res; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } static int zbx_calculate_value(double t, zbx_matrix_t *coefficients, zbx_fit_t fit, double *value) { if (!ZBX_VALID_MATRIX(coefficients)) goto error; if (FIT_LINEAR == fit) *value = ZBX_MATRIX_EL(coefficients, 0, 0) + ZBX_MATRIX_EL(coefficients, 1, 0) * t; else if (FIT_POLYNOMIAL == fit) *value = zbx_polynomial_value(t, coefficients); else if (FIT_EXPONENTIAL == fit) *value = exp(ZBX_MATRIX_EL(coefficients, 0, 0) + ZBX_MATRIX_EL(coefficients, 1, 0) * t); else if (FIT_LOGARITHMIC == fit) *value = ZBX_MATRIX_EL(coefficients, 0, 0) + ZBX_MATRIX_EL(coefficients, 1, 0) * log(t); else if (FIT_POWER == fit) *value = exp(ZBX_MATRIX_EL(coefficients, 0, 0) + ZBX_MATRIX_EL(coefficients, 1, 0) * log(t)); else goto error; return SUCCEED; error: THIS_SHOULD_NEVER_HAPPEN; return FAIL; } int zbx_fit_code(char *fit_str, zbx_fit_t *fit, unsigned *k, char **error) { if ('\0' == *fit_str || 0 == strcmp(fit_str, "linear")) { *fit = FIT_LINEAR; *k = 0; } else if (0 == strncmp(fit_str, "polynomial", strlen("polynomial"))) { *fit = FIT_POLYNOMIAL; if (SUCCEED != zbx_is_uint_range(fit_str + strlen("polynomial"), k, 1, 6)) { *error = zbx_strdup(*error, "polynomial degree is invalid"); return FAIL; } } else if (0 == strcmp(fit_str, "exponential")) { *fit = FIT_EXPONENTIAL; *k = 0; } else if (0 == strcmp(fit_str, "logarithmic")) { *fit = FIT_LOGARITHMIC; *k = 0; } else if (0 == strcmp(fit_str, "power")) { *fit = FIT_POWER; *k = 0; } else { *error = zbx_strdup(*error, "invalid 'fit' parameter"); return FAIL; } return SUCCEED; } int zbx_mode_code(char *mode_str, zbx_mode_t *mode, char **error) { if ('\0' == *mode_str || 0 == strcmp(mode_str, "value")) { *mode = MODE_VALUE; } else if (0 == strcmp(mode_str, "max")) { *mode = MODE_MAX; } else if (0 == strcmp(mode_str, "min")) { *mode = MODE_MIN; } else if (0 == strcmp(mode_str, "delta")) { *mode = MODE_DELTA; } else if (0 == strcmp(mode_str, "avg")) { *mode = MODE_AVG; } else { *error = zbx_strdup(*error, "invalid 'mode' parameter"); return FAIL; } return SUCCEED; } static void zbx_log_expression(double now, zbx_fit_t fit, int k, zbx_matrix_t *coeffs) { /* x is item value, t is time in seconds counted from now */ if (FIT_LINEAR == fit) { zabbix_log(LOG_LEVEL_DEBUG, "fitted expression is: x = (" ZBX_FS_DBL ") + (" ZBX_FS_DBL ") * (" ZBX_FS_DBL " + t)", ZBX_MATRIX_EL(coeffs, 0, 0), ZBX_MATRIX_EL(coeffs, 1, 0), now); } else if (FIT_POLYNOMIAL == fit) { char *polynomial = NULL; size_t alloc, offset; while (0 <= k) { zbx_snprintf_alloc(&polynomial, &alloc, &offset, "(" ZBX_FS_DBL ") * (" ZBX_FS_DBL " + t) ^ %d", ZBX_MATRIX_EL(coeffs, k, 0), now, k); if (0 < k--) zbx_snprintf_alloc(&polynomial, &alloc, &offset, " + "); } zabbix_log(LOG_LEVEL_DEBUG, "fitted expression is: x = %s", polynomial); zbx_free(polynomial); } else if (FIT_EXPONENTIAL == fit) { zabbix_log(LOG_LEVEL_DEBUG, "fitted expression is: x = (" ZBX_FS_DBL ") * exp( (" ZBX_FS_DBL ") * (" ZBX_FS_DBL " + t) )", exp(ZBX_MATRIX_EL(coeffs, 0, 0)), ZBX_MATRIX_EL(coeffs, 1, 0), now); } else if (FIT_LOGARITHMIC == fit) { zabbix_log(LOG_LEVEL_DEBUG, "fitted expression is: x = (" ZBX_FS_DBL ") + (" ZBX_FS_DBL ") * log(" ZBX_FS_DBL " + t)", ZBX_MATRIX_EL(coeffs, 0, 0), ZBX_MATRIX_EL(coeffs, 1, 0), now); } else if (FIT_POWER == fit) { zabbix_log(LOG_LEVEL_DEBUG, "fitted expression is: x = (" ZBX_FS_DBL ") * (" ZBX_FS_DBL " + t) ^ (" ZBX_FS_DBL ")", exp(ZBX_MATRIX_EL(coeffs, 0, 0)), now, ZBX_MATRIX_EL(coeffs, 1, 0)); } else THIS_SHOULD_NEVER_HAPPEN; } double zbx_forecast(double *t, double *x, int n, double now, double time, zbx_fit_t fit, unsigned k, zbx_mode_t mode) { zbx_matrix_t *coefficients = NULL; double left, right, result; int res; if (1 == n) { if (MODE_VALUE == mode || MODE_MAX == mode || MODE_MIN == mode || MODE_AVG == mode) return x[0]; if (MODE_DELTA == mode) return 0.0; THIS_SHOULD_NEVER_HAPPEN; return ZBX_MATH_ERROR; } else if (FIT_POLYNOMIAL == fit) { if ((unsigned)n <= k) return ZBX_MATH_ERROR; } zbx_matrix_struct_alloc(&coefficients); if (SUCCEED != (res = zbx_regression(t, x, n, fit, k, coefficients))) goto out; zbx_log_expression(now, fit, (int)k, coefficients); if (MODE_VALUE == mode) { res = zbx_calculate_value(now + time, coefficients, fit, &result); goto out; } if (0.0 == time) { if (MODE_MAX == mode || MODE_MIN == mode || MODE_AVG == mode) { res = zbx_calculate_value(now + time, coefficients, fit, &result); } else if (MODE_DELTA == mode) { result = 0.0; res = SUCCEED; } else { THIS_SHOULD_NEVER_HAPPEN; res = FAIL; } goto out; } if (FIT_LINEAR == fit || FIT_EXPONENTIAL == fit || FIT_LOGARITHMIC == fit || FIT_POWER == fit) { /* fit is monotone, therefore maximum and minimum are either at now or at now + time */ if (SUCCEED != zbx_calculate_value(now, coefficients, fit, &left) || SUCCEED != zbx_calculate_value(now + time, coefficients, fit, &right)) { res = FAIL; goto out; } if (MODE_MAX == mode) { result = (left > right ? left : right); } else if (MODE_MIN == mode) { result = (left < right ? left : right); } else if (MODE_DELTA == mode) { result = (left > right ? left - right : right - left); } else if (MODE_AVG == mode) { if (FIT_LINEAR == fit) { result = 0.5 * (left + right); } else if (FIT_EXPONENTIAL == fit) { result = (right - left) / time / ZBX_MATRIX_EL(coefficients, 1, 0); } else if (FIT_LOGARITHMIC == fit) { result = right + ZBX_MATRIX_EL(coefficients, 1, 0) * (log(1.0 + time / now) * now / time - 1.0); } else if (FIT_POWER == fit) { if (-1.0 != ZBX_MATRIX_EL(coefficients, 1, 0)) result = (right * (now + time) - left * now) / time / (ZBX_MATRIX_EL(coefficients, 1, 0) + 1.0); else result = exp(ZBX_MATRIX_EL(coefficients, 0, 0)) * log(1.0 + time / now) / time; } else { THIS_SHOULD_NEVER_HAPPEN; res = FAIL; goto out; } } else { THIS_SHOULD_NEVER_HAPPEN; res = FAIL; goto out; } res = SUCCEED; } else if (FIT_POLYNOMIAL == fit) { if (MODE_MAX == mode || MODE_MIN == mode || MODE_DELTA == mode) { res = zbx_polynomial_minmax(now, time, mode, coefficients, &result); } else if (MODE_AVG == mode) { result = (zbx_polynomial_antiderivative(now + time, coefficients) - zbx_polynomial_antiderivative(now, coefficients)) / time; res = SUCCEED; } else { THIS_SHOULD_NEVER_HAPPEN; res = FAIL; } } else { THIS_SHOULD_NEVER_HAPPEN; res = FAIL; } out: zbx_matrix_free(coefficients); if (SUCCEED != res) { result = ZBX_MATH_ERROR; } else if (ZBX_IS_NAN(result)) { zabbix_log(LOG_LEVEL_DEBUG, "numerical error"); result = ZBX_MATH_ERROR; } /* these checks are needed in case of +/- infinity */ else if (DBL_MAX < result) { result = DBL_MAX; } else if (-DBL_MAX > result) { result = -DBL_MAX; } return result; } double zbx_timeleft(double *t, double *x, int n, double now, double threshold, zbx_fit_t fit, unsigned k) { zbx_matrix_t *coefficients = NULL; double current, result = -1.0; int res; if (1 == n) return (x[0] == threshold ? 0.0 : DBL_MAX); zbx_matrix_struct_alloc(&coefficients); if (SUCCEED != (res = zbx_regression(t, x, n, fit, k, coefficients))) goto out; zbx_log_expression(now, fit, (int)k, coefficients); if (SUCCEED != (res = zbx_calculate_value(now, coefficients, fit, ¤t))) { goto out; } else if (current == threshold) { result = 0.0; goto out; } if (FIT_LINEAR == fit) { result = (threshold - ZBX_MATRIX_EL(coefficients, 0, 0)) / ZBX_MATRIX_EL(coefficients, 1, 0) - now; } else if (FIT_POLYNOMIAL == fit) { res = zbx_polynomial_timeleft(now, threshold, coefficients, &result); } else if (FIT_EXPONENTIAL == fit) { result = (log(threshold) - ZBX_MATRIX_EL(coefficients, 0, 0)) / ZBX_MATRIX_EL(coefficients, 1, 0) - now; } else if (FIT_LOGARITHMIC == fit) { result = exp((threshold - ZBX_MATRIX_EL(coefficients, 0, 0)) / ZBX_MATRIX_EL(coefficients, 1, 0)) - now; } else if (FIT_POWER == fit) { result = exp((log(threshold) - ZBX_MATRIX_EL(coefficients, 0, 0)) / ZBX_MATRIX_EL(coefficients, 1, 0)) - now; } else { THIS_SHOULD_NEVER_HAPPEN; res = FAIL; } out: if (SUCCEED != res) { result = ZBX_MATH_ERROR; } else if (ZBX_IS_NAN(result)) { zabbix_log(LOG_LEVEL_DEBUG, "numerical error"); result = ZBX_MATH_ERROR; } else if (0.0 > result || DBL_MAX < result) { result = DBL_MAX; } zbx_matrix_free(coefficients); return result; }