| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- #!/usr/bin/env drgn
- #
- # Copyright (C) 2023 Tejun Heo <tj@kernel.org>
- # Copyright (C) 2023 Meta Platforms, Inc. and affiliates.
- desc = """
- This is a drgn script to monitor workqueues. For more info on drgn, visit
- https://github.com/osandov/drgn.
- total Total number of work items executed by the workqueue.
- infl The number of currently in-flight work items.
- CPUtime Total CPU time consumed by the workqueue in seconds. This is
- sampled from scheduler ticks and only provides ballpark
- measurement. "nohz_full=" CPUs are excluded from measurement.
- CPUitsv The number of times a concurrency-managed work item hogged CPU
- longer than the threshold (workqueue.cpu_intensive_thresh_us)
- and got excluded from concurrency management to avoid stalling
- other work items.
- CMW/RPR For per-cpu workqueues, the number of concurrency-management
- wake-ups while executing a work item of the workqueue. For
- unbound workqueues, the number of times a worker was repatriated
- to its affinity scope after being migrated to an off-scope CPU by
- the scheduler.
- mayday The number of times the rescuer was requested while waiting for
- new worker creation.
- rescued The number of work items executed by the rescuer.
- """
- import signal
- import re
- import time
- import json
- import drgn
- from drgn.helpers.linux.list import list_for_each_entry
- import argparse
- parser = argparse.ArgumentParser(description=desc,
- formatter_class=argparse.RawTextHelpFormatter)
- parser.add_argument('workqueue', metavar='REGEX', nargs='*',
- help='Target workqueue name patterns (all if empty)')
- parser.add_argument('-i', '--interval', metavar='SECS', type=float, default=1,
- help='Monitoring interval (0 to print once and exit)')
- parser.add_argument('-j', '--json', action='store_true',
- help='Output in json')
- args = parser.parse_args()
- workqueues = prog['workqueues']
- WQ_UNBOUND = prog['WQ_UNBOUND']
- WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM']
- PWQ_STAT_STARTED = prog['PWQ_STAT_STARTED'] # work items started execution
- PWQ_STAT_COMPLETED = prog['PWQ_STAT_COMPLETED'] # work items completed execution
- PWQ_STAT_CPU_TIME = prog['PWQ_STAT_CPU_TIME'] # total CPU time consumed
- PWQ_STAT_CPU_INTENSIVE = prog['PWQ_STAT_CPU_INTENSIVE'] # wq_cpu_intensive_thresh_us violations
- PWQ_STAT_CM_WAKEUP = prog['PWQ_STAT_CM_WAKEUP'] # concurrency-management worker wakeups
- PWQ_STAT_REPATRIATED = prog['PWQ_STAT_REPATRIATED'] # unbound workers brought back into scope
- PWQ_STAT_MAYDAY = prog['PWQ_STAT_MAYDAY'] # maydays to rescuer
- PWQ_STAT_RESCUED = prog['PWQ_STAT_RESCUED'] # linked work items executed by rescuer
- PWQ_NR_STATS = prog['PWQ_NR_STATS']
- class WqStats:
- def __init__(self, wq):
- self.name = wq.name.string_().decode()
- self.unbound = wq.flags & WQ_UNBOUND != 0
- self.mem_reclaim = wq.flags & WQ_MEM_RECLAIM != 0
- self.stats = [0] * PWQ_NR_STATS
- for pwq in list_for_each_entry('struct pool_workqueue', wq.pwqs.address_of_(), 'pwqs_node'):
- for i in range(PWQ_NR_STATS):
- self.stats[i] += int(pwq.stats[i])
- def dict(self, now):
- return { 'timestamp' : now,
- 'name' : self.name,
- 'unbound' : self.unbound,
- 'mem_reclaim' : self.mem_reclaim,
- 'started' : self.stats[PWQ_STAT_STARTED],
- 'completed' : self.stats[PWQ_STAT_COMPLETED],
- 'cpu_time' : self.stats[PWQ_STAT_CPU_TIME],
- 'cpu_intensive' : self.stats[PWQ_STAT_CPU_INTENSIVE],
- 'cm_wakeup' : self.stats[PWQ_STAT_CM_WAKEUP],
- 'repatriated' : self.stats[PWQ_STAT_REPATRIATED],
- 'mayday' : self.stats[PWQ_STAT_MAYDAY],
- 'rescued' : self.stats[PWQ_STAT_RESCUED], }
- def table_header_str():
- return f'{"":>24} {"total":>8} {"infl":>5} {"CPUtime":>8} '\
- f'{"CPUitsv":>7} {"CMW/RPR":>7} {"mayday":>7} {"rescued":>7}'
- def table_row_str(self):
- cpu_intensive = '-'
- cmw_rpr = '-'
- mayday = '-'
- rescued = '-'
- if self.unbound:
- cmw_rpr = str(self.stats[PWQ_STAT_REPATRIATED]);
- else:
- cpu_intensive = str(self.stats[PWQ_STAT_CPU_INTENSIVE])
- cmw_rpr = str(self.stats[PWQ_STAT_CM_WAKEUP])
- if self.mem_reclaim:
- mayday = str(self.stats[PWQ_STAT_MAYDAY])
- rescued = str(self.stats[PWQ_STAT_RESCUED])
- out = f'{self.name[-24:]:24} ' \
- f'{self.stats[PWQ_STAT_STARTED]:8} ' \
- f'{max(self.stats[PWQ_STAT_STARTED] - self.stats[PWQ_STAT_COMPLETED], 0):5} ' \
- f'{self.stats[PWQ_STAT_CPU_TIME] / 1000000:8.1f} ' \
- f'{cpu_intensive:>7} ' \
- f'{cmw_rpr:>7} ' \
- f'{mayday:>7} ' \
- f'{rescued:>7} '
- return out.rstrip(':')
- exit_req = False
- def sigint_handler(signr, frame):
- global exit_req
- exit_req = True
- def main():
- # handle args
- table_fmt = not args.json
- interval = args.interval
- re_str = None
- if args.workqueue:
- for r in args.workqueue:
- if re_str is None:
- re_str = r
- else:
- re_str += '|' + r
- filter_re = re.compile(re_str) if re_str else None
- # monitoring loop
- signal.signal(signal.SIGINT, sigint_handler)
- while not exit_req:
- now = time.time()
- if table_fmt:
- print()
- print(WqStats.table_header_str())
- for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'):
- stats = WqStats(wq)
- if filter_re and not filter_re.search(stats.name):
- continue
- if table_fmt:
- print(stats.table_row_str())
- else:
- print(stats.dict(now))
- if interval == 0:
- break
- time.sleep(interval)
- if __name__ == "__main__":
- main()
|