diff --git a/src/libs/zbxalgo/prediction.c b/src/libs/zbxalgo/prediction.c index 438d7bc8200..26b560e1d1f 100644 --- a/src/libs/zbxalgo/prediction.c +++ b/src/libs/zbxalgo/prediction.c @@ -424,7 +424,7 @@ static int zbx_regression(double *t, double *x, int n, zbx_fit_t fit, int k, zbx { if (0 == isfinite(coefficients->elements[i])) { - zabbix_log(LOG_LEVEL_DEBUG, "regression produced non-finite coefficient"); + zabbix_log(LOG_LEVEL_DEBUG, "%s(): regression produced non-finite coefficient", __func__); res = FAIL; goto out; } @@ -979,7 +979,7 @@ static void zbx_log_expression(double now, zbx_fit_t fit, int k, zbx_matrix_t *c 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; + double result; int res; if (1 == n) @@ -1007,20 +1007,32 @@ double zbx_forecast(double *t, double *x, int n, double now, double time, zbx_fi /* linear 2-point estimate for linear and logarithmic fits in value mode. */ if ((FIT_LINEAR == fit || FIT_LOGARITHMIC == fit) && MODE_VALUE == mode) { - int i_min_t = 0, i_max_t = 0; - double t_span; + int i_min_t, i_max_t; + double delta_t; - for (int i = 1; i < n; i++) + /* The value cache may return data in either ascending or descending */ + /* time order. Find indices of min and max timestamp. */ + + if (t[0] < t[n-1]) + { + i_min_t = 0; + i_max_t = n - 1; + } + else if (t[0] > t[n-1]) { - if (t[i] < t[i_min_t]) i_min_t = i; - if (t[i] > t[i_max_t]) i_max_t = i; + i_min_t = n - 1; + i_max_t = 0; } + else + i_min_t = i_max_t = 0; - if (0.0 < (t_span = t[i_max_t] - t[i_min_t])) + if (0.0 < (delta_t = t[i_max_t] - t[i_min_t])) { - double slope = (x[i_max_t] - x[i_min_t]) / t_span; + double slope = (x[i_max_t] - x[i_min_t]) / delta_t; result = x[i_max_t] + slope * (now + time - t[i_max_t]); + + /* NaN and +/-Inf in 'result' are handled below, on return */ res = SUCCEED; } } @@ -1058,6 +1070,8 @@ double zbx_forecast(double *t, double *x, int n, double now, double time, zbx_fi if (FIT_LINEAR == fit || FIT_EXPONENTIAL == fit || FIT_LOGARITHMIC == fit || FIT_POWER == fit) { + double left, right; + /* 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)) @@ -1178,42 +1192,75 @@ double zbx_timeleft(double *t, double *x, int n, double now, double threshold, z if (SUCCEED != (res = zbx_regression(t, x, n, fit, k, coefficients))) { - /* When regression fails due to overflow with extreme values, fall back to a linear */ - /* 2-point slope estimate using the oldest and newest data points. The value cache may */ - /* return data in either ascending or descending time order, so scan for the actual */ - /* min-t and max-t indices rather than assuming array ordering. */ + /* When regression fails due to overflow with extreme values, fall back to a linear */ + /* 2-point slope estimate using the oldest and newest data points. */ + if (FIT_LINEAR == fit || FIT_LOGARITHMIC == fit) { - int i_min_t = 0, i_max_t = 0; - double t_span, slope, val_now; + int i_min_t, i_max_t; + double delta_t; - for (int i = 1; i < n; i++) + /* The value cache may return data in either ascending or descending */ + /* time order. Find indices of min and max timestamp. */ + + if (t[0] < t[n-1]) + { + i_min_t = 0; + i_max_t = n - 1; + } + else if (t[0] > t[n-1]) { - if (t[i] < t[i_min_t]) i_min_t = i; - if (t[i] > t[i_max_t]) i_max_t = i; + i_min_t = n - 1; + i_max_t = 0; } + else + i_min_t = i_max_t = 0; - if (0.0 < (t_span = t[i_max_t] - t[i_min_t])) + if (0.0 < (delta_t = t[i_max_t] - t[i_min_t])) { - slope = (x[i_max_t] - x[i_min_t]) / t_span; + double slope = (x[i_max_t] - x[i_min_t]) / delta_t; if (0 == isfinite(slope)) { /* Slope overflowed — use trend direction and newest value to decide. */ result = (x[i_max_t] > x[i_min_t]) ? ((x[i_max_t] >= threshold) ? DBL_MAX : 0.0) : - ((x[i_max_t] <= threshold) ? 0.0 : DBL_MAX); + ((x[i_max_t] <= threshold) ? DBL_MAX : 0.0); } else { - val_now = x[i_max_t] + slope * (now - t[i_max_t]); - - if (0 != isfinite(val_now) && val_now > threshold && 0.0 > slope) - result = (threshold - val_now) / slope; - else if (val_now > threshold) - result = DBL_MAX; + double val_now = x[i_max_t] + slope * (now - t[i_max_t]); + + if (0 != isnan(val_now)) + goto out; + + if (0 != isfinite(val_now)) + { + if (val_now > threshold) + { + if (0.0 > slope) + result = (threshold - val_now) / slope; + else + result = DBL_MAX; + } + else if (val_now < threshold) + { + if (0.0 < slope) + result = (threshold - val_now) / slope; + else + result = DBL_MAX; + } + else + result = 0.0; + } else - result = 0.0; + { + /* val_now overflowed — use trend direction and newest */ + /* value to decide. */ + result = (x[i_max_t] > x[i_min_t]) ? + ((x[i_max_t] >= threshold) ? DBL_MAX : 0.0) : + ((x[i_max_t] <= threshold) ? DBL_MAX : 0.0); + } } res = SUCCEED; diff --git a/src/libs/zbxcalc/func_eval.c b/src/libs/zbxcalc/func_eval.c index c1fba0fc802..0798b1be15c 100644 --- a/src/libs/zbxcalc/func_eval.c +++ b/src/libs/zbxcalc/func_eval.c @@ -2294,13 +2294,10 @@ clean: static int evaluate_FORECAST(zbx_variant_t *value, const zbx_dc_evaluate_item_t *item, const char *parameters, const zbx_timespec_t *ts, zbx_history_selector_t *selector, char **error) { - char *fit_str = NULL, *mode_str = NULL; - double *t = NULL, *x = NULL; int nparams, i, ret = FAIL, seconds = 0, nvalues = 0; zbx_history_selector_t time_selector = {0}; unsigned int k = 0; zbx_vector_history_record_t values; - zbx_timespec_t zero_time; zbx_fit_t fit; zbx_mode_t mode; zbx_timespec_t ts_end = *ts; @@ -2343,12 +2340,17 @@ static int evaluate_FORECAST(zbx_variant_t *value, const zbx_dc_evaluate_item_t if (3 <= nparams) { + char *fit_str = NULL; + if (SUCCEED != get_function_parameter_str(parameters, 3, &fit_str) || SUCCEED != zbx_fit_code(fit_str, &fit, &k, error)) { *error = zbx_strdup(*error, "invalid fourth parameter"); + zbx_free(fit_str); goto out; } + + zbx_free(fit_str); } else { @@ -2357,12 +2359,17 @@ static int evaluate_FORECAST(zbx_variant_t *value, const zbx_dc_evaluate_item_t if (4 == nparams) { + char *mode_str = NULL; + if (SUCCEED != get_function_parameter_str(parameters, 4, &mode_str) || SUCCEED != zbx_mode_code(mode_str, &mode, error)) { *error = zbx_strdup(*error, "invalid fifth parameter"); + zbx_free(mode_str); goto out; } + + zbx_free(mode_str); } else { @@ -2391,8 +2398,10 @@ static int evaluate_FORECAST(zbx_variant_t *value, const zbx_dc_evaluate_item_t if (0 < values.values_num) { - t = (double *)zbx_malloc(t, (size_t)values.values_num * sizeof(double)); - x = (double *)zbx_malloc(x, (size_t)values.values_num * sizeof(double)); + zbx_timespec_t zero_time; + + double *t = (double *)zbx_malloc(t, (size_t)values.values_num * sizeof(double)); + double *x = (double *)zbx_malloc(x, (size_t)values.values_num * sizeof(double)); zero_time.sec = values.values[values.values_num - 1].timestamp.sec; zero_time.ns = values.values[values.values_num - 1].timestamp.ns; @@ -2419,6 +2428,8 @@ static int evaluate_FORECAST(zbx_variant_t *value, const zbx_dc_evaluate_item_t zbx_variant_set_dbl(value, zbx_forecast(t, x, values.values_num, ts->sec - zero_time.sec - 1.0e-9 * (zero_time.ns + 1), time_selector.value, fit, k, mode)); + zbx_free(x); + zbx_free(t); } else { @@ -2429,12 +2440,6 @@ static int evaluate_FORECAST(zbx_variant_t *value, const zbx_dc_evaluate_item_t out: zbx_history_record_vector_destroy(&values, item->value_type); - zbx_free(fit_str); - zbx_free(mode_str); - - zbx_free(t); - zbx_free(x); - zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret)); return ret; diff --git a/tests/libs/zbxcalc/zbx_evaluate_function.yaml b/tests/libs/zbxcalc/zbx_evaluate_function.yaml index 5000bcee522..838a79caff1 100644 --- a/tests/libs/zbxcalc/zbx_evaluate_function.yaml +++ b/tests/libs/zbxcalc/zbx_evaluate_function.yaml @@ -1637,7 +1637,7 @@ out: return: SUCCEED value: 1.7976931348623158e+308 --- -test case: Evaluate timeleft(#2,wait 0) +test case: Evaluate timeleft(#2,-1) from DBL_MAX to 0, threshold -1 in the past in: history: - itemid: 1 @@ -1650,10 +1650,44 @@ in: time: 2017-01-10 10:03:00.000000000 +00:00 function: timeleft params: '#2,-1' +out: + return: SUCCEED + value: 1.7976931348623158e+308 +--- +test case: Evaluate timeleft(#2,-1) from DBL_MAX to 0, threshold -1 practically instantly reached +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 1.7976931348623158e+308 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 0.0 + ts: 2017-01-10 10:02:00.000000000 +00:00 + time: 2017-01-10 10:02:00.000000001 +00:00 + function: timeleft + params: '#2,-1' out: return: SUCCEED value: 0 --- +test case: Evaluate timeleft(#2,-1) from 0 to DBL_MAX, threshold -1 never +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 0.0 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 1.7976931348623158e+308 + ts: 2017-01-10 10:02:00.000000000 +00:00 + time: 2017-01-10 10:03:00.000000000 +00:00 + function: timeleft + params: '#2,-1' +out: + return: SUCCEED + value: 1.7976931348623158e+308 +--- test case: Evaluate timeleft(5m,65) in: history: @@ -4826,6 +4860,26 @@ in: out: return: SUCCEED value: 272384191.768584 +--- +test case: Evaluate timeleft(#3,1.7976931348623158e+308,"logarithmic") returning 0 +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 1.7976931348623155e+308 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 1.7976931348623156e+308 + ts: 2017-01-10 10:02:00.000000000 +00:00 + - value: 1.7976931348623157e+308 + ts: 2017-01-10 10:03:00.000000000 +00:00 + time: 2017-01-10 10:04:00.000000000 +00:00 + function: timeleft + params: '#3,1.7976931348623158e+308,"logarithmic"' +out: + return: SUCCEED + value: 0 +--- test case: Evaluate timeleft(#3,1.7976931348623158e+308,"logarithmic") in: history: @@ -5702,4 +5756,96 @@ db data: - [10.0, 9] trends (10): - [2.2250738585072014e-308, 10] +--- +test case: Evaluate timeleft(#5,6.0, 60) +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 1.0 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 2.0 + ts: 2017-01-10 10:02:00.000000000 +00:00 + - value: 3.0 + ts: 2017-01-10 10:03:00.000000000 +00:00 + - value: 4.0 + ts: 2017-01-10 10:04:00.000000000 +00:00 + - value: 5.0 + ts: 2017-01-10 10:05:00.000000000 +00:00 + time: 2017-01-10 10:05:00.000000000 +00:00 + function: timeleft + params: '#5,6.0' +out: + return: SUCCEED + value: 60 +--- +test case: Evaluate timeleft(#5,6.0, 1) +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 1.0 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 2.0 + ts: 2017-01-10 10:02:00.000000000 +00:00 + - value: 3.0 + ts: 2017-01-10 10:03:00.000000000 +00:00 + - value: 4.0 + ts: 2017-01-10 10:04:00.000000000 +00:00 + - value: 5.0 + ts: 2017-01-10 10:05:00.000000000 +00:00 + time: 2017-01-10 10:05:59.000000000 +00:00 + function: timeleft + params: '#5,6.0' +out: + return: SUCCEED + value: 1 +--- +test case: Evaluate timeleft(#5,6.0, 0) +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 1.0 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 2.0 + ts: 2017-01-10 10:02:00.000000000 +00:00 + - value: 3.0 + ts: 2017-01-10 10:03:00.000000000 +00:00 + - value: 4.0 + ts: 2017-01-10 10:04:00.000000000 +00:00 + - value: 5.0 + ts: 2017-01-10 10:05:00.000000000 +00:00 + time: 2017-01-10 10:06:00.000000000 +00:00 + function: timeleft + params: '#5,6.0' +out: + return: SUCCEED + value: 0 +--- +test case: Evaluate timeleft(#5,6.0, 1.7976931348623158e+308) +in: + history: + - itemid: 1 + value type: ITEM_VALUE_TYPE_FLOAT + data: + - value: 1.0 + ts: 2017-01-10 10:01:00.000000000 +00:00 + - value: 2.0 + ts: 2017-01-10 10:02:00.000000000 +00:00 + - value: 3.0 + ts: 2017-01-10 10:03:00.000000000 +00:00 + - value: 4.0 + ts: 2017-01-10 10:04:00.000000000 +00:00 + - value: 5.0 + ts: 2017-01-10 10:05:00.000000000 +00:00 + time: 2017-01-10 10:06:01.000000000 +00:00 + function: timeleft + params: '#5,6.0' +out: + return: SUCCEED + value: 1.7976931348623158e+308 ...