blob: c6af55ce64e097ce9d4838e637b31b6e983ee903 [file] [log] [blame]
robrajapakse51d4eda2022-10-11 10:16:20 -07001#!/usr/bin/python3
2#from calendar import c
3import sys
4import os
5import copy
6import argparse
7import statistics
8import glob
9import subprocess
10import re
11import time
12
13from string import digits
14
15class LogLine:
16 remove_digits = str.maketrans('', '', digits)
17 def __init__(self):
18 self.lineNum = 0
19 self.timeStamp = 0
20 self.delta = 0
21 self.deltaDiff = 0
22 self.text = "none"
23 self.textKey = "none"
24
25 def parse(self, index, line, priorTimeStamp):
26 _line = line.strip()
27 words = _line.split("]", 1)
28 timeString = words[0].strip(" [")
29 self.lineNum = index
30 self.timeStamp = float(timeString)
31 self.delta = self.timeStamp - priorTimeStamp
32 self.text = words[1][:150]
33 self.textKey = self.text.translate(self.remove_digits)
34 priorTimeStamp = self.timeStamp
35 return self
36
37 def getTextKey(self):
38 textKey = self.textKey
39 return textKey
40
41 def print(self):
42 print("I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text))
43
44 def toString(self):
45 return "I, {:5d}, T, {:8.4f}, D, {: .4f}, DD, ({: .4f}) {}".format(self.lineNum, self.timeStamp, self.delta, self.deltaDiff, self.text)
46
47def sortByDelta(item):
48 return item.delta
49
50def sortByTimeStamp(item):
51 return item.timeStamp
52
53class LogLineListStats:
54 def __init__(self):
55 self.numItems = 0
56 self.firstTimeStamp = 0
57 self.lastTimeStamp = 0
58 self.deltaSum = 0
59 self.deltaDiffSum = 0
60 self.status = "unknown"
61 self.name = "unknown"
62
63 def print(self):
64 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))
65
66 def add(self, _other):
67 if (_other.firstTimeStamp< self.firstTimeStamp):
68 self.firstTimeStamp = _other.firstTimeStamp
69
70 if (_other.lastTimeStamp > self.lastTimeStamp):
71 self.lastTimeStamp = _other.lastTimeStamp
72 self.deltaSum += _other.deltaSum
73
74
75# ------------------------------------------------------
76
77class LogLineList:
78
79 def __init__(self, _name= ""):
80 self.list = []
81 self.name = _name
82
83 def clear(self):
84 self.list.clear()
85
86 def append(self, item):
87 self.list.append(item)
88
89 def print(self, numItems=None):
90 printLineNum = 0
91 timeStart = 0
92 sumDelta = 0
93 sumDeltaDiff = 0
94 print("List: {}", self.name)
95 for item in self.list:
96 if (timeStart==0):
97 timeStart = item.timeStamp
98 timeOffset = item.timeStamp - timeStart
99 sumDelta += item.delta
100 sumDeltaDiff += item.deltaDiff
101 printLineNum += 1
102 printLine = "{:4d} {:.4f} {: .4f} ({: .4f}) | {} ".format(printLineNum, timeOffset, sumDelta, sumDeltaDiff, item.toString())
103 print(printLine)
104 if (numItems!=None):
105 numItems -= 1
106 if (numItems<=0):
107 break
108
109 def find(self, word):
110 itemList = []
111 for item in self.list:
112 if item.text.find(word) != -1:
113 itemList.append(item)
114 return itemList
115 def findFirst(self, word):
116 itemList = self.find(word)
117 if (itemList!=None):
118 if (len(itemList)>0):
119 return itemList[0]
120 return None
121
122 def findTextKey(self, textKey):
123 itemList = []
124 for item in self.list:
125 if item.getTextKey()==textKey:
126 itemList.append(item)
127 if (len(itemList)==0):
128 return None
129 return itemList[0]
130
131 def findItem(self, item):
132 textKey = item.getTextKey()
133 return self.findTextKey(textKey)
134
135 def findExactItem(self, item):
136 text = item.text
137 return self.find(text)
138
139 def filter(self, startKeyWord, endKeyWord, delta=0):
140 resultsList = LogLineList()
141 startTime = self.findFirst(startKeyWord).timeStamp
142 endTime = self.findFirst(endKeyWord).timeStamp
143 for item in self.list:
144 if ((item.timeStamp >= startTime) and (item.timeStamp<=endTime)):
145 if (item.timeStamp == startTime):
146 item.delta = 0
147 if ((item.delta > delta) or ((item.timeStamp == startTime))):
148 resultsList.append(item)
149 resultsList.name = self.name
150 return resultsList
151
152
153 def findCommon(self, otherList):
154 commonList = LogLineList()
155 commonList.name = self.name + "_common"
156 notCommonList = LogLineList()
157 notCommonList.name = self.name + "_notCommon"
158 numFoundItems = 0
159 numNotFoundItems = 0
160 for item in self.list:
161 dm1 = otherList.findExactItem(item)
162 _item = copy.deepcopy(item)
163 if dm1!=None:
164 commonList.append(_item)
165 numFoundItems += 1
166 else:
167 notCommonList.append(_item)
168 numNotFoundItems += 1
169 print("FindCommon {} {} {} {}".format(len(self.list), len(otherList.list), numFoundItems, numNotFoundItems ))
170 return commonList, notCommonList
171
172 def difference(self, otherList):
173 diffList = LogLineList()
174 diffList.name = otherList.name + "Diff"
175 for item in self.list:
176 thisItem = copy.deepcopy(item)
177 otherItem = otherList.findItem(item)
178 if (item.text.find("EXT4-fs (sda11): recovery complete")!=-1):
179 print("here")
180 if otherItem==None:
181 print("LogLineList::difference() !Error, other does not have {}".format(item.text))
182 else:
183 thisItem.deltaDiff = otherItem.delta - item.delta
184
185 diffList.append(thisItem)
186 return diffList
187
188 def analyze(self, checkPeriod = True, includeFirst = True):
189 numItems = 0
190 firstTimeStamp = 0
191 firstDelta = 0
192 lastTimeStamp = 0
193 deltaSum = 0
194 deltaDiffSum = 0
195 for item in self.list:
196 numItems += 1
197 deltaSum += item.delta
198 deltaDiffSum += item.deltaDiff
199 if firstTimeStamp==0:
200 firstTimeStamp = item.timeStamp
201 firstDelta = item.delta
202 deltaSum = 0
203 deltaDiffSum = 0
204 if (item.timeStamp<firstTimeStamp):
205 firstTimeStamp = item.timeStamp
206 firstDelta = item.delta
207
208 if (item.timeStamp > lastTimeStamp):
209 lastTimeStamp = item.timeStamp
210 timePeriod = lastTimeStamp - firstTimeStamp
211 status = "pass"
212 if (checkPeriod):
213 diff = timePeriod - deltaSum
214 if (abs(diff)>0.0001):
215 print("LogLineList::Analyze() {} ERROR! TimePeriod:{}, CumulativeDelta: {} ".format(self.name, timePeriod, deltaSum))
216 status = "ERROR"
217 logLineListStats = LogLineListStats()
218 logLineListStats.numItems = numItems
219 logLineListStats.firstTimeStamp = firstTimeStamp
220 logLineListStats.lastTimeStamp = lastTimeStamp
221 logLineListStats.deltaSum = deltaSum
222 logLineListStats.deltaDiffSum = deltaDiffSum
223 logLineListStats.status = status
224 logLineListStats.name = self.name
225 return logLineListStats
226
227 def addList(self, otherList):
228 self.list.extend(otherList.list)
229 self.list = sorted(self.list, key=sortByTimeStamp)
230
231
232class LogFile:
233 priorTimeStamp = 0.0
234 def __init__(self, _fileName = ""):
235 self.logLineList = LogLineList()
236 if (_fileName!=""):
237 self.load(_fileName)
238
239 def loadLines(self, lines):
240 logLineList = LogLineList()
241 for index, line in enumerate(lines):
242 logLine = LogLine().parse(index, line, self.priorTimeStamp)
243 self.priorTimeStamp = logLine.timeStamp
244 logLineList.append(logLine)
245 return logLineList
246
247 def load(self, _fileName):
248 self.name = _fileName
249 try:
250 file = open(_fileName, 'r')
251 lines = file.readlines()
252 self.logLineList = self.loadLines(lines)
253 file.close()
254 except:
255 print("Error, file '{}' does not exist".format(self.name))
256
257 def print(self, numItems=None):
258 self.logLineList.print(numItems)
259
260# -----------------------------------------------------
261
262class MetricSet:
263 def __init__(self, _names):
264 self.columnNames = _names
265 self.rowColArray = []
266 self.rowSum = []
267 self.rowMax = []
268 self.rowMin = []
269 self.rowStd = []
270 self.rowMedian = []
271 for col in self.columnNames:
272 self.rowSum.append(0)
273 self.rowMax.append(0)
274 self.rowMin.append(sys.maxsize)
275 self.rowStd.append(0)
276 self.rowMedian.append(0)
277
278 def appendSet(self, values):
279 self.rowColArray.append(values)
280
281 def print(self):
282 print("{}".format(" Line#"), end='')
283 for words in self.columnNames:
284 print(", '{}'".format(words), end='')
285 print("")
286
287 for row, values in enumerate(self.rowColArray):
288 print("{:6d}".format(row), end='')
289 for col, value in enumerate(values):
290 print(", {:.3f}".format(value), end='')
291 print("")
292
293 print("{}".format(" MAX"), end='')
294 for value in self.rowMax:
295 print(", {:.3f}".format(value), end='')
296 print("")
297
298
299 print("{}".format(" AVE"), end='')
300 for value in self.rowSum:
301 print(", {:.3f}".format(value), end='')
302 print("")
303
304 print("{}".format(" MIN"), end='')
305 for value in self.rowMin:
306 print(", {:2.3f}".format(value), end='')
307 print("")
308
309 print("{}".format(" STD"), end='')
310 for value in self.rowStd:
311 print(", {:2.3f}".format(value), end='')
312 print("")
313
314 print("{}".format("MEDIAN"), end='')
315 for value in self.rowMedian:
316 print(", {:2.3f}".format(value), end='')
317 print("")
318
319 def analyze(self):
320 stdCols = []
321 numCols = len(self.columnNames)
322 numRows = len(self.rowColArray)
323 for col in range(numCols):
324 stdCols.append([])
325
326 # compute sum
327 for row, values in enumerate(self.rowColArray):
328 for col, value in enumerate(values):
329 self.rowSum[col] += value
330 if value > self.rowMax[col]:
331 self.rowMax[col] = value
332 if value < self.rowMin[col]:
333 self.rowMin[col] = value
334
335 # compute std
336 for col in range(numCols):
337 for row in range(numRows):
338 try:
339 val = self.rowColArray[row][col]
340 stdCols[col].append(val)
341 except IndexError:
342 i = 3
343 for col, colList in enumerate(stdCols):
344 stdValue = 0
345 if (len(colList)>0):
346 stdValue = statistics.pstdev(colList)
347 stdMedian = statistics.median(colList)
348 self.rowStd[col] = stdValue
349 self.rowMedian[col] = stdMedian
350
351 #compute average
352 for col, value in enumerate(self.rowSum):
353 if numRows > 0:
354 self.rowSum[col] = self.rowSum[col] / numRows
355 else:
356 self.rowSum[col] = 0
357
358class AnalyzeFile:
359 initFirstTime = 0
360 initSecondTime = 0
361
362 def __init__(self, _fileName, _keyWords = ["init first", "init second", "boot_completed"]):
363 self.fileName = _fileName
364 self.logFile = LogFile(_fileName)
365 self.workingList = []
366 self.keyWords = _keyWords
367
368 def report(self):
369 print("-----------------------")
370 print("Reporting on '{}'".format(self.fileName))
371 for word in self.keyWords:
372 item = self.logFile.logLineList.findFirst(word)
373 item.print()
374 print("-----------------------")
375
376 def getMetrics(self, metricsSet):
377 values = []
378 for word in self.keyWords:
379 item = self.logFile.logLineList.findFirst(word)
380 if item is not None:
381 values.append(item.timeStamp)
382 else:
383 print("Did not find {} ".format(word))
384 metricsSet.appendSet(values)
385
386 def keyWordReport(self, keyword):
387 numItems = 0
388 cumd = 0
389 items = self.logFile.logLineList.find(keyword)
390 for item in items:
391 item.print()
392 numItems += 1
393 cumd += item.delta
394 print("Num {} found = {}, Sum delay = {:.2f} ".format(keyword, numItems, cumd))
395
396 for item in items:
397 lineKeywords = item.text.split(" ")
398 if (len(lineKeywords)>2):
399 if lineKeywords[2] == "Service":
400 tookIndex = item.text.find("took")
401 if (tookIndex!=None):
402 tookTime = item.text[tookIndex:tookIndex+10]
403 print("{} took {}".format(lineKeywords[3], tookTime))
404
405
406class Analyzer:
407 def __init__(self):
408 self.fileName = []
409
410 def rebootAndRunCmdToFile(self, fileNamePrefix, msgPrefix, Cmd, numTimes, startIndex):
411 captured = False
412 error = False
413 filenameNum = ""
414 for i in range(numTimes):
415 postfix = str(i+startIndex)
416 filenameNum = fileNamePrefix + "-" + postfix + ".txt"
417 print(msgPrefix + " to {}".format(filenameNum))
418 # try 5 times to capure status 'boot_completed'
419 for i in range(5):
420 captured = False
421 rebootCmd = "adb shell su root reboot"
422 fullCmd = Cmd + " > {}".format(filenameNum)
423 x = os.system(rebootCmd)
424 if (x!=0):
425 print("Error")
426 error = True
427 break
428 time.sleep(45)
429 x = os.system(fullCmd)
430 if (x!=0):
431 print("Error")
432 error = True
433 break
434 # check for boot complete
435 try:
436 checkBootComplete = "grep boot_complete {}".format(filenameNum)
437 output = subprocess.check_output(checkBootComplete, shell=True)
438 captured = True
439 break
440 except:
441 captured = False
442 print("trying again for {}".format(filenameNum))
443 if not captured:
444 print("ERROR - failed to capture {}".format(filenameNum))
445 if error:
446 os.system("rm {}".format(filenameNum))
447 return captured
448
449 def getBuildID(self):
450 buildIDCmd = "adb shell su root getprop ro.build.version.incremental"
451 buildString = subprocess.check_output(buildIDCmd, shell = True)
452 numberList = re.findall(r'\d+', buildString.decode('ISO-8859-1') )
453 if (numberList==None): return 0
454 if (len(numberList)==0): return 0
455 buildID = numberList[0]
456 return buildID
457
458 def pullDmesgLogs(self, BuildID, numTimes, startIndex):
459 fileNamePrefix = BuildID
460 msgPrefix = "Pulling Kernel dmesg logs"
461 cmd = "adb shell su root dmesg"
462 return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
463
464 def pullLogcatLogs(self, BuildID, numTimes, startIndex):
465 fileNamePrefix = "LC-"+BuildID
466 msgPrefix = "Pulling Kernel Logcat"
467 cmd = "adb logcat -b all -d"
468 return self.rebootAndRunCmdToFile(fileNamePrefix, msgPrefix, cmd, numTimes, startIndex)
469
470 def runBootAnalyze(self, filename, numTimes, startIndex):
471 ABT = os.environ["ANDROID_BUILD_TOP"]
472 if (len(ABT)<=0):
473 print("ERROR - ANDROID_BUILD_TOP not set")
474 BAFILE = "BA-" + filename + "-" + str(numTimes + startIndex) + ".txt"
475 BACmd = ABT + "/system/extras/boottime_tools/bootanalyze/bootanalyze.py -c " + ABT + "/system/extras/boottime_tools/bootanalyze/config.yaml -n 20 -r -t > " + BAFILE
476 print(BACmd)
477 x = os.system(BACmd)
478 if (x!=0):
479 print("ERROR running bootanalze")
480 return False
481 return True
482
483 def pullAll(self):
484 BuildID = self.getBuildID()
485 Cmd = "adb bugreport bugreport-{}".format(BuildID)
486 print(Cmd)
487 x = os.system(Cmd)
488 if (x!=0):
489 print("ERROR Pulling all data")
490 return False
491 self.pullDmesgLogs(BuildID, 20, 0)
492 self.pullLogcatLogs(BuildID, 2, 0)
493 self.runBootAnalyze(BuildID, 20, 0)
494 self.summaryReportOnDmesgLogFiles(BuildID, 20)
495
496 def summaryReportOnDmesgLogFiles(self, BuildID, numFiles):
497 metricKeyWords = ["init first", "init second", "boot_completed"]
498 metricSet = MetricSet(metricKeyWords)
499 print("Summary report on log files with build ID {}".format(BuildID))
500 dirList = glob.glob("{}*.txt".format(BuildID))
501 numFilesAnalyzed = 0
502 for index, file in enumerate(dirList):
503 analyzeFile = AnalyzeFile(file, metricKeyWords)
504 #check it's a kernel log file
505 item = analyzeFile.logFile.logLineList.findFirst("build.fingerprint")
506 if (item!=None):
507 #check if it has the correct build ID
508 if (item.text.find(BuildID)==-1):
509 continue
510 else:
511 print("BuildID {} not found in file {} fingerprint {}".format(BuildID, file, item))
512 continue
513 analyzeFile.getMetrics(metricSet)
514 numFilesAnalyzed += 1
515 if ((index+1)>=numFiles):
516 break
517 if (numFilesAnalyzed>0):
518 metricSet.analyze()
519 metricSet.print()
520 else:
521 print("No files criteria {}* and build.fingerprint with {}".format(BuildID, BuildID))
522
523 def rename(self, BuildID1, BuildID2, fileType):
524 print("Summary report on log files with build ID {}".format(BuildID1))
525 dirList = glob.glob("*{}*".format(BuildID1))
526 for index, file in enumerate(dirList):
527 findRes = file.find(BuildID1)
528 if (findRes!=-1):
529 newFile = file.replace(BuildID1, BuildID2, 1)
530 newFile += fileType
531 os.system("mv {} {}".format(file, newFile))
532
533
534parser = argparse.ArgumentParser(description='pull all data files from seahawk and run dmesg summary report. The data files will be prefixed with the build ID')
535
536parser.add_argument("-plc", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull logcat numTimes from seahawk")
537parser.add_argument("-pdm", nargs=3, metavar=('<BuildID>', '<numTimes>', '<startIndex>'), help="pull dmesg logs numTimes from seahawk")
538parser.add_argument("-pba", nargs=2, metavar=('<BuildID>', '<numTimes>'), help="pull bootanalyze numTimes from seahawk")
539parser.add_argument("-rd", nargs=2, metavar=('<BuildID>', '<numFiles>'), help="summary report on <numFiles> dmesg log files named <BuildID>-*.txt in current directory")
540parser.add_argument("-pA", action='store_true', help="pull all data from seahawk a default number of times")
541parser.add_argument("-t", nargs="*", help="test - do not use")
542args = parser.parse_args()
543
544
545if args.pdm!=None:
546 Analyzer().pullDmesgLogs(args.pdm[0], int(args.pdm[1]), int(args.pdm[2]))
547
548if args.plc!=None:
549 Analyzer().pullLogcatLogs(args.plc[0], int(args.plc[1]), int(args.plc[2]))
550
551if args.pba!=None:
552 Analyzer().runBootAnalyze(args.pba[0], int(args.pba[1]), 0)
553
554if args.pA!=None:
555 Analyzer().pullAll()
556
557if args.rd!=None:
558 Analyzer().summaryReportOnDmesgLogFiles(args.rd[0], int(args.rd[1]))
559
560if args.t!=None:
561 Analyzer().getBuildID()
562