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

Monitoring API #1691

Merged
merged 61 commits into from
Apr 22, 2016
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
2af55db
Add gcloud.monitoring with fetching of metric descriptors.
rimey Mar 30, 2016
a03740e
Add fetching of resource descriptors.
rimey Mar 30, 2016
6f57e6a
Add querying of time series.
rimey Mar 31, 2016
8ea9fc5
Add unit tests for everything but time series.
rimey Apr 4, 2016
702f874
Added some links to the documentation, and a comment.
rimey Apr 5, 2016
80843b0
Dropped two nested generators.
rimey Apr 5, 2016
1d4fcaa
Require start time and end time to be datetimes rather than strings.
rimey Apr 5, 2016
a5bab44
Change how query intervals are specified.
rimey Apr 6, 2016
3b88363
Change how one filters on the resource type.
rimey Apr 6, 2016
a1959b3
Add some pseudo-enums providing constants you can use if you wish.
rimey Apr 6, 2016
ac12ade
More unit tests, and a few related changes.
rimey Apr 7, 2016
380e7e4
Added a metric_type property.
rimey Apr 7, 2016
451f419
The descriptor classes are no longer named tuples.
rimey Apr 8, 2016
70366de
Move Query into a new query.py file.
rimey Apr 8, 2016
1e72784
Isolated the pandas-dependent code in _dataframe.py.
rimey Apr 8, 2016
0937a1a
Add unit tests for _build_dataframe().
rimey Apr 8, 2016
f5cc186
Make the pseudo-enums importable from gcloud.monitoring.
rimey Apr 10, 2016
19745e7
Skip tests of _build_dataframe() if pandas is not available.
rimey Apr 11, 2016
6ce7994
Fix lint errors.
rimey Apr 11, 2016
4f047a5
Add "NO COVER" pragmas due to not having pandas in the testenv:cover …
rimey Apr 11, 2016
27a508c
Add a py27-pandas tox test environment.
rimey Apr 11, 2016
7c0acce
as_matrix() -> values
rimey Apr 12, 2016
bc1ff05
Utilize the pseudo-enums in the docstrings and add a few more usage e…
rimey Apr 15, 2016
95f9147
Add some basic usage documentation.
rimey Apr 15, 2016
1b231bc
Fix some remaining references to the pseudo-enums in a docstring.
rimey Apr 16, 2016
1092fea
Add prompts (">>>") to examples showing interactive usage.
rimey Apr 16, 2016
a4795bb
Add basic system tests.
rimey Apr 16, 2016
adb7e10
Expect that the service will never return an empty name, type, or key.
rimey Apr 16, 2016
eb73882
Use print() in docstring examples.
rimey Apr 19, 2016
aeeba8f
Add pandas to the intersphinx configuration.
rimey Apr 19, 2016
5f85d99
Alphabetical order.
rimey Apr 19, 2016
a780431
Change TOP_RESOURCE_LABELS from list to tuple.
rimey Apr 19, 2016
7432956
Rephrase a complex if-statement.
rimey Apr 19, 2016
663aeca
p -> point
rimey Apr 19, 2016
f07abbd
Use DataFrame.from_records() to build from a list of rows.
rimey Apr 19, 2016
c0b4b11
Add a comment about the system test getting no time series data.
rimey Apr 19, 2016
e43e8f5
Use Query.DEFAULT_METRIC_TYPE cross-reference in doc strings.
rimey Apr 19, 2016
9bfafcf
Add some docstring text about NotFound exceptions.
rimey Apr 19, 2016
4428641
Rename "filter" parameter to "filter_" in private methods.
rimey Apr 19, 2016
f040b1b
Move status comments into module docstrings.
rimey Apr 19, 2016
fcd2d22
Use :data: for docstring cross references to pseudo-enums.
rimey Apr 19, 2016
41e8ff2
Change some empty lists to empty tuples.
rimey Apr 19, 2016
4f47836
_NOW -> _UTCNOW
rimey Apr 19, 2016
474b42d
Remove docstring text explaining about named tuples.
rimey Apr 19, 2016
259da03
Reformat a docstring example.
rimey Apr 19, 2016
9bb980d
_page_size -> page_size
rimey Apr 19, 2016
3ed53f5
Introduce an _iter_fragments() helper method.
rimey Apr 20, 2016
b2563ee
Make _build_query_params() a generator.
rimey Apr 20, 2016
e8a6f43
Use six.iteritems().
rimey Apr 20, 2016
b336e61
Reorganize test data around DIMENSIONS instead of N and M.
rimey Apr 20, 2016
cb7ec37
Restyle an import.
rimey Apr 20, 2016
6bccf2f
Unpack points more elegantly.
rimey Apr 20, 2016
fe8ee01
Remove pylint comment disabling redefined-builtin.
rimey Apr 21, 2016
1f5e9e7
Cache the label map without having to disable pylint.
rimey Apr 21, 2016
2d29693
Excercise the test harness fully by reading past the available respon…
rimey Apr 21, 2016
2dfd890
Change some more empty lists into empty tuples.
rimey Apr 21, 2016
681292d
filter/filter_ -> filter_string
rimey Apr 22, 2016
b6bf58e
'%Y-%m-%dT%H:%M:%S.%fZ' -> _RFC3339_MICROS
rimey Apr 22, 2016
a088e33
Use parentheses instead of backslashes.
rimey Apr 22, 2016
ae124ae
Reorganize a hard-to-understand bit of code for building multi-level …
rimey Apr 22, 2016
13e98c0
type -> type_
rimey Apr 22, 2016
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
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,5 +292,6 @@
intersphinx_mapping = {
'httplib2': ('http://bitworking.org/projects/httplib2/doc/html/', None),
'oauth2client': ('http://oauth2client.readthedocs.org/en/latest/', None),
'pandas': ('http://pandas.pydata.org/pandas-docs/stable/', None),
'python': ('https://docs.python.org/', None),
}
13 changes: 13 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@
logging-metric
logging-sink

