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 | 26 +++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/include/trace/events/workqueue.h b/include/trace/events/workqueue.h index 12ac24586835..bbba34e40470 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 ab1371ed890a..74f595396227 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -3758,6 +3758,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 restarts = 0; worker_lock_callback(pool); raw_spin_lock_irq(&pool->lock); @@ -3780,8 +3783,23 @@ static void bh_worker(struct worker *worker) if (assign_work(work, worker, NULL)) process_scheduled_works(worker); - } while (keep_working(pool) && - --nr_restarts && time_before(jiffies, end)); + + if (!keep_working(pool)) + break; + + if (!--nr_restarts) { + budget_exhausted = true; + break; + } + + if (!time_before(jiffies, end)) { + budget_exhausted = true; + timeout = true; + break; + } + + restarts++; + } while (1); worker_set_flags(worker, WORKER_PREP); done: @@ -3789,6 +3807,10 @@ static void bh_worker(struct worker *worker) kick_pool(pool); raw_spin_unlock_irq(&pool->lock); worker_unlock_callback(pool); + + if (budget_exhausted) + trace_workqueue_bh_budget_yield(pool, restarts, timeout, + pool->attrs->nice == HIGHPRI_NICE_LEVEL); } /* -- 2.55.0
