Skip to content

Commit

Permalink
feat(uvloop): Support event loop utilization queries
Browse files Browse the repository at this point in the history
  • Loading branch information
cphoward committed Oct 16, 2023
1 parent 0e9ff6c commit e49e540
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 0 deletions.
15 changes: 15 additions & 0 deletions tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,21 @@ def scheduler():

class TestBaseUV(_TestBase, UVTestCase):

def test_event_loop_utilization(self):
self.assertTrue(self.loop.event_loop_utilization() == (0., 0., 0.))

async def run():
await asyncio.sleep(0.2)
time.sleep(0.05)
return self.loop._event_loop_utilization()

i, a, p = self.loop.run_until_complete(run())
self.assertTrue(100 < i < 400)
self.assertTrue(a > 0.)
self.assertTrue(0. < p < 1.)

self.assertTrue(self.loop.event_loop_utilization() == (0., 0., 0.))

def test_loop_create_future(self):
fut = self.loop.create_future()
self.assertTrue(isinstance(fut, asyncio.Future))
Expand Down
9 changes: 9 additions & 0 deletions uvloop/includes/uv.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ cdef extern from "uv.h" nogil:
UV_REQ_TYPE_PRIVATE,
UV_REQ_TYPE_MAX

ctypedef enum uv_loop_option:
UV_LOOP_BLOCK_SIGNAL = 0,
UV_METRICS_IDLE_TIME

ctypedef enum uv_run_mode:
UV_RUN_DEFAULT = 0,
UV_RUN_ONCE,
Expand Down Expand Up @@ -281,6 +285,7 @@ cdef extern from "uv.h" nogil:
int uv_loop_init(uv_loop_t* loop)
int uv_loop_close(uv_loop_t* loop)
int uv_loop_alive(uv_loop_t* loop)
int uv_loop_configure(uv_loop_t* loop, uv_loop_option option, ...)
int uv_loop_fork(uv_loop_t* loop)
int uv_backend_fd(uv_loop_t* loop)

Expand Down Expand Up @@ -458,6 +463,10 @@ cdef extern from "uv.h" nogil:
uv_calloc_func calloc_func,
uv_free_func free_func)

# Metrics

uint64_t uv_metrics_idle_time(uv_loop_t* loop)

# Process

ctypedef void (*uv_exit_cb)(uv_process_t*, int64_t exit_status,
Expand Down
1 change: 1 addition & 0 deletions uvloop/loop.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ cdef class Loop:

cdef _close(self)
cdef _stop(self, exc)
cdef uint64_t _event_loop_idle_time(self)
cdef uint64_t _time(self)

cdef inline _queue_write(self, UVStream stream)
Expand Down
1 change: 1 addition & 0 deletions uvloop/loop.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Loop:
def call_at(
self, when: float, callback: Callable[..., Any], *args: Any, context: Optional[Any] = ...
) -> asyncio.TimerHandle: ...
def _event_loop_utilization(self) -> Tuple[float, float, float]: ...
def time(self) -> float: ...
def stop(self) -> None: ...
def run_forever(self) -> None: ...
Expand Down
27 changes: 27 additions & 0 deletions uvloop/loop.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ cdef class Loop:
self._recv_buffer_in_use = 0

err = uv.uv_loop_init(self.uvloop)
if err < 0:
raise convert_error(err)

err = uv.uv_loop_configure(self.uvloop, uv.UV_METRICS_IDLE_TIME)
if err < 0:
raise convert_error(err)
self.uvloop.data = <void*> self
Expand Down Expand Up @@ -527,6 +531,7 @@ cdef class Loop:

self._thread_id = PyThread_get_thread_ident()
self._running = 1
self._loop_start_time = self._time()

self.handler_check__exec_writes.start()
self.handler_idle.start()
Expand Down Expand Up @@ -628,6 +633,10 @@ cdef class Loop:
self._default_executor = None
executor.shutdown(wait=False)

cdef uint64_t _event_loop_idle_time(self):
"""Returns number of nanoseconds the loop has been idle"""
return uv.uv_metrics_idle_time(self.uvloop)

cdef uint64_t _time(self):
# asyncio doesn't have a time cache, neither should uvloop.
uv.uv_update_time(self.uvloop) # void
Expand Down Expand Up @@ -1337,6 +1346,24 @@ cdef class Loop:
return self.call_later(
when - self.time(), callback, *args, context=context)

def _event_loop_utilization(self):
"""Returns idle and active time in milliseconds and the percentage of
time the event loop is active
"""

idle = 0.
active = 0.
utilization = 0.

if not self._running:
return idle, active, utilization

idle = self._event_loop_idle_time() / 10 ** 6
active = self._time() - self._loop_start_time - idle
utilization = active / (active + idle)

return idle, active, utilization

def time(self):
"""Return the time according to the event loop's clock.
Expand Down

0 comments on commit e49e540

Please sign in to comment.