forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
7.7 KiB
311 lines
7.7 KiB
# 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)
|
|
|