-
Notifications
You must be signed in to change notification settings - Fork 644
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
Prometheus metric exporter #378
Changes from 10 commits
9850fcc
5d83631
20c190a
a4692e1
8493acd
cb58432
8f4f968
f359148
8299efc
4aa9d4d
e5f2cf1
0d3e4b4
19489bf
4db034a
3c2fc3a
509cf96
35f975d
c34ea93
aaa5448
3023ff1
37ea8ee
77178b8
35b19b3
555bc36
bfd815b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
opentelemetry.sdk.metrics.export.aggregate | ||
========================================== | ||
|
||
.. automodule:: opentelemetry.sdk.metrics.export.aggregate | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
opentelemetry.sdk.metrics.export.batcher | ||
========================================== | ||
|
||
.. automodule:: opentelemetry.sdk.metrics.export.batcher | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
opentelemetry.sdk.metrics.export | ||
========================================== | ||
|
||
.. automodule:: opentelemetry.sdk.metrics.export | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,16 @@ | ||
opentelemetry.sdk.metrics package | ||
========================================== | ||
|
||
Submodules | ||
---------- | ||
|
||
.. toctree:: | ||
|
||
opentelemetry.sdk.metrics.export | ||
opentelemetry.sdk.metrics.export.aggregate | ||
opentelemetry.sdk.metrics.export.batcher | ||
|
||
.. automodule:: opentelemetry.sdk.metrics | ||
:members: | ||
:undoc-members: | ||
:show-inheritance: | ||
:show-inheritance: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Changelog | ||
|
||
## Unreleased | ||
|
||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
OpenTelemetry Prometheus Exporter | ||
============================= | ||
|
||
|pypi| | ||
|
||
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-prometheus.svg | ||
:target: https://pypi.org/project/opentelemetry-ext-prometheus/ | ||
|
||
This library allows to export metrics data to `Prometheus <https://prometheus.io/>`_. | ||
|
||
Installation | ||
------------ | ||
|
||
:: | ||
|
||
pip install opentelemetry-ext-prometheus | ||
|
||
|
||
Usage | ||
----- | ||
|
||
The **OpenTelemetry Prometheus Exporter** allows to export `OpenTelemetry`_ metrics to `Prometheus`_. | ||
|
||
|
||
.. _Prometheus: https://prometheus.io/ | ||
.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/ | ||
|
||
.. code:: python | ||
|
||
from opentelemetry import metrics | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
from opentelemetry.ext.prometheus import PrometheusMetricsExporter | ||
from opentelemetry.sdk.metrics import Counter, Meter | ||
from opentelemetry.sdk.metrics.export import ConsoleMetricsExporter | ||
from opentelemetry.sdk.metrics.export.controller import PushController | ||
|
||
# Meter is responsible for creating and recording metrics | ||
meter = Meter() | ||
metrics.set_preferred_meter_implementation(meter) | ||
# exporter to export metrics to Prometheus | ||
port = 8000 | ||
address = "localhost" | ||
prefix = "MyAppPrefix" | ||
exporter = PrometheusMetricsExporter(port, address, prefix) | ||
# controller collects metrics created from meter and exports it via the | ||
# exporter every interval | ||
controller = PushController(meter, exporter, 5) | ||
|
||
counter = meter.create_metric( | ||
"available memory", | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"available memory", | ||
"bytes", | ||
int, | ||
Counter, | ||
("environment",), | ||
) | ||
|
||
# Labelsets are used to identify key-values that are associated with a specific | ||
# metric that you want to record. These are useful for pre-aggregation and can | ||
# be used to store custom dimensions pertaining to a metric | ||
label_set = meter.get_label_set({"environment": "staging"}) | ||
|
||
counter.add(25, label_set) | ||
# We sleep for 5 seconds, exported value should be 25 | ||
time.sleep(5) | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
References | ||
---------- | ||
|
||
* `Prometheus <https://prometheus.io/>`_ | ||
* `OpenTelemetry Project <https://opentelemetry.io/>`_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Copyright 2020, OpenTelemetry Authors | ||
# | ||
# 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. | ||
# | ||
[metadata] | ||
name = opentelemetry-ext-prometheus | ||
description = Prometheus Metric Exporter for OpenTelemetry | ||
long_description = file: README.rst | ||
long_description_content_type = text/x-rst | ||
author = OpenTelemetry Authors | ||
author_email = cncf-opentelemetry-contributors@lists.cncf.io | ||
url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-prometheus | ||
platforms = any | ||
license = Apache-2.0 | ||
classifiers = | ||
Development Status :: 3 - Alpha | ||
Intended Audience :: Developers | ||
License :: OSI Approved :: Apache Software License | ||
Programming Language :: Python | ||
Programming Language :: Python :: 3 | ||
Programming Language :: Python :: 3.4 | ||
Programming Language :: Python :: 3.5 | ||
Programming Language :: Python :: 3.6 | ||
Programming Language :: Python :: 3.7 | ||
|
||
[options] | ||
python_requires = >=3.4 | ||
package_dir= | ||
=src | ||
packages=find_namespace: | ||
install_requires = | ||
prometheus_client >= 0.5.0, < 1.0.0 | ||
opentelemetry-api | ||
opentelemetry-sdk | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to have a min version of the sdk that includes the metrics sdk changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't exist yet, we can add it when is available |
||
|
||
[options.packages.find] | ||
where = src |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Copyright 2020, OpenTelemetry Authors | ||
# | ||
# 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. | ||
import os | ||
|
||
import setuptools | ||
|
||
BASE_DIR = os.path.dirname(__file__) | ||
VERSION_FILENAME = os.path.join( | ||
BASE_DIR, "src", "opentelemetry", "ext", "prometheus", "version.py" | ||
) | ||
PACKAGE_INFO = {} | ||
with open(VERSION_FILENAME) as f: | ||
exec(f.read(), PACKAGE_INFO) | ||
|
||
setuptools.setup(version=PACKAGE_INFO["__version__"]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# Copyright 2020, OpenTelemetry Authors | ||
# | ||
# 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. | ||
|
||
"""Prometheus Metrics Exporter for OpenTelemetry.""" | ||
|
||
import logging | ||
import re | ||
from typing import Sequence | ||
|
||
from prometheus_client import start_http_server | ||
from prometheus_client.core import ( | ||
REGISTRY, | ||
CollectorRegistry, | ||
CounterMetricFamily, | ||
GaugeMetricFamily, | ||
UnknownMetricFamily, | ||
) | ||
|
||
from opentelemetry.metrics import Counter, Gauge, Measure, Metric | ||
from opentelemetry.sdk.metrics.export import ( | ||
MetricRecord, | ||
MetricsExporter, | ||
MetricsExportResult, | ||
) | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
DEFAULT_PORT = 8000 | ||
|
||
|
||
class PrometheusMetricsExporter(MetricsExporter): | ||
"""Prometheus metric exporter for OpenTelemetry. | ||
|
||
Args: | ||
port: The Prometheus port to be used. | ||
address: The Prometheus address to be used. | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
prefix: single-word application prefix relevant to the domain the metric belongs to. | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
|
||
def __init__( | ||
self, port: int = DEFAULT_PORT, address: str = "", prefix: str = "" | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
): | ||
self._port = port | ||
self._address = address | ||
self._collector = CustomCollector(prefix) | ||
|
||
start_http_server(port=self._port, addr=str(self._address)) | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
REGISTRY.register(self._collector) | ||
|
||
def export( | ||
self, metric_records: Sequence[MetricRecord] | ||
) -> MetricsExportResult: | ||
self._collector.add_metrics_data(metric_records) | ||
return MetricsExportResult.SUCCESS | ||
|
||
def shutdown(self) -> None: | ||
REGISTRY.unregister(self._collector) | ||
|
||
|
||
class CustomCollector: | ||
""" CustomCollector represents the Prometheus Collector object | ||
https://github.com/prometheus/client_python#custom-collectors | ||
""" | ||
|
||
def __init__(self, prefix: str = ""): | ||
self._prefix = prefix | ||
self._metrics_to_export = [] | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def add_metrics_data(self, metric_records: Sequence[MetricRecord]): | ||
self._metrics_to_export.append(metric_records) | ||
|
||
def collect(self): | ||
"""Collect fetches the metrics from OpenTelemetry | ||
and delivers them as Prometheus Metrics. | ||
Collect is invoked every time a prometheus.Gatherer is run | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a link to some API that shows how this works? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The custom collector will use same logic as regular Prometheus Collector, pulling mechanism is configured by time intervals but is also triggered by other input like the user refreshing Prometheus visualization UI https://github.com/prometheus/client_python#custom-collectors |
||
for example when the HTTP endpoint is invoked by Prometheus. | ||
""" | ||
|
||
for metric_batch in list(self._metrics_to_export): | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for metric_record in metric_batch: | ||
prometheus_metric = self._translate_to_prometheus( | ||
metric_record | ||
) | ||
if prometheus_metric: | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
yield prometheus_metric | ||
self._metrics_to_export.remove(metric_batch) | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def _translate_to_prometheus(self, metric_record: MetricRecord): | ||
prometheus_metric = None | ||
label_values = metric_record.label_set.labels.values() | ||
metric_name = "" | ||
if self._prefix != "": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can do |
||
metric_name = self._prefix + "_" | ||
metric_name += sanitize(metric_record.metric.name) | ||
|
||
if isinstance(metric_record.metric, Counter): | ||
ocelotl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
prometheus_metric = CounterMetricFamily( | ||
name=metric_name, | ||
documentation=metric_record.metric.description, | ||
labels=metric_record.metric.label_keys, | ||
) | ||
prometheus_metric.add_metric( | ||
labels=label_values, value=metric_record.aggregator.check_point | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
elif isinstance(metric_record.metric, Gauge): | ||
prometheus_metric = GaugeMetricFamily( | ||
name=metric_name, | ||
documentation=metric_record.metric.description, | ||
) | ||
prometheus_metric.add_metric( | ||
labels=label_values, value=metric_record.aggregator.check_point | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
# TODO: Add support for histograms when supported in OT | ||
mauriciovasquezbernal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
elif isinstance(metric_record.metric, Measure): | ||
prometheus_metric = UnknownMetricFamily( | ||
name=metric_name, | ||
documentation=metric_record.metric.description, | ||
) | ||
prometheus_metric.add_metric( | ||
labels=label_values, value=metric_record.aggregator.check_point | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
|
||
else: | ||
logger.warning( | ||
"Unsupported metric type. %s", type(metric_record.metric) | ||
) | ||
return prometheus_metric | ||
|
||
|
||
_NON_LETTERS_NOR_DIGITS_RE = re.compile(r"[^\w]", re.UNICODE | re.IGNORECASE) | ||
|
||
|
||
def sanitize(key): | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" sanitize the given metric name or label according to Prometheus rule. | ||
Replace all characters other than [A-Za-z0-9_] with '_'. | ||
""" | ||
return _NON_LETTERS_NOR_DIGITS_RE.sub("_", key) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Copyright 2020, OpenTelemetry Authors | ||
# | ||
# 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. | ||
|
||
__version__ = "0.4.dev0" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we synchronizing the versions to the api and sdk packages? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we are doing this in all other ext packages
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Copyright 2020, OpenTelemetry Authors | ||
hectorhdzg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# | ||
# 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we leaving this blank?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is not blank it says unreleased, once is released changelogs will make sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@c24t are we doing changelogs on this fashion? I wonder if there's an easier way to manage these than attempting to update changelogs in all repos as we go along. Then again, this may play better with pypi.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how we started the other changelogs. The idea is that each PR that makes a relevant change should add a line under "unreleased", and those lines get moved under a release-specific header when we cut the release.