Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(uvloop): Support event loop utilization queries #579

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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