Skip to content

Commit 3facc5c

Browse files
author
Bill Prin
committed
Add Factory Methods for Metric/Resource/Timeseries
1 parent f64507d commit 3facc5c

File tree

5 files changed

+255
-0
lines changed

5 files changed

+255
-0
lines changed

gcloud/monitoring/client.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,22 @@
2828
https://cloud.google.com/monitoring/api/v3/
2929
"""
3030

31+
import datetime
32+
3133
from gcloud.client import JSONClient
3234
from gcloud.monitoring.connection import Connection
3335
from gcloud.monitoring.group import Group
36+
from gcloud.monitoring.metric import Metric
3437
from gcloud.monitoring.metric import MetricDescriptor
3538
from gcloud.monitoring.metric import MetricKind
3639
from gcloud.monitoring.metric import ValueType
3740
from gcloud.monitoring.query import Query
41+
from gcloud.monitoring.resource import Resource
3842
from gcloud.monitoring.resource import ResourceDescriptor
43+
from gcloud.monitoring.timeseries import Point
44+
from gcloud.monitoring.timeseries import TimeSeries
45+
46+
_UTCNOW = datetime.datetime.utcnow # To be replaced by tests.
3947

4048

4149
class Client(JSONClient):
@@ -195,6 +203,126 @@ def metric_descriptor(self, type_,
195203
display_name=display_name,
196204
)
197205

206+
@staticmethod
207+
def metric(type_, labels):
208+
"""Factory for constructing metric objects.
209+
210+
:class:`~gcloud.monitoring.metric.Metric` objects are typically
211+
created to write custom metric values. The type should match the
212+
metric type specified in the
213+
:class:`~gcloud.monitoring.metric.MetricDescriptor` used to
214+
create the custom metric::
215+
216+
>>> metric = client.metric('custom.googleapis.com/my_metric',
217+
... labels={
218+
... 'status': 'successful',
219+
... })
220+
221+
:type type_: string
222+
:param type_: The metric type name.
223+
224+
:type labels: dict
225+
:param labels: A mapping from label names to values for all labels
226+
enumerated in the associated
227+
:class:`~gcloud.monitoring.metric.MetricDescriptor`.
228+
229+
:rtype: :class:`~gcloud.monitoring.metric.Metric`
230+
:returns: The metric object.
231+
"""
232+
return Metric(type=type_, labels=labels)
233+
234+
@staticmethod
235+
def resource(type_, labels):
236+
"""Factory for constructing monitored resource objects.
237+
238+
A monitored resource object (
239+
:class:`~gcloud.monitoring.resource.Resource`) is
240+
typically used to create a
241+
:class:`~gcloud.monitoring.timeseries.TimeSeries` object.
242+
243+
For a list of possible monitored resource types and their associated
244+
labels, see:
245+
246+
https://cloud.google.com/monitoring/api/resources
247+
248+
:type type_: string
249+
:param type_: The monitored resource type name.
250+
251+
:type labels: dict
252+
:param labels: A mapping from label names to values for all labels
253+
enumerated in the associated
254+
:class:`~gcloud.monitoring.resource.ResourceDescriptor`,
255+
except that ``project_id`` can and should be omitted
256+
when writing time series data.
257+
258+
:rtype: :class:`~gcloud.monitoring.resource.Resource`
259+
:returns: A monitored resource object.
260+
"""
261+
return Resource(type_, labels)
262+
263+
@staticmethod
264+
def time_series(metric, resource, value,
265+
end_time=None, start_time=None):
266+
"""Construct a time series object for a single data point.
267+
268+
.. note::
269+
270+
While :class:`~gcloud.monitoring.timeseries.TimeSeries` objects
271+
returned by the API typically have multiple data points,
272+
:class:`~gcloud.monitoring.timeseries.TimeSeries` objects
273+
sent to the API must have at most one point.
274+
275+
For example::
276+
277+
>>> timeseries = client.time_series(metric, resource, 1.23,
278+
... end_time=end)
279+
280+
For more information, see:
281+
282+
https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries
283+
284+
:type metric: :class:`~gcloud.monitoring.metric.Metric`
285+
:param metric: A :class:`~gcloud.monitoring.metric.Metric` object.
286+
287+
:type resource: :class:`~gcloud.monitoring.resource.Resource`
288+
:param resource: A :class:`~gcloud.monitoring.resource.Resource`
289+
object.
290+
291+
:type value: bool, int, string, or float
292+
:param value:
293+
The value of the data point to create for the
294+
:class:`~gcloud.monitoring.timeseries.TimeSeries`.
295+
296+
.. note::
297+
298+
The Python type of the value will determine the
299+
`class`:ValueType: sent to the API, which must match the value
300+
type specified in the metric descriptor. For example, a Python
301+
float will be sent to the API as a :data:`ValueType.DOUBLE`.
302+
303+
:type end_time: :class:`~datetime.datetime`
304+
:param end_time:
305+
The end time for the point to be included in the time series.
306+
Assumed to be UTC if no time zone information is present.
307+
Defaults to the current time, as obtained by calling
308+
:meth:`datetime.datetime.utcnow`.
309+
310+
:type start_time: :class:`~datetime.datetime`
311+
:param start_time:
312+
The start time for the point to be included in the time series.
313+
Assumed to be UTC if no time zone information is present
314+
Defaults to None. If the start time is unspecified,
315+
the API interprets the start time to be the same as the end time.
316+
317+
:rtype: :class:`~gcloud.monitoring.timeseries.TimeSeries`
318+
:returns: A time series object.
319+
"""
320+
if end_time is None:
321+
end_time = _UTCNOW()
322+
point = Point(value=value, start_time=start_time, end_time=end_time)
323+
return TimeSeries(metric=metric, resource=resource, metric_kind=None,
324+
value_type=None, points=[point])
325+
198326
def fetch_metric_descriptor(self, metric_type):
199327
"""Look up a metric descriptor by type.
200328

