#!/usr/bin/env python # # Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """Analyze block trace""" import collections import os import re import string import sys # ex) <...>-52 [001] ...1 1.362574: block_bio_queue: 8,16 R 0 + 8 [kworker/u8:1] RE_BLOCK = r'.+-([0-9]+).*\s+([0-9]+\.[0-9]+):\s+block_bio_queue:\s+([0-9]+)\,([0-9]+)\s(\S+)\s+([0-9]+)\s+\+\s+([0-9]+)\s+\[([^\]]+)' # ex) <...>-453 [001] d..4 3.181854: sched_blocked_reason: pid=471 iowait=1 caller=__wait_on_buffer+0x24/0x2c RE_SCHED_BLOCKED_READSON = r'.+-([0-9]+)\s+\[([0-9]+)\]\s.*\s+([0-9]+\.[0-9]+):\s+sched_blocked_reason:\spid=([0-9]+)\siowait=([01])\scaller=(\S+)' # ex) -0 [000] d..3 3.181864: sched_switch: prev_comm=swapper/0 prev_pid=0 prev_prio=120 prev_state=R ==> next_comm=ueventd next_pid=471 next_prio=120 RE_SCHED_SWITCH = r'.+-([0-9]+)\s+\[([0-9]+)\]\s.*\s+([0-9]+\.[0-9]+):\s+sched_switch:\sprev_comm=(.+)\sprev_pid=([0-9]+)\sprev_prio=([0-9]+)\sprev_state=(\S+).*next_comm=(.+)\snext_pid=([0-9]+)\snext_prio=([0-9]+)' # dev_num = major * MULTIPLIER + minor DEV_MAJOR_MULTIPLIER = 1000 # dm access is remapped to disk access. So account differently DM_MAJOR = 253 MAX_PROCESS_DUMP = 10 class RwEvent: def __init__(self, block_num, start_time, size): self.block_num = block_num self.start_time = start_time self.size = size def get_string_pos(strings, string_to_find): for i, s in enumerate(strings): if s == string_to_find: return i return -1 class ProcessData: def __init__(self, name): self.name = name self.reads = {} # k : dev_num, v : [] of reads self.per_device_total_reads = {} self.writes = {} self.per_device_total_writes = {} self.total_reads = 0 self.total_writes = 0 self.total_dm_reads = 0 self.total_dm_writes = 0 def add_read_event(self, major, minor, event): devNum = major * DEV_MAJOR_MULTIPLIER + minor; events = self.reads.get(devNum) if not events: events = [] self.reads[devNum] = events self.per_device_total_reads[devNum] = 0 events.append(event) self.total_reads += event.size self.per_device_total_reads[devNum] += event.size def add_write_event(self, major, minor, event): devNum = major * DEV_MAJOR_MULTIPLIER + minor; events = self.writes.get(devNum) if not events: events = [] self.writes[devNum] = events self.per_device_total_writes[devNum] = 0 events.append(event) self.total_writes += event.size self.per_device_total_writes[devNum] += event.size def add_dm_read(self, size): self.total_dm_reads += size def add_dm_write(self, size): self.total_dm_writes += size def dump(self): print "Process,", self.name print " total reads,", self.total_reads print " total writes,", self.total_writes print " total dm reads,", self.total_dm_reads print " total dm writes,", self.total_dm_writes print " R per device" sorted_r = collections.OrderedDict(sorted(self.per_device_total_reads.items(), \ key = lambda item: item[1], reverse = True)) for i in range(len(sorted_r)): dev = sorted_r.popitem(last=False) print " ", dev[0],dev[1] print " W per device" sorted_w = collections.OrderedDict(sorted(self.per_device_total_writes.items(), \ key = lambda item: item[1], reverse = True)) for i in range(len(sorted_w)): dev = sorted_w.popitem(last=False) print " ", dev[0],dev[1] class IoTrace: def __init__(self): self.ios = {} #K: process name, v:ProcessData self.total_reads = 0 self.total_writes = 0 self.total_reads_per_device = {} #K: block num, V: total blocks self.total_writes_per_device = {} self.total_dm_reads = {} #K: devnum, V: blocks self.total_dm_writes = {} self.re_block = re.compile(RE_BLOCK) def parse(self, l): match = self.re_block.match(l) if not match: return False try: self.do_parse_bio_queue(l, match) except ValueError: print "cannot parse:", l raise return True def do_parse_bio_queue(self, l, match): pid = match.group(1) start_time = float(match.group(2))*1000 #ms major = int(match.group(3)) minor = int(match.group(4)) devNum = major * DEV_MAJOR_MULTIPLIER + minor; operation = match.group(5) block_num = int(match.group(6)) size = int(match.group(7)) process = match.group(8) + "-" + pid event = RwEvent(block_num, start_time, size) io = self.ios.get(process) if not io: io = ProcessData(process) self.ios[process] = io if major == DM_MAJOR: devNum = major * DEV_MAJOR_MULTIPLIER + minor; if 'R' in operation[0]: if devNum not in self.total_dm_reads: self.total_dm_reads[devNum] = 0 self.total_dm_reads[devNum] += size io.add_dm_read(size) elif 'W' in operation[0]: if devNum not in self.total_dm_writes: self.total_dm_writes[devNum] = 0 self.total_dm_writes[devNum] += size io.add_dm_write(size) return if 'R' in operation[0]: io.add_read_event(major, minor, event) self.total_reads += size per_device = self.total_reads_per_device.get(devNum) if not per_device: self.total_reads_per_device[devNum] = 0 self.total_reads_per_device[devNum] += size elif 'W' in operation[0]: io.add_write_event(major, minor, event) self.total_writes += size per_device = self.total_writes_per_device.get(devNum) if not per_device: self.total_writes_per_device[devNum] = 0 self.total_writes_per_device[devNum] += size def dump(self): print "total read blocks,", self.total_reads print "total write blocks,", self.total_writes print "Total DM R" for dev,size in self.total_dm_reads.items(): print dev, size print "Total DM W" for dev,size in self.total_dm_writes.items(): print dev, size print "**Process total R/W" sorted_by_total_rw = collections.OrderedDict(sorted(self.ios.items(), \ key = lambda item: item[1].total_reads + item[1].total_writes, reverse = True)) for i in range(MAX_PROCESS_DUMP): process = sorted_by_total_rw.popitem(last=False) if not process: break process[1].dump() print "**Process total W" sorted_by_total_w = collections.OrderedDict(sorted(self.ios.items(), \ key = lambda item: item[1].total_writes, reverse = True)) for i in range(5): process = sorted_by_total_w.popitem(last=False) if not process: break process[1].dump() print "**Device total R" sorted_by_total_r = collections.OrderedDict(sorted(self.total_reads_per_device.items(), \ key = lambda item: item[1], reverse = True)) for i in range(len(sorted_by_total_r)): dev = sorted_by_total_r.popitem(last=False) print dev[0],dev[1] print "**Device total W" sorted_by_total_w = collections.OrderedDict(sorted(self.total_writes_per_device.items(), \ key = lambda item: item[1], reverse = True)) for i in range(len(sorted_by_total_w)): dev = sorted_by_total_w.popitem(last=False) print dev[0],dev[1] class SchedProcess: def __init__(self, pid): self.pid = pid self.name = "unknown" self.total_execution_time = 0.0 self.total_io_wait_time = 0.0 self.total_other_wait_time = 0.0 self.waiting_calls = {} # k: waiting_call, v : waiting counter self.io_waiting_call_times = {} # k: waiting_call, v: total wait time self.in_iowait = False self.last_waiting_call = None self.last_switch_out_time = 0.0 self.last_switch_in_time = 0.0 self.last_core = -1 self.execution_time_per_core = {} # k: core, v : time self.io_latency_histograms = {} # k : delay in ms, v : count def handle_reason(self, current_time, iowait, waiting_call): #if self.pid == 1232: # print current_time, iowait, waiting_call if iowait == 1: self.in_iowait = True self.last_waiting_call = waiting_call call_counter = self.waiting_calls.get(waiting_call, 0) call_counter += 1 self.waiting_calls[waiting_call] = call_counter def handle_switch_out(self, current_time, out_state, priority, name, core): #if self.pid == 1232: # print "out", current_time, out_state if self.name != name: self.name = name self.last_switch_out_time = current_time if self.last_switch_in_time == 0.0: # switch in not recorded. ignore this one return execution_time = current_time - self.last_switch_in_time self.total_execution_time += execution_time core_execution_time = self.execution_time_per_core.get(core, 0.0) core_execution_time += execution_time self.execution_time_per_core[core] = core_execution_time def handle_switch_in(self, current_time, priority, name, core): #if self.pid == 1232: # print "in", current_time, self.in_iowait if self.name != name: self.name = name self.last_switch_in_time = current_time if self.last_switch_out_time == 0.0: # in without out, probably 1st self.in_iowait = False return wait_time = current_time - self.last_switch_out_time if self.in_iowait: self.total_io_wait_time += wait_time total_waiting_call_time = self.io_waiting_call_times.get(self.last_waiting_call, 0.0) total_waiting_call_time += wait_time self.io_waiting_call_times[self.last_waiting_call] = total_waiting_call_time wait_time_ms = int(wait_time*10) / 10.0 # resolution up to 0.1 ms histogram_count = self.io_latency_histograms.get(wait_time_ms, 0) histogram_count += 1 self.io_latency_histograms[wait_time_ms] = histogram_count else: self.total_other_wait_time += wait_time self.in_iowait = False def dump(self): print "PID:", self.pid, " name:", self.name print " total execution time:", self.total_execution_time,\ " io wait:", self.total_io_wait_time, " other wait:", self.total_other_wait_time sorted_data = collections.OrderedDict(sorted(self.execution_time_per_core.items(), \ key = lambda item: item[0], reverse = False)) print " Core execution:", sorted_data sorted_data = collections.OrderedDict(sorted(self.waiting_calls.items(), \ key = lambda item: item[1], reverse = True)) print " Wait calls:", sorted_data sorted_data = collections.OrderedDict(sorted(self.io_waiting_call_times.items(), \ key = lambda item: item[1], reverse = True)) print " IO Wait time per wait calls:", sorted_data sorted_data = collections.OrderedDict(sorted(self.io_latency_histograms.items(), \ key = lambda item: item[0], reverse = False)) print " Wait time histogram:", sorted_data class SchedTrace: def __init__(self): self.re_switch = re.compile(RE_SCHED_SWITCH) self.re_reason = re.compile(RE_SCHED_BLOCKED_READSON) self.processes = {} # key: pid, v : SchedProcess def parse(self, l): checked_reason = False match = self.re_switch.match(l) if not match: match = self.re_reason.match(l) checked_reason = True if not match: return False try: if checked_reason: self.do_handle_reason(l, match) else: self.do_handle_switch(l, match) except ValueError: print "cannot parse:", l raise return True def do_handle_switch(self, l, match): current_pid = int(match.group(1)) cpu_core = int(match.group(2)) current_time = float(match.group(3))*1000 #ms out_name = match.group(4) out_pid = int(match.group(5)) out_prio = int(match.group(6)) out_state = match.group(7) in_name = match.group(8) in_pid = int(match.group(9)) in_prio = int(match.group(10)) out_process = self.processes.get(out_pid) if not out_process: out_process = SchedProcess(out_pid) self.processes[out_pid] = out_process in_process = self.processes.get(in_pid) if not in_process: in_process = SchedProcess(in_pid) self.processes[in_pid] = in_process out_process.handle_switch_out(current_time, out_state, out_prio, out_name, cpu_core) in_process.handle_switch_in(current_time, in_prio, in_name, cpu_core) def do_handle_reason(self, l, match): current_pid = int(match.group(1)) cpu_core = int(match.group(2)) current_time = float(match.group(3))*1000 #ms pid = int(match.group(4)) iowait = int(match.group(5)) waiting_call = match.group(6) process = self.processes.get(pid) if not process: process = SchedProcess(pid) self.processes[pid] = process process.handle_reason(current_time, iowait, waiting_call) def dump(self): sorted_by_total_execution = collections.OrderedDict(sorted(self.processes.items(), \ key = lambda item: item[1].total_io_wait_time, reverse = True)) for k, v in sorted_by_total_execution.iteritems(): if v.total_execution_time > 10.0 or v.total_io_wait_time != 0.0: v.dump() def main(argv): if (len(argv) < 2): print "check_io_trace_all.py filename" return filename = argv[1] io_trace = IoTrace() sched_trace = SchedTrace() with open(filename) as f: for l in f: if io_trace.parse(l): continue sched_trace.parse(l) io_trace.dump() print "\n\n\n" sched_trace.dump() if __name__ == '__main__': main(sys.argv)