|
25 | 25 | import os
|
26 | 26 | import platform
|
27 | 27 | import threading
|
| 28 | +from importlib import metadata |
28 | 29 | from typing import (
|
29 | 30 | Callable,
|
30 | 31 | Dict,
|
|
41 | 42 | )
|
42 | 43 |
|
43 | 44 | import attr
|
44 |
| -from prometheus_client import CollectorRegistry, Counter, Gauge, Histogram, Metric |
| 45 | +from pkg_resources import parse_version |
| 46 | +from prometheus_client import ( |
| 47 | + CollectorRegistry, |
| 48 | + Counter, |
| 49 | + Gauge, |
| 50 | + Histogram, |
| 51 | + Metric, |
| 52 | + generate_latest, |
| 53 | +) |
45 | 54 | from prometheus_client.core import (
|
46 | 55 | REGISTRY,
|
47 | 56 | GaugeHistogramMetricFamily,
|
48 | 57 | GaugeMetricFamily,
|
49 | 58 | )
|
50 | 59 |
|
51 | 60 | from twisted.python.threadpool import ThreadPool
|
| 61 | +from twisted.web.resource import Resource |
| 62 | +from twisted.web.server import Request |
52 | 63 |
|
53 | 64 | # This module is imported for its side effects; flake8 needn't warn that it's unused.
|
54 | 65 | import synapse.metrics._reactor_metrics # noqa: F401
|
55 | 66 | from synapse.metrics._gc import MIN_TIME_BETWEEN_GCS, install_gc_manager
|
56 |
| -from synapse.metrics._twisted_exposition import MetricsResource, generate_latest |
57 | 67 | from synapse.metrics._types import Collector
|
58 | 68 | from synapse.types import StrSequence
|
59 | 69 | from synapse.util import SYNAPSE_VERSION
|
|
81 | 91 | single process." (source: https://prometheus.io/docs/concepts/jobs_instances/)
|
82 | 92 | """
|
83 | 93 |
|
| 94 | +CONTENT_TYPE_LATEST = "text/plain; version=0.0.4; charset=utf-8" |
| 95 | +""" |
| 96 | +Content type of the latest text format for Prometheus metrics. |
| 97 | +
|
| 98 | +Pulled directly from the prometheus_client library. |
| 99 | +""" |
| 100 | + |
| 101 | + |
| 102 | +def _set_prometheus_client_use_created_metrics(new_value: bool) -> None: |
| 103 | + """ |
| 104 | + Sets whether prometheus_client should expose `_created`-suffixed metrics for |
| 105 | + all gauges, histograms and summaries. |
| 106 | +
|
| 107 | + There is no programmatic way in the old versions of `prometheus_client` to disable |
| 108 | + this without poking at internals; the proper way in the old `prometheus_client` |
| 109 | + versions (> `0.14.0` < `0.18.0`) is to use an environment variable which |
| 110 | + prometheus_client loads at import time. For versions > `0.18.0`, we can use the |
| 111 | + dedicated `disable_created_metrics()`/`enable_created_metrics()`. |
| 112 | +
|
| 113 | + The motivation for disabling these `_created` metrics is that they're a waste of |
| 114 | + space as they're not useful but they take up space in Prometheus. It's not the end |
| 115 | + of the world if this doesn't work. |
| 116 | + """ |
| 117 | + import prometheus_client.metrics |
| 118 | + |
| 119 | + if hasattr(prometheus_client.metrics, "_use_created"): |
| 120 | + prometheus_client.metrics._use_created = new_value |
| 121 | + # Just log an error for old versions that don't support disabling the unecessary |
| 122 | + # metrics. It's not the end of the world if this doesn't work as it just means extra |
| 123 | + # wasted space taken up in Prometheus but things keep working. |
| 124 | + elif parse_version(metadata.version("prometheus_client")) < parse_version("0.14.0"): |
| 125 | + logger.error( |
| 126 | + "Can't disable `_created` metrics in prometheus_client (unsupported `prometheus_client` version, too old)" |
| 127 | + ) |
| 128 | + # If the attribute doesn't exist on a newer version, this is a sign that the brittle |
| 129 | + # hack is broken. We should consider updating the minimum version of |
| 130 | + # `prometheus_client` to a version (> `0.18.0`) where we can use dedicated |
| 131 | + # `disable_created_metrics()`/`enable_created_metrics()` functions. |
| 132 | + else: |
| 133 | + raise Exception( |
| 134 | + "Can't disable `_created` metrics in prometheus_client (brittle hack broken?)" |
| 135 | + ) |
| 136 | + |
| 137 | + |
| 138 | +# Set this globally so it applies wherever we generate/collect metrics |
| 139 | +_set_prometheus_client_use_created_metrics(False) |
| 140 | + |
84 | 141 |
|
85 | 142 | class _RegistryProxy:
|
86 | 143 | @staticmethod
|
@@ -508,6 +565,23 @@ def register_threadpool(name: str, threadpool: ThreadPool) -> None:
|
508 | 565 | )
|
509 | 566 |
|
510 | 567 |
|
| 568 | +class MetricsResource(Resource): |
| 569 | + """ |
| 570 | + Twisted ``Resource`` that serves prometheus metrics. |
| 571 | + """ |
| 572 | + |
| 573 | + isLeaf = True |
| 574 | + |
| 575 | + def __init__(self, registry: CollectorRegistry = REGISTRY): |
| 576 | + self.registry = registry |
| 577 | + |
| 578 | + def render_GET(self, request: Request) -> bytes: |
| 579 | + request.setHeader(b"Content-Type", CONTENT_TYPE_LATEST.encode("ascii")) |
| 580 | + response = generate_latest(self.registry) |
| 581 | + request.setHeader(b"Content-Length", str(len(response))) |
| 582 | + return response |
| 583 | + |
| 584 | + |
511 | 585 | __all__ = [
|
512 | 586 | "Collector",
|
513 | 587 | "MetricsResource",
|
|
0 commit comments