Skip to content

Commit e49e540

Browse files
committed
feat(uvloop): Support event loop utilization queries
1 parent 0e9ff6c commit e49e540

File tree

5 files changed

+53
-0
lines changed

5 files changed

+53
-0
lines changed

tests/test_base.py

+15
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,21 @@ def scheduler():
737737

738738
class TestBaseUV(_TestBase, UVTestCase):
739739

740+
def test_event_loop_utilization(self):
741+
self.assertTrue(self.loop.event_loop_utilization() == (0., 0., 0.))
742+
743+
async def run():
744+
await asyncio.sleep(0.2)
745+
time.sleep(0.05)
746+
return self.loop._event_loop_utilization()
747+
748+
i, a, p = self.loop.run_until_complete(run())
749+
self.assertTrue(100 < i < 400)
750+
self.assertTrue(a > 0.)
751+
self.assertTrue(0. < p < 1.)
752+
753+
self.assertTrue(self.loop.event_loop_utilization() == (0., 0., 0.))
754+
740755
def test_loop_create_future(self):
741756
fut = self.loop.create_future()
742757
self.assertTrue(isinstance(fut, asyncio.Future))

uvloop/includes/uv.pxd

+9
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ cdef extern from "uv.h" nogil:
202202
UV_REQ_TYPE_PRIVATE,
203203
UV_REQ_TYPE_MAX
204204

205+
ctypedef enum uv_loop_option:
206+
UV_LOOP_BLOCK_SIGNAL = 0,
207+
UV_METRICS_IDLE_TIME
208+
205209
ctypedef enum uv_run_mode:
206210
UV_RUN_DEFAULT = 0,
207211
UV_RUN_ONCE,
@@ -281,6 +285,7 @@ cdef extern from "uv.h" nogil:
281285
int uv_loop_init(uv_loop_t* loop)
282286
int uv_loop_close(uv_loop_t* loop)
283287
int uv_loop_alive(uv_loop_t* loop)
288+
int uv_loop_configure(uv_loop_t* loop, uv_loop_option option, ...)
284289
int uv_loop_fork(uv_loop_t* loop)
285290
int uv_backend_fd(uv_loop_t* loop)
286291

@@ -458,6 +463,10 @@ cdef extern from "uv.h" nogil:
458463
uv_calloc_func calloc_func,
459464
uv_free_func free_func)
460465

466+
# Metrics
467+
468+
uint64_t uv_metrics_idle_time(uv_loop_t* loop)
469+
461470
# Process
462471

463472
ctypedef void (*uv_exit_cb)(uv_process_t*, int64_t exit_status,

uvloop/loop.pxd

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ cdef class Loop:
138138

139139
cdef _close(self)
140140
cdef _stop(self, exc)
141+
cdef uint64_t _event_loop_idle_time(self)
141142
cdef uint64_t _time(self)
142143

143144
cdef inline _queue_write(self, UVStream stream)

uvloop/loop.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Loop:
3737
def call_at(
3838
self, when: float, callback: Callable[..., Any], *args: Any, context: Optional[Any] = ...
3939
) -> asyncio.TimerHandle: ...
40+
def _event_loop_utilization(self) -> Tuple[float, float, float]: ...
4041
def time(self) -> float: ...
4142
def stop(self) -> None: ...
4243
def run_forever(self) -> None: ...

uvloop/loop.pyx

+27
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ cdef class Loop:
162162
self._recv_buffer_in_use = 0
163163

164164
err = uv.uv_loop_init(self.uvloop)
165+
if err < 0:
166+
raise convert_error(err)
167+
168+
err = uv.uv_loop_configure(self.uvloop, uv.UV_METRICS_IDLE_TIME)
165169
if err < 0:
166170
raise convert_error(err)
167171
self.uvloop.data = <void*> self
@@ -527,6 +531,7 @@ cdef class Loop:
527531

528532
self._thread_id = PyThread_get_thread_ident()
529533
self._running = 1
534+
self._loop_start_time = self._time()
530535

531536
self.handler_check__exec_writes.start()
532537
self.handler_idle.start()
@@ -628,6 +633,10 @@ cdef class Loop:
628633
self._default_executor = None
629634
executor.shutdown(wait=False)
630635

636+
cdef uint64_t _event_loop_idle_time(self):
637+
"""Returns number of nanoseconds the loop has been idle"""
638+
return uv.uv_metrics_idle_time(self.uvloop)
639+
631640
cdef uint64_t _time(self):
632641
# asyncio doesn't have a time cache, neither should uvloop.
633642
uv.uv_update_time(self.uvloop) # void
@@ -1337,6 +1346,24 @@ cdef class Loop:
13371346
return self.call_later(
13381347
when - self.time(), callback, *args, context=context)
13391348

1349+
def _event_loop_utilization(self):
1350+
"""Returns idle and active time in milliseconds and the percentage of
1351+
time the event loop is active
1352+
"""
1353+
1354+
idle = 0.
1355+
active = 0.
1356+
utilization = 0.
1357+
1358+
if not self._running:
1359+
return idle, active, utilization
1360+
1361+
idle = self._event_loop_idle_time() / 10 ** 6
1362+
active = self._time() - self._loop_start_time - idle
1363+
utilization = active / (active + idle)
1364+
1365+
return idle, active, utilization
1366+
13401367
def time(self):
13411368
"""Return the time according to the event loop's clock.
13421369

0 commit comments

Comments
 (0)