2 # A new home for the reporting code.
4 # This code is part of the LWN git data miner.
6 # Copyright 2007-13 Eklektix, Inc.
7 # Copyright 2007-13 Jonathan Corbet <corbet@lwn.net>
9 # This file may be distributed under the terms of the GNU General
10 # Public License, version 2.
24 def SetHTMLOutput(file):
39 # HTML output support stuff.
42 HClasses
= ['Even', 'Odd']
46 <tr><th colspan=3>%s</th></tr>
49 def BeginReport(title
):
52 Outfile
.write('\n%s\n' % title
)
54 HTMLfile
.write(THead
% title
)
57 TRow
= ''' <tr class="%s">
58 <td>%s</td><td align="right">%d</td><td align="right">%.1f%%</td></tr>
61 TRowStr
= ''' <tr class="%s">
62 <td>%s</td><td align="right">%d</td><td>%s</td></tr>
65 def ReportLine(text
, count
, pct
):
69 Outfile
.write ('%-25s %4d (%.1f%%)\n' % (text
, count
, pct
))
71 HTMLfile
.write(TRow
% (HClasses
[HTMLclass
], text
, count
, pct
))
74 def ReportLineStr(text
, count
, extra
):
78 Outfile
.write ('%-25s %4d %s\n' % (text
, count
, extra
))
80 HTMLfile
.write(TRowStr
% (HClasses
[HTMLclass
], text
, count
, extra
))
85 HTMLfile
.write('</table>\n\n')
88 # Comparison and report generation functions.
90 def ComparePCount(h1
, h2
):
91 return len(h2
.patches
) - len(h1
.patches
)
93 def ReportByPCount(hlist
, cscount
):
94 hlist
.sort(ComparePCount
)
96 BeginReport('Developers with the most changesets')
98 pcount
= len(h
.patches
)
99 changed
= max(h
.added
, h
.removed
)
100 delta
= h
.added
- h
.removed
102 ReportLine(h
.name
, pcount
, (pcount
*100.0)/cscount
)
104 if count
>= ListCount
:
108 def CompareLChanged(h1
, h2
):
109 return max(h2
.added
, h2
.removed
) - max(h1
.added
, h1
.removed
)
111 def ReportByLChanged(hlist
, totalchanged
):
112 hlist
.sort(CompareLChanged
)
114 BeginReport('Developers with the most changed lines')
116 pcount
= len(h
.patches
)
117 changed
= max(h
.added
, h
.removed
)
118 delta
= h
.added
- h
.removed
119 if (h
.added
+ h
.removed
) > 0:
120 ReportLine(h
.name
, changed
, (changed
*100.0)/totalchanged
)
122 if count
>= ListCount
:
126 def CompareLRemoved(h1
, h2
):
127 return (h2
.removed
- h2
.added
) - (h1
.removed
- h1
.added
)
129 def ReportByLRemoved(hlist
, totalremoved
):
130 hlist
.sort(CompareLRemoved
)
132 BeginReport('Developers with the most lines removed')
134 pcount
= len(h
.patches
)
135 changed
= max(h
.added
, h
.removed
)
136 delta
= h
.added
- h
.removed
138 ReportLine(h
.name
, -delta
, (-delta
*100.0)/totalremoved
)
140 if count
>= ListCount
:
144 def CompareEPCount(e1
, e2
):
145 return e2
.count
- e1
.count
147 def ReportByPCEmpl(elist
, cscount
):
148 elist
.sort(CompareEPCount
)
150 BeginReport('Top changeset contributors by employer')
153 ReportLine(e
.name
, e
.count
, (e
.count
*100.0)/cscount
)
155 if count
>= ListCount
:
160 def CompareELChanged(e1
, e2
):
161 return e2
.changed
- e1
.changed
163 def ReportByELChanged(elist
, totalchanged
):
164 elist
.sort(CompareELChanged
)
166 BeginReport('Top lines changed by employer')
169 ReportLine(e
.name
, e
.changed
, (e
.changed
*100.0)/totalchanged
)
171 if count
>= ListCount
:
177 def CompareSOBs(h1
, h2
):
178 return len(h2
.signoffs
) - len(h1
.signoffs
)
180 def ReportBySOBs(hlist
):
181 hlist
.sort(CompareSOBs
)
184 totalsobs
+= len(h
.signoffs
)
186 BeginReport('Developers with the most signoffs (total %d)' % totalsobs
)
188 scount
= len(h
.signoffs
)
190 ReportLine(h
.name
, scount
, (scount
*100.0)/totalsobs
)
192 if count
>= ListCount
:
197 # Reviewer reporting.
199 def CompareRevs(h1
, h2
):
200 return len(h2
.reviews
) - len(h1
.reviews
)
202 def ReportByRevs(hlist
):
203 hlist
.sort(CompareRevs
)
206 totalrevs
+= len(h
.reviews
)
208 BeginReport('Developers with the most reviews (total %d)' % totalrevs
)
210 scount
= len(h
.reviews
)
212 ReportLine(h
.name
, scount
, (scount
*100.0)/totalrevs
)
214 if count
>= ListCount
:
221 def CompareTests(h1
, h2
):
222 return len(h2
.tested
) - len(h1
.tested
)
224 def ReportByTests(hlist
):
225 hlist
.sort(CompareTests
)
228 totaltests
+= len(h
.tested
)
230 BeginReport('Developers with the most test credits (total %d)' % totaltests
)
232 scount
= len(h
.tested
)
234 ReportLine(h
.name
, scount
, (scount
*100.0)/totaltests
)
236 if count
>= ListCount
:
240 def CompareTestCred(h1
, h2
):
241 return h2
.testcred
- h1
.testcred
243 def ReportByTestCreds(hlist
):
244 hlist
.sort(CompareTestCred
)
247 totaltests
+= h
.testcred
249 BeginReport('Developers who gave the most tested-by credits (total %d)' % totaltests
)
252 ReportLine(h
.name
, h
.testcred
, (h
.testcred
*100.0)/totaltests
)
254 if count
>= ListCount
:
261 # Reporter reporting.
263 def CompareReports(h1
, h2
):
264 return len(h2
.reports
) - len(h1
.reports
)
266 def ReportByReports(hlist
):
267 hlist
.sort(CompareReports
)
270 totalreps
+= len(h
.reports
)
272 BeginReport('Developers with the most report credits (total %d)' % totalreps
)
274 scount
= len(h
.reports
)
276 ReportLine(h
.name
, scount
, (scount
*100.0)/totalreps
)
278 if count
>= ListCount
:
282 def CompareRepCred(h1
, h2
):
283 return h2
.repcred
- h1
.repcred
285 def ReportByRepCreds(hlist
):
286 hlist
.sort(CompareRepCred
)
289 totalreps
+= h
.repcred
291 BeginReport('Developers who gave the most report credits (total %d)' % totalreps
)
294 ReportLine(h
.name
, h
.repcred
, (h
.repcred
*100.0)/totalreps
)
296 if count
>= ListCount
:
303 def CompareVersionCounts(h1
, h2
):
304 if h1
.versions
and h2
.versions
:
305 return len(h2
.versions
) - len(h1
.versions
)
312 def MissedVersions(hv
, allv
):
313 missed
= [v
for v
in allv
if v
not in hv
]
315 return ' '.join(missed
)
317 def ReportVersions(hlist
):
318 hlist
.sort(CompareVersionCounts
)
319 BeginReport('Developers represented in the most kernel versions')
321 allversions
= hlist
[0].versions
323 ReportLineStr(h
.name
, len(h
.versions
), MissedVersions(h
.versions
, allversions
))
325 if count
>= ListCount
:
330 def CompareESOBs(e1
, e2
):
331 return e2
.sobs
- e1
.sobs
333 def ReportByESOBs(elist
):
334 elist
.sort(CompareESOBs
)
339 BeginReport('Employers with the most signoffs (total %d)' % totalsobs
)
342 ReportLine(e
.name
, e
.sobs
, (e
.sobs
*100.0)/totalsobs
)
344 if count
>= ListCount
:
348 def CompareHackers(e1
, e2
):
349 return len(e2
.hackers
) - len(e1
.hackers
)
351 def ReportByEHackers(elist
):
352 elist
.sort(CompareHackers
)
355 totalhackers
+= len(e
.hackers
)
357 BeginReport('Employers with the most hackers (total %d)' % totalhackers
)
359 nhackers
= len(e
.hackers
)
361 ReportLine(e
.name
, nhackers
, (nhackers
*100.0)/totalhackers
)
363 if count
>= ListCount
:
368 def DevReports(hlist
, totalchanged
, cscount
, totalremoved
):
369 ReportByPCount(hlist
, cscount
)
370 ReportByLChanged(hlist
, totalchanged
)
371 ReportByLRemoved(hlist
, totalremoved
)
375 ReportByTestCreds(hlist
)
376 ReportByReports(hlist
)
377 ReportByRepCreds(hlist
)
379 def EmplReports(elist
, totalchanged
, cscount
):
380 ReportByPCEmpl(elist
, cscount
)
381 ReportByELChanged(elist
, totalchanged
)
383 ReportByEHackers(elist
)
386 # Who are the unknown hackers?
389 empl
= h
.employer
[0][0][1].name
390 return h
.email
[0] == empl
or empl
== '(Unknown)'
392 def ReportUnknowns(hlist
, cscount
):
394 # Trim the list to just the unknowns; try to work properly whether
395 # mapping to (Unknown) is happening or not.
397 ulist
= [ h
for h
in hlist
if IsUnknown(h
) ]
398 ulist
.sort(ComparePCount
)
400 BeginReport('Developers with unknown affiliation')
402 pcount
= len(h
.patches
)
404 ReportLine(h
.name
, pcount
, (pcount
*100.0)/cscount
)
406 if count
>= ListCount
:
412 def ReportByFileType(hacker_list
):
416 BeginReport('Developer contributions by type')
417 for h
in hacker_list
:
419 for patch
in h
.patches
:
420 # Get a summary by hacker
421 for (filetype
, (added
, removed
)) in patch
.filetypes
.iteritems():
422 if by_hacker
.has_key(filetype
):
423 by_hacker
[filetype
][patch
.ADDED
] += added
424 by_hacker
[filetype
][patch
.REMOVED
] += removed
426 by_hacker
[filetype
] = [added
, removed
]
429 if total
.has_key(filetype
):
430 total
[filetype
][patch
.ADDED
] += added
431 total
[filetype
][patch
.REMOVED
] += removed
433 total
[filetype
] = [added
, removed
, []]
435 # Print a summary by hacker
437 for filetype
, counters
in by_hacker
.iteritems():
438 print '\t', filetype
, counters
439 h_added
= by_hacker
[filetype
][patch
.ADDED
]
440 h_removed
= by_hacker
[filetype
][patch
.REMOVED
]
441 total
[filetype
][2].append([h
.name
, h_added
, h_removed
])
443 # Print the global summary
444 BeginReport('Contributions by type and developers')
445 for filetype
, (added
, removed
, hackers
) in total
.iteritems():
446 print filetype
, added
, removed
447 for h
, h_added
, h_removed
in hackers
:
448 print '\t%s: [%d, %d]' % (h
, h_added
, h_removed
)
450 # Print the very global summary
451 BeginReport('General contributions by type')
452 for filetype
, (added
, removed
, hackers
) in total
.iteritems():
453 print filetype
, added
, removed
456 # The file access report is a special beast.
458 def FileAccessReport(name
, accesses
, total
):
459 outf
= open(name
, 'w')
460 files
= accesses
.keys()
464 outf
.write('%6d %6.1f%% %s\n' % (a
, (100.0*a
)/total
, file))