Skip to content

Commit 6c37258

Browse files
cfriedtnashif
authored andcommitted
scripts: add script to extract top-ten bug-bashers
This script allows us to programmatically query bug-bashers within a user-supplied time-window. For example, we held a "Bug Bash Week" August 1-7, 2021 (it was announced a week early though). The output of the script prints the "top ten" bug bashers in tab-separated columns in descending order. The first column is the number of bugs squashed and the second column is the github user id. Signed-off-by: Christopher Friedt <[email protected]>
1 parent e3fda63 commit 6c37258

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,7 @@
587587
/samples/subsys/pm/ @nashif @ceolin
588588
/samples/tfm_integration/ @ioannisg @microbuilder
589589
/samples/userspace/ @dcpleung @nashif
590+
/scripts/release/bug_bash.py @cfriedt
590591
/scripts/coccicheck @himanshujha199640 @JuliaLawall
591592
/scripts/coccinelle/ @himanshujha199640 @JuliaLawall
592593
/scripts/coredump/ @dcpleung

scripts/release/bug_bash.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2021, Facebook
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
"""Query the Top-Ten Bug Bashers
7+
8+
This script will query the top-ten Bug Bashers in a specified date window.
9+
10+
Usage:
11+
./scripts/bug-bash.py -t ~/.ghtoken -b 2021-07-26 -e 2021-08-07
12+
GITHUB_TOKEN="..." ./scripts/bug-bash.py -b 2021-07-26 -e 2021-08-07
13+
"""
14+
15+
import argparse
16+
from datetime import datetime, timedelta
17+
import operator
18+
import os
19+
20+
# Requires PyGithub
21+
from github import Github
22+
23+
24+
def parse_args():
25+
parser = argparse.ArgumentParser()
26+
parser.add_argument('-a', '--all', dest='all',
27+
help='Show all bugs squashed', action='store_true')
28+
parser.add_argument('-t', '--token', dest='tokenfile',
29+
help='File containing GitHub token', metavar='FILE')
30+
parser.add_argument('-b', '--begin', dest='begin', help='begin date (YYYY-mm-dd)',
31+
metavar='date', type=valid_date_type, required=True)
32+
parser.add_argument('-e', '--end', dest='end', help='end date (YYYY-mm-dd)',
33+
metavar='date', type=valid_date_type, required=True)
34+
35+
args = parser.parse_args()
36+
37+
if args.end < args.begin:
38+
raise ValueError(
39+
'end date {} is before begin date {}'.format(args.end, args.begin))
40+
41+
if args.tokenfile:
42+
with open(args.tokenfile, 'r') as file:
43+
token = file.read()
44+
token = token.strip()
45+
else:
46+
if 'GITHUB_TOKEN' not in os.environ:
47+
raise ValueError('No credentials specified')
48+
token = os.environ['GITHUB_TOKEN']
49+
50+
setattr(args, 'token', token)
51+
52+
return args
53+
54+
55+
class BugBashTally(object):
56+
def __init__(self, gh, begin_date, end_date):
57+
"""Create a BugBashTally object with the provided Github object,
58+
begin datetime object, and end datetime object"""
59+
self._gh = gh
60+
self._repo = gh.get_repo('zephyrproject-rtos/zephyr')
61+
self._begin_date = begin_date
62+
self._end_date = end_date
63+
64+
self._issues = []
65+
self._pulls = []
66+
67+
def get_tally(self):
68+
"""Return a dict with (key = user, value = score)"""
69+
tally = dict()
70+
for p in self.get_pulls():
71+
user = p.user.login
72+
tally[user] = tally.get(user, 0) + 1
73+
74+
return tally
75+
76+
def get_rev_tally(self):
77+
"""Return a dict with (key = score, value = list<user>) sorted in
78+
descending order"""
79+
# there may be ties!
80+
rev_tally = dict()
81+
for user, score in self.get_tally().items():
82+
if score not in rev_tally:
83+
rev_tally[score] = [user]
84+
else:
85+
rev_tally[score].append(user)
86+
87+
# sort in descending order by score
88+
rev_tally = dict(
89+
sorted(rev_tally.items(), key=operator.itemgetter(0), reverse=True))
90+
91+
return rev_tally
92+
93+
def get_top_ten(self):
94+
"""Return a dict with (key = score, value = user) sorted in
95+
descending order"""
96+
top_ten = []
97+
for score, users in self.get_rev_tally().items():
98+
# do not sort users by login - hopefully fair-ish
99+
for user in users:
100+
if len(top_ten) == 10:
101+
return top_ten
102+
103+
top_ten.append(tuple([score, user]))
104+
105+
return top_ten
106+
107+
def get_pulls(self):
108+
"""Return GitHub pull requests that squash bugs in the provided
109+
date window"""
110+
if self._pulls:
111+
return self._pulls
112+
113+
self.get_issues()
114+
115+
return self._pulls
116+
117+
def get_issues(self):
118+
"""Return GitHub issues representing bugs in the provided date
119+
window"""
120+
if self._issues:
121+
return self._issues
122+
123+
cutoff = self._end_date + timedelta(1)
124+
issues = self._repo.get_issues(state='closed', labels=[
125+
'bug'], since=self._begin_date)
126+
127+
for i in issues:
128+
# the PyGithub API and v3 REST API do not facilitate 'until'
129+
# or 'end date' :-/
130+
if i.closed_at < self._begin_date or i.closed_at > cutoff:
131+
continue
132+
133+
ipr = i.pull_request
134+
if ipr is None:
135+
# ignore issues without a linked pull request
136+
continue
137+
138+
prid = int(ipr.html_url.split('/')[-1])
139+
pr = self._repo.get_pull(prid)
140+
if not pr.merged:
141+
# pull requests that were not merged do not count
142+
continue
143+
144+
self._pulls.append(pr)
145+
self._issues.append(i)
146+
147+
return self._issues
148+
149+
150+
# https://gist.github.com/monkut/e60eea811ef085a6540f
151+
def valid_date_type(arg_date_str):
152+
"""custom argparse *date* type for user dates values given from the
153+
command line"""
154+
try:
155+
return datetime.strptime(arg_date_str, "%Y-%m-%d")
156+
except ValueError:
157+
msg = "Given Date ({0}) not valid! Expected format, YYYY-MM-DD!".format(arg_date_str)
158+
raise argparse.ArgumentTypeError(msg)
159+
160+
161+
def print_top_ten(top_ten):
162+
"""Print the top-ten bug bashers"""
163+
for score, user in top_ten:
164+
# print tab-separated value, to allow for ./script ... > foo.csv
165+
print('{}\t{}'.format(score, user))
166+
167+
168+
def main():
169+
args = parse_args()
170+
bbt = BugBashTally(Github(args.token), args.begin, args.end)
171+
if args.all:
172+
# print one issue per line
173+
issues = bbt.get_issues()
174+
pulls = bbt.get_pulls()
175+
n = len(issues)
176+
m = len(pulls)
177+
assert n == m
178+
for i in range(0, n):
179+
print('{}\t{}\t{}'.format(
180+
issues[i].number, pulls[i].user.login, pulls[i].title))
181+
else:
182+
# print the top ten
183+
print_top_ten(bbt.get_top_ten())
184+
185+
186+
if __name__ == '__main__':
187+
main()

scripts/requirements-extras.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ imgtool>=1.7.1
2020

2121
# used by nanopb module to generate sources from .proto files
2222
protobuf
23+
24+
# used by scripts/release/bug_bash.py for generating top ten bug squashers
25+
PyGithub

0 commit comments

Comments
 (0)