2 # A new home for the reporting code.
4 # This code is part of the LWN git data miner.
6 # Copyright 2007-16 Eklektix, Inc.
7 # Copyright 2007-16 Jonathan Corbet <corbet@lwn.net>
9 # This file may be distributed under the terms of the GNU General
10 # Public License, version 2.
25 def SetHTMLOutput(file):
29 def SetrSTOutput(file):
44 <table cellspacing=3 class="OddEven">
45 <tr><th colspan=3>%s</th></tr>
52 ==================================== =====
54 ==================================== =====
57 def BeginReport(title
):
58 Outfile
.write('\n%s\n' % title
)
60 HTMLfile
.write(THead
% title
)
62 rSTfile
.write(RHead
% title
)
64 TRow
= ' <tr><td>%s</td><td align="right">%d</td><td align="right">%.1f%%</td></tr>\n'
65 TRowStr
= ' <tr><td>%s</td><td align="right">%d</td><td>%s</td></tr>\n'
67 def ReportLine(text
, count
, pct
):
70 Outfile
.write ('%-25s %4d (%.1f%%)\n' % (text
, count
, pct
))
72 HTMLfile
.write(TRow
% (text
, count
, pct
))
74 rSTfile
.write(" %-36s %d (%.1f%%)\n" % (text
.strip(), count
, pct
))
76 def ReportLineStr(text
, count
, extra
):
79 Outfile
.write ('%-25s %4d %s\n' % (text
, count
, extra
))
81 HTMLfile
.write(TRowStr
% (text
, count
, extra
))
83 rSTfile
.write('%-36s %d %s\n' % (text
, count
, extra
))
87 HTMLfile
.write('</table>\n\n')
89 rSTfile
.write(' ==================================== =====\n\n')
92 # Comparison and report generation functions.
97 def ReportByPCount(hlist
, cscount
):
98 hlist
.sort(key
= ComparePCount
, reverse
= True)
100 BeginReport('Developers with the most changesets')
102 pcount
= len(h
.patches
)
103 changed
= max(h
.added
, h
.removed
)
104 delta
= h
.added
- h
.removed
106 ReportLine(h
.name
, pcount
, (pcount
*100.0)/cscount
)
108 if count
>= ListCount
:
112 def CompareLChanged(h
):
115 def ReportByLChanged(hlist
, totalchanged
):
116 hlist
.sort(key
= CompareLChanged
, reverse
= True)
118 BeginReport('Developers with the most changed lines')
120 pcount
= len(h
.patches
)
122 ReportLine(h
.name
, h
.changed
, (h
.changed
*100.0)/totalchanged
)
124 if count
>= ListCount
:
128 def CompareLRemoved(h
):
129 return (h
.removed
- h
.added
)
131 def ReportByLRemoved(hlist
, totalremoved
):
132 hlist
.sort(key
= CompareLRemoved
, reverse
= True)
134 BeginReport('Developers with the most lines removed')
136 pcount
= len(h
.patches
)
137 changed
= max(h
.added
, h
.removed
)
138 delta
= h
.added
- h
.removed
140 ReportLine(h
.name
, -delta
, (-delta
*100.0)/totalremoved
)
142 if count
>= ListCount
:
146 def CompareEPCount(e
):
149 def ReportByPCEmpl(elist
, cscount
):
150 elist
.sort(key
= CompareEPCount
, reverse
= True)
152 BeginReport('Top changeset contributors by employer')
155 ReportLine(e
.name
, e
.count
, (e
.count
*100.0)/cscount
)
157 if count
>= ListCount
:
162 def CompareELChanged(e
):
165 def ReportByELChanged(elist
, totalchanged
):
166 elist
.sort(key
= CompareELChanged
, reverse
= True)
168 BeginReport('Top lines changed by employer')
171 ReportLine(e
.name
, e
.changed
, (e
.changed
*100.0)/totalchanged
)
173 if count
>= ListCount
:
180 return len(h
.signoffs
)
182 def ReportBySOBs(hlist
):
183 hlist
.sort(key
= CompareSOBs
, reverse
= True)
186 totalsobs
+= len(h
.signoffs
)
188 BeginReport('Developers with the most signoffs (total %d)' % totalsobs
)
190 scount
= len(h
.signoffs
)
192 ReportLine(h
.name
, scount
, (scount
*100.0)/totalsobs
)
194 if count
>= ListCount
:
199 # Reviewer reporting.
202 return len(h
.reviews
)
204 def ReportByRevs(hlist
):
205 hlist
.sort(key
= CompareRevs
, reverse
= True)
208 totalrevs
+= len(h
.reviews
)
210 BeginReport('Developers with the most reviews (total %d)' % totalrevs
)
212 scount
= len(h
.reviews
)
214 ReportLine(h
.name
, scount
, (scount
*100.0)/totalrevs
)
216 if count
>= ListCount
:
226 def ReportByTests(hlist
):
227 hlist
.sort(key
= CompareTests
, reverse
= True)
230 totaltests
+= len(h
.tested
)
232 BeginReport('Developers with the most test credits (total %d)' % totaltests
)
234 scount
= len(h
.tested
)
236 ReportLine(h
.name
, scount
, (scount
*100.0)/totaltests
)
238 if count
>= ListCount
:
242 def CompareTestCred(h
):
245 def ReportByTestCreds(hlist
):
246 hlist
.sort(key
= CompareTestCred
, reverse
= True)
249 totaltests
+= h
.testcred
251 BeginReport('Developers who gave the most tested-by credits (total %d)' % totaltests
)
254 ReportLine(h
.name
, h
.testcred
, (h
.testcred
*100.0)/totaltests
)
256 if count
>= ListCount
:
263 # Reporter reporting.
265 def CompareReports(h
):
266 return len(h
.reports
)
268 def ReportByReports(hlist
):
269 hlist
.sort(key
= CompareReports
, reverse
= True)
272 totalreps
+= len(h
.reports
)
274 BeginReport('Developers with the most report credits (total %d)' % totalreps
)
276 scount
= len(h
.reports
)
278 ReportLine(h
.name
, scount
, (scount
*100.0)/totalreps
)
280 if count
>= ListCount
:
284 def CompareRepCred(h
):
287 def ReportByRepCreds(hlist
):
288 hlist
.sort(key
= CompareRepCred
, reverse
= True)
291 totalreps
+= h
.repcred
293 BeginReport('Developers who gave the most report credits (total %d)' % totalreps
)
296 ReportLine(h
.name
, h
.repcred
, (h
.repcred
*100.0)/totalreps
)
298 if count
>= ListCount
:
305 def CompareVersionCounts(h
):
307 return len(h
.versions
)
310 def MissedVersions(hv
, allv
):
311 missed
= [v
for v
in allv
if v
not in hv
]
313 return ' '.join(missed
)
315 def ReportVersions(hlist
):
316 hlist
.sort(key
= CompareVersionCounts
, reverse
= True)
317 BeginReport('Developers represented in the most kernel versions')
319 allversions
= hlist
[0].versions
321 ReportLineStr(h
.name
, len(h
.versions
), MissedVersions(h
.versions
, allversions
))
323 if count
>= ListCount
:
331 def ReportByESOBs(elist
):
332 elist
.sort(key
= CompareESOBs
, reverse
= True)
337 BeginReport('Employers with the most signoffs (total %d)' % totalsobs
)
340 ReportLine(e
.name
, e
.sobs
, (e
.sobs
*100.0)/totalsobs
)
342 if count
>= ListCount
:
346 def CompareHackers(e
):
347 return len(e
.hackers
)
349 def ReportByEHackers(elist
):
350 elist
.sort(key
= CompareHackers
, reverse
= True)
353 totalhackers
+= len(e
.hackers
)
355 BeginReport('Employers with the most hackers (total %d)' % totalhackers
)
357 nhackers
= len(e
.hackers
)
359 ReportLine(e
.name
, nhackers
, (nhackers
*100.0)/totalhackers
)
361 if count
>= ListCount
:
366 def DevReports(hlist
, totalchanged
, cscount
, totalremoved
):
367 ReportByPCount(hlist
, cscount
)
368 ReportByLChanged(hlist
, totalchanged
)
369 ReportByLRemoved(hlist
, totalremoved
)
373 ReportByTestCreds(hlist
)
374 ReportByReports(hlist
)
375 ReportByRepCreds(hlist
)
377 def EmplReports(elist
, totalchanged
, cscount
):
378 ReportByPCEmpl(elist
, cscount
)
379 ReportByELChanged(elist
, totalchanged
)
381 ReportByEHackers(elist
)
384 # Who are the unknown hackers?
387 empl
= h
.employer
[0][0][1].name
388 return h
.email
[0] == empl
or empl
== '(Unknown)'
390 def ReportUnknowns(hlist
, cscount
):
392 # Trim the list to just the unknowns; try to work properly whether
393 # mapping to (Unknown) is happening or not.
395 ulist
= [ h
for h
in hlist
if IsUnknown(h
) ]
396 ulist
.sort(key
= ComparePCount
, reverse
= True)
398 BeginReport('Developers with unknown affiliation')
400 pcount
= len(h
.patches
)
402 ReportLine(h
.name
, pcount
, (pcount
*100.0)/cscount
)
404 if count
>= ListCount
:
410 def ReportByFileType(hacker_list
):
414 BeginReport('Developer contributions by type')
415 for h
in hacker_list
:
417 for patch
in h
.patches
:
418 # Get a summary by hacker
419 for (filetype
, (added
, removed
)) in patch
.filetypes
.iteritems():
420 if by_hacker
.has_key(filetype
):
421 by_hacker
[filetype
][patch
.ADDED
] += added
422 by_hacker
[filetype
][patch
.REMOVED
] += removed
424 by_hacker
[filetype
] = [added
, removed
]
427 if total
.has_key(filetype
):
428 total
[filetype
][patch
.ADDED
] += added
429 total
[filetype
][patch
.REMOVED
] += removed
431 total
[filetype
] = [added
, removed
, []]
433 # Print a summary by hacker
434 # FIXME why isn't this using Outfile?
436 for filetype
, counters
in by_hacker
.iteritems():
437 print('\t', filetype
, counters
)
438 h_added
= by_hacker
[filetype
][patch
.ADDED
]
439 h_removed
= by_hacker
[filetype
][patch
.REMOVED
]
440 total
[filetype
][2].append([h
.name
, h_added
, h_removed
])
442 # Print the global summary
443 BeginReport('Contributions by type and developers')
444 for filetype
, (added
, removed
, hackers
) in total
.iteritems():
445 print(filetype
, added
, removed
)
446 for h
, h_added
, h_removed
in hackers
:
447 print('\t%s: [%d, %d]' % (h
, h_added
, h_removed
))
449 # Print the very global summary
450 BeginReport('General contributions by type')
451 for filetype
, (added
, removed
, hackers
) in total
.iteritems():
452 print(filetype
, added
, removed
)
455 # The file access report is a special beast.
457 def FileAccessReport(name
, accesses
, total
):
458 outf
= open(name
, 'w')
459 files
= sorted(accesses
)
462 outf
.write('%6d %6.1f%% %s\n' % (a
, (100.0*a
)/total
, file))