gcloud/monitoring/metric.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ def __repr__(self):
319319
class Metric(collections.namedtuple('Metric', 'type labels')):
320320
"""A specific metric identified by specifying values for all labels.
321321
322+
The preferred way to construct a metric object is using the
323+
:meth:`~gcloud.monitoring.client.Client.metric` factory method
324+
of the :class:`~gcloud.monitoring.client.Client` class.
325+
322326
:type type: string
323327
:param type: The metric type name.
324328

gcloud/monitoring/resource.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ def __repr__(self):
158158
class Resource(collections.namedtuple('Resource', 'type labels')):
159159
"""A monitored resource identified by specifying values for all labels.
160160
161+
The preferred way to construct a resource object is using the
162+
:meth:`~gcloud.monitoring.client.Client.resource` factory method
163+
of the :class:`~gcloud.monitoring.client.Client` class.
164+
161165
:type type: string
162166
:param type: The resource type name.
163167

gcloud/monitoring/test_client.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,120 @@ def test_metric_descriptor_factory(self):
157157
self.assertEqual(descriptor.description, DESCRIPTION)
158158
self.assertEqual(descriptor.display_name, '')
159159

160+
def test_metric_factory(self):
161+
TYPE = 'custom.googleapis.com/my_metric'
162+
LABELS = {
163+
'instance_name': 'my-instance'
164+
}
165+
166+
client = self._makeOne(project=PROJECT, credentials=_Credentials())
167+
client.connection = _Connection() # For safety's sake.
168+
metric = client.metric(TYPE, LABELS)
169+
self.assertEqual(metric.type, TYPE)
170+
self.assertEqual(metric.labels, LABELS)
171+
172+
def test_resource_factory(self):
173+
TYPE = 'https://cloud.google.com/monitoring/api/resources'
174+
LABELS = {
175+
'instance_id': 'my-instance-id',
176+
'zone': 'us-central1-f'
177+
}
178+
179+
client = self._makeOne(project=PROJECT, credentials=_Credentials())
180+
client.connection = _Connection() # For safety's sake.
181+
resource = client.resource(TYPE, LABELS)
182+
self.assertEqual(resource.type, TYPE)
183+
self.assertEqual(resource.labels, LABELS)
184+
185+
def test_timeseries_factory_gauge(self):
186+
import datetime
187+
from gcloud._testing import _Monkey
188+
import gcloud.monitoring.client
189+
METRIC_TYPE = 'custom.googleapis.com/my_metric'
190+
METRIC_LABELS = {
191+
'status': 'successful'
192+
}
193+
194+
RESOURCE_TYPE = 'gce_instance'
195+
RESOURCE_LABELS = {
196+
'instance_id': '1234567890123456789',
197+
'zone': 'us-central1-f'
198+
}
199+
200+
VALUE = 42
201+
TIME1 = datetime.datetime.utcnow()
202+
203+
client = self._makeOne(project=PROJECT, credentials=_Credentials())
204+
client.connection = _Connection() # For safety's sake.
205+
metric = client.metric(METRIC_TYPE, METRIC_LABELS)
206+
resource = client.resource(RESOURCE_TYPE, RESOURCE_LABELS)
207+
208+
# Construct a time series assuming a gauge metric.
209+
timeseries = client.time_series(metric, resource, VALUE,
210+
end_time=TIME1)
211+
self.assertEqual(timeseries.metric, metric)
212+
self.assertEqual(timeseries.resource, resource)
213+
self.assertEqual(len(timeseries.points), 1)
214+
self.assertEqual(timeseries.points[0].value, VALUE)
215+
self.assertIsNone(timeseries.points[0].start_time)
216+
self.assertEqual(timeseries.points[0].end_time, TIME1)
217+
218+
TIME2 = datetime.datetime.utcnow()
219+
# Construct a couple of time series assuming a gauge metric,
220+
# using the current time.
221+
with _Monkey(gcloud.monitoring.client, _UTCNOW=lambda: TIME2):
222+
timeseries_no_end = client.time_series(metric, resource, VALUE)
223+
224+
self.assertEqual(timeseries_no_end.points[0].end_time, TIME2)
225+
self.assertIsNone(timeseries_no_end.points[0].start_time)
226+
227+
def test_timeseries_factory_cumulative(self):
228+
import datetime
229+
MY_CUMULATIVE_METRIC = 'custom.googleapis.com/my_cumulative_metric'
230+
METRIC_LABELS = {
231+
'status': 'successful'
232+
}
233+
234+
RESOURCE_TYPE = 'gce_instance'
235+
RESOURCE_LABELS = {
236+
'instance_id': '1234567890123456789',
237+
'zone': 'us-central1-f'
238+
}
239+
240+
client = self._makeOne(project=PROJECT, credentials=_Credentials())
241+
client.connection = _Connection() # For safety's sake.
242+
resource = client.resource(RESOURCE_TYPE, RESOURCE_LABELS)
243+
244+
VALUE = 42
245+
VALUE2 = 43
246+
RESET_TIME = datetime.datetime.utcnow()
247+
TIME1 = datetime.datetime.utcnow()
248+
TIME2 = datetime.datetime.utcnow()
249+
250+
# Construct a time series assuming a cumulative metric.
251+
cumulative_metric = client.metric(MY_CUMULATIVE_METRIC, METRIC_LABELS)
252+
cumulative_timeseries = client.time_series(cumulative_metric,
253+
resource,
254+
VALUE,
255+
start_time=RESET_TIME,
256+
end_time=TIME1)
257+
258+
cumulative_timeseries2 = client.time_series(cumulative_metric,
259+
resource,
260+
VALUE2,
261+
start_time=RESET_TIME,
262+
end_time=TIME2)
263+
264+
self.assertEqual(cumulative_timeseries.points[0].start_time,
265+
RESET_TIME)
266+
self.assertEqual(cumulative_timeseries.points[0].end_time, TIME1)
267+
self.assertEqual(cumulative_timeseries.points[0].value, VALUE)
268+
self.assertEqual(cumulative_timeseries2.points[0].start_time,
269+
RESET_TIME)
270+
self.assertEqual(cumulative_timeseries2.points[0].end_time,
271+
TIME2)
272+
self.assertEqual(cumulative_timeseries2.points[0].value, VALUE2)
273+
160274
def test_fetch_metric_descriptor(self):
161275
TYPE = 'custom.googleapis.com/my_metric'
162276
NAME = 'projects/{project}/metricDescriptors/{type}'.format(

gcloud/monitoring/timeseries.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ class TimeSeries(collections.namedtuple(
3232
'TimeSeries', 'metric resource metric_kind value_type points')):
3333
"""A single time series of metric values.
3434
35+
The preferred way to construct a
36+
:class:`~gcloud.monitoring.timeseries.TimeSeries` object is
37+
using the :meth:`~gcloud.monitoring.client.Client.time_series` factory
38+
method of the :class:`~gcloud.monitoring.client.Client` class.
39+
3540
:type metric: :class:`~gcloud.monitoring.metric.Metric`
3641
:param metric: A metric object.
3742

0 commit comments

Comments
 (0)