Skip to content

Commit 09b44fb

Browse files
committed
Add asyncio task benchmark counters
1 parent c22e9c9 commit 09b44fb

3 files changed

Lines changed: 113 additions & 0 deletions

File tree

Lib/asyncio/base_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ def create_task(self, coro, **kwargs):
468468
469469
Return a task object.
470470
"""
471+
tasks._benchmark_count('py_loop_create_task_calls')
471472
self._check_closed()
472473
if self._task_factory is not None:
473474
return self._task_factory(self, coro, **kwargs)

Lib/asyncio/tasks.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,33 @@
3232
# This uses itertools.count() instead of a "+= 1" operation because the latter
3333
# is not thread safe. See bpo-11866 for a longer explanation.
3434
_task_name_counter = itertools.count(1).__next__
35+
_benchmark_counters = {
36+
'py_create_task_calls': 0,
37+
'py_loop_create_task_calls': 0,
38+
'py_gather_calls': 0,
39+
'py_gather_input_count': 0,
40+
'py_gather_done_futures': 0,
41+
'py_gather_callback_registrations': 0,
42+
}
43+
44+
45+
def _benchmark_count(name, delta=1):
46+
_benchmark_counters[name] = _benchmark_counters.get(name, 0) + delta
47+
48+
49+
def _reset_benchmark_counters():
50+
for key in list(_benchmark_counters):
51+
_benchmark_counters[key] = 0
52+
if _c_reset_benchmark_counters is not None:
53+
_c_reset_benchmark_counters()
54+
55+
56+
def _get_benchmark_counters():
57+
counters = dict(_benchmark_counters)
58+
if _c_get_benchmark_counters is not None:
59+
for key, value in _c_get_benchmark_counters().items():
60+
counters[key] = value
61+
return counters
3562

3663

3764
def current_task(loop=None):
@@ -384,13 +411,19 @@ def __wakeup(self, future):
384411
else:
385412
# _CTask is needed for tests.
386413
Task = _CTask = _asyncio.Task
414+
_c_get_benchmark_counters = getattr(_asyncio, '_get_benchmark_counters', None)
415+
_c_reset_benchmark_counters = getattr(_asyncio, '_reset_benchmark_counters', None)
416+
if '_c_get_benchmark_counters' not in globals():
417+
_c_get_benchmark_counters = None
418+
_c_reset_benchmark_counters = None
387419

388420

389421
def create_task(coro, **kwargs):
390422
"""Schedule the execution of a coroutine object in a spawn task.
391423
392424
Return a Task object.
393425
"""
426+
_benchmark_count('py_create_task_calls')
394427
loop = events.get_running_loop()
395428
return loop.create_task(coro, **kwargs)
396429

@@ -798,6 +831,7 @@ def gather(*coros_or_futures, return_exceptions=False):
798831
after catching an exception (raised by one of the awaitables) from
799832
gather won't cancel any other awaitables.
800833
"""
834+
_benchmark_count('py_gather_calls')
801835
if not coros_or_futures:
802836
loop = events.get_event_loop()
803837
outer = loop.create_future()
@@ -876,6 +910,7 @@ def _done_callback(fut, cur_task=cur_task):
876910
outer = None # bpo-46672
877911
for arg in coros_or_futures:
878912
if arg not in arg_to_fut:
913+
_benchmark_count('py_gather_input_count')
879914
fut = ensure_future(arg, loop=loop)
880915
if loop is None:
881916
loop = futures._get_loop(fut)
@@ -888,11 +923,13 @@ def _done_callback(fut, cur_task=cur_task):
888923
nfuts += 1
889924
arg_to_fut[arg] = fut
890925
if fut.done():
926+
_benchmark_count('py_gather_done_futures')
891927
done_futs.append(fut)
892928
else:
893929
if cur_task is not None:
894930
futures.future_add_to_awaited_by(fut, cur_task)
895931
fut.add_done_callback(_done_callback)
932+
_benchmark_count('py_gather_callback_registrations')
896933

897934
else:
898935
# There's a duplicate Future object in coros_or_futures.

