compaction-times.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. # report time spent in compaction
  2. # Licensed under the terms of the GNU GPL License version 2
  3. # testing:
  4. # 'echo 1 > /proc/sys/vm/compact_memory' to force compaction of all zones
  5. import os
  6. import sys
  7. import re
  8. import signal
  9. signal.signal(signal.SIGPIPE, signal.SIG_DFL)
  10. usage = "usage: perf script report compaction-times.py -- [-h] [-u] [-p|-pv] [-t | [-m] [-fs] [-ms]] [pid|pid-range|comm-regex]\n"
  11. class popt:
  12. DISP_DFL = 0
  13. DISP_PROC = 1
  14. DISP_PROC_VERBOSE=2
  15. class topt:
  16. DISP_TIME = 0
  17. DISP_MIG = 1
  18. DISP_ISOLFREE = 2
  19. DISP_ISOLMIG = 4
  20. DISP_ALL = 7
  21. class comm_filter:
  22. def __init__(self, re):
  23. self.re = re
  24. def filter(self, pid, comm):
  25. m = self.re.search(comm)
  26. return m == None or m.group() == ""
  27. class pid_filter:
  28. def __init__(self, low, high):
  29. self.low = (0 if low == "" else int(low))
  30. self.high = (0 if high == "" else int(high))
  31. def filter(self, pid, comm):
  32. return not (pid >= self.low and (self.high == 0 or pid <= self.high))
  33. def set_type(t):
  34. global opt_disp
  35. opt_disp = (t if opt_disp == topt.DISP_ALL else opt_disp|t)
  36. def ns(sec, nsec):
  37. return (sec * 1000000000) + nsec
  38. def time(ns):
  39. return "%dns" % ns if opt_ns else "%dus" % (round(ns, -3) / 1000)
  40. class pair:
  41. def __init__(self, aval, bval, alabel = None, blabel = None):
  42. self.alabel = alabel
  43. self.blabel = blabel
  44. self.aval = aval
  45. self.bval = bval
  46. def __add__(self, rhs):
  47. self.aval += rhs.aval
  48. self.bval += rhs.bval
  49. return self
  50. def __str__(self):
  51. return "%s=%d %s=%d" % (self.alabel, self.aval, self.blabel, self.bval)
  52. class cnode:
  53. def __init__(self, ns):
  54. self.ns = ns
  55. self.migrated = pair(0, 0, "moved", "failed")
  56. self.fscan = pair(0,0, "scanned", "isolated")
  57. self.mscan = pair(0,0, "scanned", "isolated")
  58. def __add__(self, rhs):
  59. self.ns += rhs.ns
  60. self.migrated += rhs.migrated
  61. self.fscan += rhs.fscan
  62. self.mscan += rhs.mscan
  63. return self
  64. def __str__(self):
  65. prev = 0
  66. s = "%s " % time(self.ns)
  67. if (opt_disp & topt.DISP_MIG):
  68. s += "migration: %s" % self.migrated
  69. prev = 1
  70. if (opt_disp & topt.DISP_ISOLFREE):
  71. s += "%sfree_scanner: %s" % (" " if prev else "", self.fscan)
  72. prev = 1
  73. if (opt_disp & topt.DISP_ISOLMIG):
  74. s += "%smigration_scanner: %s" % (" " if prev else "", self.mscan)
  75. return s
  76. def complete(self, secs, nsecs):
  77. self.ns = ns(secs, nsecs) - self.ns
  78. def increment(self, migrated, fscan, mscan):
  79. if (migrated != None):
  80. self.migrated += migrated
  81. if (fscan != None):
  82. self.fscan += fscan
  83. if (mscan != None):
  84. self.mscan += mscan
  85. class chead:
  86. heads = {}
  87. val = cnode(0);
  88. fobj = None
  89. @classmethod
  90. def add_filter(cls, filter):
  91. cls.fobj = filter
  92. @classmethod
  93. def create_pending(cls, pid, comm, start_secs, start_nsecs):
  94. filtered = 0
  95. try:
  96. head = cls.heads[pid]
  97. filtered = head.is_filtered()
  98. except KeyError:
  99. if cls.fobj != None:
  100. filtered = cls.fobj.filter(pid, comm)
  101. head = cls.heads[pid] = chead(comm, pid, filtered)
  102. if not filtered:
  103. head.mark_pending(start_secs, start_nsecs)
  104. @classmethod
  105. def increment_pending(cls, pid, migrated, fscan, mscan):
  106. head = cls.heads[pid]
  107. if not head.is_filtered():
  108. if head.is_pending():
  109. head.do_increment(migrated, fscan, mscan)
  110. else:
  111. sys.stderr.write("missing start compaction event for pid %d\n" % pid)
  112. @classmethod
  113. def complete_pending(cls, pid, secs, nsecs):
  114. head = cls.heads[pid]
  115. if not head.is_filtered():
  116. if head.is_pending():
  117. head.make_complete(secs, nsecs)
  118. else:
  119. sys.stderr.write("missing start compaction event for pid %d\n" % pid)
  120. @classmethod
  121. def gen(cls):
  122. if opt_proc != popt.DISP_DFL:
  123. for i in cls.heads:
  124. yield cls.heads[i]
  125. @classmethod
  126. def str(cls):
  127. return cls.val
  128. def __init__(self, comm, pid, filtered):
  129. self.comm = comm
  130. self.pid = pid
  131. self.val = cnode(0)
  132. self.pending = None
  133. self.filtered = filtered
  134. self.list = []
  135. def __add__(self, rhs):
  136. self.ns += rhs.ns
  137. self.val += rhs.val
  138. return self
  139. def mark_pending(self, secs, nsecs):
  140. self.pending = cnode(ns(secs, nsecs))
  141. def do_increment(self, migrated, fscan, mscan):
  142. self.pending.increment(migrated, fscan, mscan)
  143. def make_complete(self, secs, nsecs):
  144. self.pending.complete(secs, nsecs)
  145. chead.val += self.pending
  146. if opt_proc != popt.DISP_DFL:
  147. self.val += self.pending
  148. if opt_proc == popt.DISP_PROC_VERBOSE:
  149. self.list.append(self.pending)
  150. self.pending = None
  151. def enumerate(self):
  152. if opt_proc == popt.DISP_PROC_VERBOSE and not self.is_filtered():
  153. for i, pelem in enumerate(self.list):
  154. sys.stdout.write("%d[%s].%d: %s\n" % (self.pid, self.comm, i+1, pelem))
  155. def is_pending(self):
  156. return self.pending != None
  157. def is_filtered(self):
  158. return self.filtered
  159. def display(self):
  160. if not self.is_filtered():
  161. sys.stdout.write("%d[%s]: %s\n" % (self.pid, self.comm, self.val))
  162. def trace_end():
  163. sys.stdout.write("total: %s\n" % chead.str())
  164. for i in chead.gen():
  165. i.display(),
  166. i.enumerate()
  167. def compaction__mm_compaction_migratepages(event_name, context, common_cpu,
  168. common_secs, common_nsecs, common_pid, common_comm,
  169. common_callchain, nr_migrated, nr_failed):
  170. chead.increment_pending(common_pid,
  171. pair(nr_migrated, nr_failed), None, None)
  172. def compaction__mm_compaction_isolate_freepages(event_name, context, common_cpu,
  173. common_secs, common_nsecs, common_pid, common_comm,
  174. common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken):
  175. chead.increment_pending(common_pid,
  176. None, pair(nr_scanned, nr_taken), None)
  177. def compaction__mm_compaction_isolate_migratepages(event_name, context, common_cpu,
  178. common_secs, common_nsecs, common_pid, common_comm,
  179. common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken):
  180. chead.increment_pending(common_pid,
  181. None, None, pair(nr_scanned, nr_taken))
  182. def compaction__mm_compaction_end(event_name, context, common_cpu,
  183. common_secs, common_nsecs, common_pid, common_comm,
  184. common_callchain, zone_start, migrate_start, free_start, zone_end,
  185. sync, status):
  186. chead.complete_pending(common_pid, common_secs, common_nsecs)
  187. def compaction__mm_compaction_begin(event_name, context, common_cpu,
  188. common_secs, common_nsecs, common_pid, common_comm,
  189. common_callchain, zone_start, migrate_start, free_start, zone_end,
  190. sync):
  191. chead.create_pending(common_pid, common_comm, common_secs, common_nsecs)
  192. def pr_help():
  193. global usage
  194. sys.stdout.write(usage)
  195. sys.stdout.write("\n")
  196. sys.stdout.write("-h display this help\n")
  197. sys.stdout.write("-p display by process\n")
  198. sys.stdout.write("-pv display by process (verbose)\n")
  199. sys.stdout.write("-t display stall times only\n")
  200. sys.stdout.write("-m display stats for migration\n")
  201. sys.stdout.write("-fs display stats for free scanner\n")
  202. sys.stdout.write("-ms display stats for migration scanner\n")
  203. sys.stdout.write("-u display results in microseconds (default nanoseconds)\n")
  204. comm_re = None
  205. pid_re = None
  206. pid_regex = "^(\d*)-(\d*)$|^(\d*)$"
  207. opt_proc = popt.DISP_DFL
  208. opt_disp = topt.DISP_ALL
  209. opt_ns = True
  210. argc = len(sys.argv) - 1
  211. if argc >= 1:
  212. pid_re = re.compile(pid_regex)
  213. for i, opt in enumerate(sys.argv[1:]):
  214. if opt[0] == "-":
  215. if opt == "-h":
  216. pr_help()
  217. exit(0);
  218. elif opt == "-p":
  219. opt_proc = popt.DISP_PROC
  220. elif opt == "-pv":
  221. opt_proc = popt.DISP_PROC_VERBOSE
  222. elif opt == '-u':
  223. opt_ns = False
  224. elif opt == "-t":
  225. set_type(topt.DISP_TIME)
  226. elif opt == "-m":
  227. set_type(topt.DISP_MIG)
  228. elif opt == "-fs":
  229. set_type(topt.DISP_ISOLFREE)
  230. elif opt == "-ms":
  231. set_type(topt.DISP_ISOLMIG)
  232. else:
  233. sys.exit(usage)
  234. elif i == argc - 1:
  235. m = pid_re.search(opt)
  236. if m != None and m.group() != "":
  237. if m.group(3) != None:
  238. f = pid_filter(m.group(3), m.group(3))
  239. else:
  240. f = pid_filter(m.group(1), m.group(2))
  241. else:
  242. try:
  243. comm_re=re.compile(opt)
  244. except:
  245. sys.stderr.write("invalid regex '%s'" % opt)
  246. sys.exit(usage)
  247. f = comm_filter(comm_re)
  248. chead.add_filter(f)