Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow using a metric type in the Query method select_metrics. #2735

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 89 additions & 21 deletions monitoring/google/cloud/monitoring/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ def filter(self):
def select_interval(self, end_time, start_time=None):
"""Copy the query and set the query time interval.

Example::
Example:

.. code-block:: python

import datetime

Expand Down Expand Up @@ -196,7 +198,9 @@ def select_interval(self, end_time, start_time=None):
def select_group(self, group_id):
"""Copy the query and add filtering by group.

Example::
Example:

.. code-block:: python

query = query.select_group('1234567')

Expand All @@ -216,7 +220,9 @@ def select_projects(self, *args):
This is only useful if the target project represents a Stackdriver
account containing the specified monitored projects.

Examples::
Examples:

.. code-block:: python

query = query.select_projects('project-1')
query = query.select_projects('project-1', 'project-2')
Expand All @@ -235,31 +241,43 @@ def select_projects(self, *args):
def select_resources(self, *args, **kwargs):
"""Copy the query and add filtering by resource labels.

Examples::
Examples:

.. code-block:: python

query = query.select_resources(zone='us-central1-a')
query = query.select_resources(zone_prefix='europe-')
query = query.select_resources(resource_type='gce_instance')

A keyword argument ``<label>=<value>`` ordinarily generates a filter
expression of the form::
expression of the form:

.. code-block:: python

resource.label.<label> = "<value>"

However, by adding ``"_prefix"`` or ``"_suffix"`` to the keyword,
you can specify a partial match.

``<label>_prefix=<value>`` generates::
``<label>_prefix=<value>`` generates:

.. code-block:: python

resource.label.<label> = starts_with("<value>")

``<label>_suffix=<value>`` generates::
``<label>_suffix=<value>`` generates:

.. code-block:: python

resource.label.<label> = ends_with("<value>")

As a special case, ``"resource_type"`` is treated as a special
pseudo-label corresponding to the filter object ``resource.type``.
For example, ``resource_type=<value>`` generates::
For example, ``resource_type=<value>`` generates:

.. code-block:: python

resource.label.<label> = ends_with("<value>")

resource.type = "<value>"

Expand Down Expand Up @@ -293,43 +311,77 @@ def select_resources(self, *args, **kwargs):
def select_metrics(self, *args, **kwargs):
"""Copy the query and add filtering by metric labels.

Examples::
Examples:

.. code-block:: python

query = query.select_metrics(instance_name='myinstance')
query = query.select_metrics(instance_name_prefix='mycluster-')
query = query.select_metrics(
metric_type='compute.googleapis.com/instance/cpu/utilization')

A keyword argument ``<label>=<value>`` ordinarily generates a filter
expression of the form::
expression of the form:

.. code-block:: python

metric.label.<label> = "<value>"

However, by adding ``"_prefix"`` or ``"_suffix"`` to the keyword,
you can specify a partial match.

``<label>_prefix=<value>`` generates::
``<label>_prefix=<value>`` generates:

.. code-block:: python

metric.label.<label> = starts_with("<value>")

``<label>_suffix=<value>`` generates::
``<label>_suffix=<value>`` generates:

.. code-block:: python

metric.label.<label> = ends_with("<value>")

As a special case, ``"metric_type"`` is treated as a special
pseudo-label corresponding to the filter object ``metric.type``.
For example, ``metric_type=<value>`` generates:

.. code-block:: python

metric.type = "<value>"

See the `supported metrics`_.

.. note::

Currently, the query can only support a single metric type. Given
this, prefix and suffix filtering is not supported for
``"metric_type"``.

If the label's value type is ``INT64``, a similar notation can be
used to express inequalities:

``<label>_less=<value>`` generates::
``<label>_less=<value>`` generates:

.. code-block:: python

metric.label.<label> < <value>

``<label>_lessequal=<value>`` generates::
``<label>_lessequal=<value>`` generates:

.. code-block:: python

metric.label.<label> <= <value>

``<label>_greater=<value>`` generates::
``<label>_greater=<value>`` generates:

.. code-block:: python

metric.label.<label> > <value>

``<label>_greaterequal=<value>`` generates::
``<label>_greaterequal=<value>`` generates:

.. code-block:: python

