blob: be47905e0cc3304271f6bdcf68e558f13f66d995 [file] [log] [blame]
#!/usr/bin/python3
import re, sys, statistics , os
from optparse import OptionParser
from collections import defaultdict
from dynticks_testing_lib import parse_cpulist
class Noise:
def __init__(self, ts):
self.start = ts
self.end = None
def complete(self, end):
self.end = end
def duration(self):
if self.end is None:
return None
else:
return self.end - self.start
def match_exit(self, ne):
return False
def histable(self):
return True
class SchedLatNoise(Noise):
def __init__(self, next_task, ts):
super().__init__(ts)
self.next_task = next_task
def create_hist(self):
return SchedLatHist(self)
def histable(self):
return not isinstance(self.next_task, IdleTask)
class IrqWorkNoise(Noise):
def create_hist(self):
return IrqWorkHist(self)
class LocalTimerNoise(Noise):
def create_hist(self):
return LocalTimerHist(self)
class PageFaultNoise(Noise):
def create_hist(self):
return PageFaultHist(self)
class ReschedVecNoise(Noise):
def create_hist(self):
return ReschedVecHist(self)
class SchedNoise(Noise):
def __init__(self, next_task, ts):
super().__init__(ts)
self.next_task = next_task
def create_hist(self):
return SchedHist(self)
def histable(self):
return not isinstance(self.next_task, IdleTask)
class SchedPreemptNoise(Noise):
def __init__(self, next_task, ts):
super().__init__(ts)
self.next_task = next_task
def create_hist(self):
return SchedPreemptHist(self)
def match_exit(self, n):
return isinstance(self, n)
def histable(self):
return not isinstance(self.next_task, IdleTask)
class TickDepNoise(Noise):
def __init__(self, ts, dep):
super().__init__(ts)
self.dep = dep
def create_hist(self):
return TickDepHist(self, self.dep)
class SoftirqNoise(Noise):
def __init__(self, ts, vec):
super().__init__(ts)
self.vec = vec
def create_hist(self):
return SoftirqHist(self, self.vec)
class CallbackNoise(Noise):
def __init__(self, ts, func):
super().__init__(ts)
self.func = func
def create_hist(self):
return CallbackHist(self, self.func, self.name())
class CsdNoise(CallbackNoise):
def name(self):
return "CSD (IPI)"
def match_exit(self, ne):
return self.func == ne.func
class TimerNoise(CallbackNoise):
def name(self):
return "Timer"
class WorkqueueNoise(CallbackNoise):
def name(self):
return "Workqueue"
class SyscallNoise(CallbackNoise):
def name(self):
return "Syscall"
def match_exit(self, ne):
return self.func == ne.func
class UnknownNoise(Noise):
def create_hist(self):
return UnknownHist(self)
def __repr__(self):
return "Unknown"
class Hist:
def __init__(self, n):
self.nr_noise = 0
self.duration_list = []
self.add(n)
def add(self, n):
self.nr_noise += 1
duration = n.duration()
if duration is not None:
self.duration_list.append(duration)
class TickDepHist(Hist):
def __init__(self, n, dep):
super().__init__(n)
self.dep = dep
def noise_match(self, n):
return isinstance(n, TickDepNoise)
def __repr__(self):
return "Tick dep[%s]" % self.dep
class IrqWorkHist(Hist):
def noise_match(self, n):
return isinstance(n, IrqWorkNoise)
def __repr__(self):
return "Irq work"
class LocalTimerHist(Hist):
def noise_match(self, n):
return isinstance(n, LocalTimerNoise)
def __repr__(self):
return "Local timer"
class PageFaultHist(Hist):
def noise_match(self, n):
return isinstance(n, PageFaultNoise)
def __repr__(self):
return "Page fault"
class ReschedVecHist(Hist):
def noise_match(self, n):
return isinstance(n, ReschedVecNoise)
def __repr__(self):
return "Reschedule IPI"
class SchedLatHist(Hist):
def __init__(self, n):
super().__init__(n)
self.next_task = n.next_task
def noise_match(self, n):
return isinstance(n, SchedLatNoise) and n.next_task is self.next_task
def __repr__(self):
return "Wakeup Latency"
class SchedPreemptHist(Hist):
def __init__(self, n):
super().__init__(n)
self.next_task = n.next_task
def noise_match(self, n):
return isinstance(n, SchedPreemptNoise) and n.next_task is self.next_task
def __repr__(self):
return "Preempt. noise"
class SchedHist(Hist):
def __init__(self, n):
super().__init__(n)
self.next_task = n.next_task
def noise_match(self, n):
return isinstance(n, SchedNoise) and n.next_task is self.next_task
def __repr__(self):
return "Sched switch: %s:%d" % (self.next_task.comm, self.next_task.pid)
class SoftirqHist(Hist):
def __init__(self, n, vec):
super().__init__(n)
self.vec = vec
def noise_match(self, n):
return isinstance(n, SoftirqNoise) and self.vec == n.vec
def __repr__(self):
return "Softirq[%s]" % (self.vec)
class CallbackHist(Hist):
def __init__(self, n, func, name):
super().__init__(n)
self.func = func
self.noise_class = n.__class__
self.name = name
def noise_match(self, n):
return isinstance(n, self.noise_class) and self.func == n.func
def __repr__(self):
return "%s (func = %s)" % (self.name, self.func)
class UnknownHist(Hist):
def noise_match(self, n):
return isinstance(n, UnknownNoise)
def __repr__(self):
return "Unknown"
class Task:
current = {}
CTX_UNKNOWN = -1
CTX_KERNEL = 0
CTX_USER = 1
def __init__(self, pid, comm, cpu, state=None):
self.pid = pid
self.comm = comm
self.cpus = set([cpu])
self.curr_cpu = cpu
self.curr_state = state
self.noise_stack = []
self.hists = []
self.nr_noise = 0
self.seq_nr_noise = 0
self.nr_hists = 0
self.noise_duration = 0
self.ctx = Task.CTX_UNKNOWN
self.kernel_entry_ts = -1
def sched_out(self, next_task, ts, state):
self.curr_state = state
if self.is_preempted():
n = SchedPreemptNoise(self, ts)
self.push_noise(n)
n = SchedNoise(next_task, ts)
self.push_noise(n)
next_task.sched_in(ts, self.curr_cpu)
def sched_in(self, ts, cpu):
self.curr_cpu = cpu
self.cpus.add(cpu)
Task.current[cpu] = self
self.pop_noise_match_class(ts, SchedNoise)
# if a match exist, the task has been previously preempted
self.pop_noise_match_exit(ts, SchedPreemptNoise)
def add_noise(self, n):
self.nr_noise += 1
self.add_hist(n)
def push_noise(self, n):
self.seq_nr_noise += 1
self.noise_stack.append(n)
def __pop_noise(self, ts):
if len(self.noise_stack) == 0:
return
n = self.noise_stack.pop()
if ts is not None:
n.complete(ts)
self.add_noise(n)
def pop_noise_match_class(self, ts, noise_class):
if len(self.noise_stack) == 0:
return
if not isinstance(self.noise_stack[-1], noise_class):
print("Expected last event was sched_switch on pid %d ts: %d" % (self.pid, ts))
return
self.__pop_noise(ts)
def pop_noise_match_exit(self, ts, ne):
for n in self.noise_stack[::-1]:
if n.match_exit(ne):
self.noise_stack.remove(n)
n.complete(ts)
self.add_noise(n)
return
def add_hist(self, n):
if not n.histable():
return
self.nr_hists += 1
for h in self.hists:
if h.noise_match(n):
h.add(n)
return
self.hists.append(n.create_hist())
def flush_noise_stack(self, ts):
# Single event can account their duration here
if self.seq_nr_noise > 1:
ts = None
while len(self.noise_stack):
self.__pop_noise(ts)
self.seq_nr_noise = 0
def kernel_exit(self, ts):
if self.ctx == Task.CTX_KERNEL:
self.noise_duration += ts - self.kernel_entry_ts
# Issue unknown noise if none happened during kernel round-trip
if self.seq_nr_noise == 0:
n = UnknownNoise(self.kernel_entry_ts)
n.complete(ts)
self.add_hist(n)
elif self.ctx != Task.CTX_UNKNOWN:
print("Unexpected kernel exit at %d (lost traces?)" % ts)
self.ctx = Task.CTX_USER
self.flush_noise_stack(ts)
def kernel_enter(self, ts):
if self.ctx != Task.CTX_USER and self.ctx != Task.CTX_UNKNOWN:
print("Unexpected kernel entry at %d (lost traces?)" % ts)
self.kernel_entry_ts = ts
self.ctx = Task.CTX_KERNEL
def is_akawened(self):
return len(self.noise_stack) > 0 and isinstance(self.noise_stack[-1], SchedLatNoise)
def is_preempted(self):
return self.curr_state == 'R' and not isinstance(self, IdleTask)
def __repr__(self):
ret = "Task: %s Pid: %d" % (self.comm, self.pid)
ret += " cpus: %s" % self.cpus.__repr__().strip()
if len(self.cpus) > 1:
ret += " (Migrated!!!)"
return ret
class IdleTask(Task):
pass
taskdict = { }
idle_taskdict = { }
def get_task(pid, comm, cpu, state=None):
if pid == 0:
if cpu in idle_taskdict:
return (False, idle_taskdict[cpu])
else:
t = IdleTask(pid, comm, cpu, state)
idle_taskdict[cpu] = t
else:
if pid in taskdict:
return (False, taskdict[pid])
else:
t = Task(pid, comm, cpu, state)
taskdict[pid] = t
return (True, t)
def tick_stop(line, task, cpu, ts):
# tick_stop: success=1 dependency=NONE
s = re.search("tick_stop: .+?dependency=(\w+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
dep = s.group(1)
if dep != "NONE":
n = TickDepNoise(ts, dep)
task.push_noise(n)
return True
def rcu_dyntick(line, task, cpu, ts):
s = re.search("rcu_dyntick: (\S+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
what = s.group(1)
if what in ("Start", "StartIrq"):
task.kernel_exit(ts)
elif what in ("End", "EndIrq"):
task.kernel_enter(ts)
return True
def sched_wakeup(line, task, cpu, ts):
s = re.search("(?:sched_wakeup:\s+)?(.+?):([0-9]+)\s+\[([0-9]+)\]\s+CPU:([0-9]+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
(wake_comm, wake_pid) = (s.group(1), int(s.group(2)))
(_, wake_task) = get_task(wake_pid, wake_comm, cpu)
n = SchedLatNoise(wake_task, ts)
wake_task.push_noise(n)
return True
def sched_switch(line, task, cpu, ts):
# trace format
# ex: sched_switch: prev_comm=dynticks-testin prev_pid=140026 prev_prio=120 prev_state=S ==> next_comm=swapper/21 next_pid=0 next_prio=120
s = re.search("sched_switch:\s+prev_comm=.+? prev_pid=[0-9]+.+?prev_state=(\w)\s+==> next_comm=(.+?) next_pid=([0-9]+)", line, re.S)
if s is None:
# trace-cmd format
# ex: sched_switch: swapper/27:0 [120] R ==> kworker/27:1:578 [120]
s = re.search("sched_switch:\s+.+?:[0-9]+.+?\[.+?\]\s+([A-Z])\s+==> (.+?):([0-9]+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
(state, next_comm, next_pid) = (s.group(1), s.group(2), int(s.group(3)))
(is_new, next_task) = get_task(next_pid, next_comm, cpu, state)
# True: the task has been woken up
if next_task.is_akawened():
next_task.pop_noise_match_class(ts, SchedLatNoise)
task.sched_out(next_task, ts, state)
return True
def softirq_entry(line, task, cpu, ts):
s = re.search("softirq_entry:\s+vec=[0-9] \[action=(.+?)\]", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
vec = s.group(1)
n = SoftirqNoise(ts, vec)
#n.complete(ts)
task.push_noise(n)
return True
def irq_work_entry(line, task, cpu, ts):
s = re.search("irq_work_entry:.+", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
n = IrqWorkNoise(ts)
#n.complete(ts)
task.push_noise(n)
return True
def page_fault_user(line, task, cpu, ts):
s = re.search("page_fault_user:.+", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
n = PageFaultNoise(ts)
#n.complete(ts)
task.push_noise(n)
return True
def reschedule_entry(line, task, cpu, ts):
s = re.search("reschedule_entry:.+", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
n = ReschedVecNoise(ts)
#n.complete(ts)
task.push_noise(n)
return True
def page_fault_kernel(line, task, cpu, ts):
s = re.search("page_fault_kernel:.+", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
n = PageFaultNoise(ts)
#n.complete(ts)
task.push_noise(n)
return True
def local_timer_entry(line, task, cpu, ts):
s = re.search("local_timer_entry:.+", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
n = LocalTimerNoise(ts)
#n.complete(ts)
task.push_noise(n)
return True
def csd_function_entry(line, task, cpu, ts):
s = re.search("csd_function_entry:\s+func=([^,]+),", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
func = s.group(1)
n = CsdNoise(ts, func)
#n.complete(ts)
task.push_noise(n)
return True
def csd_function_exit(line, task, cpu, ts):
s = re.search("csd_function_exit:\s+func=([^,]+),", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
func = s.group(1)
n = CsdNoise(ts, func)
task.pop_noise_match_exit(ts, n)
return True
def timer_expire_entry(line, task, cpu, ts):
s = re.search("timer_expire_entry: timer=\S+ function=(\S+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
timer = s.group(1)
n = TimerNoise(ts, timer)
#n.complete(ts)
task.push_noise(n)
return True
def hrtimer_expire_entry(line, task, cpu, ts):
# trace format:
# hrtimer_expire_entry: hrtimer=0xff41d0a32a1c7db8 now=7260211852499 function=hrtimer_wakeup/0x0
# trace-cmd format:
# hrtimer_expire_entry: hrtimer=00000000fd65e54e function=tick_nohz_handler now=340292315972110
s = re.search("hrtimer_expire_entry: hrtimer=\S+ .*?function=(\S+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
timer = s.group(1)
n = TimerNoise(ts, timer)
#n.complete(ts)
task.push_noise(n)
return True
def workqueue_execute_start(line, task, cpu, ts):
s = re.search("workqueue_execute_start: work struct [^:]+: function (\S+)", line, re.S)
if s is None:
print(line)
sys.exit(-1)
return False
work = s.group(1)
n = WorkqueueNoise(ts, work)
#n.complete(ts)
task.push_noise(n)
return True
def syscall_enter(line, task, cpu, ts):
s = re.search("(sys_[^(]+)[(]", line, re.S)
if s is None:
return False
name = s.group(1)
n = SyscallNoise(ts, name)
#n.complete(ts)
task.push_noise(n)
return True
def syscall_exit(line, task, cpu, ts):
s = re.search("(sys_[^(]+) ->", line, re.S)
if s is None:
return False
name = s.group(1)
n = SyscallNoise(ts, name)
#n.complete(ts)
task.pop_noise_match_exit(ts, n)
return True
def syscall(line, task, cpu, ts):
if syscall_enter(line, task, cpu, ts):
return True
elif syscall_exit(line, task, cpu, ts):
return True
else:
return False
unhandled_events = set()
def unhandled_event(line, task, cpu, ts):
s = re.match("([^: (]+).+", line, re.S)
if s is None:
print("Unhandled event parsing issue: %s" % line)
sys.exit(-1)
unhandled_events.add(s.group(1))
return True
"""""""""""""""""
CMD-LINE PARSER
"""""""""""""""""
def parse_arguments():
parser = OptionParser(usage="usage: %prog [options] trace_file")
parser.add_option("-t", "--task",
dest="exact_tasks",
action="append",
default=[],
help="Tasks pattern to filter (can be specified multiple times"
"It matches any tasks that starts with the specified pattern"
)
parser.add_option("-p", "--pid-task",
dest="pid_tasks",
action="append",
help="Match PID of the task",
default=[]
)
parser.add_option("-c", "--cpulist",
action="callback",
callback=cpulist_callback,
type="string", nargs=1, dest="cpulist_raw",
help="List of nohz_full CPUs (ex: 2-11,13-18)"
)
(options, args) = parser.parse_args()
trace_file = args[0]
return options, trace_file
def task_matches_filters(task_name, task_pid, exact_tasks=None, pid_tasks=None):
exact_tasks = exact_tasks or []
pid_tasks = pid_tasks or []
# No flags provided
if not exact_tasks and not pid_tasks:
return True
# -t flag: match exact task name
if task_name in exact_tasks:
return True
# -p flag: match the PID
if str(task_pid) in pid_tasks:
return True
return False
cpulist = None
def cpulist_callback(option, opt, value, parser):
global cpulist
cpulist = parse_cpulist(value)
def get_irq_nohzfull_intersect(cpulist):
smp_list = defaultdict(set)
for vec in os.listdir("/proc/irq"):
path = "/proc/irq/%s" % vec
if not os.path.isdir(path) or vec == "0":
continue
path = os.path.join(path, "smp_affinity_list")
f_list = open(path, "r")
affinity = f_list.read()
f_list.close()
# Be sure IRQ affinity isn't set for nohz CPUs
cpus = parse_cpulist(affinity)
res = set(cpulist) & set(cpus)
if len(res):
smp_list[frozenset(res)].add(vec)
return smp_list
def get_thread_siblings_list():
thread_siblings_list = {};
cpu_folders = [entry for entry in os.listdir("/sys/devices/system/cpu/") if re.match(r'^cpu\d+$', entry)]
for hw_thread_s in cpu_folders:
hw_thread = hw_thread_s.replace("cpu","")
path = "/sys/devices/system/cpu/cpu%s/" % hw_thread
path = os.path.join(path, "topology/thread_siblings_list")
s_list = open(path, "r")
hw_threads_list = s_list.read();
s_list.close()
hw_threads = parse_cpulist(hw_threads_list)
thread_siblings_list[hw_thread] = hw_threads
return thread_siblings_list
"""""""""""""""""
/ CMD-LINE PARSER
"""""""""""""""""
events = [
("tick_stop:", tick_stop),
("rcu_dyntick:", rcu_dyntick),
("sched_wakeup", sched_wakeup),
("sched_switch", sched_switch),
("softirq_entry", softirq_entry),
("irq_work_entry", irq_work_entry),
("page_fault_user", page_fault_user),
("reschedule_entry", reschedule_entry),
("local_timer_entry", local_timer_entry),
("page_fault_kernel", page_fault_kernel),
("csd_function_exit", csd_function_exit),
("csd_function_entry", csd_function_entry),
("timer_expire_entry", timer_expire_entry),
("hrtimer_expire_entry", hrtimer_expire_entry),
("workqueue_execute_start", workqueue_execute_start),
("sys_", syscall),
("", unhandled_event), # must be last!
]
TRACE_REG = re.compile("^\s+(.+?)-([0-9]+)\s+\[([0-9]{3})\].+?([0-9]+.[0-9]{6}):\s+(.+?)$", re.S)
nr = 0
(options, trace_file) = parse_arguments()
try:
f = open(trace_file)
except FileNotFoundError as e:
print("File '%s' not found.\n" % trace_file)
exit(0)
fsize = os.path.getsize(trace_file)
cursor = 0
# check whether SMT is enabled
smt_path = "/sys/devices/system/cpu/smt/active"
if not os.path.exists(smt_path):
print("Can't find %s " % smt_path)
smt = open(smt_path).read().strip()
if smt == "1":
print("SMT is enabled. On nohz_full with isolated CPU it should be disabled.\n")
# check irq affinity on nohz CPUs
if cpulist:
print("=== IRQ AFFINITY ON NOHZ_FULL CPUs ===")
smp_list = get_irq_nohzfull_intersect(cpulist)
for cpus, vectors in smp_list.items():
sorted_vectors = ', '.join(map(str, sorted(vectors)))
sorted_cpus = ', '.join(map(str, sorted(cpus)))
print("Wrong IRQ affinity on vectors:", end = " ")
print(sorted_vectors, sep=", ")
print("These CPUs are nohz_full:", end = " ")
print(sorted_cpus, sep=", ")
print()
# if SMT is set, siblings thread should NOT be isolated
if smt == "1":
print("=== SIBLINGS THREAD <=> NOHZ_FULL ===")
siblings_list = get_thread_siblings_list()
# none of the siblings should be in cpulist
for hw_thread in siblings_list:
siblings_nohz_intersect = set(siblings_list[hw_thread]) & set(cpulist)
if len(siblings_nohz_intersect):
print("!!!WARN!!! SMT threads should NOT be isolated:", end = " ")
print(siblings_list[hw_thread], sep=", ")
print("found:", end=" ")
print(siblings_nohz_intersect, sep=", ")
print()
while True:
line = f.readline()
if not line:
break
cursor += float(len(line))
sys.stderr.write("Processing %f%%\r" % (cursor / fsize * 100))
#sys.stderr.write("Processing line: %d\r" % nr)
nr += 1
s = TRACE_REG.match(line)
if s is not None:
comm = s.group(1)
pid = int(s.group(2))
cpu = int(s.group(3))
raw_ts = s.group(4).split(".")
ts = (int(raw_ts[0]) * (10**6)) + int(raw_ts[1])
rest = s.group(5)
if cpulist is not None and cpu not in cpulist:
continue
(is_new, task) = get_task(pid, comm, cpu)
if is_new:
task.sched_in(0, cpu)
for e in events:
if rest.startswith(e[0]):
if e[1](rest, task, cpu, ts):
break
#print("%s %d %d %d" % (comm, pid, cpu, ts))
tasklist = [i[1] for i in taskdict.items()]
for t in tasklist:
t.flush_noise_stack(None)
print("Unhandled events: %s" % ",".join(unhandled_events))
print()
#tasklist_sorted = sorted(tasklist, key = lambda t : t.nr_hists, reverse = True)
def sort_duration(t):
total = 0
for h in t.hists:
total += sum(h.duration_list)
return total
tasklist_sorted = sorted(tasklist, key = lambda t : t.noise_duration, reverse = True)
#tasklist_sorted = sorted(tasklist, key = sort_duration, reverse = True)
#sorted(tasklist, key = lambda t : t.nr_hists, reverse = True)
for t in tasklist_sorted:
if t.nr_hists == 0:
continue
if task_matches_filters(t.comm, t.pid, options.exact_tasks, options.pid_tasks) == False:
continue
#t.hists = sorted(t.hists, key = lambda h : h.nr_noise, reverse = True)
#t.hists = sorted(t.hists, key = lambda h : sum(h.duration_list), reverse = True)
t.hists = sorted(t.hists, key = lambda h : sum(h.duration_list), reverse = True)
print(t)
total_duration = 0
for h in t.hists:
print("\t{: <45} Nr:{: >10}".format(*[h.__repr__(), h.nr_noise]), end = "")
if len(h.duration_list) == 0:
print("\tDuration: Unknown")
else:
duration = sum(h.duration_list)
print("\tDuration:{: >12}".format(duration))
total_duration += duration
#print("Nr: %d\n" % len(h.noise_list))
print()
print("\t%s%s" % (" " * 46, "Total: {: >6}".format(t.nr_hists)), end = "")
print("\tTotal: {: >14}\n".format(t.noise_duration))
"""
for snap in sorted(Snaps):
for e in Snaps[snap]:
(ts, pid, waketime) = e
print("Lat: %d of %s:%d switch at %d wake at %d" % (snap, Tasks[pid].comm, pid, ts, waketime))
print("Total: %d Avg: %d Stdev: %d" % (sum(Delays), statistics.mean(Delays), statistics.stdev(Delays)))
"""