diff --git a/dev-tools/git-utils/lines_of_code.png b/dev-tools/git-utils/lines_of_code.png index 565a0d58d46f908749aa5b46a42afd5b25ca75e7..9e71628d65a5a9e10cb4d50d17c2dbc4560b29e6 100644 Binary files a/dev-tools/git-utils/lines_of_code.png and b/dev-tools/git-utils/lines_of_code.png differ diff --git a/dev-tools/git-utils/lines_of_code.py b/dev-tools/git-utils/lines_of_code.py new file mode 100644 index 0000000000000000000000000000000000000000..d0ea68a04d22d086bc97831356a04b9ea96860e5 --- /dev/null +++ b/dev-tools/git-utils/lines_of_code.py @@ -0,0 +1,343 @@ +from __future__ import print_function +import os +import re +from datetime import datetime +from email.utils import parsedate +from enum import Enum +from ROOT import * + + +time_offset = 0 + +class FileTypes: + CORE, FTEST, UTEST, PYCODE, MACROS, GUI, PYAPI, THIRD, UNDEF, TOTAL = range(10) + descr=["Core", "Functional Tests", "Unit Tests", "*.py", "macros", "GUI", "PythonAPI", "Third", "Undef"] + + +def filePython(x): + if ".py" in x and not ".pypp." in x: return True + return False + + +def fileCpp(x): + if ".h" in x or ".cpp" in x: return True + return False + + +def dirCore(x): + if "/Core/Algorithms" in x: return True + if "/Core/FormFactors" in x: return True + if "/Core/Samples" in x: return True + if "/Core/StandardSamples" in x: return True + if "/Core/Tools" in x: return True + if "/Core/Fitting" in x: return True + if "/Core/inc" in x: return True + if "/Core/src" in x: return True + if "/Fit/Factory" in x: return True + if "/Fit/FitKernel" in x: return True + if "/Fit/StandardFits" in x: return True + if "/Core/Geometry" in x: return True + return False + + +def dirPyAPI(x): + if "/Core/PythonAPI" in x: return True + if "/Fit/PythonAPI" in x: return True + return False + + +def dirFuncTest(x): + if "/App/" in x: return True + if "/Tests/FunctionalTests/TestCore" in x: return True + if "/Tests/FunctionalTests/TestFit" in x: return True + if "/Tests/FunctionalTests/TestPyCore" in x: return True + if "/Tests/FunctionalTests/TestPyFit" in x: return True + return False + + +def dirGUI(x): + if "/GUI/coregui" in x and not "widgetbox" in x and not "qttools" in x: return True + if "/GUI/main" in x: return True + if "/AppGUI/coregui" in x: return True + if "/BASuite" in x: return True + return False + + +def dirThirdParty(x): + if "/ThirdParty" in x: return True + return False + + +def dirSkip(x): + if "/pub/core" in x: return True + return False + + +def dirUnitTests(x): + if "/UnitTests/" in x: return True + if "/Tests/UnitTests/TestCore/" in x: return True + if "/Tests/UnitTests/TestFit/" in x: return True + return False + + +def filetype(x): + """ + Returns type of file + """ + result = FileTypes.UNDEF + + if dirSkip(x): + return result + + if fileCpp(x) and dirCore(x): + result = FileTypes.CORE + + elif (fileCpp(x) or filePython(x)) and dirFuncTest(x): + result = FileTypes.FTEST + + elif dirUnitTests(x): + result = FileTypes.UTEST + + elif dirGUI(x): + result = FileTypes.GUI + + elif dirPyAPI(x): + result = FileTypes.PYAPI + + elif dirThirdParty(x): + result = FileTypes.THIRD + + return result + + +class Commit: + def __init__(self): + self.date = datetime.today() + self.adds = 0 + self.dels = 0 + self.locs_for_type = [] + for i in range(FileTypes.TOTAL): + self.locs_for_type.append(0) + self.hsh = None + self.who = None + self.cmt = None + + def increment_loc(self, file_type): + self.locs_for_type[file_type] += 1 + self.adds += 1 + + def decrement_loc(self, file_type): + self.locs_for_type[file_type] -= 1 + self.dels += 1 + + +class HistoryCollector: + def __init__(self): + self.history = Commit() + self.data = [] + self.locs = 0 + self.fc = 0 + self.file_type_ppp = FileTypes.UNDEF + self.file_type_mmm = FileTypes.UNDEF + + def pop(self): + if not self.history.adds: + return + pstr="%s %8u %5s %5s %7s %s \t%s"%(self.history.date, self.locs,'+'+str(self.history.adds),'-'+str(self.history.dels), self.history.hsh, self.history.who, self.history.cmt.strip()) + print(self.history.locs_for_type[0], pstr) + self.data.append(self.history) + tmp = list(self.history.locs_for_type) + self.history = Commit() + self.history.locs_for_type = tmp + + def run(self): + nnn = 0 + for x in os.popen('git log develop --reverse -p'): + nnn += 1 + if x.startswith('commit'): + self.pop() + self.history.hsh = x[7:14] + + if x.startswith('Author'): + self.history.who = x.replace("Author: ", '').replace('\n', '') + self.history.who = re.sub(">.*", "", self.history.who) + self.history.who = re.sub(".*<", "", self.history.who) + + if x.startswith('Date'): + self.fc = 1 + self.history.date = datetime(*parsedate(x[5:])[:7]) + #t=datetime.mktime(parsedate(x[5:])) + + if self.fc == 2: + self.history.cmt = x[:-1] + self.fc = 0 + + if self.fc == 1: + if len(x) == 1: + self.fc = 2 + + if x.startswith('+++'): + self.file_type_ppp = filetype(x) + + if x.startswith('---'): + self.file_type_mmm = filetype(x) + + if x.startswith('+') and not x.startswith('+++'): + self.history.increment_loc(self.file_type_ppp) + if self.file_type_ppp <FileTypes.PYAPI: + self.locs += 1 + + if x.startswith('-') and not x.startswith('---'): + self.history.decrement_loc(self.file_type_mmm) + if self.file_type_mmm <FileTypes.PYAPI: + self.locs -= 1 + + # if nnn>1000000: + # break + + self.pop() + + +def create_time_histogram(history, title, fileType): + global time_offset + c = history[0] + td_first = TDatime(c.date.year, c.date.month, c.date.day, c.date.hour, c.date.minute, c.date.second) + c = history[-1] + td_last = TDatime(c.date.year, c.date.month, c.date.day, c.date.hour, c.date.minute, c.date.second) + + time_offset = int(td_first.Convert()) - 7*24*3600 # one week before first commit + xmin = 0 + xmax = int(td_last.Convert()) - time_offset + + ntimebins = (xmax - xmin)/3600 # one timebin per day + # print("CORE {1} FTEST {2} UTEST {3} GUI {4} PYAPI {5}".format(c.locs_for_type[])) + + print("{0}:{1}".format(title, c.locs_for_type[fileType])) + + result = TH1D(title, title, ntimebins, xmin, xmax) + result.GetXaxis().SetTimeDisplay(1) + result.GetXaxis().SetTimeFormat("#splitline{%d/%m}{%Y}") + result.GetYaxis().SetLabelSize(0.02) + result.GetXaxis().SetLabelSize(0.02) + # result.GetXaxis().SetNdivisions(512) + result.GetXaxis().SetTimeOffset(time_offset) + + refhist = TH1D(title+"_ref", title+"_ref", ntimebins, xmin, xmax) + + # filling histogram + for c in history: + td = TDatime(c.date.year, c.date.month, c.date.day, c.date.hour, c.date.minute, c.date.second) + xx = td.Convert() - time_offset + result.Fill(xx, c.locs_for_type[fileType]) + refhist.Fill(xx) + + # normalizing bin content on number of entries per bin + for i_bin in range(1, result.GetNbinsX()+1): + nentries = refhist.GetBinContent(i_bin) + bin_content = float(result.GetBinContent(i_bin)) + if bin_content<0: + print(i_bin, nentries, bin_content) + if nentries != 0: + bin_content /= nentries + result.SetBinContent(i_bin, bin_content) + + # filling empty bins with values from previous day + prev_content = 0 + for i_bin in range(1, result.GetNbinsX()+1): + if result.GetBinContent(i_bin) == 0: + result.SetBinContent(i_bin, prev_content) + prev_content = result.GetBinContent(i_bin) + + return result + + +def hold_root_graphics(): + """ + Holds ROOT graphics while listening to ctr-C event + """ + Interrupt = False + while not Interrupt: + Interrupt = gSystem.ProcessEvents() + gSystem.Sleep(10) + + +def plot_loc_number(history): + global time_offset + + selected_hist = [FileTypes.CORE, FileTypes.FTEST, FileTypes.UTEST, FileTypes.GUI, FileTypes.PYAPI] + a_colors = [kAzure+1, kOrange, kRed, kGreen, kYellow-7, kAzure, kGray+1] + + a_histograms = [] + hstack = THStack("hstack","Number of Lines of Code") + hstack.SetTitle("") + + legend = TLegend(0.14, 0.65, 0.4, 0.84) + legend.SetBorderSize(1) + for i in range(0, len(selected_hist)): + i_hist = selected_hist[i] + hist = create_time_histogram(history, FileTypes.descr[selected_hist[i]], i_hist) + hist.SetLineColor(a_colors[i_hist]) + hist.SetFillColor(a_colors[i_hist]) + a_histograms.append(hist) + + + # adding histograms to legend in right order + for i_hist in range(len(a_histograms)-1, -1, -1): + legend.AddEntry(a_histograms[i_hist], a_histograms[i_hist].GetTitle(), "f") + + + #preparing canvas + c1 = TCanvas( 'gisasfw_loc', 'Number of lines of code in BornAgain project', 1024, 768) + c1.cd() + gPad.SetGrid() + gPad.SetBottomMargin(0.10) + gPad.SetLeftMargin(0.10) + gPad.SetTopMargin(0.10) + gPad.SetRightMargin(0.10) + # a_histograms[0].Draw("HIST") + + # drawing + for h in a_histograms: + hstack.Add(h,"][") + hstack.Draw("HIST") + hstack.GetXaxis().SetTimeDisplay(1) + #hstack.GetXaxis().SetTimeFormat("%d/%m") + hstack.GetXaxis().SetTimeFormat("#splitline{%b}{%Y}") + hstack.GetXaxis().SetLabelSize(0.025) + hstack.GetYaxis().SetLabelSize(0.025) + hstack.GetXaxis().SetLabelOffset(0.02) + hstack.GetXaxis().SetNdivisions(522) + hstack.GetXaxis().SetTimeOffset(time_offset) + hstack.SetMaximum(190e+03) + + # + legend.Draw() + c1.Modified() + c1.Update() + gPad.RedrawAxis() + gPad.RedrawAxis("G") + + c1.Print("lines_of_code.png") + hold_root_graphics() + + +def process_loc_number(targetfolder = "../.."): + print("Hello World") + + prevfolder = os.getcwd() + os.chdir(targetfolder) + + collector = HistoryCollector() + collector.run() + + os.chdir(prevfolder) + + plot_loc_number(collector.data) + + + + + +if __name__ == '__main__': + process_loc_number() +