Bottom-Half (BH) workqueues execute work items in softirq context. To prevent softirqs from starving user and kernel threads, bh_worker() enforces execution limits (i.e., BH_WORKER_JIFFIES and BH_WORKER_RESTARTS).
When keep_working() is still true but either the time slice or restart count is exhausted, bh_worker() yields execution and re-raises the softirq via kick_bh_pool(). Currently, there is no observability into when a BH worker hits these limits and is forced to yield. Add the workqueue_bh_budget_yield tracepoint, emitted when bh_worker() exits the processing loop with pending work items remaining. It records, the worker pool ID, executing CPU, number of loop restarts consumed, a boolean flag indicating whether the yield was due to a time slice timeout, and a boolean flag indicating whether this is a high-priority BH pool. Signed-off-by: Aaron Tomlin <[email protected]> --- include/trace/events/workqueue.h | 39 ++++++++++++++++++++++++++++++++ kernel/workqueue.c | 13 +++++++++++ 2 files changed, 52 insertions(+) diff --git a/include/trace/events/workqueue.h b/include/trace/events/workqueue.h index 013cfa472f6d..ad1d0ff1a96d 100644 --- a/include/trace/events/workqueue.h +++ b/include/trace/events/workqueue.h @@ -9,6 +9,7 @@ #include <linux/workqueue.h> struct pool_workqueue; +struct worker_pool; /** * workqueue_queue_work - called when a work gets queued @@ -233,6 +234,44 @@ TRACE_EVENT(workqueue_rescued, __entry->cpu) ); +/** + * workqueue_bh_budget_yield - called when a BH worker yields due to budget exhaustion + * @pool: pointer to struct worker_pool + * @restarts: number of restarts executed + * @timeout: whether execution hit the time limit (BH_WORKER_JIFFIES) + * @highpri: whether this is a high-priority BH pool + * + * This event occurs when a bottom-half (BH) worker pool running in softirq + * context exhausts its execution time slice or restart limit and must yield + * execution. + */ +TRACE_EVENT(workqueue_bh_budget_yield, + + TP_PROTO(struct worker_pool *pool, int restarts, bool timeout, bool highpri), + + TP_ARGS(pool, restarts, timeout, highpri), + + TP_STRUCT__entry( + __field( int, pool_id ) + __field( int, cpu ) + __field( int, restarts ) + __field( bool, timeout ) + __field( bool, highpri ) + ), + + TP_fast_assign( + __entry->pool_id = pool->id; + __entry->cpu = pool->cpu; + __entry->restarts = restarts; + __entry->timeout = timeout; + __entry->highpri = highpri; + ), + + TP_printk("pool_id=%d cpu=%d restarts=%d timeout=%d highpri=%d", + __entry->pool_id, __entry->cpu, __entry->restarts, + __entry->timeout, __entry->highpri) +); + #endif /* _TRACE_WORKQUEUE_H */ /* This part must be outside protection */ diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 6f6fe2068389..1c25df68f7ae 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3756,6 +3756,9 @@ static void bh_worker(struct worker *worker) struct worker_pool *pool = worker->pool; int nr_restarts = BH_WORKER_RESTARTS; unsigned long end = jiffies + BH_WORKER_JIFFIES; + bool budget_exhausted = false; + bool timeout = false; + int executed_restarts = 0; worker_lock_callback(pool); raw_spin_lock_irq(&pool->lock); @@ -3781,12 +3784,22 @@ static void bh_worker(struct worker *worker) } while (keep_working(pool) && --nr_restarts && time_before(jiffies, end)); + if (keep_working(pool)) { + budget_exhausted = true; + timeout = !time_before(jiffies, end); + executed_restarts = BH_WORKER_RESTARTS - nr_restarts; + } + worker_set_flags(worker, WORKER_PREP); done: worker_enter_idle(worker); kick_pool(pool); raw_spin_unlock_irq(&pool->lock); worker_unlock_callback(pool); + + if (budget_exhausted) + trace_workqueue_bh_budget_yield(pool, executed_restarts, timeout, + pool->attrs->nice == HIGHPRI_NICE_LEVEL); } /* -- 2.55.0
