Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 3f55267

Browse files
committed
Wrap epoll.poll instead of runUntilCurrent
1 parent 3dbc342 commit 3f55267

File tree

2 files changed

+84
-69
lines changed

2 files changed

+84
-69
lines changed

Diff for: synapse/metrics/__init__.py

+1-69
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import functools
1615
import itertools
1716
import logging
1817
import os
1918
import platform
2019
import threading
21-
import time
2220
from typing import (
23-
Any,
2421
Callable,
2522
Dict,
2623
Generic,
@@ -33,7 +30,6 @@
3330
Type,
3431
TypeVar,
3532
Union,
36-
cast,
3733
)
3834

3935
import attr
@@ -44,11 +40,9 @@
4440
GaugeMetricFamily,
4541
)
4642

47-
from twisted.internet import reactor
48-
from twisted.internet.base import ReactorBase
4943
from twisted.python.threadpool import ThreadPool
5044

51-
import synapse
45+
import synapse.metrics._reactor_metrics
5246
from synapse.metrics._exposition import (
5347
MetricsResource,
5448
generate_latest,
@@ -368,16 +362,6 @@ def collect(self) -> Iterable[Metric]:
368362
REGISTRY.register(CPUMetrics())
369363

370364

371-
#
372-
# Twisted reactor metrics
373-
#
374-
375-
tick_time = Histogram(
376-
"python_twisted_reactor_tick_time",
377-
"Tick time of the Twisted reactor (sec)",
378-
buckets=[0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.5, 1, 2, 5],
379-
)
380-
381365
#
382366
# Federation Metrics
383367
#
@@ -429,8 +413,6 @@ def collect(self) -> Iterable[Metric]:
429413
" ".join([platform.system(), platform.release()]),
430414
).set(1)
431415

432-
last_ticked = time.time()
433-
434416
# 3PID send info
435417
threepid_send_requests = Histogram(
436418
"synapse_threepid_send_requests_with_tries",
@@ -478,56 +460,6 @@ def register_threadpool(name: str, threadpool: ThreadPool) -> None:
478460
)
479461

480462

481-
class ReactorLastSeenMetric:
482-
def collect(self) -> Iterable[Metric]:
483-
cm = GaugeMetricFamily(
484-
"python_twisted_reactor_last_seen",
485-
"Seconds since the Twisted reactor was last seen",
486-
)
487-
cm.add_metric([], time.time() - last_ticked)
488-
yield cm
489-
490-
491-
REGISTRY.register(ReactorLastSeenMetric())
492-
493-
F = TypeVar("F", bound=Callable[..., Any])
494-
495-
496-
def runUntilCurrentTimer(reactor: ReactorBase, func: F) -> F:
497-
@functools.wraps(func)
498-
def f(*args: Any, **kwargs: Any) -> Any:
499-
start = time.time()
500-
ret = func(*args, **kwargs)
501-
end = time.time()
502-
503-
# record the amount of wallclock time spent running pending calls.
504-
# This is a proxy for the actual amount of time between reactor polls,
505-
# since about 25% of time is actually spent running things triggered by
506-
# I/O events, but that is harder to capture without rewriting half the
507-
# reactor.
508-
tick_time.observe(end - start)
509-
510-
# Update the time we last ticked, for the metric to test whether
511-
# Synapse's reactor has frozen
512-
global last_ticked
513-
last_ticked = end
514-
515-
return ret
516-
517-
return cast(F, f)
518-
519-
520-
try:
521-
# Ensure the reactor has all the attributes we expect
522-
reactor.runUntilCurrent # type: ignore
523-
524-
# runUntilCurrent is called when we have pending calls. It is called once
525-
# per iteratation after fd polling.
526-
reactor.runUntilCurrent = runUntilCurrentTimer(reactor, reactor.runUntilCurrent) # type: ignore
527-
except AttributeError:
528-
pass
529-
530-
531463
__all__ = [
532464
"MetricsResource",
533465
"generate_latest",

Diff for: synapse/metrics/_reactor_metrics.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Copyright 2022 The Matrix.org Foundation C.I.C.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import select
16+
import time
17+
from typing import Any, Iterable, List, Tuple
18+
19+
from prometheus_client import Histogram, Metric
20+
from prometheus_client.core import REGISTRY, GaugeMetricFamily
21+
22+
from twisted.internet import reactor
23+
24+
#
25+
# Twisted reactor metrics
26+
#
27+
28+
tick_time = Histogram(
29+
"python_twisted_reactor_tick_time",
30+
"Tick time of the Twisted reactor (sec)",
31+
buckets=[0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.5, 1, 2, 5],
32+
)
33+
34+
35+
class EpollWrapper:
36+
"""a wrapper for an epoll object which records the time between polls"""
37+
38+
def __init__(self, poller: "select.epoll"):
39+
self.last_polled = time.time()
40+
self._poller = poller
41+
42+
def poll(self, *args, **kwargs) -> List[Tuple[int, int]]: # type: ignore[no-untyped-def]
43+
# record the time since poll() was last called. This gives a good proxy for
44+
# how long it takes to run everything in the reactor - ie, how long anything
45+
# waiting for the next tick will have to wait.
46+
tick_time.observe(time.time() - self.last_polled)
47+
48+
ret = self._poller.poll(*args, **kwargs)
49+
50+
self.last_polled = time.time()
51+
return ret
52+
53+
def __getattr__(self, item: str) -> Any:
54+
return getattr(self._poller, item)
55+
56+
57+
class ReactorLastSeenMetric:
58+
def __init__(self, epoll_wrapper: EpollWrapper):
59+
self._epoll_wrapper = epoll_wrapper
60+
61+
def collect(self) -> Iterable[Metric]:
62+
cm = GaugeMetricFamily(
63+
"python_twisted_reactor_last_seen",
64+
"Seconds since the Twisted reactor was last seen",
65+
)
66+
cm.add_metric([], time.time() - self._epoll_wrapper.last_polled)
67+
yield cm
68+
69+
70+
try:
71+
# if the reactor has a `_poller` attribute, which is an `epoll` object
72+
# (ie, it's an EPollReactor), we wrap the `epoll` with a thing that will
73+
# measure the time between ticks
74+
from select import epoll
75+
76+
poller = reactor._poller # type: ignore[attr-defined]
77+
except (AttributeError, ImportError):
78+
pass
79+
else:
80+
if isinstance(poller, epoll):
81+
poller = EpollWrapper(poller)
82+
reactor._poller = poller # type: ignore[attr-defined]
83+
REGISTRY.register(ReactorLastSeenMetric(poller))

0 commit comments

Comments
 (0)