def log_sample( self, data, labels, time_window_start, time_window_end, created_at=None ): if not isinstance(data, data_types._VertaDataType): raise TypeError( "expected a supported VertaDataType, found {}".format(type(data)) ) if data._type_string() != self.type: raise TypeError( "expected a {}, found {}".format(self.type, data._type_string()) ) if not created_at: created_at = time_utils.now() content = json.dumps(data._as_dict()) created_at_millis = time_utils.epoch_millis(created_at) window_start_millis = time_utils.epoch_millis(time_window_start) window_end_millis = time_utils.epoch_millis(time_window_end) msg = CreateSummarySample( summary_id=self.id, summary_type_name=data._type_string(), content=content, labels=labels, created_at_millis=created_at_millis, time_window_start_at_millis=window_start_millis, time_window_end_at_millis=window_end_millis, ) endpoint = "/api/v1/summaries/createSample" response = self._conn.make_proto_request("POST", endpoint, body=msg) result_msg = self._conn.must_proto_response(response, SummarySampleProto) return SummarySample(self._conn, self._conf, result_msg)
def __init__( self, summary_query=None, ids=None, labels=None, time_window_start=None, time_window_end=None, aggregation=None, created_after=None, page_number=1, page_limit=None, ): if summary_query is None: summary_query = SummaryQuery() self._msg = FindSummarySampleRequest( filter=FilterQuerySummarySample( sample_ids=arg_handler.extract_ids(ids) if ids else None, labels=arg_handler.maybe(_labels_proto, labels), time_window_start_at_millis=time_utils.epoch_millis( time_window_start), time_window_end_at_millis=time_utils.epoch_millis( time_window_end), created_at_after_millis=time_utils.epoch_millis(created_after), ), page_number=page_number, page_limit=pagination_utils.page_limit_to_proto(page_limit), ) self.summary_query = summary_query self.aggregation = aggregation
def create( self, name, channel, workspace=None, created_at=None, updated_at=None, ): """ Create a new notification channel. Parameters ---------- name : str A unique name for this notification channel. channel : :mod:`~verta.monitoring.notification_channel` The configuration for this notification channel. workspace : str, optional Workspace in which to create this notification channel. Defaults to the client's default workspace. created_at : datetime.datetime or int, optional An override creation time to assign to this channel. Either a timezone aware datetime object or unix epoch milliseconds. updated_at : datetime.datetime or int, optional An override update time to assign to this channel. Either a timezone aware datetime object or unix epoch milliseconds. Returns ------- :class:`NotificationChannel` Notification channel. Examples -------- .. code-block:: python from verta.monitoring.notification_channel import SlackNotificationChannel channels = Client().monitoring.notification_channels channel = notification_channels.create( "Slack alerts", SlackNotificationChannel("https://hooks.slack.com/services/.../.../......"), ) """ if workspace is None: workspace = self._client.get_workspace() ctx = _Context(self._conn, self._conf) return NotificationChannel._create( self._conn, self._conf, ctx, name=name, channel=channel, workspace=workspace, created_at_millis=time_utils.epoch_millis(created_at), updated_at_millis=time_utils.epoch_millis(updated_at), )
def test_creation_override_datetimes(self, summary, strs): strs = iter(strs) alerter = FixedAlerter(comparison.GreaterThan(0.7)) created_at = time_utils.now() - datetime.timedelta(weeks=1) updated_at = time_utils.now() - datetime.timedelta(days=1) last_evaluated_at = time_utils.now() - datetime.timedelta(hours=1) created_at_millis = time_utils.epoch_millis(created_at) updated_at_millis = time_utils.epoch_millis(updated_at) last_evaluated_at_millis = time_utils.epoch_millis(last_evaluated_at) # as datetime alert = summary.alerts.create( next(strs), alerter, _created_at=created_at, _updated_at=updated_at, _last_evaluated_at=last_evaluated_at, ) assert alert._msg.created_at_millis == created_at_millis assert alert._msg.updated_at_millis == updated_at_millis assert alert._msg.last_evaluated_at_millis == last_evaluated_at_millis # as millis alert = summary.alerts.create( next(strs), alerter, _created_at=created_at_millis, _updated_at=updated_at_millis, _last_evaluated_at=last_evaluated_at_millis, ) assert alert._msg.created_at_millis == created_at_millis assert alert._msg.updated_at_millis == updated_at_millis assert alert._msg.last_evaluated_at_millis == last_evaluated_at_millis
def test_creation_datetime(self): time_window_start = time_utils.now() - datetime.timedelta(weeks=1) time_window_end = time_utils.now() - datetime.timedelta(days=1) created_after = time_utils.now() - datetime.timedelta(hours=1) time_window_start_millis = time_utils.epoch_millis(time_window_start) time_window_end_millis = time_utils.epoch_millis(time_window_end) created_after_millis = time_utils.epoch_millis(created_after) # as datetime sample_query = SummarySampleQuery( time_window_start=time_window_start, time_window_end=time_window_end, created_after=created_after, ) proto_request = sample_query._to_proto_request() assert (proto_request.filter.time_window_start_at_millis == time_window_start_millis) assert proto_request.filter.time_window_end_at_millis == time_window_end_millis assert proto_request.filter.created_at_after_millis == created_after_millis # as millis sample_query = SummarySampleQuery( time_window_start=time_window_start_millis, time_window_end=time_window_end_millis, created_after=created_after_millis, ) proto_request = sample_query._to_proto_request() assert (proto_request.filter.time_window_start_at_millis == time_window_start_millis) assert proto_request.filter.time_window_end_at_millis == time_window_end_millis assert proto_request.filter.created_at_after_millis == created_after_millis
def test_creation_datetime(self, client, strs, created_entities): strs = iter(strs) notification_channels = client.operations.notification_channels created_at = time_utils.now() - datetime.timedelta(weeks=1) updated_at = time_utils.now() - datetime.timedelta(days=1) created_at_millis = time_utils.epoch_millis(created_at) updated_at_millis = time_utils.epoch_millis(updated_at) # as datetime channel = notification_channels.create( next(strs), SlackNotificationChannel(next(strs)), created_at=created_at, updated_at=updated_at, ) created_entities.append(channel) assert channel._msg.created_at_millis == created_at_millis assert channel._msg.updated_at_millis == updated_at_millis # as millis channel = notification_channels.create( next(strs), SlackNotificationChannel(next(strs)), created_at=created_at_millis, updated_at=updated_at_millis, ) created_entities.append(channel) assert channel._msg.created_at_millis == created_at_millis assert channel._msg.updated_at_millis == updated_at_millis
def log_sample(self, data, labels, time_window_start, time_window_end, created_at=None): """Log a summary sample for this summary. Parameters ---------- data A :mod:`VertaDataType <verta.data_types>` consistent with the type of this summary. labels : dict of str to str, optional A mapping between label keys and values. time_window_start : datetime.datetime or int Either a timezone aware datetime object or unix epoch milliseconds. time_window_end : datetime.datetime or int Either a timezone aware datetime object or unix epoch milliseconds. created_after : datetime.datetime or int, optional Either a timezone aware datetime object or unix epoch milliseconds. Defaults to now, but offered as a parameter to permit backfilling of summary samples. Returns ------- :class:`~verta.monitoring.summaries.summary_sample.SummarySample` A persisted summary sample. """ if not isinstance(data, data_types._VertaDataType): raise TypeError( "expected a supported VertaDataType, found {}".format( type(data))) if data._type_string() != self.type: raise TypeError("expected a {}, found {}".format( self.type, data._type_string())) if not created_at: created_at = time_utils.now() content = json.dumps(data._as_dict()) created_at_millis = time_utils.epoch_millis(created_at) window_start_millis = time_utils.epoch_millis(time_window_start) window_end_millis = time_utils.epoch_millis(time_window_end) msg = CreateSummarySample( summary_id=self.id, summary_type_name=data._type_string(), content=content, labels=labels, created_at_millis=created_at_millis, time_window_start_at_millis=window_start_millis, time_window_end_at_millis=window_end_millis, ) endpoint = "/api/v1/summaries/createSample" response = self._conn.make_proto_request("POST", endpoint, body=msg) result_msg = self._conn.must_proto_response(response, SummarySampleProto) return SummarySample(self._conn, self._conf, result_msg)
def _to_proto_request(self): return FindSummarySampleRequest( filter=FilterQuerySummarySample( find_summaries=self._find_summaries, sample_ids=self._sample_ids, labels=self._labels, time_window_start_at_millis=time_utils.epoch_millis( self._time_window_start), time_window_end_at_millis=time_utils.epoch_millis( self._time_window_end), created_at_after_millis=time_utils.epoch_millis( self._created_after), ), page_number=self._page_number, page_limit=pagination_utils.page_limit_to_proto(self._page_limit), )
def _update_last_evaluated_at(self, last_evaluated_at=None): if last_evaluated_at is None: last_evaluated_at = time_utils.now() millis = time_utils.epoch_millis(last_evaluated_at) alert_msg = _AlertService.Alert(last_evaluated_at_millis=millis, ) self._update(alert_msg)
def test_creation_datetime(self, monitored_entity, strs, created_entities): strs = iter(strs) alerts = monitored_entity.alerts alerter = FixedAlerter(comparison.GreaterThan(0.7)) sample_query = SummarySampleQuery() created_at = time_utils.now() - datetime.timedelta(weeks=1) updated_at = time_utils.now() - datetime.timedelta(days=1) last_evaluated_at = time_utils.now() - datetime.timedelta(hours=1) created_at_millis = time_utils.epoch_millis(created_at) updated_at_millis = time_utils.epoch_millis(updated_at) last_evaluated_at_millis = time_utils.epoch_millis(last_evaluated_at) # as datetime alert = alerts.create( next(strs), alerter, sample_query, created_at=created_at, updated_at=updated_at, last_evaluated_at=last_evaluated_at, ) created_entities.append(alert) assert alert._msg.created_at_millis == created_at_millis assert alert._msg.updated_at_millis == updated_at_millis assert alert._msg.last_evaluated_at_millis == last_evaluated_at_millis # as millis alert = alerts.create( next(strs), alerter, sample_query, created_at=created_at_millis, updated_at=updated_at_millis, last_evaluated_at=last_evaluated_at_millis, ) created_entities.append(alert) assert alert._msg.created_at_millis == created_at_millis assert alert._msg.updated_at_millis == updated_at_millis assert alert._msg.last_evaluated_at_millis == last_evaluated_at_millis
def _build_summary_filter(self, **kwargs): summary_query = kwargs.get("summary_query", None) summaries_proto = maybe(lambda q: q._to_proto_request(), summary_query) sample_ids = kwargs.get("sample_id", None) window_start = kwargs.get("time_window_start", None) window_start_at_millis = maybe(lambda t: time_utils.epoch_millis(t), window_start) window_end = kwargs.get("time_window_end", None) window_end_at_millis = maybe(lambda t: time_utils.epoch_millis(t), window_end) labels = kwargs.get("labels", None) labels_proto = maybe(Summary._labels_proto, labels) return FilterQuerySummarySample( find_summaries=summaries_proto, sample_ids=sample_ids, labels=labels_proto, time_window_start_at_millis=window_start_at_millis, time_window_end_at_millis=window_end_at_millis, )
def test_update_last_evaluated_at(self, summary): name = _utils.generate_default_name() alerter = FixedAlerter(comparison.GreaterThan(0.7)) alert = summary.alerts.create(name, alerter) alert._fetch_with_no_cache() initial = alert._msg.last_evaluated_at_millis alert._update_last_evaluated_at() alert._fetch_with_no_cache() assert alert._msg.last_evaluated_at_millis > initial yesterday = time_utils.now() - datetime.timedelta(days=1) yesterday_millis = time_utils.epoch_millis(yesterday) alert._update_last_evaluated_at(yesterday) alert._fetch_with_no_cache() assert alert._msg.last_evaluated_at_millis == yesterday_millis
def test_update_last_evaluated_at(self, summary): name = _utils.generate_default_name() alerter = FixedAlerter(comparison.GreaterThan(0.7)) alert = summary.alerts.create(name, alerter) alert._fetch_with_no_cache() initial = alert._msg.last_evaluated_at_millis alert._update_last_evaluated_at() alert._fetch_with_no_cache() assert alert._msg.last_evaluated_at_millis > initial yesterday = time_utils.now() - datetime.timedelta(days=1) yesterday_millis = time_utils.epoch_millis(yesterday) # TODO: remove following line when backend stops round to nearest sec yesterday_millis = round(yesterday_millis, -3) alert._update_last_evaluated_at(yesterday) alert._fetch_with_no_cache() assert alert._msg.last_evaluated_at_millis == yesterday_millis
def test_update_last_evaluated_at(self, monitored_entity, created_entities): alerts = monitored_entity.alerts name = _utils.generate_default_name() alerter = FixedAlerter(comparison.GreaterThan(0.7)) sample_query = SummarySampleQuery() alert = alerts.create(name, alerter, sample_query) created_entities.append(alert) alert._fetch_with_no_cache() initial = alert._msg.last_evaluated_at_millis alert._update_last_evaluated_at() alert._fetch_with_no_cache() assert alert._msg.last_evaluated_at_millis > initial yesterday = time_utils.now() - datetime.timedelta(days=1) yesterday_millis = time_utils.epoch_millis(yesterday) # TODO: remove following line when backend stops round to nearest sec yesterday_millis = round(yesterday_millis, -3) alert._update_last_evaluated_at(yesterday) alert._fetch_with_no_cache() assert alert._msg.last_evaluated_at_millis == yesterday_millis
def set_status(self, status, event_time=None): """ Set the status of this alert. .. note:: There should usually be no need to manually set an alert to alerting, as the Verta platform monitors alerts and their summary samples. Parameters ---------- status : :mod:`~verta.monitoring.alert.status` Alert status. event_time : datetime.datetime or int, optional An override event time to assign to this alert status update. Either a timezone aware datetime object or unix epoch milliseconds. Examples -------- .. code-block:: python from verta.monitoring.alert.status import Ok alert.set_status(Ok()) """ msg = status._to_proto_request() msg.alert_id = self.id if event_time: msg.event_time_millis = time_utils.epoch_millis(event_time) endpoint = "/api/v1/alerts/updateAlertStatus" response = self._conn.make_proto_request("POST", endpoint, body=msg) self._conn.must_response(response) self._clear_cache() return True
def test_naive_dt(self): naive_dt = datetime.now() assert naive_dt.tzinfo is None with pytest.warns(UserWarning): millis = time_utils.epoch_millis(naive_dt) _check_positive_millis(millis)
def test_aware_dt(self): aware_dt = datetime.now(time_utils.utc) assert aware_dt.tzinfo is not None _check_positive_millis(time_utils.epoch_millis(aware_dt))
def test_pass_through_positive_int(self): epoch_millis = 5 _check_positive_millis(time_utils.epoch_millis(epoch_millis))
def get_or_create( self, name=None, channel=None, workspace=None, created_at=None, updated_at=None, id=None, ): """Get or create a notification channel by name. Either `name` or `id` can be provided but not both. If `id` is provided, this will act only as a get method and no object will be created. Parameters ---------- name : str, optional A unique name for this notification channel. channel : :mod:`~verta.monitoring.notification_channel`, optional The configuration for this notification channel. workspace : str, optional Workspace in which to create this notification channel. Defaults to the client's default workspace. created_at : datetime.datetime or int, optional An override creation time to assign to this channel. Either a timezone aware datetime object or unix epoch milliseconds. updated_at : datetime.datetime or int, optional An override update time to assign to this channel. Either a timezone aware datetime object or unix epoch milliseconds. id : int, optional Notification channel ID. This should not be provided if `name` is provided. Returns ------- :class:`NotificationChannel` Notification channel. Examples -------- .. code-block:: python from verta.monitoring.notification_channel import SlackNotificationChannel channels = Client().monitoring.notification_channels channel = notification_channels.get_or_create( "Slack alerts", SlackNotificationChannel("https://hooks.slack.com/services/.../.../......"), ) # get it back later with the same method channel = notification_channels.get_or_create( "Slack alerts", ) """ if name and id: raise ValueError("cannot specify both `name` and `id`") if workspace and id: raise ValueError( "cannot specify both `workspace` and `id`;" " getting by ID does not require a workspace name" ) name = self._client._set_from_config_if_none(name, "notification_channel") if workspace is None: workspace = self._client.get_workspace() resource_name = "Notification Channel" param_names = "`channel`, `created_at`, or `updated_at`" params = (channel, created_at, updated_at) if id is not None: channel = NotificationChannel._get_by_id(self._conn, self._conf, id) _utils.check_unnecessary_params_warning( resource_name, "id {}".format(id), param_names, params, ) else: channel = NotificationChannel._get_or_create_by_name( self._conn, name, lambda name: NotificationChannel._get_by_name( self._conn, self._conf, name, workspace, ), lambda name: NotificationChannel._create( self._conn, self._conf, _Context(self._conn, self._conf), name=name, channel=channel, workspace=workspace, created_at_millis=time_utils.epoch_millis(created_at), updated_at_millis=time_utils.epoch_millis(updated_at), ), lambda: _utils.check_unnecessary_params_warning( resource_name, "name {}".format(name), param_names, params, ), ) return channel
def test_none(self): assert time_utils.epoch_millis(None) is None
def create( self, name, alerter, notification_channels=None, labels=None, starting_from=None, _created_at=None, _updated_at=None, _last_evaluated_at=None, ): """ Create a new alert. Parameters ---------- name : str A unique name for this alert. alerter : :mod:`~verta.monitoring.alert` The configuration for this alert. notification_channels : list of :class:`~verta.monitoring.notification_channel.entities.NotificationChannel`, optional Channels for this alert to propagate notifications to. labels : dict of str to list of str, optional Alert on samples that have at least one of these labels. A mapping between label keys and lists of corresponding label values. starting_from : datetime.datetime or int, optional Alert on samples associated with periods after this time; useful for monitoring samples representing past data. Either a timezone aware datetime object or unix epoch milliseconds. Returns ------- :class:`Alert` Alert. Examples -------- .. code-block:: python alert = summary.alerts.create( name="MSE", alerter=alerter, notification_channels=[channel], ) """ if self._summary is None: raise RuntimeError( "this Alert cannot be used to create because it was not" " obtained via summary.alerts") summary_sample_query = SummarySampleQuery( summary_query=self._build_summary_query(), labels=labels, time_window_start=time_utils.epoch_millis(starting_from), ) if notification_channels is None: notification_channels = [] for channel in notification_channels: Alert._validate_notification_channel(channel) ctx = _Context(self._conn, self._conf) return Alert._create( self._conn, self._conf, ctx, name=name, monitored_entity_id=(self._monitored_entity_id or self._summary.monitored_entity_id), alerter=alerter, summary_sample_query=summary_sample_query, notification_channels=notification_channels, created_at_millis=time_utils.epoch_millis(_created_at), updated_at_millis=time_utils.epoch_millis(_updated_at), last_evaluated_at_millis=time_utils.epoch_millis( _last_evaluated_at), )