# Copyright (C) 2005-2025 Splunk Inc. All Rights Reserved. import copy import datetime from ITOA.itoa_common import is_valid_dict, is_string_numeric, is_valid_str from ITOA.setup_logging import logger import custom_threshold_windows.constants as CustomThresholdWindowConstants def update_custom_aggregate_thresholds(threshold_object, percentage_modifier): """ Modifies an aggregate_threshold policy by the percentage adjustment specified (total change to be passed in) @param threshold_object: the aggregate_threshold policy @param percentage_modifier: float that should already be calculated """ if 'thresholdLevels' in threshold_object: for level in threshold_object.get('thresholdLevels'): level['thresholdValue'] = round(level['thresholdValue'] * percentage_modifier, 3) return True return False def calculate_custom_thresholds(kpi, active_window): """ If the given KPI needs to recalculate the custom thresholds for the active window, perform calculations and update existing entries on the KPI. @type kpi: dict @param kpi: the kpi object itself @type active_window: dict @param active_window: the custom threshold window with threshold percentage differences @rtype: dict @return: the kpi dict (modified or not) """ if not kpi.get('active_custom_threshold_window', ''): logger.warn( 'KPI {} could not recalculate custom thresholds because no custom threshold window was marked active.' .format(kpi.get('_key', '')) ) return kpi kpi['recalculate_custom_thresholds'] = False kpi_window = kpi.get('active_custom_threshold_window', '') if not is_valid_str(kpi_window): logger.error('Somehow custom threshold windows stored on KPI {} is malformed. Fixing KPI and skipping.' .format(kpi.get('_key'))) kpi['active_custom_threshold_window'] = '' return kpi if not active_window: logger.error('No custom threshold window passed in when trying to calculate new thresholds.') return kpi if active_window.get('window_type', '') == CustomThresholdWindowConstants.TYPE_PERCENTAGE: percentage_config = active_window.get('window_config_percentage', 0) if percentage_config == 0: # If value is for some reason not set, do not modify logger.warn( 'Custom threshold window {} does not have a percentage modification set.' .format(active_window.get('_key'))) return kpi elif abs(percentage_config) > 200: logger.warn('Custom threshold window {} has a percentage set as greater than maximum' ' absolute value of 200. Calculating custom thresholds based on 200.' .format(active_window.get('_key'))) percentage_config = 200 if percentage_config > 0 else -200 percentage_modifier = 1.00 + (percentage_config / 100) if kpi.get('time_variate_thresholds', False): kpi['time_variate_thresholds_specification_custom'] = copy.deepcopy( kpi.get('time_variate_thresholds_specification')) for policy in kpi['time_variate_thresholds_specification_custom']['policies']: successful_update = update_custom_aggregate_thresholds( kpi['time_variate_thresholds_specification_custom']['policies'][policy]['aggregate_thresholds'], percentage_modifier) if not successful_update: logger.error('KPI {} had no thresholdLevels to update for an aggregate threshold ' 'configuration. No modifications were applied to the base threshold levels.' .format(kpi.get('_key'))) else: kpi['aggregate_thresholds_custom'] = copy.deepcopy(kpi.get('aggregate_thresholds')) successful_update = update_custom_aggregate_thresholds(kpi['aggregate_thresholds_custom'], percentage_modifier) if not successful_update: logger.error('KPI {} had no thresholdLevels to update for its aggregate thresholds. No ' 'modification were applied to the base threshold levels.' .format(kpi.get('_key'))) else: logger.error('Customer set static custom threshold window thresholds, which is not available in the' 'beta. Please fix and use the percentage adjustment for thresholds.') return kpi def get_human_readable_time_str(timestamp): """ Get a human-readable string representing the provided timestamp in the machine's timezone. We use the machine timezone as we don't have any user-specific information. :param timestamp: Unix-timestamp :type timestamp: int :return: str """ return datetime.datetime.utcfromtimestamp(timestamp).astimezone().strftime('%c (%Z)')