.. toctree::
:maxdepth: 0
:hidden:
:caption: Cloud Monitoring

monitoring-usage
Client <monitoring-client>
monitoring-metric
monitoring-resource
monitoring-query
monitoring-timeseries
monitoring-label

.. toctree::
:maxdepth: 0
:hidden:
Expand Down
16 changes: 16 additions & 0 deletions docs/monitoring-client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Monitoring Client
=================

.. automodule:: gcloud.monitoring.client
:members:
:undoc-members:
:show-inheritance:

Connection
~~~~~~~~~~

.. automodule:: gcloud.monitoring.connection
:members:
:undoc-members:
:show-inheritance:

7 changes: 7 additions & 0 deletions docs/monitoring-label.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Label Descriptors
=================

.. automodule:: gcloud.monitoring.label
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/monitoring-metric.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Metric Descriptors
==================

.. automodule:: gcloud.monitoring.metric
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/monitoring-query.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Time Series Query
=================

.. automodule:: gcloud.monitoring.query
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/monitoring-resource.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Resource Descriptors
====================

.. automodule:: gcloud.monitoring.resource
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/monitoring-timeseries.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Time Series
===========

.. automodule:: gcloud.monitoring.timeseries
:members:
:undoc-members:
:show-inheritance:
155 changes: 155 additions & 0 deletions docs/monitoring-usage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
Using the API
=============


Introduction
------------

With the Monitoring API, you can work with Stackdriver metric data
pertaining to monitored resources in Google Cloud Platform (GCP)
or elsewhere.

Essential concepts:

- Metric data is associated with a **monitored resource**. A monitored
resource has a *resource type* and a set of *resource labels* —
key-value pairs — that identify the particular resource.
- A **metric** further identifies the particular kind of data that
is being collected. It has a *metric type* and a set of *metric
labels* that, when combined with the resource labels, identify
a particular time series.
- A **time series** is a collection of data points associated with
points or intervals in time.

Please refer to the documentation for the `Monitoring API`_ for
more information.

At present, this client library supports querying of time series,
metric descriptors, and resource descriptors.

.. _Monitoring API: https://cloud.google.com/monitoring/api/


The Monitoring Client Object
----------------------------

