Changelog 2006-08-31
[mining-tools:carnarvon.git] / pycarnarvon / Web.py
1 # Copyright (C) 2006 Alvaro Navarro Clemente
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU Library General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 #
17 # Authors : Alvaro Navarro <anavarro@gsyc.escet.urjc.es>
18
19 """
20 Web class 
21
22
23 @author:       Alvaro Navarro
24 @organization: Grupo de Sistemas y Comunicaciones, Universidad Rey Juan Carlos
25 @copyright:    Universidad Rey Juan Carlos (Madrid, Spain)
26 @license:      GNU GPL version 2 or any later version
27 @contact:      anavarro@gsyc.escet.urjc.es
28 """
29
30 import sys
31 import os
32 from time import gmtime, strftime
33 import Globals as gb
34 import Database as dbmodule
35 import Stats as stmodule
36 import SVG as svgmodule
37
38 class Web:
39
40     def __init__(self):
41         # Where to save the webpage
42         self.path = ''
43         self.templates = ''
44         self.filename = ''
45         self.dates = None
46         self.styles = ['newstyle.css']
47         self.images = [ 'background.jpg',
48                         'bg.gif',
49                         'carnarvon-logo.png']
50
51     def generate_head(self):
52         """
53         Generates header file
54         """
55         html = self.read_file(self.templates + "/header.html")
56
57         return html
58
59     def generate_menu(self, index=0):
60         """
61         Generate right menu
62         """
63         html = ''
64         html += '<div id=\"navcontainer\">\n'
65         html += '<b> navigation </b>\n'
66         html += '<br/><br/>\n'
67         html += '<ul id=\"navlinks\">\n'
68         for d in self.dates:
69             if index:
70                 #html += '<li><a href=stats_' + str(d) + "/" + str(d) + ".html>" + d + "</a></li>\n"
71                 html += '<li><a href=stats_' + str(d) + "/index.html>" + d + "</a></li>\n"
72             else:
73                 #html += '<li><a href=../stats_' + str(d) + "/" + str(d) + ".html>" + d + "</a></li>\n"
74                 html += '<li><a href=../stats_' + str(d) + "/index.html>" + d + "</a></li>\n"
75
76         html += "</ul><br/><br/><b>powered by <a href=http://carnarvon.tigris.org>carnarvon</a>"
77         html += "</b><br/></div>\n"
78
79         return html
80
81     def read_file(self, file):
82
83         files = open(file,'r')
84
85         aux = ''
86         while (1):
87             l = files.readline()
88             l = l[:-1]
89             if not l:
90                 break
91             aux += l + "\n"
92
93         files.close()
94
95         return aux
96
97     def generate_html(self, dates):
98         # Create HTML output from metadatafile, graphs and metrics
99         self.dates = dates
100
101         #output = open(self.path + "/" + self.filename + ".html", 'w')
102         output = open(self.path + "/" + "index.html", 'w')
103
104         head = self.generate_head()
105         body = self.generate_body()
106         html = head + body
107
108         output.write(html)
109         output.close()
110
111         # Copy images and css
112         gb.Globals.createdir(self.path + "/images/")
113
114         try:
115             for img in self.images:
116                 os.system("cp -fR " + self.templates + "/images/" + img + " " + self.path + "/images/")
117             for stl in self.styles:
118                 os.system("cp -fR " + self.templates + "/" + stl + " " + self.path)
119         except:
120             pass
121
122     def generate_body(self):
123         pass
124
125 class WebIndex(Web):
126
127     def __init__(self):
128         Web.__init__(self)
129         self.path = gb.Globals.workspace
130         self.templates = gb.Globals.templates
131         self.filename = "index"
132         gb.Globals.createdir(self.path)
133
134     def generate_body(self):
135         # Create HTML output from metadatafile, graphs and metrics
136
137         html = "<body>\n"
138         html += "<div class=\"header\" style=\"height:76px;\"><b>Archaeology Stats</b></div>\n"
139         html += "<div id=\"logo\">\n"
140         html += "<a style=\"background-image: url(images/carnarvon-logo.png);\"\n"
141         html += "href=\"http://carnarvon.tigris.org\"\n"
142         html += "title=\"Carnarvon Project Homepage\"></a>\n"
143         html += "</div>\n"
144         html += "<div style=\"float:left;\">\n"
145         html += "</div>\n"
146         menu = self.generate_menu(index=1)
147         html += menu + "\n</body></html>"
148
149         return html
150
151
152 class WebStat(Web):
153
154     def __init__(self, date, target, directory, dir_id, flags):
155         """
156         Constructor
157         
158         @type  date: String
159         @param date: Date of the current stamp
160         @type  target: database_object
161         @param target: Database which store the data
162         @type  flags: String
163         @param flags: flags for gnuplot
164         """
165         Web.__init__(self)
166
167         self.metrics = {'size'     :0,
168                         'age'      :0,
169                         'aging'    :0,
170                         'relaging' :0,
171                         'rel5a'    :0,
172                         'abs5a'    :0,
173                         'progeria' :0,
174                         'orphy'    :0,
175                         'date_50'  :''}
176
177         self.source = { 'lines': 'lines.dat',
178                         'commiters' : 'commiters.dat',
179                         'files': 'files.dat'}
180                         #'revlines': 'revLines.dat'}
181
182         self.graphs = {'lines': ['line_diff.png',
183                                  'line_peragg.png'],
184
185                        'commiters' : ['commiters_Total_Lines.png',
186                                       'commiters_Modified_Files.png'],
187
188                        'files' : ['files_All_Lines.png',
189                                   'files_Number_Authors.png',
190                                   'files_Number_Revisions.png',
191                                   'files_Last_Modified.png'],
192
193                        'revlines': ['revLines_revision_Agg.png',
194                                     'revLines_revision_lines.png']
195                        }
196
197         self.date = date.replace("_","-")
198         self.filedate = date
199         self.db = target
200         self.flags = flags
201         self.directory = directory
202         self.dir_id = dir_id
203
204         # Path to disk data
205         self.filename = str(self.filedate)
206         self.path_without_dir = gb.Globals.workspace + "/stats_" + self.filename + "/"
207         self.path = gb.Globals.workspace + "/stats_" + self.filename + "/" + self.directory + "/"
208         gb.Globals.createdir(self.path)
209
210         # Path to the html's templates
211         self.templates = gb.Globals.templates
212
213         # Path to database data
214         self.table = 'annotates_' + date
215
216     def generate_graphs(self):
217         import os
218
219         os.system("cd " + self.path + " && gnuplot *plot 2> /dev/null")
220
221     def read_file2table(self, file):
222
223         files = open(str(self.path) + "/" + file,'r')
224
225         aux = ''
226
227         while (1):
228             l = files.readline()
229             l = l[:-1]
230             if not l:
231                 break
232             if l[0] == '#':
233                 pass
234             if l == "":
235                 pass
236
237             fields = l.split('\t')
238             aux += "<tr>"
239             for f in fields:
240                 aux += "<td> " + str(f) + "</td>\n"
241             aux += "</tr>\n"
242
243         files.close()
244
245         return aux
246
247     def get_general_stats(self):
248         lines = self.db.query("count(distinct(line_id))", self.table)[0][0]
249         commiters = self.db.query("count(distinct(commiter_id))", self.table)[0][0]
250         files = self.db.query("count(distinct(file_id))", self.table)[0][0]
251
252         return lines, commiters, files
253
254     def generate_body(self):
255         # Create HTML output from metadatafile, graphs and metrics
256
257         """
258         try:
259             print "         => Generating Stats"
260             self.generate_stats()
261             print "         => Generating Graphs"
262             self.generate_graphs()
263             print "         => Generating Metrics"
264             self.generate_metrics()
265         except:
266             pass
267         """
268         print "         => Generating Stats"
269         self.generate_stats()
270         print "         => Generating Graphs"
271         self.generate_graphs()
272         print "         => Generating Metrics"
273         self.generate_metrics()
274         print "         => Generating General Stats"
275         lines, commiters, files = self.get_general_stats()
276
277         html = ''
278         html += "<body>\n"
279         html += "<div class=\"header\" style=\"height:76px;\"><b>Archaeology Stats: " + self.filename.replace('_','/') + "</b></div>\n"
280         html += "<div id=\"logo\">\n"
281         html += "<a style=\"background-image: url(images/carnarvon-logo.png);\"\n"
282         html += "href=\"http://carnarvon.tigris.org\"\n"
283         html += "title=\"Carnarvon Project Homepage\"></a>\n"
284         html += "</div>\n"
285         html += "<div style=\"float:left;\">\n"
286         html += "<h3 class=\"title\"> metrics </h3>\n"
287         html += "<table border=0>\n"
288         html += "<tr><td> <b>Size</b> </td><td> " + str(self.metrics['size']) + " SLOC </td></tr>\n"
289         html += "<tr><td> <b>Age</b> </td><td> " + str(self.metrics['age']) + " months</td></tr>\n"
290         html += "<tr><td> <b>Aging</b> </td><td> " + str(self.metrics['aging']) + " SLOC-month</td></tr>\n"
291         html += "<tr><td> <b>Relative Aging</b> </td><td> %2.2f </td></tr>\n" % self.metrics['relaging']
292         html += "<tr><td> <b>Relative 5 years</b> </td><td> %2.2f </td></tr>\n" % self.metrics['rel5a']
293         html += "<tr><td> <b>Absolute 5 years</b> </td><td> %2.2f </td></tr>\n" % self.metrics['abs5a']
294         html += "<tr><td> <b>Progeria</b> </td><td> %2.2f </td></tr>\n" % self.metrics['progeria']
295         html += "<tr><td> <b>Orphaned</b> </td><td>" + str(self.metrics['orphy']) + "</td></tr>\n"
296         html += "<tr><td> <b>50% of code</b> </td><td>" + str(self.metrics['date_50']) + "</td></tr>\n"
297         html += "<tr><td> <b>Total Lines</b> </td><td>" + str(lines) + "</td></tr>\n"
298         html += "<tr><td> <b>Total Commiters</b> </td><td>" + str(commiters) + "</td></tr>\n"
299         html += "<tr><td> <b>Total Files</b> </td><td>" + str(files) + "</td></tr>\n"
300         html += "</table>\n"
301
302         # Tables and graphs
303         for fdate in self.source:
304              html += "<h3 class=\"title\"> " + fdate + " </h3>\n"
305              images = self.graphs[fdate]
306              for img in images:
307                  html += "<img border=\"0\" src=\"" + img + "\" height=480 width=640>\n"
308
309
310         if self.dir_id:
311             html += self.create_directories(entire=0)
312             html += self.create_files()
313         else:
314             html += self.create_directories(entire=1)
315
316         html += "</div>\n"
317
318         menu = self.generate_menu()
319         html += menu + "\n</body></html>"
320
321         return html
322
323     def create_files(self):
324         files = self.db.query("distinct(f.file)", self.table + " as a, files as f", "dir_id=" + self.dir_id + " and a.file_id = f.file_id")
325
326         html = "<h3 class=\"title\"> Files </h3>\n"
327         for (file,) in files:
328             html += "<a href=" + self.path_without_dir + str(file[1:]) + "/index.html>" + str(file[1:]) + "</a><br>\n"
329
330         html += "<br><br>"
331         return html
332
333
334     def create_directories(self, entire=0):
335
336         if entire == 0:
337             dirs = self.db.query("module_id, module", "directories", "father_dir=" + self.dir_id)
338         else:
339             dirs = self.db.query("module_id, module", "directories")
340
341
342         html = "<h3 class=\"title\"> Directories </h3>\n"
343         for (module_id, module) in dirs:
344             html += "<a href=" + self.path_without_dir + str(module[1:]) + "/index.html>" + str(module[1:]) + "</a><br>\n"
345
346         html += "<br><br>"
347         return html
348
349     def generate_stats(self):
350         # create new object stats
351         st = stmodule.Stats(self.path, self.db, self.dir_id, self.date, self.flags)
352         st.setDates(self.table)
353
354         # Stats from database
355         #st.revisionStats(self.table)
356         st.linesInTimeStats(self.table)
357         st.commiterStats(self.table)
358         st.fileStats(self.table)
359
360     def get_orphaning(self):
361
362         sum = 0
363         totalCommiters = self.db.query("commiter_id", "commiters")
364         for (commiter,) in totalCommiters:
365             totalLines = self.db.query("count(*)", self.table, "commiter_id=" + commiter)
366             # Last modification date
367             lastdate = self.db.query("max(date)", self.table, "commiter_id=" + commiter)
368             try:
369                 lastyear = str(lastdate[0][0]).split("-")[0]
370                 lastmonth = str(lastdate[0][0]).split("-")[1]
371                 # Actual timestamp
372                 actualmonth = self.date.split("-")[1]
373                 actualyear = self.date.split("-")[0]
374                 # number of months between dates
375                 total = (int(actualyear) - int(lastyear) - 1) * 12 + (12 - int(lastmonth)) + int(actualmonth)
376                 total = int(total) * int(totalLines[0][0])
377                 sum += int(total)
378             except:
379                 total = 0
380
381         return sum
382
383     def generate_metrics(self):
384         """
385         get metrics from file
386         """
387
388         try:
389             input = open(self.path + '/' + self.source['lines'], 'r')
390         except:
391             sys.exit("Error: File " + str(self.source['lines']) + " not found")
392
393         #actualyear = strftime('%d-%m-%Y').split("-")[2]
394         #actualmonth = strftime('%d-%m-%Y').split("-")[1]
395
396         actualyear = self.date.split("-")[0]
397         actualmonth = self.date.split("-")[1]
398
399         # Extract metrics from metadata stored in .dat files
400         sum = 0
401         months = 0
402         stored = 0
403         while 1:
404             line = input.readline()
405             if not line:
406                 break
407             if line[0] == '#':
408                 pass
409             else:
410                 percent = float(line.split('\t')[5])
411                 if (percent >= 50) and not stored:
412                     year = int(line.split('\t')[1])
413                     ms = int(line.split('\t')[2])
414                     #print "* 50% of code on: " + str(year) + "/" + str(ms)
415                     total = (int(actualyear) - year - 1) * 12 + (12 - ms) + int(actualmonth)
416                     stored = 1
417
418                 lastMonth = int(line.split('\t')[3])
419                 sum += lastMonth
420                 if lastMonth > 0:
421                     months+=1
422
423         # indexes
424         self.metrics['size'] = lastMonth
425         self.metrics['age'] = months
426         self.metrics['aging'] = sum - lastMonth
427         self.metrics['relaging'] = (sum-lastMonth)*1.0/lastMonth
428         self.metrics['rel5a'] = (sum-lastMonth)*1.0/(lastMonth * 60)
429         self.metrics['abs5a'] = (sum-lastMonth)*1.0/(100000 * 60)
430         self.metrics['progeria'] = ((sum-lastMonth)*1.0/lastMonth) / total
431         self.metrics['date_50'] = str(year) + "/" + str(ms)
432         self.metrics['orphy'] = self.get_orphaning()
433
434
435 class WebFileStat(WebStat):
436     def __init__(self, date, target, file, file_id):
437         Web.__init__(self)
438         self.date = date.replace("_","-")
439         self.filedate = date
440         self.db = target
441         self.file = file
442         self.file_id = file_id
443
444         # Path to disk data
445         self.filename = str(self.filedate)
446         self.path = gb.Globals.workspace + "/stats_" + self.filename + "/" + self.file + "/"
447         gb.Globals.createdir(self.path)
448
449         # Path to the html's templates
450         self.templates = gb.Globals.templates
451
452         # Path to database data
453         self.table = 'annotates_' + date
454
455     def generate_svg(self):
456
457         files = self.db.query("file_id, file", "files", "file_id=" + self.file_id)
458
459         for (file_id, file) in files:
460             revisions = self.db.query("revision", self.table, "file_id="+str(file_id))
461             revs = []
462             for rev in revisions:
463                 revs.append(int(str(rev[0]).split('.')[1]))
464
465             line_graph =svgmodule.LinesGraph()
466             line_graph.print_to_file("/tmp/fichero_id_"+str(file_id)+".svg", revs)
467             #bar_graph = svgmodule.BarGraph()
468             #bar_graph.print_to_file("fichero_id_2.svg", revs)
469
470     def generate_functions(self):
471         pass
472
473     def generate_body(self):
474         html = ''
475         html += "<body>\n"
476         html += "<div class=\"header\" style=\"height:76px;\"><b>Archaeology Stats: " + self.filename.replace('_','/') + "</b></div>\n"
477         html += "<div id=\"logo\">\n"
478         html += "<a style=\"background-image: url(images/carnarvon-logo.png);\"\n"
479         html += "href=\"http://carnarvon.tigris.org\"\n"
480         html += "title=\"Carnarvon Project Homepage\"></a>\n"
481         html += "</div>\n"
482         html += "<div style=\"float:left;\">\n"
483         html += "<h3 class=\"title\"> metrics </h3>\n"
484
485         html += "</div>\n"
486
487         menu = self.generate_menu()
488         html += menu + "\n</body></html>"
489
490
491         return html
492