blob: c6af55ce64e097ce9d4838e637b31b6e983ee903 [file] [log] [blame]
#!/usr/bin/python3
#from calendar import c
import sys
import os
import copy
import argparse
import statistics
import glob
import subprocess
import re
import time
from string import digits
class LogLine:
remove_digits = str.maketrans('', '', digits)
def __init__(self):
self.lineNum = 0
self.timeStamp = 0
self.delta = 0
self.deltaDiff = 0
self.text = "none"
self.textKey = "none"
def parse(self, index, line, priorTimeStamp):
_line = line.strip()
words = _line.split("]", 1)
timeString = words[0].strip(" [")
self.lineNum = index
self.timeStamp = float(timeString)
self.delta = self.timeStamp - priorTimeStamp
self.text = words[1][:150]
self.textKey = self.text.translate(self.remove_digits)
priorTimeStamp = self.timeStamp
return self
def getTextKey(self):
textKey = self.textKey
return textKey
def print(self):
print("I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text))
def toString(self):
return "I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text)
def sortByDelta(item):
return item.delta
def sortByTimeStamp(item):
return item.timeStamp
class LogLineListStats:
def __init__(self):
self.numItems = 0
self.firstTimeStamp = 0
self.lastTimeStamp = 0
self.deltaSum = 0
self.deltaDiffSum = 0
self.status = "unknown"
self.name = "unknown"
def print(self):
print("Name {:25} NumItems {:4d} FirstTimeStamp {:.3f}, lastTimeStamp {:.3f}, deltaTime {:.3f} deltaSum {:.3f}, deltaDiffSum {:.3f} Status {}".format(self.name, self.numItems, self.firstTimeStamp, self.lastTimeStamp, (self.lastTimeStamp - self.firstTimeStamp), self.deltaSum, self.deltaDiffSum, self.status))
def add(self, _other):
if (_other.firstTimeStamp< self.firstTimeStamp):
self.firstTimeStamp = _other.firstTimeStamp
if (_other.lastTimeStamp > self.lastTimeStamp):
self.lastTimeStamp = _other.lastTimeStamp
self.deltaSum += _other.deltaSum
# ------------------------------------------------------
class LogLineList:
def __init__(self, _name= ""):
self.list = []
self.name = _name
def clear(self):
self.list.clear()
def append(self, item):
self.list.append(item)
def print(self, numItems=None):
printLineNum = 0
timeStart = 0
sumDelta = 0
sumDeltaDiff = 0
print("List: {}", self.name)
for item in self.list:
if (timeStart==0):
timeStart = item.timeStamp
timeOffset = item.timeStamp - timeStart
sumDelta += item.delta
sumDeltaDiff += item.deltaDiff
printLineNum += 1
printLine = "{:4d} {:.4f} {: .4f} ({: .4f}) | {} ".format(printLineNum, timeOffset, sumDelta, sumDeltaDiff, item.toString())
print(printLine)
if (numItems!=None):
numItems -= 1
if (numItems<=0):
break
def find(self, word):
itemList = []
for item in self.list:
if item.text.find(word) != -1:
itemList.append(item)
return itemList
def findFirst(self, word):
itemList = self.find(word)
if (itemList!=None):
if (len(itemList)>0):
return itemList[0]
return None
def findTextKey(self, textKey):
itemList = []
for item in self.list:
if item.getTextKey()==textKey:
itemList.append(item)
if (len(itemList)==0):
return None
return itemList[0]
def findItem(self, item):
textKey = item.getTextKey()
return self.findTextKey(textKey)
def findExactItem(self, item):
text = item.text
return self.find(text)
def filter(self, startKeyWord, endKeyWord, delta=0):
resultsList = LogLineList()
startTime = self.findFirst(startKeyWord).timeStamp
endTime = self.findFirst(endKeyWord).timeStamp
for item in self.list:
if ((item.timeStamp >= startTime) and (item.timeStamp<=endTime)):
if (item.timeStamp == startTime):
item.delta = 0
if ((item.delta > delta) or ((item.timeStamp == startTime))):
resultsList.append(item)
resultsList.name = self.name
return resultsList
def findCommon(self, otherList):
commonList = LogLineList()
commonList.name = self.name + "_common"
notCommonList = LogLineList()
notCommonList.name = self.name + "_notCommon"
numFoundItems = 0
numNotFoundItems = 0
for item in self.list:
dm1 = otherList.findExactItem(item)
_item = copy.deepcopy(item)
if dm1!=None:
commonList.append(_item)
numFoundItems += 1
else:
notCommonList.append(_item)
numNotFoundItems += 1
print("FindCommon {} {} {} {}".format(len(self.list), len(otherList.list), numFoundItems, numNotFoundItems ))
return commonList, notCommonList
def difference(self, otherList):
diffList = LogLineList()
diffList.name = otherList.name + "Diff"
for item in self.list:
thisItem = copy.deepcopy(item)
otherItem = otherList.findItem(item)
if (item.text.find("EXT4-fs (sda11): recovery complete")!=-1):
print("here")
if otherItem==None:
print("LogLineList::difference() !Error, other does not have {}".format(item.text))
else:
thisItem.deltaDiff = otherItem.delta - item.delta
diffList.append(thisItem)
return diffList
def analyze(self, checkPeriod = True, includeFirst = True):
numItems = 0
firstTimeStamp = 0
firstDelta = 0
lastTimeStamp = 0
deltaSum = 0
deltaDiffSum = 0
for item in self.list:
numItems += 1
deltaSum += item.delta
deltaDiffSum += item.deltaDiff
if firstTimeStamp==0:
firstTimeStamp = item.timeStamp
firstDelta = item.delta
deltaSum = 0
deltaDiffSum = 0
if (item.timeStamp<firstTimeStamp):
firstTimeStamp = item.timeStamp
firstDelta = item.delta
if (item.timeStamp > lastTimeStamp):
lastTimeStamp = item.timeStamp
timePeriod = lastTimeStamp - firstTimeStamp
status = "pass"
if (checkPeriod):
diff = timePeriod - deltaSum
if (abs(diff)>0.0001):
print("LogLineList::Analyze() {} ERROR! TimePeriod:{}, CumulativeDelta: {} ".format(self.name, timePeriod, deltaSum))
status = "ERROR"
logLineListStats = LogLineListStats()
logLineListStats.numItems = numItems
logLineListStats.firstTimeStamp = firstTimeStamp
logLineListStats.lastTimeStamp = lastTimeStamp
logLineListStats.deltaSum = deltaSum
logLineListStats.deltaDiffSum = deltaDiffSum
logLineListStats.status = status
logLineListStats.name = self.name
return logLineListStats
def addList(self, otherList):
self.list.extend(otherList.list)
self.list = sorted(self.list, key=sortByTimeStamp)
class LogFile:
priorTimeStamp = 0.0
def __init__(self, _fileName = ""):
self.logLineList = LogLineList()
if (_fileName!=""):
self.load(_fileName)
def loadLines(self, lines):
logLineList = LogLineList()
for index, line in enumerate(lines):
logLine = LogLine().parse(index, line, self.priorTimeStamp)
self.priorTimeStamp = logLine.timeStamp
logLineList.append(logLine)
return logLineList
def load(self, _fileName):
self.name = _fileName
try:
file = open(_fileName, 'r')
lines = file.readlines()
self.logLineList = self.loadLines(lines)
file.close()
except:
print("Error, file '{}' does not exist".format(self.name))
def print(self, numItems=None):
self.logLineList.print(numItems)
# -----------------------------------------------------
class MetricSet:
def __init__(self, _names):
self.columnNames = _names
self.rowColArray = []
self.rowSum = []
self.rowMax = []
self.rowMin = []
self.rowStd = []
self.rowMedian = []
for col in self.columnNames:
self.rowSum.append(0)
self.rowMax.append(0)
self.rowMin.append(sys.maxsize)
self.rowStd.append(0)
self.rowMedian.append(0)
def appendSet(self, values):
self.rowColArray.append(values)
def print(self):
print("{}".format(" Line#"), end='')
for words in self.columnNames:
print(", '{}'".format(words), end='')
print("")
for row, values in enumerate(self.rowColArray):
print("{:6d}".format(row), end='')
for col, value in enumerate(values):
print(", {:.3f}".format(value), end='')
print("")
print("{}".format(" MAX"), end='')
for value in self.rowMax:
print(", {:.3f}".format(value), end='')
print("")
print("{}".format(" AVE"), end='')
for value in self.rowSum:
print(", {:.3f}".format(value), end='')
print("")
print("{}".format(" MIN"), end='')
for value in self.rowMin:
print(", {:2.3f}".format(value), end='')
print("")
print("{}".format(" STD"), end='')
for value in self.rowStd:
print(", {:2.3f}".format(value), end='')
print("")
print("{}".format("MEDIAN"), end='')
for value in self.rowMedian:
print(", {:2.3f}".format(value), end='')
print("")
def analyze(self):
stdCols = []
numCols = len(self.columnNames)
numRows = len(self.rowColArray)
for col in range(numCols):
stdCols.append([])
# compute sum
for row, values in enumerate(self.rowColArray):
for col, value in enumerate(values):
self.rowSum[col] += value
if value > self.rowMax[col]:
self.rowMax[col] = value
if value < self.rowMin[col]:
self.rowMin[col] = value
# compute std
for col in range(numCols):
for row in range(numRows):
try:
val = self.rowColArray[row][col]
stdCols[col].append(val)
except IndexError:
i = 3
for col, colList in enumerate(stdCols):
stdValue = 0
if (len(colList)>0):
stdValue = statistics.pstdev(colList)
stdMedian = statistics.median(colList)
self.rowStd[col] = stdValue
self.rowMedian[col] = stdMedian
#compute average
for col, value in enumerate(self.rowSum):
if numRows > 0:
self.rowSum[col] = self.rowSum[col] / numRows
else:
self.rowSum[col] = 0
class AnalyzeFile:
initFirstTime = 0
initSecondTime = 0
def __init__(self, _fileName, _keyWords = ["init first", "init second", "boot_completed"]):
self.fileName = _fileName
self.logFile = LogFile(_fileName)
self.workingList = []
self.keyWords = _keyWords
def report(self):
print("-----------------------")
print("Reporting on '{}'".format(self.fileName))
for word in self.keyWords:
item = self.logFile.logLineList.findFirst(word)
item.print()
print("-----------------------")
def getMetrics(self, metricsSet):
values = []
for word in self.keyWords:
item = self.logFile.logLineList.findFirst(word)
if item is not None:
values.append(item.timeStamp)
else:
print("Did not find {} ".format(word))
metricsSet.appendSet(values)
def keyWordReport(self, keyword):
numItems = 0
cumd = 0
items = self.logFile.logLineList.find(keyword)
for item in items:
item.print()
numItems += 1
cumd += item.delta
print("Num {} found = {}, Sum delay = {:.2f} ".format(keyword, numItems, cumd))
for item in items:
lineKeywords = item.text.split(" ")
if (len(lineKeywords)>2):
if lineKeywords[2] == "Service":
tookIndex = item.text.find("took")
if (tookIndex!=None):
tookTime = item.text[tookIndex:tookIndex+10]
print("{} took {}".format(lineKeywords[3], tookTime))
class Analyzer:
def __init__(self):
self.fileName = []
def rebootAndRunCmdToFile(self, fileNamePrefix, msgPrefix, Cmd, numTimes, startIndex):
captured = False
error = False
filenameNum = ""
for i in range(numTimes):
postfix = str(i+startIndex)
filenameNum = fileNamePrefix + "-" + postfix + ".txt"
print(msgPrefix + " to {}".format(filenameNum))
# try 5 times to capure status 'boot_completed'
for i in range(5):
captured = False
rebootCmd = "adb shell su root reboot"
fullCmd = Cmd + " > {}".format(filenameNum)
x = os.system(rebootCmd)
if (x!=0):
print("Error")
error = True
break
time.sleep(45)
x = os.system(fullCmd)
if (x!=0):
print("Error")
error = True
break
# check for boot complete
try:
checkBootComplete = "grep boot_complete {}".format(filenameNum)
output = subprocess.check_output(checkBootComplete, shell=True)
captured = True
break
except:
captured = False
print("trying again for {}".format(filenameNum))
if not captured:
print("ERROR - failed to capture {}".format(filenameNum))
if error:
os.system("rm {}".format(filenameNum))
return captured
def getBuildID(self):
buildIDCmd = "adb shell su root getprop ro.build.version.incremental"
buildString = subprocess.check_output(buildIDCmd, shell = True)
numberList = re.findall(r'\d+', buildString.decode('ISO-8859-1') )
if (numberList==None): return 0
if (len(numberList)==0): return 0
buildID = numberList[0]
return buildID
def pullDmesgLogs(self, BuildID, numTimes, startIndex):
fileNamePrefix = BuildID
msgPrefix = "Pulling Kernel dmesg logs"
cmd = "adb shell su root dmesg"
return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
def pullLogcatLogs(self, BuildID, numTimes, startIndex):
fileNamePrefix = "LC-"+BuildID
msgPrefix = "Pulling Kernel Logcat"
cmd = "adb logcat -b all -d"
return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
def runBootAnalyze(self, filename, numTimes, startIndex):
ABT = os.environ["ANDROID_BUILD_TOP"]
if (len(ABT)<=0):
print("ERROR - ANDROID_BUILD_TOP not set")
BAFILE = "BA-" + filename + "-" + str(numTimes + startIndex) + ".txt"
BACmd = ABT + "/system/extras/boottime_tools/bootanalyze/bootanalyze.py -c " + ABT + "/system/extras/boottime_tools/bootanalyze/config.yaml -n 20 -r -t > " + BAFILE
print(BACmd)
x = os.system(BACmd)
if (x!=0):
print("ERROR running bootanalze")
return False
return True
def pullAll(self):
BuildID = self.getBuildID()
Cmd = "adb bugreport bugreport-{}".format(BuildID)
print(Cmd)
x = os.system(Cmd)
if (x!=0):
print("ERROR Pulling all data")
return False
self.pullDmesgLogs(BuildID, 20, 0)
self.pullLogcatLogs(BuildID, 2, 0)
self.runBootAnalyze(BuildID, 20, 0)
self.summaryReportOnDmesgLogFiles(BuildID, 20)
def summaryReportOnDmesgLogFiles(self, BuildID, numFiles):
metricKeyWords = ["init first", "init second", "boot_completed"]
metricSet = MetricSet(metricKeyWords)
print("Summary report on log files with build ID {}".format(BuildID))
dirList = glob.glob("{}*.txt".format(BuildID))
numFilesAnalyzed = 0
for index, file in enumerate(dirList):
analyzeFile = AnalyzeFile(file, metricKeyWords)
#check it's a kernel log file
item = analyzeFile.logFile.logLineList.findFirst("build.fingerprint")
if (item!=None):
#check if it has the correct build ID
if (item.text.find(BuildID)==-1):
continue
else:
print("BuildID {} not found in file {} fingerprint {}".format(BuildID, file, item))
continue
analyzeFile.getMetrics(metricSet)
numFilesAnalyzed += 1
if ((index+1)>=numFiles):
break
if (numFilesAnalyzed>0):
metricSet.analyze()
metricSet.print()
else:
print("No files criteria {}* and build.fingerprint with {}".format(BuildID, BuildID))
def rename(self, BuildID1, BuildID2, fileType):
print("Summary report on log files with build ID {}".format(BuildID1))
dirList = glob.glob("*{}*".format(BuildID1))
for index, file in enumerate(dirList):
findRes = file.find(BuildID1)
if (findRes!=-1):
newFile = file.replace(BuildID1, BuildID2, 1)
newFile += fileType
os.system("mv {} {}".format(file, newFile))
parser = argparse.ArgumentParser(description='pull all data files from seahawk and run dmesg summary report. The data files will be prefixed with the build ID')
parser.add_argument("-plc", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull logcat numTimes from seahawk")
parser.add_argument("-pdm", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull dmesg logs numTimes from seahawk")
parser.add_argument("-pba", nargs=2, metavar=('<BuildID>', '<numTimes>'), help="pull bootanalyze numTimes from seahawk")
parser.add_argument("-rd", nargs=2, metavar=('<BuildID>', '<numFiles>'), help="summary report on <numFiles> dmesg log files named <BuildID>-*.txt in current directory")
parser.add_argument("-pA", action='store_true', help="pull all data from seahawk a default number of times")
parser.add_argument("-t", nargs="*", help="test - do not use")
args = parser.parse_args()
if args.pdm!=None:
Analyzer().pullDmesgLogs(args.pdm[0], int(args.pdm[1]), int(args.pdm[2]))
if args.plc!=None:
Analyzer().pullLogcatLogs(args.plc[0], int(args.plc[1]), int(args.plc[2]))
if args.pba!=None:
Analyzer().runBootAnalyze(args.pba[0], int(args.pba[1]), 0)
if args.pA!=None:
Analyzer().pullAll()
if args.rd!=None:
Analyzer().summaryReportOnDmesgLogFiles(args.rd[0], int(args.rd[1]))
if args.t!=None:
Analyzer().getBuildID()