#!/usr/bin/python # This file is part of the LibreOffice project. # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import sys import os import getopt import csv colsResult = {} allTests = [] def parseFile(dirname, filename, lastCommit): curTestComment, total = None, None path = os.path.join(dirname, filename) trigger = "desc: Trigger: Client Request: " trigger_len = len(trigger) totals = "totals: " totals_len = len(totals) with open(path,'r') as callgrindFile: lines = callgrindFile.readlines() for line in lines: if line.startswith(trigger): curTestComment = line[trigger_len:].replace("\n","") elif line.startswith(totals): total = line[totals_len:].replace("\n","") if curTestComment is None or total is None: return None testName = os.path.basename(dirname).replace(".test.core","") lastCommitId, lastCommitDate = lastCommit if lastCommitId not in colsResult: colsResult[lastCommitId] = {} colsResult[lastCommitId]['date'] = lastCommitDate colsResult[lastCommitId]['values'] = {} colsResult[lastCommitId]['values'][curTestComment] = total return [lastCommitId, lastCommitDate, testName, curTestComment, total, filename] def processDirectory(rootDir, needsCsvHeader, lastCommit): results = [] if needsCsvHeader: results.append(["lastCommit", "lastCommitDate", "test filename", "dump comment", "count", "filename"]) for dirName, subdirList, fileList in os.walk(rootDir): files = [f for f in fileList if f.startswith("callgrind.out.")] for fname in files: found = parseFile(dirName, fname, lastCommit) if found is not None: results.append(found) return results def getLastCommitInfo(): stream = os.popen("git log --date=iso") line = stream.readline() commitId = line.replace("commit ","").replace("\n","") line = stream.readline() line = stream.readline() commitDate = line.replace("Date: ","").replace("\n","").strip() return commitId, commitDate def displayUsage(): usage = """ Parses the callgrind results of make perfcheck Arguments : --csv-file\t\t the target CSV file - new or containing previous tests - default : perfcheckResult.csv --source-directory\t directory containing make perfcheck output - default : ./workdir/CppunitTest --alert-type\t\t mode for calculating alerts - valid values : previous first --alert-value\t\t alert threshold in % - default = 10 --help\t\t this message Columned output is dumped into csv-file + ".col" Alerts, if any, are displayed in standard output """ print(usage) class WrongArguments(Exception): pass def analyzeArgs(args): try: opts, args = getopt.getopt(args, 'x', [ 'csv-file=', 'source-directory=', 'alert-type=', 'alert-value=', 'help']) except getopt.GetoptError: raise WrongArguments targetFileName = "perfcheckResult.csv" sourceDirectory = "./workdir/CppunitTest" alertType = "" alertValue = 10 for o, a in opts: if o == '--help': displayUsage() sys.exit() elif o == "--csv-file": targetFileName = a elif o == "--source-directory": sourceDirectory = a elif o == "--alert-type": alertType = a elif o == "--alert-value": alertValue = float(a) else: raise WrongArguments return targetFileName, sourceDirectory, alertType, alertValue def readCsvFile(targetFilename): with open(targetFilename, 'r') as csvfile: reader = csv.reader(csvfile, delimiter="\t") # skip header next(reader) for line in reader: # do not process empty lines if not line: continue curId, curDate, curTestName, curTestComment, curValue = line if curTestComment not in allTests: allTests.append(curTestComment) if curId not in colsResult: colsResult[curId] = {} colsResult[curId]['date'] = curDate colsResult[curId]['values'] = {} colsResult[curId]['values'][curTestComment] = curValue if __name__ == '__main__': #check args try: targetFileName, sourceDirectory, alertType, alertValue = analyzeArgs(sys.argv[1:]) except WrongArguments: displayUsage() sys.exit(1) # check if sourceDirectorty exists if not os.path.isdir(sourceDirectory): print("sourceDirectory %s not found - Aborting" % (sourceDirectory)) sys.exit(1) # read the complete CSV file if os.path.isfile(targetFileName): readCsvFile(targetFileName) needsCsvHeader = False else: needsCsvHeader = True # last commit Id lastCommitId, lastCommitDate = getLastCommitInfo() # walker through directory if lastCommitId not in colsResult: lastCommit = (lastCommitId, lastCommitDate) results = processDirectory(sourceDirectory, needsCsvHeader, lastCommit) ppResults = "\n".join(["\t".join(row) for row in results]) print('\nNew results\n' + ppResults) # append raw result with open(targetFileName,'a') as csvfile: writer = csv.writer(csvfile, delimiter='\t') writer.writerows(results) print("\nCSV file written at " + targetFileName + '\n') else: print("\nCSV file up to date " + targetFileName + '\n') # build columned output # header mLine = '\t'.join(["commit", "date"] + allTests) + '\n' alertTest = {} with open(targetFileName + '.col','w') as fileResult: for k in colsResult: mLine += k + "\t" + colsResult[k]['date'] + "\t" for t in allTests: if t in colsResult[k]['values']: mValue= colsResult[k]['values'][t] if t not in alertTest: alertTest[t] = {} alertTest[t][colsResult[k]['date']] = mValue else: mValue = "" mLine += mValue + "\t" mLine += "\n" # write columned result fileResult.write(mLine) print("Columned file written at " + targetFileName + '.col\n') # check for Alerts if alertType == "": sys.exit(1) alertResult = "" for t in alertTest: testDict = alertTest[t] # sort keylist = sorted(testDict.keys()) maxVal = float(testDict[keylist[-1]]) minVal = 0 if alertType == "previous": if len(keylist) > 1: minVal = float(testDict[keylist[-2]]) else: minVal = float(testDict[keylist[0]]) if minVal != 0: delta = 100 * ((maxVal-minVal)/minVal) else: delta = 0 if delta > float(alertValue): alertResult += t + "\t" + "{:.2f}".format(delta) + " %\n" if alertResult != "": print("!!!!!!!! ALERT !!!!!!!\n") print(alertResult)