| # report time spent in compaction | |
| # Licensed under the terms of the GNU GPL License version 2 | |
| # testing: | |
| # 'echo 1 > /proc/sys/vm/compact_memory' to force compaction of all zones | |
| import os | |
| import sys | |
| import re | |
| import signal | |
| signal.signal(signal.SIGPIPE, signal.SIG_DFL) | |
| usage = "usage: perf script report compaction-times.py -- [-h] [-u] [-p|-pv] [-t | [-m] [-fs] [-ms]] [pid|pid-range|comm-regex]\n" | |
| class popt: | |
| DISP_DFL = 0 | |
| DISP_PROC = 1 | |
| DISP_PROC_VERBOSE=2 | |
| class topt: | |
| DISP_TIME = 0 | |
| DISP_MIG = 1 | |
| DISP_ISOLFREE = 2 | |
| DISP_ISOLMIG = 4 | |
| DISP_ALL = 7 | |
| class comm_filter: | |
| def __init__(self, re): | |
| self.re = re | |
| def filter(self, pid, comm): | |
| m = self.re.search(comm) | |
| return m == None or m.group() == "" | |
| class pid_filter: | |
| def __init__(self, low, high): | |
| self.low = (0 if low == "" else int(low)) | |
| self.high = (0 if high == "" else int(high)) | |
| def filter(self, pid, comm): | |
| return not (pid >= self.low and (self.high == 0 or pid <= self.high)) | |
| def set_type(t): | |
| global opt_disp | |
| opt_disp = (t if opt_disp == topt.DISP_ALL else opt_disp|t) | |
| def ns(sec, nsec): | |
| return (sec * 1000000000) + nsec | |
| def time(ns): | |
| return "%dns" % ns if opt_ns else "%dus" % (round(ns, -3) / 1000) | |
| class pair: | |
| def __init__(self, aval, bval, alabel = None, blabel = None): | |
| self.alabel = alabel | |
| self.blabel = blabel | |
| self.aval = aval | |
| self.bval = bval | |
| def __add__(self, rhs): | |
| self.aval += rhs.aval | |
| self.bval += rhs.bval | |
| return self | |
| def __str__(self): | |
| return "%s=%d %s=%d" % (self.alabel, self.aval, self.blabel, self.bval) | |
| class cnode: | |
| def __init__(self, ns): | |
| self.ns = ns | |
| self.migrated = pair(0, 0, "moved", "failed") | |
| self.fscan = pair(0,0, "scanned", "isolated") | |
| self.mscan = pair(0,0, "scanned", "isolated") | |
| def __add__(self, rhs): | |
| self.ns += rhs.ns | |
| self.migrated += rhs.migrated | |
| self.fscan += rhs.fscan | |
| self.mscan += rhs.mscan | |
| return self | |
| def __str__(self): | |
| prev = 0 | |
| s = "%s " % time(self.ns) | |
| if (opt_disp & topt.DISP_MIG): | |
| s += "migration: %s" % self.migrated | |
| prev = 1 | |
| if (opt_disp & topt.DISP_ISOLFREE): | |
| s += "%sfree_scanner: %s" % (" " if prev else "", self.fscan) | |
| prev = 1 | |
| if (opt_disp & topt.DISP_ISOLMIG): | |
| s += "%smigration_scanner: %s" % (" " if prev else "", self.mscan) | |
| return s | |
| def complete(self, secs, nsecs): | |
| self.ns = ns(secs, nsecs) - self.ns | |
| def increment(self, migrated, fscan, mscan): | |
| if (migrated != None): | |
| self.migrated += migrated | |
| if (fscan != None): | |
| self.fscan += fscan | |
| if (mscan != None): | |
| self.mscan += mscan | |
| class chead: | |
| heads = {} | |
| val = cnode(0); | |
| fobj = None | |
| @classmethod | |
| def add_filter(cls, filter): | |
| cls.fobj = filter | |
| @classmethod | |
| def create_pending(cls, pid, comm, start_secs, start_nsecs): | |
| filtered = 0 | |
| try: | |
| head = cls.heads[pid] | |
| filtered = head.is_filtered() | |
| except KeyError: | |
| if cls.fobj != None: | |
| filtered = cls.fobj.filter(pid, comm) | |
| head = cls.heads[pid] = chead(comm, pid, filtered) | |
| if not filtered: | |
| head.mark_pending(start_secs, start_nsecs) | |
| @classmethod | |
| def increment_pending(cls, pid, migrated, fscan, mscan): | |
| head = cls.heads[pid] | |
| if not head.is_filtered(): | |
| if head.is_pending(): | |
| head.do_increment(migrated, fscan, mscan) | |
| else: | |
| sys.stderr.write("missing start compaction event for pid %d\n" % pid) | |
| @classmethod | |
| def complete_pending(cls, pid, secs, nsecs): | |
| head = cls.heads[pid] | |
| if not head.is_filtered(): | |
| if head.is_pending(): | |
| head.make_complete(secs, nsecs) | |
| else: | |
| sys.stderr.write("missing start compaction event for pid %d\n" % pid) | |
| @classmethod | |
| def gen(cls): | |
| if opt_proc != popt.DISP_DFL: | |
| for i in cls.heads: | |
| yield cls.heads[i] | |
| @classmethod | |
| def str(cls): | |
| return cls.val | |
| def __init__(self, comm, pid, filtered): | |
| self.comm = comm | |
| self.pid = pid | |
| self.val = cnode(0) | |
| self.pending = None | |
| self.filtered = filtered | |
| self.list = [] | |
| def __add__(self, rhs): | |
| self.ns += rhs.ns | |
| self.val += rhs.val | |
| return self | |
| def mark_pending(self, secs, nsecs): | |
| self.pending = cnode(ns(secs, nsecs)) | |
| def do_increment(self, migrated, fscan, mscan): | |
| self.pending.increment(migrated, fscan, mscan) | |
| def make_complete(self, secs, nsecs): | |
| self.pending.complete(secs, nsecs) | |
| chead.val += self.pending | |
| if opt_proc != popt.DISP_DFL: | |
| self.val += self.pending | |
| if opt_proc == popt.DISP_PROC_VERBOSE: | |
| self.list.append(self.pending) | |
| self.pending = None | |
| def enumerate(self): | |
| if opt_proc == popt.DISP_PROC_VERBOSE and not self.is_filtered(): | |
| for i, pelem in enumerate(self.list): | |
| sys.stdout.write("%d[%s].%d: %s\n" % (self.pid, self.comm, i+1, pelem)) | |
| def is_pending(self): | |
| return self.pending != None | |
| def is_filtered(self): | |
| return self.filtered | |
| def display(self): | |
| if not self.is_filtered(): | |
| sys.stdout.write("%d[%s]: %s\n" % (self.pid, self.comm, self.val)) | |
| def trace_end(): | |
| sys.stdout.write("total: %s\n" % chead.str()) | |
| for i in chead.gen(): | |
| i.display(), | |
| i.enumerate() | |
| def compaction__mm_compaction_migratepages(event_name, context, common_cpu, | |
| common_secs, common_nsecs, common_pid, common_comm, | |
| common_callchain, nr_migrated, nr_failed): | |
| chead.increment_pending(common_pid, | |
| pair(nr_migrated, nr_failed), None, None) | |
| def compaction__mm_compaction_isolate_freepages(event_name, context, common_cpu, | |
| common_secs, common_nsecs, common_pid, common_comm, | |
| common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken): | |
| chead.increment_pending(common_pid, | |
| None, pair(nr_scanned, nr_taken), None) | |
| def compaction__mm_compaction_isolate_migratepages(event_name, context, common_cpu, | |
| common_secs, common_nsecs, common_pid, common_comm, | |
| common_callchain, start_pfn, end_pfn, nr_scanned, nr_taken): | |
| chead.increment_pending(common_pid, | |
| None, None, pair(nr_scanned, nr_taken)) | |
| def compaction__mm_compaction_end(event_name, context, common_cpu, | |
| common_secs, common_nsecs, common_pid, common_comm, | |
| common_callchain, zone_start, migrate_start, free_start, zone_end, | |
| sync, status): | |
| chead.complete_pending(common_pid, common_secs, common_nsecs) | |
| def compaction__mm_compaction_begin(event_name, context, common_cpu, | |
| common_secs, common_nsecs, common_pid, common_comm, | |
| common_callchain, zone_start, migrate_start, free_start, zone_end, | |
| sync): | |
| chead.create_pending(common_pid, common_comm, common_secs, common_nsecs) | |
| def pr_help(): | |
| global usage | |
| sys.stdout.write(usage) | |
| sys.stdout.write("\n") | |
| sys.stdout.write("-h display this help\n") | |
| sys.stdout.write("-p display by process\n") | |
| sys.stdout.write("-pv display by process (verbose)\n") | |
| sys.stdout.write("-t display stall times only\n") | |
| sys.stdout.write("-m display stats for migration\n") | |
| sys.stdout.write("-fs display stats for free scanner\n") | |
| sys.stdout.write("-ms display stats for migration scanner\n") | |
| sys.stdout.write("-u display results in microseconds (default nanoseconds)\n") | |
| comm_re = None | |
| pid_re = None | |
| pid_regex = "^(\d*)-(\d*)$|^(\d*)$" | |
| opt_proc = popt.DISP_DFL | |
| opt_disp = topt.DISP_ALL | |
| opt_ns = True | |
| argc = len(sys.argv) - 1 | |
| if argc >= 1: | |
| pid_re = re.compile(pid_regex) | |
| for i, opt in enumerate(sys.argv[1:]): | |
| if opt[0] == "-": | |
| if opt == "-h": | |
| pr_help() | |
| exit(0); | |
| elif opt == "-p": | |
| opt_proc = popt.DISP_PROC | |
| elif opt == "-pv": | |
| opt_proc = popt.DISP_PROC_VERBOSE | |
| elif opt == '-u': | |
| opt_ns = False | |
| elif opt == "-t": | |
| set_type(topt.DISP_TIME) | |
| elif opt == "-m": | |
| set_type(topt.DISP_MIG) | |
| elif opt == "-fs": | |
| set_type(topt.DISP_ISOLFREE) | |
| elif opt == "-ms": | |
| set_type(topt.DISP_ISOLMIG) | |
| else: | |
| sys.exit(usage) | |
| elif i == argc - 1: | |
| m = pid_re.search(opt) | |
| if m != None and m.group() != "": | |
| if m.group(3) != None: | |
| f = pid_filter(m.group(3), m.group(3)) | |
| else: | |
| f = pid_filter(m.group(1), m.group(2)) | |
| else: | |
| try: | |
| comm_re=re.compile(opt) | |
| except: | |
| sys.stderr.write("invalid regex '%s'" % opt) | |
| sys.exit(usage) | |
| f = comm_filter(comm_re) | |
| chead.add_filter(f) |