예제 #1
0
    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)
예제 #2
0
    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),
        )
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
    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
예제 #7
0
    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)
예제 #8
0
 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),
     )
예제 #9
0
    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)
예제 #10
0
    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
예제 #11
0
    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,
        )
예제 #12
0
    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
예제 #13
0
    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
예제 #14
0
    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
예제 #15
0
    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
예제 #16
0
 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)
예제 #17
0
 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))
예제 #18
0
 def test_pass_through_positive_int(self):
     epoch_millis = 5
     _check_positive_millis(time_utils.epoch_millis(epoch_millis))
예제 #19
0
    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
예제 #20
0
 def test_none(self):
     assert time_utils.epoch_millis(None) is None
예제 #21
0
    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),
        )