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