diff --git a/gcloud/monitoring/client.py b/gcloud/monitoring/client.py index 73a4da6df64d..018bda4f4248 100644 --- a/gcloud/monitoring/client.py +++ b/gcloud/monitoring/client.py @@ -28,14 +28,22 @@ https://cloud.google.com/monitoring/api/v3/ """ +import datetime + from gcloud.client import JSONClient from gcloud.monitoring.connection import Connection from gcloud.monitoring.group import Group +from gcloud.monitoring.metric import Metric from gcloud.monitoring.metric import MetricDescriptor from gcloud.monitoring.metric import MetricKind from gcloud.monitoring.metric import ValueType from gcloud.monitoring.query import Query +from gcloud.monitoring.resource import Resource from gcloud.monitoring.resource import ResourceDescriptor +from gcloud.monitoring.timeseries import Point +from gcloud.monitoring.timeseries import TimeSeries + +_UTCNOW = datetime.datetime.utcnow # To be replaced by tests. class Client(JSONClient): @@ -195,6 +203,126 @@ def metric_descriptor(self, type_, display_name=display_name, ) + @staticmethod + def metric(type_, labels): + """Factory for constructing metric objects. + + :class:`~gcloud.monitoring.metric.Metric` objects are typically + created to write custom metric values. The type should match the + metric type specified in the + :class:`~gcloud.monitoring.metric.MetricDescriptor` used to + create the custom metric:: + + >>> metric = client.metric('custom.googleapis.com/my_metric', + ... labels={ + ... 'status': 'successful', + ... }) + + :type type_: string + :param type_: The metric type name. + + :type labels: dict + :param labels: A mapping from label names to values for all labels + enumerated in the associated + :class:`~gcloud.monitoring.metric.MetricDescriptor`. + + :rtype: :class:`~gcloud.monitoring.metric.Metric` + :returns: The metric object. + """ + return Metric(type=type_, labels=labels) + + @staticmethod + def resource(type_, labels): + """Factory for constructing monitored resource objects. + + A monitored resource object ( + :class:`~gcloud.monitoring.resource.Resource`) is + typically used to create a + :class:`~gcloud.monitoring.timeseries.TimeSeries` object. + + For a list of possible monitored resource types and their associated + labels, see: + + https://cloud.google.com/monitoring/api/resources + + :type type_: string + :param type_: The monitored resource type name. + + :type labels: dict + :param labels: A mapping from label names to values for all labels + enumerated in the associated + :class:`~gcloud.monitoring.resource.ResourceDescriptor`, + except that ``project_id`` can and should be omitted + when writing time series data. + + :rtype: :class:`~gcloud.monitoring.resource.Resource` + :returns: A monitored resource object. + """ + return Resource(type_, labels) + + @staticmethod + def time_series(metric, resource, value, + end_time=None, start_time=None): + """Construct a time series object for a single data point. + + .. note:: + + While :class:`~gcloud.monitoring.timeseries.TimeSeries` objects + returned by the API typically have multiple data points, + :class:`~gcloud.monitoring.timeseries.TimeSeries` objects + sent to the API must have at most one point. + + For example:: + + >>> timeseries = client.time_series(metric, resource, 1.23, + ... end_time=end) + + For more information, see: + + https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries + + :type metric: :class:`~gcloud.monitoring.metric.Metric` + :param metric: A :class:`~gcloud.monitoring.metric.Metric` object. + + :type resource: :class:`~gcloud.monitoring.resource.Resource` + :param resource: A :class:`~gcloud.monitoring.resource.Resource` + object. + + :type value: bool, int, string, or float + :param value: + The value of the data point to create for the + :class:`~gcloud.monitoring.timeseries.TimeSeries`. + + .. note:: + + The Python type of the value will determine the + `class`:ValueType: sent to the API, which must match the value + type specified in the metric descriptor. For example, a Python + float will be sent to the API as a :data:`ValueType.DOUBLE`. + + :type end_time: :class:`~datetime.datetime` + :param end_time: + The end time for the point to be included in the time series. + Assumed to be UTC if no time zone information is present. + Defaults to the current time, as obtained by calling + :meth:`datetime.datetime.utcnow`. + + :type start_time: :class:`~datetime.datetime` + :param start_time: + The start time for the point to be included in the time series. + Assumed to be UTC if no time zone information is present + Defaults to None. If the start time is unspecified, + the API interprets the start time to be the same as the end time. + + :rtype: :class:`~gcloud.monitoring.timeseries.TimeSeries` + :returns: A time series object. + """ + if end_time is None: + end_time = _UTCNOW() + point = Point(value=value, start_time=start_time, end_time=end_time) + return TimeSeries(metric=metric, resource=resource, metric_kind=None, + value_type=None, points=[point]) + def fetch_metric_descriptor(self, metric_type): """Look up a metric descriptor by type. diff --git a/gcloud/monitoring/metric.py b/gcloud/monitoring/metric.py index ea8b93a442a2..da162479100d 100644 --- a/gcloud/monitoring/metric.py +++ b/gcloud/monitoring/metric.py @@ -319,6 +319,10 @@ def __repr__(self): class Metric(collections.namedtuple('Metric', 'type labels')): """A specific metric identified by specifying values for all labels. + The preferred way to construct a metric object is using the + :meth:`~gcloud.monitoring.client.Client.metric` factory method + of the :class:`~gcloud.monitoring.client.Client` class. + :type type: string :param type: The metric type name. diff --git a/gcloud/monitoring/resource.py b/gcloud/monitoring/resource.py index b4391f28e757..09504a99bc23 100644 --- a/gcloud/monitoring/resource.py +++ b/gcloud/monitoring/resource.py @@ -158,6 +158,10 @@ def __repr__(self): class Resource(collections.namedtuple('Resource', 'type labels')): """A monitored resource identified by specifying values for all labels. + The preferred way to construct a resource object is using the + :meth:`~gcloud.monitoring.client.Client.resource` factory method + of the :class:`~gcloud.monitoring.client.Client` class. + :type type: string :param type: The resource type name. diff --git a/gcloud/monitoring/test_client.py b/gcloud/monitoring/test_client.py index f46fd98c41c7..745a0e86d1d5 100644 --- a/gcloud/monitoring/test_client.py +++ b/gcloud/monitoring/test_client.py @@ -157,6 +157,120 @@ def test_metric_descriptor_factory(self): self.assertEqual(descriptor.description, DESCRIPTION) self.assertEqual(descriptor.display_name, '') + def test_metric_factory(self): + TYPE = 'custom.googleapis.com/my_metric' + LABELS = { + 'instance_name': 'my-instance' + } + + client = self._makeOne(project=PROJECT, credentials=_Credentials()) + client.connection = _Connection() # For safety's sake. + metric = client.metric(TYPE, LABELS) + self.assertEqual(metric.type, TYPE) + self.assertEqual(metric.labels, LABELS) + + def test_resource_factory(self): + TYPE = 'https://cloud.google.com/monitoring/api/resources' + LABELS = { + 'instance_id': 'my-instance-id', + 'zone': 'us-central1-f' + } + + client = self._makeOne(project=PROJECT, credentials=_Credentials()) + client.connection = _Connection() # For safety's sake. + resource = client.resource(TYPE, LABELS) + self.assertEqual(resource.type, TYPE) + self.assertEqual(resource.labels, LABELS) + + def test_timeseries_factory_gauge(self): + import datetime + from gcloud._testing import _Monkey + import gcloud.monitoring.client + METRIC_TYPE = 'custom.googleapis.com/my_metric' + METRIC_LABELS = { + 'status': 'successful' + } + + RESOURCE_TYPE = 'gce_instance' + RESOURCE_LABELS = { + 'instance_id': '1234567890123456789', + 'zone': 'us-central1-f' + } + + VALUE = 42 + TIME1 = datetime.datetime.utcnow() + + client = self._makeOne(project=PROJECT, credentials=_Credentials()) + client.connection = _Connection() # For safety's sake. + metric = client.metric(METRIC_TYPE, METRIC_LABELS) + resource = client.resource(RESOURCE_TYPE, RESOURCE_LABELS) + + # Construct a time series assuming a gauge metric. + timeseries = client.time_series(metric, resource, VALUE, + end_time=TIME1) + self.assertEqual(timeseries.metric, metric) + self.assertEqual(timeseries.resource, resource) + self.assertEqual(len(timeseries.points), 1) + self.assertEqual(timeseries.points[0].value, VALUE) + self.assertIsNone(timeseries.points[0].start_time) + self.assertEqual(timeseries.points[0].end_time, TIME1) + + TIME2 = datetime.datetime.utcnow() + # Construct a time series assuming a gauge metric using the current + # time + with _Monkey(gcloud.monitoring.client, _UTCNOW=lambda: TIME2): + timeseries_no_end = client.time_series(metric, resource, VALUE) + + self.assertEqual(timeseries_no_end.points[0].end_time, TIME2) + self.assertIsNone(timeseries_no_end.points[0].start_time) + + def test_timeseries_factory_cumulative(self): + import datetime + MY_CUMULATIVE_METRIC = 'custom.googleapis.com/my_cumulative_metric' + METRIC_LABELS = { + 'status': 'successful' + } + + RESOURCE_TYPE = 'gce_instance' + RESOURCE_LABELS = { + 'instance_id': '1234567890123456789', + 'zone': 'us-central1-f' + } + + client = self._makeOne(project=PROJECT, credentials=_Credentials()) + client.connection = _Connection() # For safety's sake. + resource = client.resource(RESOURCE_TYPE, RESOURCE_LABELS) + + VALUE = 42 + VALUE2 = 43 + RESET_TIME = datetime.datetime.utcnow() + TIME1 = datetime.datetime.utcnow() + TIME2 = datetime.datetime.utcnow() + + # Construct a couple of time series assuming a cumulative metric. + cumulative_metric = client.metric(MY_CUMULATIVE_METRIC, METRIC_LABELS) + cumulative_timeseries = client.time_series(cumulative_metric, + resource, + VALUE, + start_time=RESET_TIME, + end_time=TIME1) + + cumulative_timeseries2 = client.time_series(cumulative_metric, + resource, + VALUE2, + start_time=RESET_TIME, + end_time=TIME2) + + self.assertEqual(cumulative_timeseries.points[0].start_time, + RESET_TIME) + self.assertEqual(cumulative_timeseries.points[0].end_time, TIME1) + self.assertEqual(cumulative_timeseries.points[0].value, VALUE) + self.assertEqual(cumulative_timeseries2.points[0].start_time, + RESET_TIME) + self.assertEqual(cumulative_timeseries2.points[0].end_time, + TIME2) + self.assertEqual(cumulative_timeseries2.points[0].value, VALUE2) + def test_fetch_metric_descriptor(self): TYPE = 'custom.googleapis.com/my_metric' NAME = 'projects/{project}/metricDescriptors/{type}'.format( diff --git a/gcloud/monitoring/timeseries.py b/gcloud/monitoring/timeseries.py index 0b4f98189d8c..2020765e4bf8 100644 --- a/gcloud/monitoring/timeseries.py +++ b/gcloud/monitoring/timeseries.py @@ -32,6 +32,11 @@ class TimeSeries(collections.namedtuple( 'TimeSeries', 'metric resource metric_kind value_type points')): """A single time series of metric values. + The preferred way to construct a + :class:`~gcloud.monitoring.timeseries.TimeSeries` object is + using the :meth:`~gcloud.monitoring.client.Client.time_series` factory + method of the :class:`~gcloud.monitoring.client.Client` class. + :type metric: :class:`~gcloud.monitoring.metric.Metric` :param metric: A metric object.