+/*****************************************************************************
+ * rrd_hw_update.c Functions for updating a Holt-Winters RRA
+ ****************************************************************************/
+
+#include "rrd_format.h"
+#include "rrd_config.h"
+#include "rrd_hw_math.h"
+#include "rrd_hw_update.h"
+
+static void init_slope_intercept(
+ unival *coefs,
+ unsigned short CDP_scratch_idx)
+{
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of slope/intercept\n");
+#endif
+ coefs[CDP_hw_intercept].u_val = coefs[CDP_scratch_idx].u_val;
+ coefs[CDP_hw_last_intercept].u_val = coefs[CDP_scratch_idx].u_val;
+ /* initialize the slope to 0 */
+ coefs[CDP_hw_slope].u_val = 0.0;
+ coefs[CDP_hw_last_slope].u_val = 0.0;
+ /* initialize null count to 1 */
+ coefs[CDP_null_count].u_cnt = 1;
+ coefs[CDP_last_null_count].u_cnt = 1;
+}
+
+static int hw_is_violation(
+ rrd_value_t observed,
+ rrd_value_t prediction,
+ rrd_value_t deviation,
+ rrd_value_t delta_pos,
+ rrd_value_t delta_neg)
+{
+ return (observed > prediction + delta_pos * deviation
+ || observed < prediction - delta_neg * deviation);
+}
+
+int update_hwpredict(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ hw_functions_t * functions)
+{
+ rrd_value_t prediction;
+ unsigned long dependent_rra_idx, seasonal_cdp_idx;
+ unival *coefs = rrd->cdp_prep[cdp_idx].scratch;
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+
+ /* save coefficients from current prediction */
+ coefs[CDP_hw_last_intercept].u_val = coefs[CDP_hw_intercept].u_val;
+ coefs[CDP_hw_last_slope].u_val = coefs[CDP_hw_slope].u_val;
+ coefs[CDP_last_null_count].u_cnt = coefs[CDP_null_count].u_cnt;
+
+ /* retrieve the current seasonal coef */
+ dependent_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+ seasonal_cdp_idx = dependent_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+
+ rrd_value_t seasonal_coef = (dependent_rra_idx < rra_idx)
+ ? rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].u_val
+ : rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+
+ /* compute the prediction */
+ if (isnan(coefs[CDP_hw_intercept].u_val)
+ || isnan(coefs[CDP_hw_slope].u_val)
+ || isnan(seasonal_coef)) {
+ prediction = DNAN;
+
+ /* bootstrap initialization of slope and intercept */
+ if (isnan(coefs[CDP_hw_intercept].u_val) &&
+ !isnan(coefs[CDP_scratch_idx].u_val)) {
+ init_slope_intercept(coefs, CDP_scratch_idx);
+ }
+ /* if seasonal coefficient is NA, then don't update intercept, slope */
+ } else {
+ prediction = functions->predict(coefs[CDP_hw_intercept].u_val,
+ coefs[CDP_hw_slope].u_val,
+ coefs[CDP_null_count].u_cnt,
+ seasonal_coef);
+#ifdef DEBUG
+ fprintf(stderr,
+ "computed prediction: %f (intercept %f, slope %f, season %f)\n",
+ prediction, coefs[CDP_hw_intercept].u_val,
+ coefs[CDP_hw_slope].u_val, seasonal_coef);
+#endif
+ if (isnan(coefs[CDP_scratch_idx].u_val)) {
+ /* NA value, no updates of intercept, slope;
+ * increment the null count */
+ (coefs[CDP_null_count].u_cnt)++;
+ } else {
+ /* update the intercept */
+ coefs[CDP_hw_intercept].u_val =
+ functions->intercept(current_rra->par[RRA_hw_alpha].u_val,
+ coefs[CDP_scratch_idx].u_val,
+ seasonal_coef, coefs);
+
+ /* update the slope */
+ coefs[CDP_hw_slope].u_val =
+ functions->slope(current_rra->par[RRA_hw_beta].u_val, coefs);
+
+ /* reset the null count */
+ coefs[CDP_null_count].u_cnt = 1;
+#ifdef DEBUG
+ fprintf(stderr, "Updating intercept = %f, slope = %f\n",
+ coefs[CDP_hw_intercept].u_val, coefs[CDP_hw_slope].u_val);
+#endif
+ }
+ }
+
+ /* store the prediction for writing */
+ coefs[CDP_scratch_idx].u_val = prediction;
+ return 0;
+}
+
+int update_seasonal(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_coef,
+ hw_functions_t * functions)
+{
+/* TODO: extract common if subblocks in the wake of I/O optimization */
+ rrd_value_t intercept, seasonal;
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+ rra_def_t *hw_rra =
+ &(rrd->rra_def[current_rra->par[RRA_dependent_rra_idx].u_cnt]);
+
+ /* obtain cdp_prep index for HWPREDICT */
+ unsigned long hw_cdp_idx = (current_rra->par[RRA_dependent_rra_idx].u_cnt)
+ * (rrd->stat_head->ds_cnt) + ds_idx;
+ unival *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
+
+ /* update seasonal coefficient in cdp prep areas */
+ seasonal = rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val;
+ rrd->cdp_prep[cdp_idx].scratch[CDP_hw_last_seasonal].u_val = seasonal;
+ rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val =
+ seasonal_coef[ds_idx];
+
+ if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+ /* no update, store the old value unchanged,
+ * doesn't matter if it is NA */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = seasonal;
+ return 0;
+ }
+
+ /* update seasonal value for disk */
+ if (current_rra->par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
+ /* associated HWPREDICT has already been updated */
+ /* check for possible NA values */
+ if (isnan(coefs[CDP_hw_last_intercept].u_val)
+ || isnan(coefs[CDP_hw_last_slope].u_val)) {
+ /* this should never happen, as HWPREDICT was already updated */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+ } else if (isnan(seasonal)) {
+ /* initialization: intercept is not currently being updated */
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of seasonal coef %lu\n",
+ rrd->rra_ptr[rra_idx].cur_row);
+#endif
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->init_seasonality(rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ coefs[CDP_hw_last_intercept].
+ u_val);
+ } else {
+ intercept = coefs[CDP_hw_intercept].u_val;
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->seasonality(current_rra->par[RRA_seasonal_gamma].
+ u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ intercept, seasonal);
+#ifdef DEBUG
+ fprintf(stderr,
+ "Updating seasonal = %f (params: gamma %f, new intercept %f, old seasonal %f)\n",
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+ current_rra->par[RRA_seasonal_gamma].u_val,
+ intercept, seasonal);
+#endif
+ }
+ } else {
+ /* SEASONAL array is updated first, which means the new intercept
+ * hasn't be computed; so we compute it here. */
+
+ /* check for possible NA values */
+ if (isnan(coefs[CDP_hw_intercept].u_val)
+ || isnan(coefs[CDP_hw_slope].u_val)) {
+ /* Initialization of slope and intercept will occur.
+ * force seasonal coefficient to 0 or 1. */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->identity;
+ } else if (isnan(seasonal)) {
+ /* initialization: intercept will not be updated
+ * CDP_hw_intercept = CDP_hw_last_intercept; just need to
+ * subtract/divide by this baseline value. */
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of seasonal coef %lu\n",
+ rrd->rra_ptr[rra_idx].cur_row);
+#endif
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->init_seasonality(rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ coefs[CDP_hw_intercept].u_val);
+ } else {
+ /* Note that we must get CDP_scratch_idx from SEASONAL array, as CDP_scratch_idx
+ * for HWPREDICT array will be DNAN. */
+ intercept = functions->intercept(hw_rra->par[RRA_hw_alpha].u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ seasonal, coefs);
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->seasonality(current_rra->par[RRA_seasonal_gamma].
+ u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ intercept, seasonal);
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr, "seasonal coefficient set= %f\n",
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+#endif
+ return 0;
+}
+
+int update_devpredict(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx)
+{
+ /* there really isn't any "update" here; the only reason this information
+ * is stored separately from DEVSEASONAL is to preserve deviation predictions
+ * for a longer duration than one seasonal cycle. */
+ unsigned long seasonal_cdp_idx =
+ (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt)
+ * (rrd->stat_head->ds_cnt) + ds_idx;
+
+ if (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
+ /* associated DEVSEASONAL array already updated */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
+ =
+ rrd->cdp_prep[seasonal_cdp_idx].
+ scratch[CDP_last_seasonal_deviation].u_val;
+ } else {
+ /* associated DEVSEASONAL not yet updated */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
+ =
+ rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_seasonal_deviation].
+ u_val;
+ }
+ return 0;
+}
+
+int update_devseasonal(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ rrd_value_t *seasonal_dev,
+ hw_functions_t * functions)
+{
+ rrd_value_t prediction = 0, seasonal_coef = DNAN;
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+
+ /* obtain cdp_prep index for HWPREDICT */
+ unsigned long hw_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+ unsigned long hw_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ unsigned long seasonal_cdp_idx;
+ unival *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
+
+ rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val =
+ rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+ /* retrieve the next seasonal deviation value, could be NA */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val =
+ seasonal_dev[ds_idx];
+
+ /* retrieve the current seasonal_coef (not to be confused with the
+ * current seasonal deviation). Could make this more readable by introducing
+ * some wrapper functions. */
+ seasonal_cdp_idx =
+ (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt)
+ * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx)
+ /* SEASONAL array already updated */
+ seasonal_coef =
+ rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].
+ u_val;
+ else
+ /* SEASONAL array not yet updated */
+ seasonal_coef =
+ rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+
+ /* compute the abs value of the difference between the prediction and
+ * observed value */
+ if (hw_rra_idx < rra_idx) {
+ /* associated HWPREDICT has already been updated */
+ if (isnan(coefs[CDP_hw_last_intercept].u_val) ||
+ isnan(coefs[CDP_hw_last_slope].u_val) || isnan(seasonal_coef)) {
+ /* one of the prediction values is uinitialized */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+ return 0;
+ } else {
+ prediction =
+ functions->predict(coefs[CDP_hw_last_intercept].u_val,
+ coefs[CDP_hw_last_slope].u_val,
+ coefs[CDP_last_null_count].u_cnt,
+ seasonal_coef);
+ }
+ } else {
+ /* associated HWPREDICT has NOT been updated */
+ if (isnan(coefs[CDP_hw_intercept].u_val) ||
+ isnan(coefs[CDP_hw_slope].u_val) || isnan(seasonal_coef)) {
+ /* one of the prediction values is uinitialized */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+ return 0;
+ } else {
+ prediction = functions->predict(coefs[CDP_hw_intercept].u_val,
+ coefs[CDP_hw_slope].u_val,
+ coefs[CDP_null_count].u_cnt,
+ seasonal_coef);
+ }
+ }
+
+ if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+ /* no update, store existing value unchanged, doesn't
+ * matter if it is NA */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val;
+ } else
+ if (isnan
+ (rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].
+ u_val)) {
+ /* initialization */
+#ifdef DEBUG
+ fprintf(stderr, "Initialization of seasonal deviation\n");
+#endif
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->init_seasonal_deviation(prediction,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].
+ u_val);
+ } else {
+ /* exponential smoothing update */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+ functions->seasonal_deviation(rrd->rra_def[rra_idx].
+ par[RRA_seasonal_gamma].u_val,
+ prediction,
+ rrd->cdp_prep[cdp_idx].
+ scratch[CDP_scratch_idx].u_val,
+ rrd->cdp_prep[cdp_idx].
+ scratch
+ [CDP_last_seasonal_deviation].
+ u_val);
+ }
+ return 0;
+}
+
+/* Check for a failure based on a threshold # of violations within the specified
+ * window. */
+int update_failures(
+ rrd_t *rrd,
+ unsigned long cdp_idx,
+ unsigned long rra_idx,
+ unsigned long ds_idx,
+ unsigned short CDP_scratch_idx,
+ hw_functions_t * functions)
+{
+ /* detection of a violation depends on 3 RRAs:
+ * HWPREDICT, SEASONAL, and DEVSEASONAL */
+ rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+ unsigned long dev_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+ rra_def_t *dev_rra = &(rrd->rra_def[dev_rra_idx]);
+ unsigned long hw_rra_idx = dev_rra->par[RRA_dependent_rra_idx].u_cnt;
+ rra_def_t *hw_rra = &(rrd->rra_def[hw_rra_idx]);
+ unsigned long seasonal_rra_idx = hw_rra->par[RRA_dependent_rra_idx].u_cnt;
+ unsigned long temp_cdp_idx;
+ rrd_value_t deviation = DNAN;
+ rrd_value_t seasonal_coef = DNAN;
+ rrd_value_t prediction = DNAN;
+ char violation = 0;
+ unsigned short violation_cnt = 0, i;
+ char *violations_array;
+
+ /* usual checks to determine the order of the RRAs */
+ temp_cdp_idx = dev_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rra_idx < seasonal_rra_idx) {
+ /* DEVSEASONAL not yet updated */
+ deviation =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+ } else {
+ /* DEVSEASONAL already updated */
+ deviation =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_last_seasonal_deviation].
+ u_val;
+ }
+ if (!isnan(deviation)) {
+
+ temp_cdp_idx = seasonal_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rra_idx < seasonal_rra_idx) {
+ /* SEASONAL not yet updated */
+ seasonal_coef =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+ } else {
+ /* SEASONAL already updated */
+ seasonal_coef =
+ rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_seasonal].
+ u_val;
+ }
+ /* in this code block, we know seasonal coef is not DNAN, because deviation is not
+ * null */
+
+ temp_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+ if (rra_idx < hw_rra_idx) {
+ /* HWPREDICT not yet updated */
+ prediction =
+ functions->predict(rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_intercept].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_slope].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_null_count].u_cnt,
+ seasonal_coef);
+ } else {
+ /* HWPREDICT already updated */
+ prediction =
+ functions->predict(rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_last_intercept].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_hw_last_slope].u_val,
+ rrd->cdp_prep[temp_cdp_idx].
+ scratch[CDP_last_null_count].u_cnt,
+ seasonal_coef);
+ }
+
+ /* determine if the observed value is a violation */
+ if (!isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+ if (hw_is_violation
+ (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+ prediction, deviation, current_rra->par[RRA_delta_pos].u_val,
+ current_rra->par[RRA_delta_neg].u_val)) {
+ violation = 1;
+ }
+ } else {
+ violation = 1; /* count DNAN values as violations */
+ }
+
+ }
+
+ /* determine if a failure has occurred and update the failure array */
+ violation_cnt = violation;
+ violations_array = (char *) ((void *) rrd->cdp_prep[cdp_idx].scratch);
+ for (i = current_rra->par[RRA_window_len].u_cnt; i > 1; i--) {
+ /* shift */
+ violations_array[i - 1] = violations_array[i - 2];
+ violation_cnt += violations_array[i - 1];
+ }
+ violations_array[0] = violation;
+
+ if (violation_cnt < current_rra->par[RRA_failure_threshold].u_cnt)
+ /* not a failure */
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 0.0;
+ else
+ rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 1.0;
+
+ return (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+}