Modules/_asynciomodule.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ typedef struct {
186186
/* Counter for autogenerated Task names */
187187
uint64_t task_name_counter;
188188

189+
/* Internal benchmarking counters for asyncio task hot paths. */
190+
uint64_t bench_call_soon_calls;
191+
uint64_t bench_task_init_calls;
192+
uint64_t bench_task_call_step_soon_calls;
193+
uint64_t bench_task_call_step_soon_with_arg_calls;
194+
uint64_t bench_register_task_calls;
195+
uint64_t bench_future_schedule_callbacks_calls;
196+
uint64_t bench_future_schedule_callback0_calls;
197+
uint64_t bench_future_schedule_callback_list_items;
198+
189199
/* Pointer to the asyncio debug offset to avoid it to be optimized away
190200
by the compiler */
191201
void *debug_offsets;
@@ -359,6 +369,7 @@ static int
359369
call_soon(asyncio_state *state, PyObject *loop, PyObject *func, PyObject *arg,
360370
PyObject *ctx)
361371
{
372+
state->bench_call_soon_calls++;
362373
PyObject *handle;
363374

364375
if (ctx == NULL) {
@@ -423,6 +434,7 @@ static int
423434
future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
424435
{
425436
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(fut);
437+
state->bench_future_schedule_callbacks_calls++;
426438

427439
assert(fut->fut_state != STATE_PENDING);
428440

@@ -434,6 +446,7 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
434446
}
435447

436448
if (fut->fut_callback0 != NULL) {
449+
state->bench_future_schedule_callback0_calls++;
437450
/* There's a 1st callback */
438451

439452
// Beware: An evil call_soon could alter fut_callback0 or fut_context0.
@@ -472,6 +485,7 @@ future_schedule_callbacks(asyncio_state *state, FutureObj *fut)
472485
PyObject *callbacks = fut->fut_callbacks;
473486
fut->fut_callbacks = NULL;
474487
Py_ssize_t n = PyList_GET_SIZE(callbacks);
488+
state->bench_future_schedule_callback_list_items += (uint64_t)n;
475489
for (Py_ssize_t i = 0; i < n; i++) {
476490
assert(PyList_GET_SIZE(callbacks) == n);
477491
PyObject *cb_tup = PyList_GET_ITEM(callbacks, i);
@@ -2183,6 +2197,8 @@ register_task(_PyThreadStateImpl *ts, TaskObj *task)
21832197
return;
21842198
}
21852199
struct llist_node *head = &ts->asyncio_tasks_head;
2200+
asyncio_state *state = get_asyncio_state_by_def((PyObject *)task);
2201+
state->bench_register_task_calls++;
21862202
llist_insert_tail(head, &task->task_node);
21872203
}
21882204

@@ -2308,6 +2324,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
23082324
self->task_is_task = 1;
23092325

23102326
asyncio_state *state = get_asyncio_state_by_def((PyObject *)self);
2327+
state->bench_task_init_calls++;
23112328
int is_coro = is_coroutine(state, coro);
23122329
if (is_coro == -1) {
23132330
return -1;
@@ -2983,6 +3000,10 @@ TaskObj_dealloc(PyObject *self)
29833000
static int
29843001
task_call_step_soon(asyncio_state *state, TaskObj *task, PyObject *arg)
29853002
{
3003+
state->bench_task_call_step_soon_calls++;
3004+
if (arg != NULL) {
3005+
state->bench_task_call_step_soon_with_arg_calls++;
3006+
}
29863007
PyObject *cb = TaskStepMethWrapper_new(task, arg);
29873008
if (cb == NULL) {
29883009
return -1;
@@ -4327,6 +4348,58 @@ module_init(asyncio_state *state)
43274348

43284349
PyDoc_STRVAR(module_doc, "Accelerator module for asyncio");
43294350

4351+
static PyObject *
4352+
_asyncio__get_benchmark_counters(PyObject *module, PyObject *Py_UNUSED(ignored))
4353+
{
4354+
asyncio_state *state = get_asyncio_state(module);
4355+
PyObject *counters = PyDict_New();
4356+
if (counters == NULL) {
4357+
return NULL;
4358+
}
4359+
4360+
#define SET_COUNTER(name) \
4361+
do { \
4362+
PyObject *value = PyLong_FromUnsignedLongLong(state->name); \
4363+
if (value == NULL) { \
4364+
Py_DECREF(counters); \
4365+
return NULL; \
4366+
} \
4367+
if (PyDict_SetItemString(counters, #name, value) < 0) { \
4368+
Py_DECREF(value); \
4369+
Py_DECREF(counters); \
4370+
return NULL; \
4371+
} \
4372+
Py_DECREF(value); \
4373+
} while (0)
4374+
4375+
SET_COUNTER(bench_call_soon_calls);
4376+
SET_COUNTER(bench_task_init_calls);
4377+
SET_COUNTER(bench_task_call_step_soon_calls);
4378+
SET_COUNTER(bench_task_call_step_soon_with_arg_calls);
4379+
SET_COUNTER(bench_register_task_calls);
4380+
SET_COUNTER(bench_future_schedule_callbacks_calls);
4381+
SET_COUNTER(bench_future_schedule_callback0_calls);
4382+
SET_COUNTER(bench_future_schedule_callback_list_items);
4383+
4384+
#undef SET_COUNTER
4385+
return counters;
4386+
}
4387+
4388+
static PyObject *
4389+
_asyncio__reset_benchmark_counters(PyObject *module, PyObject *Py_UNUSED(ignored))
4390+
{
4391+
asyncio_state *state = get_asyncio_state(module);
4392+
state->bench_call_soon_calls = 0;
4393+
state->bench_task_init_calls = 0;
4394+
state->bench_task_call_step_soon_calls = 0;
4395+
state->bench_task_call_step_soon_with_arg_calls = 0;
4396+
state->bench_register_task_calls = 0;
4397+
state->bench_future_schedule_callbacks_calls = 0;
4398+
state->bench_future_schedule_callback0_calls = 0;
4399+
state->bench_future_schedule_callback_list_items = 0;
4400+
Py_RETURN_NONE;
4401+
}
4402+
43304403
static PyMethodDef asyncio_methods[] = {
43314404
_ASYNCIO_CURRENT_TASK_METHODDEF
43324405
_ASYNCIO_GET_EVENT_LOOP_METHODDEF
@@ -4343,6 +4416,8 @@ static PyMethodDef asyncio_methods[] = {
43434416
_ASYNCIO_ALL_TASKS_METHODDEF
43444417
_ASYNCIO_FUTURE_ADD_TO_AWAITED_BY_METHODDEF
43454418
_ASYNCIO_FUTURE_DISCARD_FROM_AWAITED_BY_METHODDEF
4419+
{"_get_benchmark_counters", _asyncio__get_benchmark_counters, METH_NOARGS, NULL},
4420+
{"_reset_benchmark_counters", _asyncio__reset_benchmark_counters, METH_NOARGS, NULL},
43464421
{NULL, NULL}
43474422
};
43484423

0 commit comments

Comments
 (0)