194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
#!/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
|
|
|
|
RE_BLOCK = r'.+\s+(block[a-z_]+):\s+'
|
|
RE_BLOCK_BIO_QUEUE = r'.+\s+([0-9]+\.[0-9]+):\s+block_bio_queue:\s+([0-9]+)\,([0-9]+)\s+([RW]\S*)\s+([0-9]+)\s+\+\s+([0-9]+)\s+\[([^\]]+)'
|
|
|
|
# dev_num = major * MULTIPLIER + minor
|
|
DEV_MAJOR_MULTIPLIER = 1000
|
|
|
|
# dm access is remapped to disk access. So account differently
|
|
DM_MAJOR = 253
|
|
|
|
class RwEvent:
|
|
def __init__(self, block_num, start_time, size):
|
|
self.block_num = block_num
|
|
self.start_time = start_time
|
|
self.size = size
|
|
self.latency = 0
|
|
def set_latency(self, latency):
|
|
self.latency = latency
|
|
|
|
def get_string_pos(strings, string_to_find):
|
|
for i, s in enumerate(strings):
|
|
if s == string_to_find:
|
|
return i
|
|
return -1
|
|
|
|
|
|
class Trace:
|
|
def __init__(self, process):
|
|
self.process = process
|
|
self.reads = [] #(start time, RwEvent)
|
|
self.writes = [] #(start time, RwEvent)
|
|
self.recent_reads = {} # K:
|
|
self.total_latency = 0
|
|
self.total_reads = 0
|
|
self.total_writes = 0
|
|
self.total_dm_reads = {} #K: devnum, V: blocks
|
|
self.total_dm_writes = {}
|
|
self.re_block_queue = re.compile(RE_BLOCK_BIO_QUEUE)
|
|
self.processes = set()
|
|
if process[-1] == '*':
|
|
print "Process name starts with", process[:-1]
|
|
self.process_name_is_prefix = True
|
|
else:
|
|
print "Process name", process
|
|
self.process_name_is_prefix = False
|
|
|
|
def parse_bio_queue(self, l):
|
|
match = self.re_block_queue.match(l)
|
|
if not match:
|
|
return
|
|
start_time = int(float(match.group(1))*1000000) #us
|
|
major = int(match.group(2))
|
|
minor = int(match.group(3))
|
|
operation = match.group(4)
|
|
block_num = int(match.group(5))
|
|
size = int(match.group(6))
|
|
process = match.group(7)
|
|
if self.process_name_is_prefix:
|
|
if not process.startswith(self.process[:-1]):
|
|
return
|
|
self.processes.add(process)
|
|
else:
|
|
if process != self.process:
|
|
return
|
|
if major == DM_MAJOR:
|
|
devNum = major * DEV_MAJOR_MULTIPLIER + minor;
|
|
if operation[0] == 'R':
|
|
if devNum not in self.total_dm_reads:
|
|
self.total_dm_reads[devNum] = 0
|
|
self.total_dm_reads[devNum] += size
|
|
elif operation[0] == 'W':
|
|
if devNum not in self.total_dm_writes:
|
|
self.total_dm_writes[devNum] = 0
|
|
self.total_dm_writes[devNum] += size
|
|
return
|
|
event = RwEvent(block_num, start_time, size)
|
|
if operation[0] == 'R':
|
|
self.reads.append((start_time, event))
|
|
self.recent_reads[block_num] = event
|
|
self.total_reads += size
|
|
elif operation[0] == 'W':
|
|
self.writes.append((start_time, event))
|
|
self.total_writes += size
|
|
|
|
def parse_rq_complete(self, l):
|
|
words = string.split(l)
|
|
cmd_pos = get_string_pos(words, "block_rq_complete:")
|
|
if cmd_pos == -1:
|
|
cmd_pos = get_string_pos(words, "block_bio_complete:")
|
|
block_num = int(words[-4])
|
|
event = self.recent_reads.get(block_num)
|
|
if not event:
|
|
return
|
|
operation = words[cmd_pos + 2]
|
|
if not operation.startswith("R"):
|
|
return
|
|
end_time = int(float(words[cmd_pos - 1][:-1])*1000000) #us
|
|
latency = end_time - event.start_time
|
|
if latency > 20000:
|
|
print "very bad latency:", latency, l
|
|
print "start time,", event.start_time
|
|
event.set_latency(latency)
|
|
del self.recent_reads[block_num]
|
|
self.total_latency += latency
|
|
|
|
def parse_block_trace(self, l, match):
|
|
try:
|
|
cmd = match.group(1)
|
|
if cmd == "block_bio_queue":
|
|
self.parse_bio_queue(l)
|
|
elif cmd == "block_rq_complete" or cmd == "block_bio_complete":
|
|
self.parse_rq_complete(l)
|
|
except ValueError:
|
|
print "cannot parse:", l
|
|
raise
|
|
|
|
def dump(self):
|
|
if self.process_name_is_prefix:
|
|
print "Processes:", self.processes
|
|
print "total read blocks,", self.total_reads
|
|
print "total write blocks,", self.total_writes
|
|
if len(self.reads) > 0:
|
|
total_read_time = self.reads[-1][0] + self.reads[-1][1].latency - self.reads[0][0]
|
|
else:
|
|
total_read_time = 0
|
|
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 "total read time,",total_read_time
|
|
read_size_histogram = {}
|
|
latency_per_read_size = {}
|
|
for (time, event) in self.reads:
|
|
if not read_size_histogram.get(event.size):
|
|
read_size_histogram[event.size] = 0
|
|
if not latency_per_read_size.get(event.size):
|
|
latency_per_read_size[event.size] = [ 0, 0] # num events, total latency
|
|
read_size_histogram[event.size] = read_size_histogram[event.size] + 1
|
|
latency_sum = latency_per_read_size[event.size]
|
|
latency_sum[0] += 1
|
|
latency_sum[1] += event.latency
|
|
read_size_histogram = collections.OrderedDict(sorted(read_size_histogram.items()))
|
|
print "read histogram"
|
|
for k,v in read_size_histogram.iteritems():
|
|
print k, ',', v
|
|
print "latency per read size"
|
|
latency_per_read_size = collections.OrderedDict(sorted(latency_per_read_size.items()))
|
|
for k,v in latency_per_read_size.iteritems():
|
|
if v[0] != 0:
|
|
print k, ',', v[1] / v[0], v[0], v[1]
|
|
|
|
def main(argv):
|
|
if (len(argv) < 3):
|
|
print "check_io_trace.py processname filename"
|
|
return
|
|
keyword = argv[1]
|
|
filename = argv[2]
|
|
trace = Trace(keyword)
|
|
prog = re.compile(RE_BLOCK)
|
|
with open(filename) as f:
|
|
for l in f:
|
|
result = prog.match(l)
|
|
if result:
|
|
trace.parse_block_trace(l, result)
|
|
trace.dump()
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv)
|