blob: 74920dd807d3526224c8b0276ebd9ec85f7ea67c [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (c) 2016 Intel Corporation.
#
# 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.
#
import os, sys, re
from collections import defaultdict
from optparse import OptionParser
class FilterTrace:
def __init__(self):
# 2 pass parsing
self.compiled_regexp = [] # regexp for 1st pass
self.compiled_regexp2 = [] # regexp for 2nd pass
self.final_text = [] # contains all lines before they are written to file
self.previous_line = {} # index in self.final_text of previous status change per
# tid + timestamp. So we can compute easily duration of
# state for 1 tid
# thread information
self.tid_names = {} # process name per tid
self.tid_states = defaultdict(int) # state per tid 0:RUNNING 1:QUEUED 2:SLEEPING
# 4:QUEUED or SLEEPING
# error checking per tid
self.t1_errors = defaultdict(int)
self.t2_errors = defaultdict(int)
self.t3_errors = defaultdict(int)
# context switch info
self.prev_tids = defaultdict(int) # previous tid per cpu when tracing new switch
# Time (relative and absolute)
self.last_timestamp_found = 0
self.last_timestamp = 0
self.reference_timestamp = 0 # time offset used in target script
self.convert = 1 / 1000.0 #1.0 / 1 * 1000 # 32kHz -> ms conversion
REGEXP_GLOSSARY = [
## irqs
# <idle>-0 [001] 294.876831: irq_handler_entry: irq=30 name=arch_timer
['^.*\[(?P<cpu_id>\d+)\]\s+(?P<foo>[\w+\.]+)\s+(?P<timestamp>\d+\.\d+):\s+' \
'irq_handler_entry:\s+irq=(?P<irq_id>\d+)\s+name=(?P<irq_name>[\w \.-_]+).*$',
self.callback_irq_start_ftrace
],
['^.*\[(?P<cpu_id>\d+)\]\s+(?P<timestamp>\d+\.\d+):\s+irq_handler_entry:' \
'\s+irq=(?P<irq_id>\d+)\s+name=(?P<irq_name>[\w \.-_]+).*$',
self.callback_irq_start_ftrace
],
# kworker/u:0-5 [000] 294.876862: irq_handler_exit: irq=30 ret=handled
['^.*\[(?P<cpu_id>\d+)\]\s+(?P<foo>[\w+\.]+)\s+(?P<timestamp>\d+\.\d+):' \
'\s+irq_handler_exit:\s+irq=(?P<irq_id>\d+)\s+ret=.*$',
self.callback_irq_stop_ftrace
],
['^.*\[(?P<cpu_id>\d+)\]\s+(?P<timestamp>\d+\.\d+):\s+irq_handler_exit:\s+' \
'irq=(?P<irq_id>\d+)\s+ret=.*$',
self.callback_irq_stop_ftrace
],
## scheduler
#sh-2505 [000] 271.147491: sched_switch: prev_comm=sh prev_pid=2505 prev_prio=120
# prev_state=S ==> next_comm=swapper next_pid=0 next_prio=120
['^.*\[(?P<cpu_id>\d+)\]\s+(?P<foo>[\w+\.]+)\s+(?P<timestamp>\d+\.\d+):\s+' \
'sched_switch:\s+.*==>\s+next_comm=(?P<next_task_name>[\w \.#-_/]+)\s+' \
'next_pid=(?P<next_tid>\d+)\s+next_prio.*$',
self.callback_context_switch_ftrace
],
['^.*\[(?P<cpu_id>\d+)\]\s+(?P<timestamp>\d+\.\d+):\s+sched_switch:\s+.*==>\s+' \
'next_comm=(?P<next_task_name>[\w \.#-_/]+)\s+next_pid=(?P<next_tid>\d+)\s+' \
'next_prio.*$',
self.callback_context_switch_ftrace
],
['^TIMESTAMP.*$', None],
['^.*$', self.callback_no_rule]
]
REGEXP_GLOSSARY2 = [
['^Ref timestamp:\s*(?P<ref_time>\d+)$', self.callback_reference_timestamp],
['^.*psel.*$', None],
['^(?P<timestamp>\d+)\s+(?P<cpu_id>\d+)\s+(?P<task_name>[\w \.#-_/]+):(?P<tid>-?\d+)' \
'\s+(?P<state>\w+).*$',
self.callback_context_switch2
],
['^Last timestamp:\s*(?P<last_timestamp>\d+).*$', self.callback_end2],
['^.*$', self.callback_no_rule2]
]
# Compile all regexp
for regexp_string, regexp_rules in REGEXP_GLOSSARY:
self.compiled_regexp.append([re.compile(regexp_string), regexp_rules])
for regexp_string, regexp_rules in REGEXP_GLOSSARY2:
self.compiled_regexp2.append([re.compile(regexp_string), regexp_rules])
def filter_line(self, line):
# Parse all regexps
for regexp, regexp_rules in self.compiled_regexp:
# Test if regexp matches
res = regexp.search(line)
# if regexp match
if res:
# Execute regexp_rules
if regexp_rules:
regexp_rules(res, line)
# Only one regexp per line
break
def filter_line2(self):
# Parse self.final_text to add duration of state per tid
# Memorize index and timestamp of previous state change per tid and compute duration of
# previous state when new state change is seen
# For last state change, duration will be from state change until "Last timestamp"
for line_index in range(0, len(self.final_text)):
line = self.final_text[line_index]
for regexp, regexp_rules in self.compiled_regexp2:
# Test if regexp matches
res = regexp.search(line)
# if regexp match
if res:
# Execute regexp_rules
if regexp_rules:
regexp_rules(res, line_index)
# Only one regexp per line
break
def callback_context_switch_post(self, timestamp, cpu_id, next_tid, name, prev_tid,
prev_task_name, prev_state):
self.last_timestamp = timestamp
if (prev_task_name != ""):
self.tid_names[prev_tid] = prev_task_name
self.prev_tids[cpu_id] = prev_tid
# short format requires storage of previous running thread per core
if (cpu_id in self.prev_tids):
prev_tid = self.prev_tids[cpu_id]
if (prev_tid in self.tid_states):
if (self.tid_states[prev_tid] > 0): # goes to queue/sleep but was not running
# choice is to do as if running and we keep simply trace of that
self.t1_errors[prev_tid] += 1
if (prev_task_name != "") and (prev_state != ""):
if (res.group('prev_state') == 'R'):
self.tid_states[prev_tid] = 1 # queued
new_state = "Q"
else:
self.tid_states[prev_tid] = 2 # sleep
new_state = "S"
else:
self.tid_states[prev_tid] = 4 # Queued or sleep
new_state = "X"
# new state of previous thread if previous thread not seen for first time
self.final_text.append("%-10d %s %-16s:%-5s %s" % (self.last_timestamp,
cpu_id,
self.tid_names[prev_tid],
prev_tid,
new_state))
# handle "next task"
if (next_tid == 0):
next_tid = -cpu_id
if (name == "swapper"):
name += str(cpu_id)
self.tid_names[next_tid] = name
if (next_tid not in self.tid_states):
# consider was queued first time we see it to trigger no transition error
self.tid_states[next_tid] = 1
if (self.tid_states[next_tid] != 1): # process lifecycle: process must be queued to run
# goes running but was not queued (missing wakeup ?)
self.t2_errors[next_tid] += 1
self.tid_states[next_tid] = 0 # running
self.final_text.append("%-10s %s %-16s:%-5s R" % (self.last_timestamp,
cpu_id,
self.tid_names[next_tid],
next_tid))
self.prev_tids[cpu_id] = next_tid
def callback_context_switch_ftrace(self, res, line):
timestamp = int(float(res.group('timestamp')) * 1000.0)
if self.reference_timestamp == 0:
self.reference_timestamp = timestamp
timestamp = timestamp - self.reference_timestamp
cpu_id = int(res.group('cpu_id'))
name = res.group('next_task_name').rstrip(' ')
tid = int(res.group('next_tid'))
self.callback_context_switch_post(timestamp, cpu_id, tid, name, 0, "", "" )
def callback_irq_start_ftrace(self, res, line):
timestamp = int(float(res.group('timestamp')) * 1000.0)
if self.reference_timestamp == 0:
self.reference_timestamp = timestamp
timestamp = timestamp - self.reference_timestamp
cpu_id = int(res.group('cpu_id'))
irq_id = int(res.group('irq_id'))
irq_name = res.group('irq_name')
self.final_text.append("%d %d irqin %d unknown_func %s 0x0" % (timestamp,
cpu_id,
irq_id,
irq_name))
def callback_irq_stop_ftrace(self, res, line):
timestamp = int(float(res.group('timestamp')) * 1000.0)
if self.reference_timestamp == 0:
self.reference_timestamp = timestamp
timestamp = timestamp - self.reference_timestamp
cpu_id = int(res.group('cpu_id'))
irq_id = int(res.group('irq_id'))
self.final_text.append("%d %d irqout %d" % (timestamp, cpu_id, irq_id))
def callback_no_rule(self, res, line):
self.final_text.append(line.rstrip('\n'))
return
def callback_end(self, res, line):
if res != None:
self.final_text.append(line.rstrip('\n'))
else:
self.final_text.append("Last timestamp: %d" % self.last_timestamp)
self.last_timestamp_found = 1
print("\nT1: goes to queued/sleep but was not running")
keys = self.t1_errors.keys()
keys.sort()
for key in keys:
print("%s: %s %d") % (self.tid_names[key], key, self.t1_errors[key])
print("\nT2: goes running but was not queued (missing wakeup ?)")
keys = self.t2_errors.keys()
keys.sort()
for key in keys:
print("%s: %s %d") % (self.tid_names[key], key, self.t2_errors[key])
print("\nT3: wakes up but was not sleeping")
keys = self.t3_errors.keys()
keys.sort()
for key in keys:
print("%s: %s %d") % (self.tid_names[key], key, self.t3_errors[key])
def callback_context_switch2(self, res, line_index):
tid = int(res.group('tid'))
self.last_timestamp = int(res.group('timestamp'))
# per tid, go back to previous line, compute stamp current - previous stamp and add
# to previous line
if (tid in self.previous_line):
duration = self.last_timestamp - self.previous_line[tid][1]
self.final_text[self.previous_line[tid][0]] = \
self.final_text[self.previous_line[tid][0]] + \
" %-10d (%-10d %.3f ms)" % (duration,
self.previous_line[tid][1] + self.reference_timestamp,
self.previous_line[tid][1] * self.convert)
self.previous_line[tid] = [line_index, self.last_timestamp]
def callback_reference_timestamp(self, res, line_index):
self.reference_timestamp = int(res.group('ref_time'))
def callback_end2(self, res, line_index):
# add diff from last timestamp to current time to all tids
if res != None:
self.last_timestamp = int(res.group('last_timestamp'))
# add timing to end of file for last switch of all threads
for tid in self.previous_line:
last_duration = self.last_timestamp - self.previous_line[tid][1]
self.final_text[self.previous_line[tid][0]] = \
self.final_text[self.previous_line[tid][0]] + \
" %-10d (%-10d %.3f ms)" % (last_duration,
self.previous_line[tid][1] + self.reference_timestamp,
self.previous_line[tid][1] * self.convert)
# dump everything
print "\n%-9s %s %-15s:%-4s STA %-9s (%-10s)" % ("TIMESTAMP", "CPU", "NAME", "PID",
"DURATION", "ABS_TIME tick/REL_TIME ms")
for i in range(0, len(self.final_text)):
print self.final_text[i]
def callback_no_rule2(self, res, line_index):
return
global filterMaster
filterMaster = FilterTrace()
def Main(argv):
usage = "Usage: %prog FILENAME [options]"
parser = OptionParser(usage = usage)
(options, args) = parser.parse_args()
if len(args) != 1:
print "Usage: type python <program>.py -h"
return
# First pass, fill each array index with 1 thread + its new status + timestamp
# (basically 1 context switch will give 2 indexes, 1 wakeup 1 index)
# only missing info is duration of state
file = open(args[0], 'r')
string = file.readline()
while string:
filterMaster.filter_line(string)
string = file.readline()
if filterMaster.last_timestamp_found == 0:
filterMaster.callback_end(None, 0)
# 2nd pass, add duration of state of tid
filterMaster.filter_line2()
if __name__=="__main__":
Main(sys.argv[1:])