metric.label.<label> >= <value>

Expand All @@ -344,6 +396,8 @@ def select_metrics(self, *args, **kwargs):

:rtype: :class:`Query`
:returns: The new query object.

.. _supported metrics: https://cloud.google.com/monitoring/api/metrics
"""
new_query = self.copy()
new_query._filter.select_metrics(*args, **kwargs)
Expand All @@ -355,11 +409,15 @@ def align(self, per_series_aligner, seconds=0, minutes=0, hours=0):
If ``per_series_aligner`` is not :data:`Aligner.ALIGN_NONE`, each time
series will contain data points only on the period boundaries.

Example::
Example:

.. code-block:: python

query = query.align(Aligner.ALIGN_MEAN, minutes=5)

It is also possible to specify the aligner as a literal string::
It is also possible to specify the aligner as a literal string:

.. code-block:: python

query = query.align('ALIGN_MEAN', minutes=5)

Expand Down Expand Up @@ -398,7 +456,9 @@ def reduce(self, cross_series_reducer, *group_by_fields):
data points.

For example, you could request an aggregated time series for each
combination of project and zone as follows::
combination of project and zone as follows:

.. code-block:: python

query = query.reduce(Reducer.REDUCE_MEAN,
'resource.project_id', 'resource.zone')
Expand Down Expand Up @@ -435,7 +495,9 @@ def iter(self, headers_only=False, page_size=None):
containing points ordered from oldest to newest.

Note that the :class:`Query` object itself is an iterable, such that
the following are equivalent::
the following are equivalent:

.. code-block:: python

for timeseries in query:
...
Expand Down Expand Up @@ -558,7 +620,9 @@ def as_dataframe(self, label=None, labels=None):

Use of this method requires that you have :mod:`pandas` installed.

Examples::
Examples:

.. code-block:: python

# Generate a dataframe with a multi-level column header including
# the resource type and all available resource and metric labels.
Expand Down Expand Up @@ -630,6 +694,10 @@ def select_metrics(self, *args, **kwargs):

See :meth:`Query.select_metrics`.
"""
new_metric_type = kwargs.pop('metric_type', None)
if new_metric_type is not None:
self.metric_type = new_metric_type

self.metric_label_filter = _build_label_filter('metric',
*args, **kwargs)

Expand Down
22 changes: 18 additions & 4 deletions monitoring/unit_tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,15 @@ def test_filter_by_resources(self):

def test_filter_by_metrics(self):
INSTANCE = 'my-instance'
NEW_METRIC_TYPE = 'compute.googleapis.com/instance/utilization'
client = _Client(project=PROJECT, connection=_Connection())
query = self._make_one(client, METRIC_TYPE)
query = query.select_metrics(instance_name=INSTANCE)
query = query.select_metrics(metric_type=NEW_METRIC_TYPE,
instance_name=INSTANCE)
expected = (
'metric.type = "{type}"'
' AND metric.label.instance_name = "{instance}"'
).format(type=METRIC_TYPE, instance=INSTANCE)
).format(type=NEW_METRIC_TYPE, instance=INSTANCE)
self.assertEqual(query.filter, expected)

def test_request_parameters_minimal(self):
Expand Down Expand Up @@ -522,15 +524,27 @@ def test_maximal(self):
obj.projects = 'project-1', 'project-2'
obj.select_resources(resource_type='some-resource',
resource_label='foo')
obj.select_metrics(metric_label_prefix='bar-')
obj.select_metrics(metric_type='some-metric',
metric_label_prefix='bar-')

expected = (
'metric.type = "{type}"'
'metric.type = "some-metric"'
' AND group.id = "1234567"'
' AND project = "project-1" OR project = "project-2"'
' AND resource.label.resource_label = "foo"'
' AND resource.type = "some-resource"'
' AND metric.label.metric_label = starts_with("bar-")'
)

self.assertEqual(str(obj), expected)

def test_select_metrics_without_type(self):
obj = self._make_one(METRIC_TYPE)
obj.select_metrics(metric_label_prefix='bar-')

expected = (
'metric.type = "{type}"'
' AND metric.label.metric_label = starts_with("bar-")'
).format(type=METRIC_TYPE)

self.assertEqual(str(obj), expected)
Expand Down