blob: cfe381a4228de9ebd9a2a53074ead6d8a5a56062 [file] [log] [blame]
#!/usr/bin/python3
# -------------------------------------------
# logcat analysis
# -------------------------------------------
from ast import keyword
from curses import keyname
import argparse
import os
from string import digits
from sbtaTools import TextFile
import datetime
import re
import shlex
import glob
class LCItem:
def __init__(self, lCTimeProcessor):
self.dateTime = 0
self.relativeTime = 0
self.key = ""
self.moduleName = ""
self.keyword = ""
self.valueMsec = 0
self.lCTimeProcessor = lCTimeProcessor
self.lines = []
def set(self, dateTime, moduleName, keyText, valueMsec):
try:
self.dateTime = dateTime
self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
self.moduleName = moduleName
self.keyword = keyText
self.key = moduleName+":" + keyText
self.valueMsec = valueMsec
except Exception as e:
errLine = "LCItem:set() ERROR Failed: " + str(e)
assert False, errLine
def copy(self, otherLCItem):
self.dateTime = otherLCItem.dataTime
self.relativeTime = otherLCItem.relativeTime
self.key = otherLCItem.key
self.moduleName = otherLCItem.moduleName
self.keyword = otherLCItem.keyword
self.valueMsec = otherLCItem.valueMsec
self.lCTimeProcessor = otherLCItem.lcTimeProcessor
self.lines = otherLCItem.lines
def appendLine(self, line):
self.lines.append(line)
def keyEqual(self, otherItem):
if self.key != otherItem.key:
return False
return True
def add(self, otherItem):
assert(self.key==otherItem.key)
self.lines.extend(otherItem.lines)
self.valueMsec = self.valueMsec + otherItem.valueMsec
return True
def addValue(self, otherLCItem):
if self.key=="":
self.copy(otherLCItem)
else:
assert(self.key==otherLCItem.key)
self.valueMsec = self.valueMsec + otherLCItem.valueMsec
return True
def divideValue(self, number): # scaler divide
self.valueMsec = self.valueMsec / number
return True
def key(self):
return self.key
def print(self):
#systemTime = self.lCTimeProcessor.toSystemTime(self.dateTime)
#relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
newTimeString = str(self.relativeTime)
if (len(self.lines)>0):
print("{} {}: {} {:.4f} - {}".format(newTimeString, self.moduleName, self.keyword, self.valueMsec, self.lines[0]))
else:
print("{} {}: {} {:.4f} -".format(newTimeString, self.moduleName, self.keyword, self.valueMsec))
def printLines(self, prefix, min):
if (len(self.lines)<min):
return
for line in self.lines:
print(" {}{}".format(prefix, line))
def findModuleName(self, lineTextWords):
colonIndex = -1
try:
colonIndex = lineTextWords.index(":")
# address case of colon with no space
moduleName = lineTextWords[colonIndex-1]
except:
moduleName = ""
if colonIndex==-1:
for word in reversed(lineTextWords):
index = word.find(":")
if index!=-1:
moduleName = word[:index]
break
moduleName = moduleName.strip()
return colonIndex, moduleName
def parseLineWithTook(self, line):
maxLineLength = 100
stage = 0
try:
words = line.split(" ")
dataTimeO = self.lCTimeProcessor.parseTimeStamp(line)
if line.find("took to complete") != -1:
stage = 1
tookIndex = line.find(" took to complete:")
uptoEnd= line[:tookIndex]
lineTextWords = uptoEnd.split()
colonIndex, moduleName = self.findModuleName(lineTextWords)
keyword = " ".join([lineTextWords[6]])
value = re.findall(r'\d+', line[tookIndex:])[-1]
value = float(value)
elif line.find("took") != -1:
stage = 2
tookIndex = line.find(" took")
uptoEnd= line[:tookIndex]
uptoBracket = uptoEnd.rfind("(")
if uptoBracket != -1:
uptoEnd = uptoEnd[:uptoBracket]
#uptoEnd = uptoEnd.replace(":", "")
lineTextWords = shlex.split(uptoEnd)
colonIndex, moduleName = self.findModuleName(lineTextWords)
# if there is colon only take words after it
if colonIndex!=-1:
lineTextWords = lineTextWords[colonIndex+1:]
numWords = len(lineTextWords)
keyword = ""
stage = 3
try:
for i in range(max(numWords-3, 0), numWords, 1):
keyword = keyword + " " + lineTextWords[i]
except Exception as e:
errLine = "LCItem:parseLineWithTook() ERROR Failed to parse1: " + str(e)
print(errLine)
assert False, errLine
# reduce length
keyword = keyword[:maxLineLength]
keyword = keyword.strip()
# using regex expression to replace all numbers
keyword = re.sub(r'\d', "_", keyword)
value = 0
stage = 4
try:
multplier = 1
tookSubstring = line[tookIndex:]
secondsIndex = tookSubstring.find("seconds")
msIndex = tookSubstring.find("ms")
if (secondsIndex!=-1):
tookSubstring = tookSubstring[:secondsIndex]
multiplier = 1000
elif msIndex != -1:
tookSubstring = tookSubstring[:msIndex]
else:
# known exception
if tookSubstring.find("properties")==-1:
errLine = "LCItem:parseLineWithTook() ERROR invalid took in substring 1B {}".format(line)
print(errLine)
assert False, errLine
return False
values = re.findall(r'[\d\.\d]+', tookSubstring)
while "." in values:
values.remove(".")
value = float(values[-1])
if line.find("seconds") != -1:
value = value * multiplier
except Exception as e:
errLine = "LCItem:parseLineWithTook() ERROR Failed to parse2: " + str(e)
print(errLine)
assert False, errLine
stage = 5
else:
return False
stage = 6
self.set(dataTimeO, moduleName, keyword, value)
stage = 7
self.lines.append(line)
return True
except Exception as e:
errLine = "LCItem:parseLineWithTook() ERROR Failed to parse3:" + str(e)
print(errLine, stage)
assert False, errLine
def parseLine(self, line):
try:
words = line.split(" ")
dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
if (dateTimeO!=None):
#lcItem = LCItem(self.lCTimeProcessor)
newLine = line[19:].rstrip()
self.set(dateTimeO, "", newLine, 0)
#self.print()
return
else:
return None
except Exception as e:
errLine = "LCItem:parseLine() ERROR Failed to parse3:" + str(e)
print(errLine)
assert False, errLine
def find(self, keyword):
if self.key.find(keyword)!=-1:
return True
for line in self.lines:
if line.find(keyword)!=-1:
return True
def createLogLine(self):
line = ""
msecs = self.dateTime.strftime("%f")
timeString = self.dateTime.strftime("%m-%d %H:%M:%S.")
#timeString = timeString + msecs[]
return line
class LCItemSet:
def __init__(self, item1, item2):
self.item1 = item1
self.item2 = item2
if (item1.key != "" and item2.key != ""):
assert(item1.key == item2.key)
if (item1.key!=""):
self.key = item1.key
else:
self.key = item2.key
self.diff = item2.valueMsec - item1.valueMsec
def __gt__(self, other):
if(self.diff>other.diff):
return True
else:
return False
def add(item):
assert(False)
def print(self, min, printAll):
self.diff = self.item2.valueMsec - self.item1.valueMsec
if abs(self.diff)<min:
return
flag = "12"
if self.item1.key=="":
flag = "-2"
if self.item2.key=="":
flag = "1-"
print("{}, {}, {}, {}, {}".format(self.key, self.item1.valueMsec, self.item2.valueMsec, self.diff, flag))
if printAll:
self.item1.printLines("1> ", 1)
self.item2.printLines("2> ", 1)
class LCItemMap:
def __init__(self):
self.map = {}
def put(self, newItem):
item = self.map.get(newItem.key)
if item==None:
self.map[newItem.key] = newItem
else:
item.add(newItem)
def print(self):
for key in self.map:
self.map[key].print()
def find(self, keyword):
lCItems = []
for index, lCItem in self.map:
if lCItem.find(keyword):
lCItems.append(lCItem)
return lCItems
def addValues(self, other):
for index, item in other.map.items():
if item.key in self.map:
self.map[item.key].addValue(item)
else:
self.map[item.key] = item
def divideValue(self, number):
for index, item in self.map:
item.divideValue(number)
class LCItemSetMap:
def __init__(self):
self.map = {}
def put(self, itemSet):
item = self.map.get(itemSet.key)
if item==None:
self.map[itemSet.key] = itemSet
else:
item.add(itemSet)
def printSorted(self, printAll):
a = sorted(self.map.items(), key=lambda x: (x[1], x[0]), reverse=True)
cumDif = 0
print("Key, Value1, Value2, diff")
for item in a:
item[1].print(1, printAll)
cumDif = cumDif + item[1].diff
print("CUMULATIVE DIFF: {}".format(cumDif))
class LCTimeProcessor:
def __init__(self):
self.firstKernelTimeStamp = 0
self.lastKernelTimeStamp = 0
self.firstSystemTimesStamp = 0
self.lastTimeStamp = 0
self.zeroRelativeTime = 0
today = datetime.datetime.now()
year = str(today.year)
self.currentYear = year[-2:] # 2022/2023
def parseTimeStamp(self, line):
try:
if len(line)<19:
return None
currentYear = self.currentYear # 22
words = line.split(" ")
timeString = words[0]
#timeString = re.sub("[^0-9: -.]", "", timeString)
timeString = timeString.strip()
timeString = timeString[:18]
timeString = currentYear + "-" + timeString
dataTimeO = datetime.datetime.strptime(timeString, "%Y-%m-%d %H:%M:%S.%f")
return dataTimeO
except Exception as e:
# If no time stamp on this line
if line.find("beginning of")!=-1:
return None
errLine = "LCItem:parseTimeStamp() ERROR Failed to parse:" + str(e)
print(errLine)
assert False, errLine
return None
def process(self, line):
timeStamp = self.parseTimeStamp(line)
if timeStamp==None:
return False
if self.firstKernelTimeStamp==0:
self.firstKernelTimeStamp = timeStamp
else:
if timeStamp < self.firstKernelTimeStamp:
return False
timeChange = timeStamp - self.lastTimeStamp
if (timeChange.total_seconds() > 68*5):
if self.firstSystemTimesStamp ==0:
self.firstSystemTimesStamp = timeStamp
self.lastKernelTimeStamp = self.lastTimeStamp
self.zeroRelativeTime = self.toSystemTime(self.firstKernelTimeStamp)
self.lastTimeStamp = timeStamp
return True
def toSystemTime(self, timeStamp):
try:
# if no systemTime is found, it must all be system time
if self.firstSystemTimesStamp==0:
self.firstSystemTimesStamp = self.firstKernelTimeStamp
self.lastKernelTimeStamp = self.lastTimeStamp
self.zeroRelativeTime = self.firstKernelTimeStamp
return timeStamp
if timeStamp >= self.firstSystemTimesStamp:
return timeStamp
else:
timeChange = timeStamp - self.lastKernelTimeStamp
systemTime = self.firstSystemTimesStamp + timeChange
return systemTime
except Exception as e:
errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
print(errLine)
assert False, errLine
def toRelativeTime(self, timeStamp):
systemTime = self.toSystemTime(timeStamp)
relativeTime = systemTime - self.zeroRelativeTime
return relativeTime
if timeStamp< self.firstSystemTimesStamp:
timeChange = timeStamp - self.lastKernelTimeStamp
systemTime = self.firstSystemTimesStamp + timeChange
return systemTime
else:
return timeStamp
def toString(self, timeStamp):
return timeStamp.strftime("%Y-%m-%d %H:%M:%S.%f")
class LCLogLine:
def __init__(self, lCTimeProcessor):
self.dateTime = 0
self.relativeTime = 0
self.lineText = ""
self.lCTimeProcessor = lCTimeProcessor
def set(self, dateTime, lineText):
self.dateTime = dateTime
self.relativeTime = self.lCTimeProcessor.toRelativeTime(self.dateTime)
self.lineText = lineText
def print(self):
newTimeString = str(self.relativeTime)
print("{}{}".format(newTimeString, self.lineText))
def parseLine(self, line):
try:
dateTimeO = self.lCTimeProcessor.parseTimeStamp(line)
if (dateTimeO!=None):
lineText = line[19:].rstrip()
self.set(dateTimeO, lineText)
return
else:
return None
except Exception as e:
errLine = "LogLine:parseLine() ERROR Failed to parse3:" + str(e)
print(errLine)
assert False, errLine
def find(self, word):
if (self.lineText.find(word)!=-1):
return True
else:
return False
def findAll(self, words):
for word in words:
if (self.lineText.find(word)==-1):
return False
return True
class LCLogFile(TextFile):
priorTimeStamp = 0.0
def __init__(self, _fileName = ""):
super(LCLogFile, self).__init__(_fileName)
self.linesWithTook = []
self.linesWithTookToComplete = []
self.linesWithoutTookToComplete = []
self.firstKernelTimeStamp = 0
self.lastKernelTimeStamp = 0
self.firstSystemTimesStamp = 0
self.lastTimeStamp = 0
self.lCTimeProcessor = LCTimeProcessor()
self.dumpLinesBeforeBeginning()
def dumpLinesBeforeBeginning(self):
# start from --------- beginning of kernel
beginningFound = False
_lines = []
for line in self.lines:
if beginningFound==True:
_lines.append(line)
self.lCTimeProcessor.process(line)
elif line.find("beginning of kernel") != -1:
beginningFound = True
self.lines = _lines
def scanTook(self):
lCItemMap = LCItemMap()
foundBeginning = False
for line in self.lines:
# start at beginning
if not foundBeginning:
if line.find("beginning of kernel=1") != -1:
foundBeginning = True
continue
# stop if boot complete
if line.find("sys.boot_completed=1") != -1:
break
if line.find("took") != -1:
self.linesWithTook.append(line.rstrip())
for line in self.linesWithTook:
lCItem = LCItem(self.lCTimeProcessor)
if lCItem.parseLineWithTook(line)==True:
lCItemMap.put(lCItem)
return lCItemMap
def print(self, numItems=None):
self.scanTook()
def convert(self, numItems=None):
lcLogLines = []
for line in self.lines:
lcLogLine = LCLogLine(self.lCTimeProcessor)
lcLogLine.parseLine(line)
lcLogLines.append(lcLogLine)
return lcLogLines
'''
def createLCFile(self, fileName):
# create LCTimeProcessor
# create LCItem
# create LCLogLine
# write LCLogLine to file
'''
class ScanFile:
def __init__(self):
self.fileName = "none"
def scanKeyWords(self, fileName):
print("Scanning {}".format(fileName))
cmd = "grep \"apexd: wait for '\/dev\/loop-control'\" {}".format(fileName)
x = os.system(cmd)
cmd = "grep \"Service 'apexd-bootstrap\" {}".format(fileName)
x = os.system(cmd)
cmd = "grep apexd.status=activated {}".format(fileName)
x = os.system(cmd)
cmd = "grep \"Service 'bpfloader'\" {}".format(fileName)
x = os.system(cmd)
cmd = "grep \"sys.boot_completed=1\" {} | head -n 1".format(fileName)
x = os.system(cmd)
def scanTook(self, fileName):
lCLogFile = LCLogFile(fileName)
lCItemMap = lCLogFile.scanTook()
def convert(self, fileName):
lCLogFile = LCLogFile(fileName)
lcItems = lCLogFile.convert()
for lcItem in lcItems:
lcItem.print()
def phases(self, fileName):
keywordFile = TextFile("keywords")
#keywords = ['init first', 'init second', "Starting phase 200", "boot_completed"]
lCLogFile = LCLogFile(fileName)
keywordSets = []
for line in keywordFile.lines:
line = line.strip()
keywordSet = line.split(", ")
keywordSets.append(keywordSet)
lcLogLines = lCLogFile.convert()
for keywordSet in keywordSets:
for lcLogLine in lcLogLines:
if lcLogLine.findAll(keywordSet)==True:
lcLogLine.print()
break
class Compare:
def __init__(self):
self.fileName = "none"
def compareLCItemMaps(self, lCItemMap1, lCItemMap2):
lCItemSetMap = LCItemSetMap()
for item1key in lCItemMap1.map:
found = False
for item2key in lCItemMap2.map:
if item2key==item1key:
lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
lCItemSetMap.put(lcItemSet)
found = True
break
# if item1Key is not in ItemMap2, add a null item
if found==False:
lCTimeProcessor = LCTimeProcessor()
nullLCItem = LCItem(lCTimeProcessor)
lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
lCItemSetMap.put(lcItemSet)
found = True
lCItemSetMap.printSorted(printAll)
return lCItemSetMap
def compareFiles(self, fileName1, fileName2, printAll):
print("---------------------------------------------------------------")
print("lcan.py -cmp {} {}".format(fileName1, fileName2))
print("---------------------------------------------------------------")
lCLogFile1 = LCLogFile(fileName1)
lCItemMap1 = lCLogFile1.scanTook()
lCLogFile2 = LCLogFile(fileName2)
lCItemMap2 = lCLogFile2.scanTook()
lCItemSetMap = LCItemSetMap()
for item1key in lCItemMap1.map:
found = False
for item2key in lCItemMap2.map:
if item2key==item1key:
lcItemSet = LCItemSet(lCItemMap1.map[item1key], lCItemMap2.map[item2key])
lCItemSetMap.put(lcItemSet)
found = True
break
# if item1Key is not in ItemMap2, add a null item
if found==False:
lCTimeProcessor = LCTimeProcessor()
nullLCItem = LCItem(lCTimeProcessor)
lcItemSet = LCItemSet(nullLCItem, lCItemMap1.map[item1key])
lCItemSetMap.put(lcItemSet)
found = True
lCItemSetMap.printSorted(printAll)
return lCItemSetMap
def getAverageOfDir(self, buildId):
#get average values for build1
dirList = glob.glob("{}/LC-{}*.txt".format(buildId, buildId))
numFiles = len(dirList)
#iterate in numerical order
lCItemMapS = LCItemMap()
for index in range(numFiles):
fileName = "{}/LC-{}-{}.txt".format(buildId, buildId, index)
#for index, fileName in enumerate(dirList):
lCLogFile = LCLogFile(fileName)
lCItemMap = lCLogFile.scanTook()
lCItemMapS.addValues(lCItemMap)
lCItemMapS.divideValue(numFiles)
return lCItemMapS
def compareDirs(self, buildId1, buildId2, printAll):
print("---------------------------------------------------------------")
print("lcan.py -cmpd {} {} {}".format(buildId1, buildId2, printAll))
print("---------------------------------------------------------------")
#get average values for build1
lCItemMap1 = self.getAverageOfDir(buildId1)
lCItemMap2 = self.getAverageOfDir(buildId2)
self.compareLCItemMaps(self, lCItemMap1, lCItemMap2)
parser = argparse.ArgumentParser()
parser.add_argument("-c", nargs=1, metavar=('<fileName>'), help="convert Logcat output to start from boot with converted timeStamps")
parser.add_argument("-k", nargs=1, metavar=('<fileName>'), help="summary on keywords")
parser.add_argument("-a", nargs=1, metavar=('<fileName>'), help="analyze file")
parser.add_argument("-cmp", nargs=3, metavar=('<fileName1>', '<fileName2>', '<brief/all>'), help="compare logcat files")
parser.add_argument("-cmpd", nargs=3, metavar=('<dirName1>', '<dirName2>', '<brief/all>'), help="compare logcat files")
parser.add_argument("-p", nargs=1, metavar=('<fileName1>'), help="phase report on log files")
args = parser.parse_args()
if args.k!=None:
scanFile = ScanFile()
scanFile.scanKeyWords(args.k[0])
if args.a!=None:
scanFile = ScanFile()
scanFile.scanTook(args.a[0])
if args.c!=None:
scanFile = ScanFile()
scanFile.convert(args.c[0])
if args.p!=None:
scanFile = ScanFile()
scanFile.phases(args.p[0])
if args.cmp!=None:
printAll = False
compare = Compare()
if (len(args.cmp)>2):
if (args.cmp[2].find("all")!=-1):
printAll = True
compare.compareFiles(args.cmp[0], args.cmp[1], printAll)
if args.cmpd!=None:
printAll = False
compare = Compare()
if (len(args.cmpd)>2):
if (args.cmpd[2].find("all")!=-1):
printAll = True
compare.compareDirs(args.cmpd[0], args.cmpd[1], printAll)