mirror of
https://github.com/element-hq/synapse.git
synced 2025-11-01 00:04:03 -04:00
Update metrics linting to be able to handle custom metrics (#18733)
Part of https://github.com/element-hq/synapse/issues/18592
This commit is contained in:
parent
e43a1cec84
commit
e16fbdcdcc
1
changelog.d/18733.misc
Normal file
1
changelog.d/18733.misc
Normal file
@ -0,0 +1 @@
|
|||||||
|
Update metrics linting to be able to handle custom metrics.
|
||||||
16
mypy.ini
16
mypy.ini
@ -1,6 +1,17 @@
|
|||||||
[mypy]
|
[mypy]
|
||||||
namespace_packages = True
|
namespace_packages = True
|
||||||
plugins = pydantic.mypy, mypy_zope:plugin, scripts-dev/mypy_synapse_plugin.py
|
# Our custom mypy plugin should remain first in this list.
|
||||||
|
#
|
||||||
|
# mypy has a limitation where it only chooses the first plugin that returns a non-None
|
||||||
|
# value for each hook (known-limitation, c.f.
|
||||||
|
# https://github.com/python/mypy/issues/19524). We workaround this by putting our custom
|
||||||
|
# plugin first in the plugin order and then manually calling any other conflicting
|
||||||
|
# plugin hooks in our own plugin followed by our own checks.
|
||||||
|
#
|
||||||
|
# If you add a new plugin, make sure to check whether the hooks being used conflict with
|
||||||
|
# our custom plugin hooks and if so, manually call the other plugin's hooks in our
|
||||||
|
# custom plugin. (also applies to if the plugin is updated in the future)
|
||||||
|
plugins = scripts-dev/mypy_synapse_plugin.py, pydantic.mypy, mypy_zope:plugin
|
||||||
follow_imports = normal
|
follow_imports = normal
|
||||||
show_error_codes = True
|
show_error_codes = True
|
||||||
show_traceback = True
|
show_traceback = True
|
||||||
@ -99,3 +110,6 @@ ignore_missing_imports = True
|
|||||||
|
|
||||||
[mypy-multipart.*]
|
[mypy-multipart.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-mypy_zope.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|||||||
@ -23,16 +23,21 @@
|
|||||||
can crop up, e.g the cache descriptors.
|
can crop up, e.g the cache descriptors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Callable, Optional, Tuple, Type, Union
|
import enum
|
||||||
|
from typing import Callable, Mapping, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
|
import attr
|
||||||
import mypy.types
|
import mypy.types
|
||||||
from mypy.erasetype import remove_instance_last_known_values
|
from mypy.erasetype import remove_instance_last_known_values
|
||||||
from mypy.errorcodes import ErrorCode
|
from mypy.errorcodes import ErrorCode
|
||||||
from mypy.nodes import ARG_NAMED_OPT, ListExpr, NameExpr, TempNode, TupleExpr, Var
|
from mypy.nodes import ARG_NAMED_OPT, ListExpr, NameExpr, TempNode, TupleExpr, Var
|
||||||
from mypy.plugin import (
|
from mypy.plugin import (
|
||||||
|
ClassDefContext,
|
||||||
|
Context,
|
||||||
FunctionLike,
|
FunctionLike,
|
||||||
FunctionSigContext,
|
FunctionSigContext,
|
||||||
MethodSigContext,
|
MethodSigContext,
|
||||||
|
MypyFile,
|
||||||
Plugin,
|
Plugin,
|
||||||
)
|
)
|
||||||
from mypy.typeops import bind_self
|
from mypy.typeops import bind_self
|
||||||
@ -41,12 +46,15 @@ from mypy.types import (
|
|||||||
CallableType,
|
CallableType,
|
||||||
Instance,
|
Instance,
|
||||||
NoneType,
|
NoneType,
|
||||||
|
Options,
|
||||||
TupleType,
|
TupleType,
|
||||||
TypeAliasType,
|
TypeAliasType,
|
||||||
TypeVarType,
|
TypeVarType,
|
||||||
UninhabitedType,
|
UninhabitedType,
|
||||||
UnionType,
|
UnionType,
|
||||||
)
|
)
|
||||||
|
from mypy_zope import plugin as mypy_zope_plugin
|
||||||
|
from pydantic.mypy import plugin as mypy_pydantic_plugin
|
||||||
|
|
||||||
PROMETHEUS_METRIC_MISSING_SERVER_NAME_LABEL = ErrorCode(
|
PROMETHEUS_METRIC_MISSING_SERVER_NAME_LABEL = ErrorCode(
|
||||||
"missing-server-name-label",
|
"missing-server-name-label",
|
||||||
@ -54,19 +62,153 @@ PROMETHEUS_METRIC_MISSING_SERVER_NAME_LABEL = ErrorCode(
|
|||||||
category="per-homeserver-tenant-metrics",
|
category="per-homeserver-tenant-metrics",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PROMETHEUS_METRIC_MISSING_FROM_LIST_TO_CHECK = ErrorCode(
|
||||||
|
"metric-type-missing-from-list",
|
||||||
|
"Every Prometheus metric type must be included in the `prometheus_metric_fullname_to_label_arg_map`.",
|
||||||
|
category="per-homeserver-tenant-metrics",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Sentinel(enum.Enum):
|
||||||
|
# defining a sentinel in this way allows mypy to correctly handle the
|
||||||
|
# type of a dictionary lookup and subsequent type narrowing.
|
||||||
|
UNSET_SENTINEL = object()
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class ArgLocation:
|
||||||
|
keyword_name: str
|
||||||
|
"""
|
||||||
|
The keyword argument name for this argument
|
||||||
|
"""
|
||||||
|
position: int
|
||||||
|
"""
|
||||||
|
The 0-based positional index of this argument
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
prometheus_metric_fullname_to_label_arg_map: Mapping[str, Optional[ArgLocation]] = {
|
||||||
|
# `Collector` subclasses:
|
||||||
|
"prometheus_client.metrics.MetricWrapperBase": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.metrics.Counter": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.metrics.Histogram": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.metrics.Gauge": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.metrics.Summary": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.metrics.Info": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.metrics.Enum": ArgLocation("labelnames", 2),
|
||||||
|
"synapse.metrics.LaterGauge": ArgLocation("labelnames", 2),
|
||||||
|
"synapse.metrics.InFlightGauge": ArgLocation("labels", 2),
|
||||||
|
"synapse.metrics.GaugeBucketCollector": ArgLocation("labelnames", 2),
|
||||||
|
"prometheus_client.registry.Collector": None,
|
||||||
|
"prometheus_client.registry._EmptyCollector": None,
|
||||||
|
"prometheus_client.registry.CollectorRegistry": None,
|
||||||
|
"prometheus_client.process_collector.ProcessCollector": None,
|
||||||
|
"prometheus_client.platform_collector.PlatformCollector": None,
|
||||||
|
"prometheus_client.gc_collector.GCCollector": None,
|
||||||
|
"synapse.metrics._gc.GCCounts": None,
|
||||||
|
"synapse.metrics._gc.PyPyGCStats": None,
|
||||||
|
"synapse.metrics._reactor_metrics.ReactorLastSeenMetric": None,
|
||||||
|
"synapse.metrics.CPUMetrics": None,
|
||||||
|
"synapse.metrics.jemalloc.JemallocCollector": None,
|
||||||
|
"synapse.util.metrics.DynamicCollectorRegistry": None,
|
||||||
|
"synapse.metrics.background_process_metrics._Collector": None,
|
||||||
|
#
|
||||||
|
# `Metric` subclasses:
|
||||||
|
"prometheus_client.metrics_core.Metric": None,
|
||||||
|
"prometheus_client.metrics_core.UnknownMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"prometheus_client.metrics_core.CounterMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"prometheus_client.metrics_core.GaugeMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"prometheus_client.metrics_core.SummaryMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"prometheus_client.metrics_core.InfoMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"prometheus_client.metrics_core.HistogramMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"prometheus_client.metrics_core.GaugeHistogramMetricFamily": ArgLocation(
|
||||||
|
"labels", 4
|
||||||
|
),
|
||||||
|
"prometheus_client.metrics_core.StateSetMetricFamily": ArgLocation("labels", 3),
|
||||||
|
"synapse.metrics.GaugeHistogramMetricFamilyWithLabels": ArgLocation(
|
||||||
|
"labelnames", 4
|
||||||
|
),
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
Map from the fullname of the Prometheus `Metric`/`Collector` classes to the keyword
|
||||||
|
argument name and positional index of the label names. This map is useful because
|
||||||
|
different metrics have different signatures for passing in label names and we just need
|
||||||
|
to know where to look.
|
||||||
|
|
||||||
|
This map should include any metrics that we collect with Prometheus. Which corresponds
|
||||||
|
to anything that inherits from `prometheus_client.registry.Collector`
|
||||||
|
(`synapse.metrics._types.Collector`) or `prometheus_client.metrics_core.Metric`. The
|
||||||
|
exhaustiveness of this list is enforced by `analyze_prometheus_metric_classes`.
|
||||||
|
|
||||||
|
The entries with `None` always fail the lint because they don't have a `labelnames`
|
||||||
|
argument (therefore, no `SERVER_NAME_LABEL`), but we include them here so that people
|
||||||
|
can notice and manually allow via a type ignore comment as the source of truth
|
||||||
|
should be in the source code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Unbound at this point because we don't know the mypy version yet.
|
||||||
|
# This is set in the `plugin(...)` function below.
|
||||||
|
MypyPydanticPluginClass: Type[Plugin]
|
||||||
|
MypyZopePluginClass: Type[Plugin]
|
||||||
|
|
||||||
|
|
||||||
class SynapsePlugin(Plugin):
|
class SynapsePlugin(Plugin):
|
||||||
|
def __init__(self, options: Options):
|
||||||
|
super().__init__(options)
|
||||||
|
self.mypy_pydantic_plugin = MypyPydanticPluginClass(options)
|
||||||
|
self.mypy_zope_plugin = MypyZopePluginClass(options)
|
||||||
|
|
||||||
|
def set_modules(self, modules: dict[str, MypyFile]) -> None:
|
||||||
|
"""
|
||||||
|
This is called by mypy internals. We have to override this to ensure it's also
|
||||||
|
called for any other plugins that we're manually handling.
|
||||||
|
|
||||||
|
Here is how mypy describes it:
|
||||||
|
|
||||||
|
> [`self._modules`] can't be set in `__init__` because it is executed too soon
|
||||||
|
> in `build.py`. Therefore, `build.py` *must* set it later before graph processing
|
||||||
|
> starts by calling `set_modules()`.
|
||||||
|
"""
|
||||||
|
super().set_modules(modules)
|
||||||
|
self.mypy_pydantic_plugin.set_modules(modules)
|
||||||
|
self.mypy_zope_plugin.set_modules(modules)
|
||||||
|
|
||||||
|
def get_base_class_hook(
|
||||||
|
self, fullname: str
|
||||||
|
) -> Optional[Callable[[ClassDefContext], None]]:
|
||||||
|
def _get_base_class_hook(ctx: ClassDefContext) -> None:
|
||||||
|
# Run any `get_base_class_hook` checks from other plugins first.
|
||||||
|
#
|
||||||
|
# Unfortunately, because mypy only chooses the first plugin that returns a
|
||||||
|
# non-None value (known-limitation, c.f.
|
||||||
|
# https://github.com/python/mypy/issues/19524), we workaround this by
|
||||||
|
# putting our custom plugin first in the plugin order and then calling the
|
||||||
|
# other plugin's hook manually followed by our own checks.
|
||||||
|
if callback := self.mypy_pydantic_plugin.get_base_class_hook(fullname):
|
||||||
|
callback(ctx)
|
||||||
|
if callback := self.mypy_zope_plugin.get_base_class_hook(fullname):
|
||||||
|
callback(ctx)
|
||||||
|
|
||||||
|
# Now run our own checks
|
||||||
|
analyze_prometheus_metric_classes(ctx)
|
||||||
|
|
||||||
|
return _get_base_class_hook
|
||||||
|
|
||||||
def get_function_signature_hook(
|
def get_function_signature_hook(
|
||||||
self, fullname: str
|
self, fullname: str
|
||||||
) -> Optional[Callable[[FunctionSigContext], FunctionLike]]:
|
) -> Optional[Callable[[FunctionSigContext], FunctionLike]]:
|
||||||
if fullname in (
|
# Strip off the unique identifier for classes that are dynamically created inside
|
||||||
"prometheus_client.metrics.Counter",
|
# functions. ex. `synapse.metrics.jemalloc.JemallocCollector@185` (this is the line
|
||||||
"prometheus_client.metrics.Histogram",
|
# number)
|
||||||
"prometheus_client.metrics.Gauge",
|
if "@" in fullname:
|
||||||
# TODO: Add other prometheus_client metrics that need checking as we
|
fullname = fullname.split("@", 1)[0]
|
||||||
# refactor, see https://github.com/element-hq/synapse/issues/18592
|
|
||||||
):
|
# Look for any Prometheus metrics to make sure they have the `SERVER_NAME_LABEL`
|
||||||
return check_prometheus_metric_instantiation
|
# label.
|
||||||
|
if fullname in prometheus_metric_fullname_to_label_arg_map.keys():
|
||||||
|
# Because it's difficult to determine the `fullname` of the function in the
|
||||||
|
# callback, let's just pass it in while we have it.
|
||||||
|
return lambda ctx: check_prometheus_metric_instantiation(ctx, fullname)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -90,7 +232,44 @@ class SynapsePlugin(Plugin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_prometheus_metric_instantiation(ctx: FunctionSigContext) -> CallableType:
|
def analyze_prometheus_metric_classes(ctx: ClassDefContext) -> None:
|
||||||
|
"""
|
||||||
|
Cross-check the list of Prometheus metric classes against the
|
||||||
|
`prometheus_metric_fullname_to_label_arg_map` to ensure the list is exhaustive and
|
||||||
|
up-to-date.
|
||||||
|
"""
|
||||||
|
|
||||||
|
fullname = ctx.cls.fullname
|
||||||
|
# Strip off the unique identifier for classes that are dynamically created inside
|
||||||
|
# functions. ex. `synapse.metrics.jemalloc.JemallocCollector@185` (this is the line
|
||||||
|
# number)
|
||||||
|
if "@" in fullname:
|
||||||
|
fullname = fullname.split("@", 1)[0]
|
||||||
|
|
||||||
|
if any(
|
||||||
|
ancestor_type.fullname
|
||||||
|
in (
|
||||||
|
# All of the Prometheus metric classes inherit from the `Collector`.
|
||||||
|
"prometheus_client.registry.Collector",
|
||||||
|
"synapse.metrics._types.Collector",
|
||||||
|
# And custom metrics that inherit from `Metric`.
|
||||||
|
"prometheus_client.metrics_core.Metric",
|
||||||
|
)
|
||||||
|
for ancestor_type in ctx.cls.info.mro
|
||||||
|
):
|
||||||
|
if fullname not in prometheus_metric_fullname_to_label_arg_map:
|
||||||
|
ctx.api.fail(
|
||||||
|
f"Expected {fullname} to be in `prometheus_metric_fullname_to_label_arg_map`, "
|
||||||
|
f"but it was not found. This is a problem with our custom mypy plugin. "
|
||||||
|
f"Please add it to the map.",
|
||||||
|
Context(),
|
||||||
|
code=PROMETHEUS_METRIC_MISSING_FROM_LIST_TO_CHECK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_prometheus_metric_instantiation(
|
||||||
|
ctx: FunctionSigContext, fullname: str
|
||||||
|
) -> CallableType:
|
||||||
"""
|
"""
|
||||||
Ensure that the `prometheus_client` metrics include the `SERVER_NAME_LABEL` label
|
Ensure that the `prometheus_client` metrics include the `SERVER_NAME_LABEL` label
|
||||||
when instantiated.
|
when instantiated.
|
||||||
@ -103,18 +282,49 @@ def check_prometheus_metric_instantiation(ctx: FunctionSigContext) -> CallableTy
|
|||||||
Python garbage collection, and Twisted reactor tick time, which shouldn't have the
|
Python garbage collection, and Twisted reactor tick time, which shouldn't have the
|
||||||
`SERVER_NAME_LABEL`. In those cases, use a type ignore comment to disable the
|
`SERVER_NAME_LABEL`. In those cases, use a type ignore comment to disable the
|
||||||
check, e.g. `# type: ignore[missing-server-name-label]`.
|
check, e.g. `# type: ignore[missing-server-name-label]`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
ctx: The `FunctionSigContext` from mypy.
|
||||||
|
fullname: The fully qualified name of the function being called,
|
||||||
|
e.g. `"prometheus_client.metrics.Counter"`
|
||||||
"""
|
"""
|
||||||
# The true signature, this isn't being modified so this is what will be returned.
|
# The true signature, this isn't being modified so this is what will be returned.
|
||||||
signature: CallableType = ctx.default_signature
|
signature = ctx.default_signature
|
||||||
|
|
||||||
|
# Find where the label names argument is in the function signature.
|
||||||
|
arg_location = prometheus_metric_fullname_to_label_arg_map.get(
|
||||||
|
fullname, Sentinel.UNSET_SENTINEL
|
||||||
|
)
|
||||||
|
assert arg_location is not Sentinel.UNSET_SENTINEL, (
|
||||||
|
f"Expected to find {fullname} in `prometheus_metric_fullname_to_label_arg_map`, "
|
||||||
|
f"but it was not found. This is a problem with our custom mypy plugin. "
|
||||||
|
f"Please add it to the map. Context: {ctx.context}"
|
||||||
|
)
|
||||||
|
# People should be using `# type: ignore[missing-server-name-label]` for
|
||||||
|
# process-level metrics that should not have the `SERVER_NAME_LABEL`.
|
||||||
|
if arg_location is None:
|
||||||
|
ctx.api.fail(
|
||||||
|
f"{signature.name} does not have a `labelnames`/`labels` argument "
|
||||||
|
"(if this is untrue, update `prometheus_metric_fullname_to_label_arg_map` "
|
||||||
|
"in our custom mypy plugin) and should probably have a type ignore comment, "
|
||||||
|
"e.g. `# type: ignore[missing-server-name-label]`. The reason we don't "
|
||||||
|
"automatically ignore this is the source of truth should be in the source code.",
|
||||||
|
ctx.context,
|
||||||
|
code=PROMETHEUS_METRIC_MISSING_SERVER_NAME_LABEL,
|
||||||
|
)
|
||||||
|
return signature
|
||||||
|
|
||||||
# Sanity check the arguments are still as expected in this version of
|
# Sanity check the arguments are still as expected in this version of
|
||||||
# `prometheus_client`. ex. `Counter(name, documentation, labelnames, ...)`
|
# `prometheus_client`. ex. `Counter(name, documentation, labelnames, ...)`
|
||||||
#
|
#
|
||||||
# `signature.arg_names` should be: ["name", "documentation", "labelnames", ...]
|
# `signature.arg_names` should be: ["name", "documentation", "labelnames", ...]
|
||||||
if len(signature.arg_names) < 3 or signature.arg_names[2] != "labelnames":
|
if (
|
||||||
|
len(signature.arg_names) < (arg_location.position + 1)
|
||||||
|
or signature.arg_names[arg_location.position] != arg_location.keyword_name
|
||||||
|
):
|
||||||
ctx.api.fail(
|
ctx.api.fail(
|
||||||
f"Expected the 3rd argument of {signature.name} to be 'labelnames', but got "
|
f"Expected argument number {arg_location.position + 1} of {signature.name} to be `labelnames`/`labels`, "
|
||||||
f"{signature.arg_names[2]}",
|
f"but got {signature.arg_names[arg_location.position]}",
|
||||||
ctx.context,
|
ctx.context,
|
||||||
)
|
)
|
||||||
return signature
|
return signature
|
||||||
@ -137,7 +347,11 @@ def check_prometheus_metric_instantiation(ctx: FunctionSigContext) -> CallableTy
|
|||||||
# ...
|
# ...
|
||||||
# ]
|
# ]
|
||||||
# ```
|
# ```
|
||||||
labelnames_arg_expression = ctx.args[2][0] if len(ctx.args[2]) > 0 else None
|
labelnames_arg_expression = (
|
||||||
|
ctx.args[arg_location.position][0]
|
||||||
|
if len(ctx.args[arg_location.position]) > 0
|
||||||
|
else None
|
||||||
|
)
|
||||||
if isinstance(labelnames_arg_expression, (ListExpr, TupleExpr)):
|
if isinstance(labelnames_arg_expression, (ListExpr, TupleExpr)):
|
||||||
# Check if the `labelnames` argument includes the `server_name` label (`SERVER_NAME_LABEL`).
|
# Check if the `labelnames` argument includes the `server_name` label (`SERVER_NAME_LABEL`).
|
||||||
for labelname_expression in labelnames_arg_expression.items:
|
for labelname_expression in labelnames_arg_expression.items:
|
||||||
@ -476,10 +690,13 @@ def is_cacheable(
|
|||||||
|
|
||||||
|
|
||||||
def plugin(version: str) -> Type[SynapsePlugin]:
|
def plugin(version: str) -> Type[SynapsePlugin]:
|
||||||
|
global MypyPydanticPluginClass, MypyZopePluginClass
|
||||||
# This is the entry point of the plugin, and lets us deal with the fact
|
# This is the entry point of the plugin, and lets us deal with the fact
|
||||||
# that the mypy plugin interface is *not* stable by looking at the version
|
# that the mypy plugin interface is *not* stable by looking at the version
|
||||||
# string.
|
# string.
|
||||||
#
|
#
|
||||||
# However, since we pin the version of mypy Synapse uses in CI, we don't
|
# However, since we pin the version of mypy Synapse uses in CI, we don't
|
||||||
# really care.
|
# really care.
|
||||||
|
MypyPydanticPluginClass = mypy_pydantic_plugin(version)
|
||||||
|
MypyZopePluginClass = mypy_zope_plugin(version)
|
||||||
return SynapsePlugin
|
return SynapsePlugin
|
||||||
|
|||||||
@ -170,7 +170,9 @@ class LaterGauge(Collector):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def collect(self) -> Iterable[Metric]:
|
def collect(self) -> Iterable[Metric]:
|
||||||
g = GaugeMetricFamily(self.name, self.desc, labels=self.labelnames)
|
# The decision to add `SERVER_NAME_LABEL` is from the `LaterGauge` usage itself
|
||||||
|
# (we don't enforce it here, one level up).
|
||||||
|
g = GaugeMetricFamily(self.name, self.desc, labels=self.labelnames) # type: ignore[missing-server-name-label]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
calls = self.caller()
|
calls = self.caller()
|
||||||
@ -304,7 +306,9 @@ class InFlightGauge(Generic[MetricsEntry], Collector):
|
|||||||
|
|
||||||
Note: may be called by a separate thread.
|
Note: may be called by a separate thread.
|
||||||
"""
|
"""
|
||||||
in_flight = GaugeMetricFamily(
|
# The decision to add `SERVER_NAME_LABEL` is from the `GaugeBucketCollector`
|
||||||
|
# usage itself (we don't enforce it here, one level up).
|
||||||
|
in_flight = GaugeMetricFamily( # type: ignore[missing-server-name-label]
|
||||||
self.name + "_total", self.desc, labels=self.labels
|
self.name + "_total", self.desc, labels=self.labels
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -328,7 +332,9 @@ class InFlightGauge(Generic[MetricsEntry], Collector):
|
|||||||
yield in_flight
|
yield in_flight
|
||||||
|
|
||||||
for name in self.sub_metrics:
|
for name in self.sub_metrics:
|
||||||
gauge = GaugeMetricFamily(
|
# The decision to add `SERVER_NAME_LABEL` is from the `InFlightGauge` usage
|
||||||
|
# itself (we don't enforce it here, one level up).
|
||||||
|
gauge = GaugeMetricFamily( # type: ignore[missing-server-name-label]
|
||||||
"_".join([self.name, name]), "", labels=self.labels
|
"_".join([self.name, name]), "", labels=self.labels
|
||||||
)
|
)
|
||||||
for key, metrics in metrics_by_key.items():
|
for key, metrics in metrics_by_key.items():
|
||||||
@ -483,7 +489,9 @@ class GaugeBucketCollector(Collector):
|
|||||||
# that bucket or below.
|
# that bucket or below.
|
||||||
accumulated_values = itertools.accumulate(bucket_values)
|
accumulated_values = itertools.accumulate(bucket_values)
|
||||||
|
|
||||||
return GaugeHistogramMetricFamilyWithLabels(
|
# The decision to add `SERVER_NAME_LABEL` is from the `GaugeBucketCollector`
|
||||||
|
# usage itself (we don't enforce it here, one level up).
|
||||||
|
return GaugeHistogramMetricFamilyWithLabels( # type: ignore[missing-server-name-label]
|
||||||
name=self._name,
|
name=self._name,
|
||||||
documentation=self._documentation,
|
documentation=self._documentation,
|
||||||
labelnames=self._labelnames,
|
labelnames=self._labelnames,
|
||||||
@ -519,16 +527,19 @@ class CPUMetrics(Collector):
|
|||||||
line = s.read()
|
line = s.read()
|
||||||
raw_stats = line.split(") ", 1)[1].split(" ")
|
raw_stats = line.split(") ", 1)[1].split(" ")
|
||||||
|
|
||||||
user = GaugeMetricFamily("process_cpu_user_seconds_total", "")
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
user = GaugeMetricFamily("process_cpu_user_seconds_total", "") # type: ignore[missing-server-name-label]
|
||||||
user.add_metric([], float(raw_stats[11]) / self.ticks_per_sec)
|
user.add_metric([], float(raw_stats[11]) / self.ticks_per_sec)
|
||||||
yield user
|
yield user
|
||||||
|
|
||||||
sys = GaugeMetricFamily("process_cpu_system_seconds_total", "")
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
sys = GaugeMetricFamily("process_cpu_system_seconds_total", "") # type: ignore[missing-server-name-label]
|
||||||
sys.add_metric([], float(raw_stats[12]) / self.ticks_per_sec)
|
sys.add_metric([], float(raw_stats[12]) / self.ticks_per_sec)
|
||||||
yield sys
|
yield sys
|
||||||
|
|
||||||
|
|
||||||
REGISTRY.register(CPUMetrics())
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
REGISTRY.register(CPUMetrics()) # type: ignore[missing-server-name-label]
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@ -83,7 +83,8 @@ gc_time = Histogram( # type: ignore[missing-server-name-label]
|
|||||||
|
|
||||||
class GCCounts(Collector):
|
class GCCounts(Collector):
|
||||||
def collect(self) -> Iterable[Metric]:
|
def collect(self) -> Iterable[Metric]:
|
||||||
cm = GaugeMetricFamily("python_gc_counts", "GC object counts", labels=["gen"])
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
cm = GaugeMetricFamily("python_gc_counts", "GC object counts", labels=["gen"]) # type: ignore[missing-server-name-label]
|
||||||
for n, m in enumerate(gc.get_count()):
|
for n, m in enumerate(gc.get_count()):
|
||||||
cm.add_metric([str(n)], m)
|
cm.add_metric([str(n)], m)
|
||||||
|
|
||||||
@ -102,7 +103,8 @@ def install_gc_manager() -> None:
|
|||||||
if running_on_pypy:
|
if running_on_pypy:
|
||||||
return
|
return
|
||||||
|
|
||||||
REGISTRY.register(GCCounts())
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
REGISTRY.register(GCCounts()) # type: ignore[missing-server-name-label]
|
||||||
|
|
||||||
gc.disable()
|
gc.disable()
|
||||||
|
|
||||||
@ -177,7 +179,8 @@ class PyPyGCStats(Collector):
|
|||||||
#
|
#
|
||||||
# Total time spent in GC: 0.073 # s.total_gc_time
|
# Total time spent in GC: 0.073 # s.total_gc_time
|
||||||
|
|
||||||
pypy_gc_time = CounterMetricFamily(
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
pypy_gc_time = CounterMetricFamily( # type: ignore[missing-server-name-label]
|
||||||
"pypy_gc_time_seconds_total",
|
"pypy_gc_time_seconds_total",
|
||||||
"Total time spent in PyPy GC",
|
"Total time spent in PyPy GC",
|
||||||
labels=[],
|
labels=[],
|
||||||
@ -185,7 +188,8 @@ class PyPyGCStats(Collector):
|
|||||||
pypy_gc_time.add_metric([], s.total_gc_time / 1000)
|
pypy_gc_time.add_metric([], s.total_gc_time / 1000)
|
||||||
yield pypy_gc_time
|
yield pypy_gc_time
|
||||||
|
|
||||||
pypy_mem = GaugeMetricFamily(
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
pypy_mem = GaugeMetricFamily( # type: ignore[missing-server-name-label]
|
||||||
"pypy_memory_bytes",
|
"pypy_memory_bytes",
|
||||||
"Memory tracked by PyPy allocator",
|
"Memory tracked by PyPy allocator",
|
||||||
labels=["state", "class", "kind"],
|
labels=["state", "class", "kind"],
|
||||||
@ -209,4 +213,5 @@ class PyPyGCStats(Collector):
|
|||||||
|
|
||||||
|
|
||||||
if running_on_pypy:
|
if running_on_pypy:
|
||||||
REGISTRY.register(PyPyGCStats())
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
REGISTRY.register(PyPyGCStats()) # type: ignore[missing-server-name-label]
|
||||||
|
|||||||
@ -115,7 +115,8 @@ class ReactorLastSeenMetric(Collector):
|
|||||||
self._call_wrapper = call_wrapper
|
self._call_wrapper = call_wrapper
|
||||||
|
|
||||||
def collect(self) -> Iterable[Metric]:
|
def collect(self) -> Iterable[Metric]:
|
||||||
cm = GaugeMetricFamily(
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
cm = GaugeMetricFamily( # type: ignore[missing-server-name-label]
|
||||||
"python_twisted_reactor_last_seen",
|
"python_twisted_reactor_last_seen",
|
||||||
"Seconds since the Twisted reactor was last seen",
|
"Seconds since the Twisted reactor was last seen",
|
||||||
)
|
)
|
||||||
@ -166,4 +167,5 @@ except Exception as e:
|
|||||||
|
|
||||||
|
|
||||||
if wrapper:
|
if wrapper:
|
||||||
REGISTRY.register(ReactorLastSeenMetric(wrapper))
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
REGISTRY.register(ReactorLastSeenMetric(wrapper)) # type: ignore[missing-server-name-label]
|
||||||
|
|||||||
@ -167,7 +167,9 @@ class _Collector(Collector):
|
|||||||
yield from m.collect()
|
yield from m.collect()
|
||||||
|
|
||||||
|
|
||||||
REGISTRY.register(_Collector())
|
# The `SERVER_NAME_LABEL` is included in the individual metrics added to this registry,
|
||||||
|
# so we don't need to worry about it on the collector itself.
|
||||||
|
REGISTRY.register(_Collector()) # type: ignore[missing-server-name-label]
|
||||||
|
|
||||||
|
|
||||||
class _BackgroundProcess:
|
class _BackgroundProcess:
|
||||||
|
|||||||
@ -188,7 +188,8 @@ def _setup_jemalloc_stats() -> None:
|
|||||||
def collect(self) -> Iterable[Metric]:
|
def collect(self) -> Iterable[Metric]:
|
||||||
stats.refresh_stats()
|
stats.refresh_stats()
|
||||||
|
|
||||||
g = GaugeMetricFamily(
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
g = GaugeMetricFamily( # type: ignore[missing-server-name-label]
|
||||||
"jemalloc_stats_app_memory_bytes",
|
"jemalloc_stats_app_memory_bytes",
|
||||||
"The stats reported by jemalloc",
|
"The stats reported by jemalloc",
|
||||||
labels=["type"],
|
labels=["type"],
|
||||||
@ -230,7 +231,8 @@ def _setup_jemalloc_stats() -> None:
|
|||||||
|
|
||||||
yield g
|
yield g
|
||||||
|
|
||||||
REGISTRY.register(JemallocCollector())
|
# This is a process-level metric, so it does not have the `SERVER_NAME_LABEL`.
|
||||||
|
REGISTRY.register(JemallocCollector()) # type: ignore[missing-server-name-label]
|
||||||
|
|
||||||
logger.debug("Added jemalloc stats")
|
logger.debug("Added jemalloc stats")
|
||||||
|
|
||||||
|
|||||||
@ -42,7 +42,10 @@ TRACK_MEMORY_USAGE = False
|
|||||||
|
|
||||||
# We track cache metrics in a special registry that lets us update the metrics
|
# We track cache metrics in a special registry that lets us update the metrics
|
||||||
# just before they are returned from the scrape endpoint.
|
# just before they are returned from the scrape endpoint.
|
||||||
CACHE_METRIC_REGISTRY = DynamicCollectorRegistry()
|
#
|
||||||
|
# The `SERVER_NAME_LABEL` is included in the individual metrics added to this registry,
|
||||||
|
# so we don't need to worry about it on the collector itself.
|
||||||
|
CACHE_METRIC_REGISTRY = DynamicCollectorRegistry() # type: ignore[missing-server-name-label]
|
||||||
|
|
||||||
cache_size = Gauge(
|
cache_size = Gauge(
|
||||||
"synapse_util_caches_cache_size",
|
"synapse_util_caches_cache_size",
|
||||||
|
|||||||
@ -59,7 +59,8 @@ class TestMauLimit(unittest.TestCase):
|
|||||||
foo: int
|
foo: int
|
||||||
bar: int
|
bar: int
|
||||||
|
|
||||||
gauge: InFlightGauge[MetricEntry] = InFlightGauge(
|
# This is a test and does not matter if it uses `SERVER_NAME_LABEL`.
|
||||||
|
gauge: InFlightGauge[MetricEntry] = InFlightGauge( # type: ignore[missing-server-name-label]
|
||||||
"test1", "", labels=["test_label"], sub_metrics=["foo", "bar"]
|
"test1", "", labels=["test_label"], sub_metrics=["foo", "bar"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user