import logging
from enum import Enum
import queue
import numpy as np
import math
from .sliding_window import SlidingWindow
class ThresholdState(Enum):
INIT = 0
START = 1
class Threshold:
def __init__(self, data_queue_size: int = 10000, data_queue_update_size: int = 1000):
self._observer = None
self.data_queue = queue.Queue(data_queue_size)
self.data_queue_update_size = data_queue_update_size
self.new_data_size = 0
self.threshold_state = ThresholdState.INIT
self.threshold = math.inf
self.metric_name = None
def set_threshold(self, threshold):
self.threshold = threshold
self.threshold_state = ThresholdState.START
self.notify_observer()
def set_metric_name(self, metric_name):
self.metric_name = metric_name
def get_threshold(self):
if self.threshold_state == ThresholdState.INIT:
return None
return self.threshold
def is_abnormal(self, data):
if self.threshold_state == ThresholdState.INIT:
return False
return data >= self.threshold
def attach_observer(self, observer: SlidingWindow):
self._observer = observer
def notify_observer(self):
if self._observer is not None:
self._observer.update(self.threshold)
def push_latest_data_to_queue(self, data):
pass
def __repr__(self):
return "Threshold"
def __str__(self):
return "Threshold"
class AbsoluteThreshold(Threshold):
def __init__(self, data_queue_size: int = 10000, data_queue_update_size: int = 1000, **kwargs):
super().__init__(data_queue_size, data_queue_update_size)
def push_latest_data_to_queue(self, data):
pass
def __repr__(self):
return "[AbsoluteThreshold]"
def __str__(self):
return "absolute"
class BoxplotThreshold(Threshold):
def __init__(self, boxplot_parameter: float = 1.5, data_queue_size: int = 10000, data_queue_update_size: int = 1000, **kwargs):
super().__init__(data_queue_size, data_queue_update_size)
self.parameter = boxplot_parameter
def _update_threshold(self):
old_threshold = self.threshold
data = list(self.data_queue.queue)
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1
self.threshold = q3 + self.parameter * iqr
if self.threshold_state == ThresholdState.INIT:
self.threshold_state = ThresholdState.START
logging.info(f"MetricName: [{self.metric_name}]'s threshold update, old is: {old_threshold} -> new is: {self.threshold}")
self.notify_observer()
def push_latest_data_to_queue(self, data):
if data < 1e-6:
return
try:
self.data_queue.put(data, block=False)
except queue.Full:
self.data_queue.get()
self.data_queue.put(data)
self.new_data_size += 1
if (self.data_queue.full() and (self.threshold_state == ThresholdState.INIT or
(self.threshold_state == ThresholdState.START and
self.new_data_size >= self.data_queue_update_size))):
self._update_threshold()
self.new_data_size = 0
def __repr__(self):
return f"[BoxplotThreshold, param is: {self.parameter}, train_size: {self.data_queue.maxsize}, update_size: {self.data_queue_update_size}]"
def __str__(self):
return "boxplot"
class NSigmaThreshold(Threshold):
def __init__(self, n_sigma_parameter: float = 3.0, data_queue_size: int = 10000, data_queue_update_size: int = 1000, **kwargs):
super().__init__(data_queue_size, data_queue_update_size)
self.parameter = n_sigma_parameter
def _update_threshold(self):
old_threshold = self.threshold
data = list(self.data_queue.queue)
mean = np.mean(data)
std = np.std(data)
self.threshold = mean + self.parameter * std
if self.threshold_state == ThresholdState.INIT:
self.threshold_state = ThresholdState.START
logging.info(f"MetricName: [{self.metric_name}]'s threshold update, old is: {old_threshold} -> new is: {self.threshold}")
self.notify_observer()
def push_latest_data_to_queue(self, data):
if data < 1e-6:
return
try:
self.data_queue.put(data, block=False)
except queue.Full:
self.data_queue.get()
self.data_queue.put(data)
self.new_data_size += 1
if (self.data_queue.full() and (self.threshold_state == ThresholdState.INIT or
(self.threshold_state == ThresholdState.START and
self.new_data_size >= self.data_queue_update_size))):
self._update_threshold()
self.new_data_size = 0
def __repr__(self):
return f"[NSigmaThreshold, param is: {self.parameter}, train_size: {self.data_queue.maxsize}, update_size: {self.data_queue_update_size}]"
def __str__(self):
return "n_sigma"
class ThresholdType(Enum):
AbsoluteThreshold = 0
BoxplotThreshold = 1
NSigmaThreshold = 2
class ThresholdFactory:
def get_threshold(self, threshold_type: ThresholdType, *args, **kwargs):
if threshold_type == ThresholdType.AbsoluteThreshold:
return AbsoluteThreshold(*args, **kwargs)
elif threshold_type == ThresholdType.BoxplotThreshold:
return BoxplotThreshold(*args, **kwargs)
elif threshold_type == ThresholdType.NSigmaThreshold:
return NSigmaThreshold(*args, **kwargs)
else:
raise ValueError(f"Invalid threshold type: {threshold_type}")