The monitoring client library generally makes its
functionality available as methods of the monitoring
:class:`~gcloud.monitoring.client.Client` class.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

A :class:`~gcloud.monitoring.client.Client` instance holds
authentication credentials and the ID of the target project with
which the metric data of interest is associated. This project ID
will often refer to a `Stackdriver account`_ binding multiple
GCP projects and AWS accounts. It can also simply be the ID of
a monitored project.

Most often the authentication credentials will be determined
implicitly from your environment. See :doc:`gcloud-auth` for
more information.

It is thus typical to create a client object as follows::

>>> from gcloud import monitoring
>>> client = monitoring.Client(project='target-project')

If you are running in Google Compute Engine or Google App Engine,
the current project is the default target project. This default
can be further overridden with the :envvar:`GCLOUD_PROJECT`
environment variable. Using the default target project is
even easier::

>>> client = monitoring.Client()

If necessary, you can pass in ``credentials`` and ``project`` explicitly::

>>> client = monitoring.Client(project='target-project', credentials=...)

.. _Stackdriver account: https://cloud.google.com/monitoring/accounts/


Monitored Resource Descriptors
------------------------------

The available monitored resource types are defined by *monitored resource
descriptors*. You can fetch a list of these with the
:meth:`~gcloud.monitoring.client.Client.list_resource_descriptors` method::

>>> for descriptor in client.list_resource_descriptors():
... print(descriptor.type)

Each :class:`~gcloud.monitoring.resource.ResourceDescriptor`
has a type, a display name, a description, and a list of
:class:`~gcloud.monitoring.label.LabelDescriptor` instances.
See the documentation about `Monitored Resources`_
for more information.

.. _Monitored Resources:
https://cloud.google.com/monitoring/api/v3/monitored-resources


Metric Descriptors
------------------

The available metric types are defined by *metric descriptors*.
They include `platform metrics`_, `agent metrics`_, and `custom metrics`_.
You can list all of these with the
:meth:`~gcloud.monitoring.client.Client.list_metric_descriptors` method::

>>> for descriptor in client.list_metric_descriptors():
... print(descriptor.type)

See :class:`~gcloud.monitoring.metric.MetricDescriptor` and the
`Metric Descriptors`_ API documentation for more information.

.. _platform metrics: https://cloud.google.com/monitoring/api/metrics
.. _agent metrics: https://cloud.google.com/monitoring/agent/
.. _custom metrics: https://cloud.google.com/monitoring/custom-metrics/
.. _Metric Descriptors:
https://cloud.google.com/monitoring/api/ref_v3/rest/v3/\
projects.metricDescriptors


Time Series Queries
-------------------

A time series includes a collection of data points and a set of
resource and metric label values.
See :class:`~gcloud.monitoring.timeseries.TimeSeries` and the
`Time Series`_ API documentation for more information.

While you can obtain time series objects by iterating over a
:class:`~gcloud.monitoring.query.Query` object, usually it is
more useful to retrieve time series data in the form of a
:class:`pandas.DataFrame`, where each column corresponds to a
single time series. For this, you must have :mod:`pandas` installed;
it is not a required dependency of ``gcloud-python``.

You can display CPU utilization across your GCE instances during
the last five minutes as follows::

>>> METRIC = 'compute.googleapis.com/instance/cpu/utilization'
>>> query = client.query(METRIC, minutes=5)
>>> print(query.as_dataframe())

:class:`~gcloud.monitoring.query.Query` objects provide a variety of
methods for refining the query. You can request temporal alignment
and cross-series reduction, and you can filter by label values.
See the client :meth:`~gcloud.monitoring.client.Client.query` method
and the :class:`~gcloud.monitoring.query.Query` class for more
information.

For example, you can display CPU utilization during the last hour
across GCE instances with names beginning with ``"mycluster-"``,
averaged over five-minute intervals and aggregated per zone, as
follows::

>>> from gcloud.monitoring import Aligner, Reducer
>>> METRIC = 'compute.googleapis.com/instance/cpu/utilization'
>>> query = client.query(METRIC, hours=1) \
... .select_metrics(instance_name_prefix='mycluster-') \
... .align(Aligner.ALIGN_MEAN, minutes=5) \
... .reduce(Reducer.REDUCE_MEAN, 'resource.zone')

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

