/* ** 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 "anomalystl.h" #include "zbxnum.h" #include "zbxeval.h" #include "zbxtime.h" ZBX_PTR_VECTOR_DECL(VV, zbx_vector_history_record_t *) ZBX_PTR_VECTOR_IMPL(VV, zbx_vector_history_record_t *) /******************************************************************************* * * * Purpose: finds how many values in stl remainder are outliers. * * * * Parameters: remainder - [IN] stl remainder values vector * * deviations_count - [IN] how much a value can be away from the * * (mad, stdevsamp or stdevpop) to get * * counted as an outlier * * devalg - [IN] function (mad, stdevsamp or stdevpop) * * to evaluate a single deviation unit * * detect_period_start - [IN] evaluate number of deviations in * * remainder starting from this time * * detect_period - [IN] evaluate number of deviations in * * remainder until this time * * result - [OUT] result - double, a percentage how * * many outliers are in remainder * * error - [OUT] the error message * * * * Return value: SUCCEED - evaluated successfully, 'result' contains new value * * FAIL - evaluation failed, 'result' contains old value * * * *******************************************************************************/ int zbx_get_percentage_of_deviations_in_stl_remainder(const zbx_vector_history_record_t *remainder, double deviations_count, const char* devalg, int detect_period_start, int detect_period_end, double *result, char **error) { int i, total_values_count = 0, deviations_detected_count = 0, ret = FAIL; double remainder_deviation, deviation_limit; zbx_statistical_func_t stat_func; zbx_vector_dbl_t remainder_values_dbl; zbx_vector_dbl_create(&remainder_values_dbl); if (0 == remainder->values_num) { *error = zbx_strdup(*error, "empty remainder"); goto out; } if (0 == strcmp("mad", devalg)) { stat_func = zbx_eval_calc_mad; } else if (0 == strcmp("stddevpop", devalg)) { stat_func = zbx_eval_calc_stddevpop; } else if (0 == strcmp("stddevsamp", devalg)) { stat_func = zbx_eval_calc_stddevsamp; } else { *error = zbx_dsprintf(*error, "undefined devalg parameter: \"%s\"", devalg); THIS_SHOULD_NEVER_HAPPEN; goto out; } zbx_vector_dbl_reserve(&remainder_values_dbl, (size_t)remainder->values_num); for (i = 0; i < remainder->values_num; i++) zbx_vector_dbl_append(&remainder_values_dbl, remainder->values[i].value.dbl); if (SUCCEED != (ret = stat_func(&remainder_values_dbl, &remainder_deviation, error))) goto out; deviation_limit = remainder_deviation * deviations_count; for (i = 0; i < remainder->values_num; i++) { if (remainder->values[i].timestamp.sec >= detect_period_start && remainder->values[i].timestamp.sec <= detect_period_end) { total_values_count++; if (fabs(remainder->values[i].value.dbl) > deviation_limit) deviations_detected_count++; } } if (0 == total_values_count) *result = 0; else *result = (double)deviations_detected_count / total_values_count; out: zbx_vector_dbl_destroy(&remainder_values_dbl); return ret; } static double nextodd(double x) { x = round(x); if (SUCCEED == zbx_double_compare(0, remainder(x, 2.0))) x += 1; return x; } static void VV_clear(zbx_vector_history_record_t *v) { zbx_history_record_vector_destroy(v, ITEM_VALUE_TYPE_FLOAT); zbx_free(v); } static int eval_loess_regression_curve(const zbx_vector_history_record_t *y, int n, int length, int ideg, int xs, int nleft, int nright, const zbx_vector_history_record_t *w, int userw, const zbx_vector_history_record_t *rw, double *ret) { int i, ret_status = FAIL, count_mid = 0; double h; zbx_vector_dbl_t r; zbx_vector_uint64_t low_mask, high_mask, mid_mask, lowmid_mask, window, low, high, mid, lowmid; double a = 0; h = MAX(xs - nleft, nright - xs); if (length > n) h += (length - n); zbx_vector_dbl_create(&r); zbx_vector_uint64_create(&window); zbx_vector_uint64_create(&mid_mask); zbx_vector_uint64_create(&low_mask); zbx_vector_uint64_create(&high_mask); zbx_vector_uint64_create(&lowmid_mask); zbx_vector_uint64_create(&low); zbx_vector_uint64_create(&high); zbx_vector_uint64_create(&mid); zbx_vector_uint64_create(&lowmid); for (i = nleft - xs; i < nright - xs + 1; i++) zbx_vector_dbl_append(&r, abs(i)); for (i = nleft - 1; i < nright; i++) zbx_vector_uint64_append(&window, (zbx_uint64_t)i); for (i = 0; i < r.values_num; i++) zbx_vector_uint64_append(&low_mask, (0.001 * h >= r.values[i]) ? 1 : 0); for (i = 0; i < r.values_num; i++) zbx_vector_uint64_append(&high_mask, (0.999 * h < r.values[i]) ? 1 : 0); for (i = 0; i < low_mask.values_num; i++) zbx_vector_uint64_append(&mid_mask, !(low_mask.values[i] | high_mask.values[i])); for (i = 0; i < high_mask.values_num; i++) zbx_vector_uint64_append(&lowmid_mask, !(high_mask.values[i])); /* filter out false entries */ for (i = 0; i < low_mask.values_num; i++) { if (1 == low_mask.values[i]) zbx_vector_uint64_append(&low, window.values[i]); } for (i = 0; i < high_mask.values_num; i++) { if (1 == high_mask.values[i]) zbx_vector_uint64_append(&high, window.values[i]); } for (i = 0; i < mid_mask.values_num; i++) { if (1 == mid_mask.values[i]) zbx_vector_uint64_append(&mid, window.values[i]); } for (i = 0; i < lowmid_mask.values_num; i++) { if (1 == lowmid_mask.values[i]) zbx_vector_uint64_append(&lowmid, window.values[i]); } for (i = 0; i < low.values_num; i++) w->values[low.values[i]].value.dbl = 1; for (i = 0; i < mid_mask.values_num; i++) { if (1 == mid_mask.values[i]) { w->values[mid.values[count_mid]].value.dbl = pow(1 - pow(r.values[i] / h, 3), 3); count_mid++; } } if (1 == userw) { for (i = 0; i < lowmid.values_num; i++) w->values[lowmid.values[i]].value.dbl *= rw->values[lowmid.values[i]].value.dbl; } for (i = 0; i < lowmid.values_num; i++) a += w->values[lowmid.values[i]].value.dbl; for (i = 0; i < high.values_num; i++) w->values[high.values[i]].value.dbl = 0; if (0 < a) { ret_status = SUCCEED; for (i = nleft - 1; i < nright; i++) w->values[i].value.dbl /= a; if (0 < h && 0 < ideg) { double c, b; a = 0; for (i = nleft - 1; i < nright; i++) a += (w->values[i].value.dbl * (i + 1)); b = xs - a; c = 0; for (i = nleft - 1; i < nright; i++) c += (w->values[i].value.dbl * pow((i + 1 - a), 2)); if (sqrt(c) > 0.001 * (n - 1)) { b /= c; for (i = nleft - 1; i < nright; i++) w->values[i].value.dbl *= ((b * ((i + 1) - a)) + 1); } } *ret = 0; for (i = nleft - 1; i < nright; i++) *ret += w->values[i].value.dbl * y->values[i].value.dbl; } zbx_vector_dbl_destroy(&r); zbx_vector_uint64_destroy(&window); zbx_vector_uint64_destroy(&high_mask); zbx_vector_uint64_destroy(&low_mask); zbx_vector_uint64_destroy(&mid_mask); zbx_vector_uint64_destroy(&lowmid_mask); zbx_vector_uint64_destroy(&low); zbx_vector_uint64_destroy(&high); zbx_vector_uint64_destroy(&mid); zbx_vector_uint64_destroy(&lowmid); return ret_status; } static void apply_loess_smoothing(const zbx_vector_history_record_t *y, int n, int length, int ideg, int njump, int userw, const zbx_vector_history_record_t *rw, zbx_vector_history_record_t *ys, const zbx_vector_history_record_t *res) { int newnj, i, nleft, nright; if (n < 2) { ys->values[0].value.dbl = y->values[0].value.dbl; return; } newnj = MIN(njump, n - 1); if (length >= n) { nleft = 1; nright = n; for (i = 0; i < n; i = i + newnj) { double nys; if (SUCCEED == eval_loess_regression_curve(y, n, length, ideg, i + 1, nleft, nright, res, userw, rw, &nys)) { ys->values[i].value.dbl = nys; } else { ys->values[i].value.dbl = y->values[i].value.dbl; } } } else { if (1 == newnj) { int nsh; nsh = (length + 1) / 2; nleft = 1; nright = length; for (i = 0; i < n; i++) { double nys; if ((i + 1) > nsh && nright != n) { nleft += 1; nright += 1; } if (SUCCEED == eval_loess_regression_curve(y, n, length, ideg, i + 1, nleft, nright, res, userw, rw, &nys)) { ys->values[i].value.dbl = nys; } else { ys->values[i].value.dbl = y->values[i].value.dbl; } } } else { int nsh; nsh = (length + 1) / 2; for (i = 1; i < n + 1; i = i + newnj) { double nys; if (i < nsh) { nleft = 1; nright = length; } else if (i >= (n - nsh + 1)) { nleft = n - length + 1; nright = n; } else { nleft = i - nsh + 1; nright = length + i - nsh; } if (SUCCEED == eval_loess_regression_curve(y, n, length, ideg, i, nleft, nright, res, userw, rw, &nys)) { ys->values[i - 1].value.dbl = nys; } else { ys->values[i - 1].value.dbl = y->values[i - 1].value.dbl; } } } } if (1 != newnj) { int k; double delta; for (i = 0; i < n - newnj; i = i + newnj) { int j; delta = (ys->values[i + newnj].value.dbl - ys->values[i].value.dbl) / newnj; for (j = i + 1; j < i + newnj; j++) ys->values[j].value.dbl = ys->values[i].value.dbl + (delta * (j - i)); } k = ((n - 1)/newnj) * newnj + 1; if (k != n) { double nys; if (SUCCEED == eval_loess_regression_curve(y, n, length, ideg, n, nleft, nright, res, userw, rw, &nys)) { ys->values[n - 1].value.dbl = nys; } else { ys->values[n - 1].value.dbl = y->values[n - 1].value.dbl; } if (k != (n - 1)) { delta = (ys->values[n - 1].value.dbl - ys->values[k - 1].value.dbl) / (n - k); for (i = k; i < n - 1; i++) ys->values[k].value.dbl = ys->values[k - 1].value.dbl + (delta * (i - k + 1)); } } } } static void combine_smooth(const zbx_vector_history_record_t *y, int n, int np, int ns, int isdeg, int nsjump, int userw, const zbx_vector_history_record_t *rw, zbx_vector_history_record_t *season, zbx_vector_history_record_t *work1, zbx_vector_history_record_t *work2, zbx_vector_history_record_t *work3, zbx_vector_history_record_t *work4) { for (int i = 0; i < np; i++) { int k, m, nleft, nright; double nval; zbx_vector_history_record_t work_2_copy; k = ((n - i - 1) / np) + 1; for (int j = 0; j < k; j++) work1->values[j].value.dbl = y->values[j * np + i].value.dbl; if (1 == userw) { for (int j = 0; j < k; j++) work3->values[i].value.dbl = rw->values[i * np + i].value.dbl; } zbx_history_record_vector_create(&work_2_copy); for (int j = 1; j < work2->values_num; j++) { zbx_history_record_t cp; cp.timestamp = work2->values[j].timestamp; cp.value.dbl = work2->values[j].value.dbl; zbx_vector_history_record_append_ptr(&work_2_copy, &cp); } apply_loess_smoothing(work1, k, ns, isdeg, nsjump, userw, work3, &work_2_copy, work4); for (int j = 1; j < work2->values_num; j++) { work2->values[j].timestamp = work_2_copy.values[j - 1].timestamp; work2->values[j].value.dbl = work_2_copy.values[j - 1].value.dbl; } zbx_history_record_vector_destroy(&work_2_copy, ITEM_VALUE_TYPE_FLOAT); nright = MIN(ns, k); if (SUCCEED == eval_loess_regression_curve(work1, k, ns, isdeg, 0, 1, nright, work4, userw, work3, &nval)) { work2->values[0].value.dbl = nval; } else { work2->values[0].value.dbl = work2->values[1].value.dbl; } nleft = MAX(1, k - ns + 1); if (SUCCEED == eval_loess_regression_curve(work1, k, ns, isdeg, k+1, nleft, k, work4, userw, work3, &nval)) { work2->values[k + 1].value.dbl = nval; } else { work2->values[k + 1].value.dbl = work2->values[k].value.dbl; } for (m = 0; m < k + 2; m++) { season->values[m * np + i].timestamp = work2->values[m].timestamp; season->values[m * np + i].value.dbl = work2->values[m].value.dbl; } } } static void eval_moving_average(const zbx_vector_history_record_t *x, int n, int length, zbx_vector_history_record_t *ave) { int i, newn; double v = 0; for (i = 0; i < length; i++) v += x->values[i].value.dbl; ave->values[0].value.dbl = v / length; newn = n - length + 1; if (newn > 1) { int k, m, j; k = length; m = 0; for (j = 1; j < newn; j++) { k += 1; m += 1; v = v - x->values[m - 1].value.dbl + x->values[k - 1].value.dbl; ave->values[j].value.dbl = v / length; } } } static double find_stl_median(zbx_vector_history_record_t *v) { zbx_vector_history_record_sort(v, (zbx_compare_func_t)zbx_history_record_float_compare); if (0 == v->values_num % 2) return (v->values[v->values_num / 2 - 1].value.dbl + v->values[v->values_num / 2].value.dbl) / 2.0; else return v->values[v->values_num / 2].value.dbl; } static void eval_robustness_weights(const zbx_vector_history_record_t *y, int n, const zbx_vector_history_record_t *fit, zbx_vector_history_record_t *rw) { int i; double med; zbx_vector_uint64_t low, high, mid; zbx_vector_history_record_t r; ZBX_UNUSED(n); zbx_vector_uint64_create(&low); zbx_vector_uint64_create(&high); zbx_vector_uint64_create(&mid); zbx_history_record_vector_create(&r); for (i = 0; i < y->values_num; i++) { zbx_history_record_t cp; cp.timestamp = y->values[i].timestamp; cp.value.dbl = fabs(y->values[i].value.dbl - fit->values[i].value.dbl); zbx_vector_history_record_append_ptr(&r, &cp); } med = 6 * find_stl_median(&r); for (i = 0; i < r.values_num; i++) { zbx_vector_uint64_append(&low, (r.values[i].value.dbl <= 0.001 * med) ? 1 : 0); zbx_vector_uint64_append(&high, (r.values[i].value.dbl > 0.999 * med) ? 1 : 0); zbx_vector_uint64_append(&mid, !(low.values[i] | high.values[i])); } for (i = 0; i < low.values_num; i++) { if (1 == low.values[i]) rw->values[i].value.dbl = 1; } for (i = 0; i < mid.values_num; i++) { if (1 == mid.values[i]) { rw->values[i].value.dbl = pow(1 - pow(r.values[i].value.dbl, 2), 2); } } for(i = 0; i < high.values_num; i++) { if (1 == high.values[i]) rw->values[i].value.dbl = 0; } zbx_history_record_vector_destroy(&r, ITEM_VALUE_TYPE_FLOAT); zbx_vector_uint64_destroy(&low); zbx_vector_uint64_destroy(&high); zbx_vector_uint64_destroy(&mid); } static void step(const zbx_vector_history_record_t *y, int n, int np, int ns, int nt, int nl, int isdeg, int itdeg, int ildeg, int nsjump, int ntjump, int nljump, int ni, int userw, zbx_vector_history_record_t *rw, zbx_vector_history_record_t *season, zbx_vector_history_record_t *trend, zbx_vector_VV_t *work) { for (int i = 0; i < ni; i++) { zbx_vector_history_record_t work_0_copy, work_1_copy, work_2_copy, work_3_copy, work_4_copy; for (int j = 0; j < n; j++) work->values[j]->values[0].value.dbl = y->values[j].value.dbl - trend->values[j].value.dbl; zbx_history_record_vector_create(&work_0_copy); zbx_history_record_vector_create(&work_1_copy); zbx_history_record_vector_create(&work_2_copy); zbx_history_record_vector_create(&work_3_copy); zbx_history_record_vector_create(&work_4_copy); for (int j = 0; j < work->values_num; j++) { zbx_history_record_t cp; cp.timestamp = work->values[j]->values[0].timestamp; cp.value.dbl = work->values[j]->values[0].value.dbl; zbx_vector_history_record_append_ptr(&work_0_copy, &cp); cp.timestamp = work->values[j]->values[1].timestamp; cp.value.dbl = work->values[j]->values[1].value.dbl; zbx_vector_history_record_append_ptr(&work_1_copy, &cp); cp.timestamp = work->values[j]->values[2].timestamp; cp.value.dbl = work->values[j]->values[2].value.dbl; zbx_vector_history_record_append_ptr(&work_2_copy, &cp); cp.timestamp = work->values[j]->values[3].timestamp; cp.value.dbl = work->values[j]->values[3].value.dbl; zbx_vector_history_record_append_ptr(&work_3_copy, &cp); cp.timestamp = work->values[j]->values[4].timestamp; cp.value.dbl = work->values[j]->values[4].value.dbl; zbx_vector_history_record_append_ptr(&work_4_copy, &cp); } combine_smooth(&work_0_copy, n, np, ns, isdeg, nsjump, userw, rw, &work_1_copy, &work_2_copy, &work_3_copy, &work_4_copy, season); eval_moving_average(&work_1_copy, n + 2 * np, np, &work_2_copy); eval_moving_average(&work_2_copy, n + np + 1, np, &work_0_copy); eval_moving_average(&work_0_copy, n + 2, 3, &work_2_copy); apply_loess_smoothing(&work_2_copy, n, nl, ildeg, nljump, 0, &work_3_copy, &work_0_copy, &work_4_copy); for (int j = np; j < np + n; j++) { season->values[j - np].value.dbl = work_1_copy.values[j].value.dbl - work_0_copy.values[j - np].value.dbl; } for (int j = 0; j < n; j++) work_0_copy.values[j].value.dbl = y->values[j].value.dbl - season->values[j].value.dbl; apply_loess_smoothing(&work_0_copy, n, nt, itdeg, ntjump, userw, rw, trend, &work_2_copy); /* save changes from copies back into work */ for (int j = 0; j < work->values_num; j++) { work->values[j]->values[0].value.dbl = work_0_copy.values[j].value.dbl; work->values[j]->values[1].value.dbl = work_1_copy.values[j].value.dbl; work->values[j]->values[2].value.dbl = work_2_copy.values[j].value.dbl; work->values[j]->values[3].value.dbl = work_3_copy.values[j].value.dbl; work->values[j]->values[4].value.dbl = work_4_copy.values[j].value.dbl; } zbx_history_record_vector_destroy(&work_0_copy, ITEM_VALUE_TYPE_FLOAT); zbx_history_record_vector_destroy(&work_1_copy, ITEM_VALUE_TYPE_FLOAT); zbx_history_record_vector_destroy(&work_2_copy, ITEM_VALUE_TYPE_FLOAT); zbx_history_record_vector_destroy(&work_3_copy, ITEM_VALUE_TYPE_FLOAT); zbx_history_record_vector_destroy(&work_4_copy, ITEM_VALUE_TYPE_FLOAT); } } int zbx_STL(const zbx_vector_history_record_t *values_in, int freq, int is_robust, int s_window, int s_degree, double t_window, int t_degree, int l_window, int l_degree, int nsjump, int ntjump, int nljump, int inner, int outer, zbx_vector_history_record_t *trend, zbx_vector_history_record_t *seasonal, zbx_vector_history_record_t *remainder, char **error) { int values_in_len, userw, ret = FAIL; zbx_vector_history_record_t weights; zbx_vector_VV_t work; double tmp; zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__); values_in_len = values_in->values_num; if (2 > freq) { *error = zbx_dsprintf(*error, "Frequency (season/h) must be greater than 1, it is: %d", freq); goto out; } if (2 * freq >= values_in_len) { *error = zbx_dsprintf(*error, "STL requires number of data elements more than two times the frequency. " "Frequency (season/h) is: %d, number of data entries is: %d", freq, values_in_len); goto out; } if (S_WINDOW_DEF == s_window) s_window = 10 * values_in_len + 1; if (S_JUMP_DEF == nsjump) nsjump = (int)(tmp = ceil((double)s_window / 10)); ZBX_UNUSED(tmp); if (T_WINDOW_DEF == t_window) t_window = nextodd(ceil(1.5 * (double)freq / (1 - (1.5 / s_window)))); if (T_JUMP_DEF == ntjump) ntjump = (int)(tmp = ceil(t_window/10)); ZBX_UNUSED(tmp); if (L_WINDOW_DEF == l_window) { double d = nextodd(freq); l_window = (int)d; } if (L_DEGREE_DEF == l_degree) l_degree = t_degree; if (L_JUMP_DEF == nljump) nljump = (int)(tmp = ceil((double)l_window / 10)); ZBX_UNUSED(tmp); if (INNER_DEF == inner) inner = (1 == is_robust) ? 1 : 2; if (OUTER_DEF == outer) outer = (1 == is_robust) ? 15 : 0; zbx_vector_history_record_reserve(seasonal, (size_t)values_in_len); zbx_vector_history_record_reserve(trend, (size_t)values_in_len); zbx_history_record_vector_create(&weights); zbx_vector_history_record_reserve(&weights, (size_t)values_in_len); zbx_vector_history_record_reserve(remainder, (size_t)values_in_len); for (int i = 0; i < values_in_len; i++) { zbx_history_record_t value1, value2, value3, value4; value1.timestamp = values_in->values[i].timestamp; value1.value.dbl = 0; zbx_vector_history_record_append_ptr(&weights, &value1); value2.timestamp = values_in->values[i].timestamp; value2.value.dbl = 0; zbx_vector_history_record_append_ptr(seasonal, &value2); value3.timestamp = values_in->values[i].timestamp; value3.value.dbl = 0; zbx_vector_history_record_append_ptr(trend, &value3); value4.timestamp = values_in->values[i].timestamp; value4.value.dbl = 0; zbx_vector_history_record_append_ptr(remainder, &value4); } zbx_vector_VV_create(&work); zbx_vector_VV_reserve(&work, (size_t)(values_in_len + 2 * freq)); for (int i = 0; i < work.values_alloc; i++) { int j; zbx_vector_history_record_t *work_temp; work_temp = (zbx_vector_history_record_t*)zbx_malloc(NULL, sizeof(zbx_vector_history_record_t)); zbx_history_record_vector_create(work_temp); for (j = 0; j < 5; j++) { zbx_history_record_t x; x.value.dbl = 0; zbx_vector_history_record_append_ptr(work_temp, &x); } zbx_vector_VV_append(&work, work_temp); } s_window = MAX(3, s_window); t_window = MAX(3, t_window); l_window = MAX(3, l_window); if (0 == (s_window % 2)) s_window += 1; if (0 == ((int)t_window % 2)) t_window += 1; if (0 == (l_window % 2)) l_window += 1; userw = 0; step(values_in, values_in_len, freq, s_window, (int)t_window, l_window, s_degree, t_degree, l_degree, nsjump, ntjump, nljump, inner, userw, &weights, seasonal, trend, &work); userw = 1; for (int i = 0; i < outer; i++) { zbx_vector_history_record_t work_0_copy; zbx_history_record_vector_create(&work_0_copy); for (int j = 0; j < values_in_len; j++) { work.values[j]->values[0].value.dbl = trend->values[j].value.dbl + seasonal->values[j].value.dbl; } for (int j = 0; j < work.values_num; j++) { zbx_history_record_t cp; cp.timestamp = work.values[j]->values[0].timestamp; cp.value.dbl = work.values[j]->values[0].value.dbl; zbx_vector_history_record_append_ptr(&work_0_copy, &cp); } eval_robustness_weights(values_in, values_in_len, &work_0_copy, &weights); step(values_in, values_in_len, freq, s_window, (int)t_window, l_window, s_degree, t_degree, l_degree, nsjump, ntjump, nljump, inner, userw, &weights, seasonal, trend, &work); zbx_history_record_vector_destroy(&work_0_copy, ITEM_VALUE_TYPE_FLOAT); } if (0 >= outer) { for (int i = 0; i < weights.values_num; i++) weights.values[i].value.dbl = 1; } for (int i = 0; i < values_in->values_num; i++) { remainder->values[i].value.dbl = values_in->values[i].value.dbl - trend->values[i].value.dbl - seasonal->values[i].value.dbl; } zbx_vector_VV_clear_ext(&work, VV_clear); zbx_vector_VV_destroy(&work); zbx_history_record_vector_destroy(&weights, ITEM_VALUE_TYPE_FLOAT); ret = SUCCEED; out: zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; }