Changelog 2006-08-31
[mining-tools:carnarvon.git] / pycarnarvon / Stats.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 Make graphs
21
22 @author:       Alvaro Navarro
23 @organization: Grupo de Sistemas y Comunicaciones, Universidad Rey Juan Carlos
24 @copyright:    Universidad Rey Juan Carlos (Madrid, Spain)
25 @license:      GNU GPL version 2 or any later version
26 @contact:      anavarro@gsyc.escet.urjc.es
27 """
28
29 import os
30 import time
31 import sys
32 import string
33 from time import gmtime, strftime
34 from Database import *
35
36 class Stats:
37
38     def __init__(self, stats_dir, db_object, dir_id, date, flags = 15):
39         self.config_start_year = 0
40         self.config_end_month = 0
41         self.config_end_year = 0
42         self.config_actual_year = date.split("-")[0]
43         self.config_actual_date = date
44         self.stats_dir = stats_dir
45         self.db = db_object
46         self.dir_id = dir_id
47         self.tic_space = int(flags)
48
49         if dir_id:
50             self.where = "dir_id=" + str(self.dir_id)
51         else:
52             self.where = ""
53         
54     def setDates(self, table):
55         start_year,end_year = self.db.query('min(date),max(date)', table, self.where)[0]
56         #try:
57         self.config_start_year = int(str(start_year).split("-")[0])
58         self.config_end_year = int(str(end_year).split("-")[0])
59         self.config_end_month = int(str(end_year).split("-")[1])
60
61         #print "start date :" + str(start_year) + " # " + str(self.config_start_year)
62         #print "end date   :" + str(end_year) + " # " + str(self.config_end_year) + "/" + str(self.config_end_month)
63         #print "actual date:" + str(self.config_actual_date) + " # " + self.config_actual_year
64         #self.config_actual_year = strftime('%d-%m-%Y').split("-")[2]
65         #self.config_actual_date = strftime('%Y-%m-%d')
66         #except ValueError:
67         #    sys.exit("\n Error while getting dates. directory with id = " + str(dir_id) " on table " + str(table) + " is empty")
68
69     def revisionStats(self,table):
70
71         # Number of lines
72         totalLines = int(self.db.query('count(*)', table, self.where)[0][0])
73         
74         # Revision numbers (lines)
75         output = open(self.stats_dir + '/revLines.dat', 'w')
76
77         output.write('#Rev\tLines\tAgg\tPer\tPerAgg\n')
78         linesTuple = self.db.query('revision, count(revision)', table, self.where, 'revision', 'revision')
79         linesList = []
80         
81         for tuple in linesTuple:
82             linesList.append([str(tuple[0]), int(tuple[1])])
83             
84         linesList.sort()
85         sum = 0
86         sumPer = 0
87         
88         for line in linesList:
89             sum += int(line[1])
90             percentage = int(line[1]) * 100.0/totalLines
91             sumPer += percentage
92
93             result  = str(line[0]) + '\t'
94             result += str(line[1]) + '\t'
95             result += str(sum) + '\t'
96             result += str(round(percentage,2)) + '\t'
97             result += str(round(sumPer,2)) + '\n'
98
99             output.write(result)
100
101         output.close()
102             
103         self.plot('revLines', 'revLines', 'Revision Stats', 'revision', 'lines', 1, 2, 'false','true')
104         self.plot('revLines', 'revLines', 'Revision Stats', 'revision', 'Agg', 1, 3, 'false','true')
105         self.plot('revLines', 'revLines', 'Revision Stats', 'revision', 'Per', 1, 4, 'false','true')
106         self.plot('revLines', 'revLines', 'Revision Stats', 'revision', 'PerAgg', 1, 5, 'false','false')
107
108
109     def linesInTimeStats(self,table):
110
111         # Number of lines in time (on a per-month basis)
112         last = 0
113         total = int(self.db.query('COUNT(*)', table, self.where)[0][0])
114
115         output = open(self.stats_dir + '/lines.dat', 'w')
116         output.write('#Id\tYear\tMonth\tAgg\tDiff\tPerAgg\tPerDiff\n')
117
118         # Try to plot the special graph (not uses plot function)
119         gnuplot_command_diff = 'set key top left;\n set style data linespoints;\n set terminal png;\n '
120         gnuplot_command_peragg = gnuplot_command_diff
121
122         gnuplot_command_diff += 'set output "' + self.stats_dir + '/line_diff.png";\n'
123         gnuplot_command_peragg += 'set output "' + self.stats_dir + '/line_peragg.png";\n'
124
125         gnuplot_command_diff += ' set xtics ('
126         gnuplot_command_peragg += ' set xtics ('
127
128         # Tics between dates
129         count = 1
130
131         for year in range(self.config_start_year, self.config_end_year+1):
132             for month in range(1,13):
133                 if year == self.config_end_year and month > self.config_end_month:
134                    break
135                 # Aggregated
136                 if self.dir_id:
137                     where = "date < '" + str(year) + "-" + str(month) + "-01 00:00:00' AND " + self.where
138                 else:
139                      where = "date < '" + str(year) + "-" + str(month) + "-01 00:00:00'"
140
141                 aggregated = int(self.db.query('COUNT(*)',table,where)[0][0])
142                 diff = aggregated - last
143                 last = aggregated
144                 relative = round(aggregated*100.0/total,2)
145                 relativeDiff = round(diff*100.0/total,2)
146
147                 result  = str(count) + '\t'
148                 result += str(year) + '\t'
149                 result += str(month) + '\t'
150                 result += str(aggregated) + '\t'
151                 result += str(diff) + '\t'
152                 result += str(relative) + '\t'
153                 result += str(relativeDiff) + '\n'
154
155                 tic = str(month) + '/' + str(year)
156
157                 if count % int(self.tic_space) == 0: # Put tic each tic_space (f.e, 15) values
158                     gnuplot_command_diff += '\"'+tic+'\" '+str(count)+','
159                     gnuplot_command_peragg += '\"'+tic+'\" '+str(count)+','
160
161                 count += 1
162
163                 output.write(result)
164
165         output.close()
166
167         gnuplot_command_diff = gnuplot_command_diff.rstrip(",") + ");\n "
168         gnuplot_command_peragg = gnuplot_command_peragg.rstrip(",") + ");\n "
169
170         gnuplot_command_diff += 'set xlabel "";\n'
171         gnuplot_command_peragg += 'set xlabel "";\n'
172
173         gnuplot_command_diff += 'set ylabel "remaining lines ";\n'
174         gnuplot_command_peragg += 'set ylabel "remaining lines (%)";\n'
175
176         gnuplot_command_diff += 'set title "SLOC remaining";\n'
177         gnuplot_command_peragg += 'set title "SLOC remaining (aggregated over time)";\n'
178
179         # END COMMENT IF DON'T DESIRE DATES IN X LABEL
180
181         begin_graph = '*'
182         end_graph = '*'
183         gnuplot_command_diff += ' plot ['+begin_graph+':'+end_graph+'] '
184         gnuplot_command_peragg += ' plot ['+begin_graph+':'+end_graph+'] '
185
186         project_name = 'lines'
187         filename = 'lines.dat'
188         gnuplot_command_diff += " '"+filename+"' using 1:5 title 'lines' ,"
189         gnuplot_command_peragg += " '"+filename+"' using 1:6 title 'lines' ,"
190
191         gnuplot_command_diff = gnuplot_command_diff.rstrip(",")
192         gnuplot_command_peragg = gnuplot_command_peragg.rstrip(",")
193
194         command_file = open(self.stats_dir + '/lines_diff.gnuplot',"w")
195         command_file.write(gnuplot_command_diff)
196         command_file.close()
197
198         command_file = open(self.stats_dir + '/lines_peragg.gnuplot',"w")
199         command_file.write(gnuplot_command_peragg)
200         command_file.close()
201
202     def commiterStats(self,table):
203         orders = ['Total_Lines', 'Modified_Files']
204         column = 2
205         for order in orders:
206             self.commiterStatsOrder(table, order, column)
207             column += 1
208
209     def commiterStatsOrder(self,table, order, column):
210
211         filename = 'commiters_' + order
212         output = open(self.stats_dir + '/' + filename + '.dat', 'w')
213         output.write('#Author\tLines\tFiles\tTSpan\tLastTime\n')
214
215         select = ' COUNT(*) as Total_Lines, COUNT(DISTINCT(file_id)) as Modified_Files, MIN(date), MAX(date)'
216         result = self.db.query(select, table, self.where, order, "commiter_id")
217
218         commiter_id = 1
219         for (lines,files,firstDate, lastDate) in result:
220             result  = str(commiter_id) + '\t'
221             result += str(lines) + '\t'
222             result += str(files) + '\t'
223             result += str(self.daysPassed(firstDate, lastDate)) + '\t'
224             result += str(self.daysPassed(lastDate, self.config_actual_date + ' 00:00:00')) + '\n'
225             commiter_id += 1
226             output.write(result)
227
228         output.close()
229
230         self.plot(filename, filename, 'Commiters', 'authors', order, 1, column)
231
232     def fileStats(self,table):
233         orders = ['All_Lines','Number_Authors','Number_Revisions','Last_Modified']
234         column = 2
235         for order in orders:
236             self.fileStatsOrder(table,order,column)
237             column += 1
238
239     def fileStatsOrder(self,table, order, column):
240         """    
241         # Number of files with all their lines with revision 1.1    
242         # Number of files with none of their lines with revision 1.1    
243         # For each file:    
244         #     * Number of distinct revisions    
245         #     * Number of distinct authors    
246         #     * Timespan from newest revision to oldest one    
247         """
248
249         # filename which contains the values
250         filename = 'files_' + order
251         output = open(self.stats_dir + '/' + filename + '.dat', 'w')
252         output.write("#FileId\tLines\tAuthors\tRevs\tLastModified\n")
253
254         # Total files
255         select  = "COUNT(*) as All_Lines, COUNT(DISTINCT(commiter_id)) as Number_Authors,"
256         select += " COUNT(DISTINCT(revision)) as Number_Revisions, MIN(date) as oldRevision, MAX(date) as Last_Modified"
257         totalfiles = self.db.query(select, table, self.where, order,"file_id")
258
259         file_id = 1
260         for (allLines, numberAuthors, numberRevisions, oldRevision, newRevision) in totalfiles:
261             try:
262                 final = str(file_id) + '\t'
263                 final += str(allLines) + '\t'
264                 final += str(numberAuthors) + '\t'
265                 final += str(numberRevisions) + '\t'
266                 final += str(self.daysPassed(oldRevision, newRevision)) + '\n'
267
268                 file_id += 1
269                 output.write(final)
270
271             except ZeroDivisionError:
272                 # File empty (or contains only comments or blank lines)
273                 pass
274
275         self.plot(filename, filename, 'Files', 'files', order, 1, column)
276
277     def daysPassed(self,old, new):
278         oldSeconds = time.mktime(time.strptime(str(old), '%Y-%m-%d %H:%M:%S'))
279         newSeconds = time.mktime(time.strptime(str(new), '%Y-%m-%d %H:%M:%S'))
280         return (round((newSeconds - oldSeconds) / (24 * 3600)))
281
282
283     def plot(self, outputFile,
284                     values,
285                     title,
286                     xlabel,
287                     ylabel,
288                     xvalue,
289                     yvalue,
290                     xlogscale = 'false',
291                     ylogscale = 'false',
292                     dataStyle = 'linespoints'):
293
294         output = open(self.stats_dir + '/' + outputFile + "_" + str(xlabel) + "_" + str(ylabel) + ".gnuplot", 'w')
295
296         # Title of the Graph
297         if (ylogscale == '1' or ylogscale == 'true') and (xlogscale == '1' or ylogscale == 'true'):
298             output.write('set title "' + title + ' (log log)"' + "\n")
299         else:
300             output.write('set title "' + title + '"\n')
301
302         # X axis Label
303         if xlogscale == '1' or xlogscale == 'true':
304             output.write('set xlabel "'+ xlabel +' (log)"' + "\n")
305         else:
306             output.write('set xlabel "'+ xlabel +'"' + "\n")
307
308         # Y axis Label
309         if ylogscale == '1' or ylogscale == 'true':
310             output.write('set ylabel "'+ ylabel +' (log)"' + "\n")
311         else:
312             output.write('set ylabel "'+ ylabel +'"' + "\n")
313
314         output.write('set autoscale' + "\n")
315         output.write('set yrange [0:]' + "\n")
316         output.write('set grid' + "\n")
317         output.write('set data style ' + dataStyle + "\n")
318         output.write('set pointsize 1.2' + "\n")
319
320         # Logscale Y
321         if ylogscale == '1' or ylogscale == 'true':
322             output.write('set logscale y' + "\n")
323
324         # Logscale X
325         if xlogscale == '1' or xlogscale == 'true':
326             output.write('set logscale x' + "\n")
327
328         output.write('set terminal png' + "\n")
329
330         mstring = 'set output "' + self.stats_dir + '/' + outputFile + '.png"' + "\n"
331         mstring += 'plot "' + self.stats_dir + '/' + values + '.dat"'
332
333         if ylogscale == '1' or ylogscale == 'true':
334             mstring += ' using ' + str(xvalue) + ':' + str(yvalue) + ' title \'' + title + ' (log) \'' + '\n'
335         else:
336             mstring += ' using ' + str(xvalue) + ':' + str(yvalue) + ' title \'' + title + '\'' + '\n'
337
338         output.write(mstring)
339         output.close()
340