>>> print(query.as_dataframe())

.. _Time Series:
https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries
34 changes: 34 additions & 0 deletions gcloud/monitoring/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Google Monitoring API wrapper."""

from gcloud.monitoring.client import Client
from gcloud.monitoring.connection import Connection
from gcloud.monitoring.label import LabelDescriptor
from gcloud.monitoring.label import LabelValueType
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 Aligner
from gcloud.monitoring.query import Query

This comment was marked as spam.

This comment was marked as spam.

from gcloud.monitoring.query import Reducer
from gcloud.monitoring.resource import Resource
from gcloud.monitoring.resource import ResourceDescriptor
from gcloud.monitoring.timeseries import Point
from gcloud.monitoring.timeseries import TimeSeries


SCOPE = Connection.SCOPE
112 changes: 112 additions & 0 deletions gcloud/monitoring/_dataframe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright 2016 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Time series as :mod:`pandas` dataframes."""

import itertools

TOP_RESOURCE_LABELS = (
'project_id',
'aws_account',
'location',
'region',
'zone',
)


def _build_dataframe(time_series_iterable,
label=None, labels=None): # pragma: NO COVER

This comment was marked as spam.

This comment was marked as spam.

"""Build a :mod:`pandas` dataframe out of time series.

:type time_series_iterable:
iterable over :class:`~gcloud.monitoring.timeseries.TimeSeries`
:param time_series_iterable:
An iterable (e.g., a query object) yielding time series.

:type label: string or None
:param label:
The label name to use for the dataframe header. This can be the name
of a resource label or metric label (e.g., ``"instance_name"``), or
the string ``"resource_type"``.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.


:type labels: list of strings, or None
:param labels:
A list or tuple of label names to use for the dataframe header.
If more than one label name is provided, the resulting dataframe
will have a multi-level column header.

Specifying neither ``label`` or ``labels`` results in a dataframe
with a multi-level column header including the resource type and
all available resource and metric labels.

Specifying both ``label`` and ``labels`` is an error.

:rtype: :class:`pandas.DataFrame`
:returns: A dataframe where each column represents one time series.
"""
import pandas # pylint: disable=import-error

This comment was marked as spam.

This comment was marked as spam.


if labels is not None:
if label is not None:
raise ValueError('Cannot specify both "label" and "labels".')
elif not labels:
raise ValueError('"labels" must be non-empty or None.')

columns = []
headers = []
for time_series in time_series_iterable:
pandas_series = pandas.Series(
data=[point.value for point in time_series.points],
index=[point.end_time for point in time_series.points],
)
columns.append(pandas_series)
headers.append(time_series.header())

# Implement a smart default of using all available labels.
if label is None and labels is None:
resource_labels = set(itertools.chain.from_iterable(
header.resource.labels for header in headers))
metric_labels = set(itertools.chain.from_iterable(
header.metric.labels for header in headers))
labels = (['resource_type'] +
_sorted_resource_labels(resource_labels) +
sorted(metric_labels))

# Assemble the columns into a DataFrame.
dataframe = pandas.DataFrame.from_records(columns).T

# Convert the timestamp strings into a DatetimeIndex.
dataframe.index = pandas.to_datetime(dataframe.index)

# Build a column Index or MultiIndex from the label values. Do not
# include level names in the column header if the user requested a
# single-level header by specifying "label".
level_names = labels or None
label_keys = labels or [label]
dataframe.columns = pandas.MultiIndex.from_arrays(
[[header.labels.get(key, '') for header in headers]
for key in label_keys],

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

names=level_names)

# Sort the rows just in case (since the API doesn't guarantee the
# ordering), and sort the columns lexicographically.
return dataframe.sort_index(axis=0).sort_index(axis=1)


def _sorted_resource_labels(labels):
"""Sort label names, putting well-known resource labels first."""
head = [label for label in TOP_RESOURCE_LABELS if label in labels]
tail = sorted(label for label in labels
if label not in TOP_RESOURCE_LABELS)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

return head + tail
Loading