wq_monitor.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env drgn
  2. #
  3. # Copyright (C) 2023 Tejun Heo <tj@kernel.org>
  4. # Copyright (C) 2023 Meta Platforms, Inc. and affiliates.
  5. desc = """
  6. This is a drgn script to monitor workqueues. For more info on drgn, visit
  7. https://github.com/osandov/drgn.
  8. total Total number of work items executed by the workqueue.
  9. infl The number of currently in-flight work items.
  10. CPUtime Total CPU time consumed by the workqueue in seconds. This is
  11. sampled from scheduler ticks and only provides ballpark
  12. measurement. "nohz_full=" CPUs are excluded from measurement.
  13. CPUitsv The number of times a concurrency-managed work item hogged CPU
  14. longer than the threshold (workqueue.cpu_intensive_thresh_us)
  15. and got excluded from concurrency management to avoid stalling
  16. other work items.
  17. CMW/RPR For per-cpu workqueues, the number of concurrency-management
  18. wake-ups while executing a work item of the workqueue. For
  19. unbound workqueues, the number of times a worker was repatriated
  20. to its affinity scope after being migrated to an off-scope CPU by
  21. the scheduler.
  22. mayday The number of times the rescuer was requested while waiting for
  23. new worker creation.
  24. rescued The number of work items executed by the rescuer.
  25. """
  26. import signal
  27. import re
  28. import time
  29. import json
  30. import drgn
  31. from drgn.helpers.linux.list import list_for_each_entry
  32. import argparse
  33. parser = argparse.ArgumentParser(description=desc,
  34. formatter_class=argparse.RawTextHelpFormatter)
  35. parser.add_argument('workqueue', metavar='REGEX', nargs='*',
  36. help='Target workqueue name patterns (all if empty)')
  37. parser.add_argument('-i', '--interval', metavar='SECS', type=float, default=1,
  38. help='Monitoring interval (0 to print once and exit)')
  39. parser.add_argument('-j', '--json', action='store_true',
  40. help='Output in json')
  41. args = parser.parse_args()
  42. workqueues = prog['workqueues']
  43. WQ_UNBOUND = prog['WQ_UNBOUND']
  44. WQ_MEM_RECLAIM = prog['WQ_MEM_RECLAIM']
  45. PWQ_STAT_STARTED = prog['PWQ_STAT_STARTED'] # work items started execution
  46. PWQ_STAT_COMPLETED = prog['PWQ_STAT_COMPLETED'] # work items completed execution
  47. PWQ_STAT_CPU_TIME = prog['PWQ_STAT_CPU_TIME'] # total CPU time consumed
  48. PWQ_STAT_CPU_INTENSIVE = prog['PWQ_STAT_CPU_INTENSIVE'] # wq_cpu_intensive_thresh_us violations
  49. PWQ_STAT_CM_WAKEUP = prog['PWQ_STAT_CM_WAKEUP'] # concurrency-management worker wakeups
  50. PWQ_STAT_REPATRIATED = prog['PWQ_STAT_REPATRIATED'] # unbound workers brought back into scope
  51. PWQ_STAT_MAYDAY = prog['PWQ_STAT_MAYDAY'] # maydays to rescuer
  52. PWQ_STAT_RESCUED = prog['PWQ_STAT_RESCUED'] # linked work items executed by rescuer
  53. PWQ_NR_STATS = prog['PWQ_NR_STATS']
  54. class WqStats:
  55. def __init__(self, wq):
  56. self.name = wq.name.string_().decode()
  57. self.unbound = wq.flags & WQ_UNBOUND != 0
  58. self.mem_reclaim = wq.flags & WQ_MEM_RECLAIM != 0
  59. self.stats = [0] * PWQ_NR_STATS
  60. for pwq in list_for_each_entry('struct pool_workqueue', wq.pwqs.address_of_(), 'pwqs_node'):
  61. for i in range(PWQ_NR_STATS):
  62. self.stats[i] += int(pwq.stats[i])
  63. def dict(self, now):
  64. return { 'timestamp' : now,
  65. 'name' : self.name,
  66. 'unbound' : self.unbound,
  67. 'mem_reclaim' : self.mem_reclaim,
  68. 'started' : self.stats[PWQ_STAT_STARTED],
  69. 'completed' : self.stats[PWQ_STAT_COMPLETED],
  70. 'cpu_time' : self.stats[PWQ_STAT_CPU_TIME],
  71. 'cpu_intensive' : self.stats[PWQ_STAT_CPU_INTENSIVE],
  72. 'cm_wakeup' : self.stats[PWQ_STAT_CM_WAKEUP],
  73. 'repatriated' : self.stats[PWQ_STAT_REPATRIATED],
  74. 'mayday' : self.stats[PWQ_STAT_MAYDAY],
  75. 'rescued' : self.stats[PWQ_STAT_RESCUED], }
  76. def table_header_str():
  77. return f'{"":>24} {"total":>8} {"infl":>5} {"CPUtime":>8} '\
  78. f'{"CPUitsv":>7} {"CMW/RPR":>7} {"mayday":>7} {"rescued":>7}'
  79. def table_row_str(self):
  80. cpu_intensive = '-'
  81. cmw_rpr = '-'
  82. mayday = '-'
  83. rescued = '-'
  84. if self.unbound:
  85. cmw_rpr = str(self.stats[PWQ_STAT_REPATRIATED]);
  86. else:
  87. cpu_intensive = str(self.stats[PWQ_STAT_CPU_INTENSIVE])
  88. cmw_rpr = str(self.stats[PWQ_STAT_CM_WAKEUP])
  89. if self.mem_reclaim:
  90. mayday = str(self.stats[PWQ_STAT_MAYDAY])
  91. rescued = str(self.stats[PWQ_STAT_RESCUED])
  92. out = f'{self.name[-24:]:24} ' \
  93. f'{self.stats[PWQ_STAT_STARTED]:8} ' \
  94. f'{max(self.stats[PWQ_STAT_STARTED] - self.stats[PWQ_STAT_COMPLETED], 0):5} ' \
  95. f'{self.stats[PWQ_STAT_CPU_TIME] / 1000000:8.1f} ' \
  96. f'{cpu_intensive:>7} ' \
  97. f'{cmw_rpr:>7} ' \
  98. f'{mayday:>7} ' \
  99. f'{rescued:>7} '
  100. return out.rstrip(':')
  101. exit_req = False
  102. def sigint_handler(signr, frame):
  103. global exit_req
  104. exit_req = True
  105. def main():
  106. # handle args
  107. table_fmt = not args.json
  108. interval = args.interval
  109. re_str = None
  110. if args.workqueue:
  111. for r in args.workqueue:
  112. if re_str is None:
  113. re_str = r
  114. else:
  115. re_str += '|' + r
  116. filter_re = re.compile(re_str) if re_str else None
  117. # monitoring loop
  118. signal.signal(signal.SIGINT, sigint_handler)
  119. while not exit_req:
  120. now = time.time()
  121. if table_fmt:
  122. print()
  123. print(WqStats.table_header_str())
  124. for wq in list_for_each_entry('struct workqueue_struct', workqueues.address_of_(), 'list'):
  125. stats = WqStats(wq)
  126. if filter_re and not filter_re.search(stats.name):
  127. continue
  128. if table_fmt:
  129. print(stats.table_row_str())
  130. else:
  131. print(stats.dict(now))
  132. if interval == 0:
  133. break
  134. time.sleep(interval)
  135. if __name__ == "__main__":
  136. main()