diff --git a/exporter/opentelemetry-exporter-prometheus/setup.cfg b/exporter/opentelemetry-exporter-prometheus/setup.cfg index f6e04dc12ad..5227e1a00fe 100644 --- a/exporter/opentelemetry-exporter-prometheus/setup.cfg +++ b/exporter/opentelemetry-exporter-prometheus/setup.cfg @@ -52,4 +52,4 @@ test = [options.entry_points] opentelemetry_exporter = - prometheus = opentelemetry.exporter.prometheus:PrometheusMetricsExporter \ No newline at end of file + prometheus = opentelemetry.exporter.prometheus:PrometheusMetricExporter \ No newline at end of file diff --git a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py index e51af6f0391..cecb577b1b1 100644 --- a/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py +++ b/exporter/opentelemetry-exporter-prometheus/src/opentelemetry/exporter/prometheus/__init__.py @@ -27,7 +27,7 @@ .. code:: python from opentelemetry import metrics - from opentelemetry.exporter.prometheus import PrometheusMetricsExporter + from opentelemetry.exporter.prometheus import PrometheusMetricExporter from opentelemetry.sdk.metrics import Meter from prometheus_client import start_http_server @@ -39,7 +39,7 @@ meter = metrics.get_meter(__name__) # exporter to export metrics to Prometheus prefix = "MyAppPrefix" - exporter = PrometheusMetricsExporter(prefix) + exporter = PrometheusMetricExporter(prefix) # Starts the collect/export pipeline for metrics metrics.get_meter_provider().start_pipeline(meter, exporter, 5) @@ -70,17 +70,21 @@ from prometheus_client.core import ( REGISTRY, CounterMetricFamily, - SummaryMetricFamily, - UnknownMetricFamily, + GaugeMetricFamily, + HistogramMetricFamily, ) -from opentelemetry._metrics import Counter, Histogram from opentelemetry.sdk._metrics.export import ( - ExportRecord, MetricExporter, - MetricsExportResult, + MetricExportResult, +) +from opentelemetry.sdk._metrics.point import ( + AggregationTemporality, + Gauge, + Histogram, + Metric, + Sum, ) -from opentelemetry.sdk.metrics.export.aggregate import MinMaxSumCountAggregator logger = logging.getLogger(__name__) @@ -97,11 +101,9 @@ def __init__(self, prefix: str = ""): self._collector = CustomCollector(prefix) REGISTRY.register(self._collector) - def export( - self, export_records: Sequence[ExportRecord] - ) -> MetricsExportResult: + def export(self, export_records: Sequence[Metric]) -> MetricExportResult: self._collector.add_metrics_data(export_records) - return MetricsExportResult.SUCCESS + return MetricExportResult.SUCCESS def shutdown(self) -> None: REGISTRY.unregister(self._collector) @@ -119,7 +121,7 @@ def __init__(self, prefix: str = ""): r"[^\w]", re.UNICODE | re.IGNORECASE ) - def add_metrics_data(self, export_records: Sequence[ExportRecord]) -> None: + def add_metrics_data(self, export_records: Sequence[Metric]) -> None: self._metrics_to_export.append(export_records) def collect(self): @@ -137,52 +139,48 @@ def collect(self): if prometheus_metric is not None: yield prometheus_metric - def _translate_to_prometheus(self, export_record: ExportRecord): + def _translate_to_prometheus(self, export_record: Metric): prometheus_metric = None label_values = [] label_keys = [] - for label_tuple in export_record.labels: - label_keys.append(self._sanitize(label_tuple[0])) - label_values.append(label_tuple[1]) + for key, value in export_record.attributes.items(): + label_keys.append(self._sanitize(key)) + label_values.append(str(value)) metric_name = "" if self._prefix != "": metric_name = self._prefix + "_" - metric_name += self._sanitize(export_record.instrument.name) + metric_name += self._sanitize(export_record.name) - description = getattr(export_record.instrument, "description", "") - if isinstance(export_record.instrument, Counter): + description = export_record.description or "" + if isinstance(export_record.point, Sum): prometheus_metric = CounterMetricFamily( name=metric_name, documentation=description, labels=label_keys ) prometheus_metric.add_metric( - labels=label_values, value=export_record.aggregator.checkpoint + labels=label_values, value=export_record.point.value + ) + elif isinstance(export_record.point, Gauge): + prometheus_metric = GaugeMetricFamily( + name=metric_name, documentation=description, labels=label_keys + ) + prometheus_metric.add_metric( + labels=label_values, value=export_record.point.value ) # TODO: Add support for histograms when supported in OT - elif isinstance(export_record.instrument, Histogram): - value = export_record.aggregator.checkpoint - if isinstance(export_record.aggregator, MinMaxSumCountAggregator): - prometheus_metric = SummaryMetricFamily( - name=metric_name, - documentation=description, - labels=label_keys, - ) - prometheus_metric.add_metric( - labels=label_values, - count_value=value.count, - sum_value=value.sum, - ) - else: - prometheus_metric = UnknownMetricFamily( - name=metric_name, - documentation=description, - labels=label_keys, - ) - prometheus_metric.add_metric(labels=label_values, value=value) - + # elif isinstance(export_record.point, Histogram): + # value = export_record.point.sum + # prometheus_metric = HistogramMetricFamily( + # name=metric_name, + # documentation=description, + # labels=label_keys, + # ) + # prometheus_metric.add_metric(labels=label_values, buckets=export_record.point.explicit_bounds, sum_value=value) + # TODO: add support for Summary once implemented + # elif isinstance(export_record.point, Summary): else: logger.warning( - "Unsupported metric type. %s", type(export_record.instrument) + "Unsupported metric type. %s", type(export_record.point) ) return prometheus_metric diff --git a/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py b/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py index 5813fba33ca..8834cd9be7a 100644 --- a/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py +++ b/exporter/opentelemetry-exporter-prometheus/tests/test_prometheus_exporter.py @@ -16,28 +16,37 @@ from unittest import mock from prometheus_client import generate_latest -from prometheus_client.core import CounterMetricFamily +from prometheus_client.core import CounterMetricFamily, GaugeMetricFamily +from opentelemetry._metrics import get_meter_provider, set_meter_provider from opentelemetry.exporter.prometheus import ( CustomCollector, - PrometheusMetricsExporter, + PrometheusMetricExporter, ) -from opentelemetry.metrics import get_meter_provider, set_meter_provider -from opentelemetry.sdk import metrics -from opentelemetry.sdk.metrics.export import ExportRecord, MetricsExportResult -from opentelemetry.sdk.metrics.export.aggregate import ( - MinMaxSumCountAggregator, - SumAggregator, +from opentelemetry.sdk._metrics import MeterProvider +from opentelemetry.sdk._metrics.export import MetricExportResult +from opentelemetry.sdk._metrics.point import ( + AggregationTemporality, + Histogram, + Metric, ) from opentelemetry.sdk.util import get_dict_as_key +from opentelemetry.test.metrictestutil import ( + _generate_gauge, + _generate_metric, + _generate_sum, + _generate_unsupported_metric, +) class TestPrometheusMetricExporter(unittest.TestCase): def setUp(self): - set_meter_provider(metrics.MeterProvider()) + set_meter_provider(MeterProvider()) self._meter = get_meter_provider().get_meter(__name__) self._test_metric = self._meter.create_counter( - "testname", "testdesc", "unit", int, + "testname", + description="testdesc", + unit="unit", ) labels = {"environment": "staging"} self._labels_key = get_dict_as_key(labels) @@ -52,7 +61,7 @@ def setUp(self): def test_constructor(self): """Test the constructor.""" with self._registry_register_patch: - exporter = PrometheusMetricsExporter("testprefix") + exporter = PrometheusMetricExporter("testprefix") self.assertEqual(exporter._collector._prefix, "testprefix") self.assertTrue(self._mock_registry_register.called) @@ -60,62 +69,55 @@ def test_shutdown(self): with mock.patch( "prometheus_client.core.REGISTRY.unregister" ) as registry_unregister_patch: - exporter = PrometheusMetricsExporter() + exporter = PrometheusMetricExporter() exporter.shutdown() self.assertTrue(registry_unregister_patch.called) def test_export(self): with self._registry_register_patch: - record = ExportRecord( - self._test_metric, - self._labels_key, - SumAggregator(), - get_meter_provider().resource, - ) - exporter = PrometheusMetricsExporter() + record = _generate_sum("sum_int", 33) + exporter = PrometheusMetricExporter() result = exporter.export([record]) # pylint: disable=protected-access self.assertEqual(len(exporter._collector._metrics_to_export), 1) - self.assertIs(result, MetricsExportResult.SUCCESS) - - def test_min_max_sum_aggregator_to_prometheus(self): - meter = get_meter_provider().get_meter(__name__) - metric = meter.create_valuerecorder( - "test@name", "testdesc", "unit", int, [] + self.assertIs(result, MetricExportResult.SUCCESS) + + # # TODO: Add unit test for histogram + def test_histogram_to_prometheus(self): + record = _generate_metric( + "test@name", + Histogram( + time_unix_nano=1641946016139533244, + start_time_unix_nano=1641946016139533244, + bucket_counts=[1, 1], + sum=579.0, + explicit_bounds=[123.0, 456.0], + aggregation_temporality=AggregationTemporality.DELTA, + ), ) - labels = {} - key_labels = get_dict_as_key(labels) - aggregator = MinMaxSumCountAggregator() - aggregator.update(123) - aggregator.update(456) - aggregator.take_checkpoint() - record = ExportRecord( - metric, key_labels, aggregator, get_meter_provider().resource - ) - collector = CustomCollector("testprefix") - collector.add_metrics_data([record]) - result_bytes = generate_latest(collector) - result = result_bytes.decode("utf-8") - self.assertIn("testprefix_test_name_count 2.0", result) - self.assertIn("testprefix_test_name_sum 579.0", result) - - def test_counter_to_prometheus(self): - meter = get_meter_provider().get_meter(__name__) - metric = meter.create_counter("test@name", "testdesc", "unit", int,) + + # collector = CustomCollector("testprefix") + # collector.add_metrics_data([record]) + # result_bytes = generate_latest(collector) + # result = result_bytes.decode("utf-8") + # self.assertIn("testprefix_test_name_count 2.0", result) + # self.assertIn("testprefix_test_name_sum 579.0", result) + + def test_sum_to_prometheus(self): labels = {"environment@": "staging", "os": "Windows"} - key_labels = get_dict_as_key(labels) - aggregator = SumAggregator() - aggregator.update(123) - aggregator.take_checkpoint() - record = ExportRecord( - metric, key_labels, aggregator, get_meter_provider().resource + record = _generate_sum( + "test@sum", + 123, + attributes=labels, + description="testdesc", + unit="testunit", ) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) for prometheus_metric in collector.collect(): self.assertEqual(type(prometheus_metric), CounterMetricFamily) - self.assertEqual(prometheus_metric.name, "testprefix_test_name") + self.assertEqual(prometheus_metric.name, "testprefix_test_sum") self.assertEqual(prometheus_metric.documentation, "testdesc") self.assertTrue(len(prometheus_metric.samples) == 1) self.assertEqual(prometheus_metric.samples[0].value, 123) @@ -127,16 +129,37 @@ def test_counter_to_prometheus(self): prometheus_metric.samples[0].labels["os"], "Windows" ) - # TODO: Add unit test once GaugeAggregator is available - # TODO: Add unit test once Measure Aggregators are available + def test_gauge_to_prometheus(self): + labels = {"environment@": "dev", "os": "Unix"} + record = _generate_gauge( + "test@gauge", + 123, + attributes=labels, + description="testdesc", + unit="testunit", + ) + collector = CustomCollector("testprefix") + collector.add_metrics_data([record]) + + for prometheus_metric in collector.collect(): + self.assertEqual(type(prometheus_metric), GaugeMetricFamily) + self.assertEqual(prometheus_metric.name, "testprefix_test_gauge") + self.assertEqual(prometheus_metric.documentation, "testdesc") + self.assertTrue(len(prometheus_metric.samples) == 1) + self.assertEqual(prometheus_metric.samples[0].value, 123) + self.assertTrue(len(prometheus_metric.samples[0].labels) == 2) + self.assertEqual( + prometheus_metric.samples[0].labels["environment_"], "dev" + ) + self.assertEqual(prometheus_metric.samples[0].labels["os"], "Unix") def test_invalid_metric(self): - meter = get_meter_provider().get_meter(__name__) - metric = StubMetric("tesname", "testdesc", "unit", int, meter) labels = {"environment": "staging"} - key_labels = get_dict_as_key(labels) - record = ExportRecord( - metric, key_labels, None, get_meter_provider().resource + record = _generate_unsupported_metric( + "tesname", + attributes=labels, + description="testdesc", + unit="testunit", ) collector = CustomCollector("testprefix") collector.add_metrics_data([record]) @@ -152,18 +175,3 @@ def test_sanitize(self): self.assertEqual(collector._sanitize(",./?;:[]{}"), "__________") self.assertEqual(collector._sanitize("TestString"), "TestString") self.assertEqual(collector._sanitize("aAbBcC_12_oi"), "aAbBcC_12_oi") - - -class StubMetric(metrics.Metric): - def __init__( - self, - name: str, - description: str, - unit: str, - value_type, - meter, - enabled: bool = True, - ): - super().__init__( - name, description, unit, value_type, meter, enabled=enabled, - )