89c077ae49
Minor fix to the help message to indicate count (it was just a copy paste of Name). Also added an example to the -p argument to show that the openstack domain is needed when specifying a project. Lastly, either capitilize the first work in the help string or don't, but be consistent. More were LC so I made the rest match. Change-Id: I89e88e51459583c2564b033f7f45223eae77f2c1
265 lines
9.9 KiB
Python
Executable File
265 lines
9.9 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
"""Generate a report on CI comments and jobs."""
|
|
|
|
import argparse
|
|
import calendar
|
|
import datetime
|
|
import json
|
|
import yaml
|
|
|
|
import requests
|
|
|
|
import comment
|
|
|
|
|
|
def query_gerrit(name, count, project, quiet=False):
|
|
"""Query gerrit and fetch the comments."""
|
|
# Include review messages in query
|
|
search = "reviewer:\"%s\"" % name
|
|
if project:
|
|
search = search + (" AND project:\"%s\"" % project)
|
|
query = ("https://review.openstack.org/changes/?q=%s&"
|
|
"o=MESSAGES&o=DETAILED_ACCOUNTS" % search)
|
|
r = requests.get(query)
|
|
try:
|
|
changes = json.loads(r.text[4:])
|
|
except ValueError:
|
|
if not quiet:
|
|
print("query: '%s' failed with:\n%s" % (query, r.text))
|
|
return []
|
|
|
|
comments = []
|
|
for change in changes:
|
|
for date, message in comment.get_comments(change, name):
|
|
if date is None:
|
|
# no comments from reviewer yet. This can happen since
|
|
# 'Uploaded patch set X.' is considered a comment.
|
|
continue
|
|
comments.append(comment.Comment(date, change['_number'],
|
|
change['subject'], message))
|
|
return sorted(comments, key=lambda comment: comment.date,
|
|
reverse=True)[0:count]
|
|
|
|
|
|
def get_votes(comments):
|
|
"""Get the stats for all of the jobs in all comments."""
|
|
last_success = None
|
|
votes = {'success': 0, 'failure': 0}
|
|
for cmt in comments:
|
|
if cmt.jobs:
|
|
for job in cmt.jobs:
|
|
if job.result == "SUCCESS":
|
|
if job.name not in votes:
|
|
votes[job.name] = {'success': 1, 'failure': 0,
|
|
'last_success': cmt}
|
|
elif votes[job.name]['success'] == 0:
|
|
votes[job.name]['success'] += 1
|
|
votes[job.name]['last_success'] = cmt
|
|
else:
|
|
votes[job.name]['success'] += 1
|
|
|
|
votes['success'] += 1
|
|
if not last_success:
|
|
last_success = cmt
|
|
elif job.result == 'FAILURE':
|
|
if job.name not in votes:
|
|
votes[job.name] = {'success': 0, 'failure': 1,
|
|
'last_success': None}
|
|
else:
|
|
votes[job.name]['failure'] += 1
|
|
|
|
votes['failure'] += 1
|
|
else:
|
|
# We got something other than
|
|
# SUCCESS or FAILURE
|
|
# for now, mark it as a failure
|
|
if job.name not in votes:
|
|
votes[job.name] = {'success': 0, 'failure': 1,
|
|
'last_success': None}
|
|
else:
|
|
votes[job.name]['failure'] += 1
|
|
votes['failure'] += 1
|
|
#print("Job %(name)s result = %(result)s" %
|
|
# {'name': job.name,
|
|
# 'result': job.result})
|
|
return votes, last_success
|
|
|
|
|
|
def generate_report(name, count, project, quiet=False):
|
|
"""Process all of the comments and generate the stats."""
|
|
result = {'name': name, 'project': project}
|
|
last_success = None
|
|
|
|
comments = query_gerrit(name, count, project)
|
|
if not comments:
|
|
print("No comments found. CI SYSTEM UNKNOWN")
|
|
return
|
|
|
|
votes, last_success = get_votes(comments)
|
|
result['last_seen'] = {'date': epoch(comments[0].date),
|
|
'age': str(comments[0].age()),
|
|
'url': comments[0].url()}
|
|
last = len(comments) - 1
|
|
result['first_seen'] = {'date': epoch(comments[last].date),
|
|
'age': str(comments[last].age()),
|
|
'url': comments[last].url()}
|
|
if not quiet:
|
|
print(" first seen: %s (%s old) %s" % (comments[last].date,
|
|
comments[last].age(),
|
|
comments[last].url()))
|
|
print(" last seen: %s (%s old) %s" % (comments[0].date,
|
|
comments[0].age(),
|
|
comments[0].url()))
|
|
if last_success:
|
|
result['last_success'] = {'date': epoch(comments[0].date),
|
|
'age': str(comments[0].age()),
|
|
'url': comments[0].url()}
|
|
if not quiet:
|
|
print(" last success: %s (%s old) %s" % (last_success.date,
|
|
last_success.age(),
|
|
last_success.url()))
|
|
else:
|
|
result['last_success'] = None
|
|
if not quiet:
|
|
print(" last success: None")
|
|
|
|
result['jobs'] = []
|
|
jobs = dict.fromkeys(votes, 0)
|
|
jobs.pop('success', None)
|
|
jobs.pop('failure', None)
|
|
for job in jobs:
|
|
reported_comments = votes[job]['success'] + votes[job]['failure']
|
|
if votes[job]['failure'] == 0:
|
|
success_rate = 100
|
|
else:
|
|
success_rate = int(votes[job]['success'] /
|
|
float(reported_comments) * 100)
|
|
|
|
if not quiet:
|
|
print(" Job %(job_name)s %(success_rate)s%% success out of "
|
|
"%(comments)s comments S=%(success)s, F=%(failures)s"
|
|
% {'success_rate': success_rate,
|
|
'job_name': job,
|
|
'comments': reported_comments,
|
|
'success': votes[job]['success'],
|
|
'failures': votes[job]['failure']})
|
|
|
|
# Only print the job's last success rate if the succes rate
|
|
# is low enough to warrant showing it.
|
|
if votes[job]['last_success'] and success_rate <= 60 and not quiet:
|
|
print(" last success: %s (%s old) %s" %
|
|
(votes[job]['last_success'].date,
|
|
votes[job]['last_success'].age(),
|
|
votes[job]['last_success'].url()))
|
|
|
|
job_entry = {'name': job, 'success_rate': success_rate,
|
|
'num_success': votes[job]['success'],
|
|
'num_failures': votes[job]['failure'],
|
|
'comments': reported_comments,
|
|
}
|
|
if votes[job]['last_success']:
|
|
job_entry['last_success'] = {
|
|
'date': epoch(votes[job]['last_success'].date),
|
|
'age': str(votes[job]['last_success'].age()),
|
|
'url': votes[job]['last_success'].url()}
|
|
else:
|
|
job_entry['last_success'] = None
|
|
|
|
result['jobs'].append(job_entry)
|
|
|
|
total = votes['success'] + votes['failure']
|
|
if total > 0:
|
|
success_rate = int(votes['success'] / float(total) * 100)
|
|
result['success_rate'] = success_rate
|
|
if not quiet:
|
|
print("Overall success rate: %s%% of %s comments" %
|
|
(success_rate, len(comments)))
|
|
|
|
return result
|
|
|
|
|
|
def epoch(timestamp):
|
|
return int(calendar.timegm(timestamp.timetuple()))
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='list most recent comment by '
|
|
'reviewer')
|
|
parser.add_argument('-n', '--name',
|
|
default="Jenkins",
|
|
help='unique gerrit name of the reviewer')
|
|
parser.add_argument('-c', '--count',
|
|
default=10,
|
|
type=int,
|
|
help='number of records to evaluate')
|
|
parser.add_argument('-i', '--input',
|
|
default=None,
|
|
help='yaml file containing list of names to search on'
|
|
'project: name'
|
|
' (overwrites -p and -n)')
|
|
parser.add_argument('-o', '--output',
|
|
default=None,
|
|
help='write the output to a file. Defaults to stdout.')
|
|
parser.add_argument('-j', '--json',
|
|
default=False,
|
|
action="store_true",
|
|
help=("generate report output in json format."))
|
|
parser.add_argument('-p', '--project',
|
|
help='only list hits for a specific project '
|
|
'(ie: openstack/cinder)')
|
|
|
|
args = parser.parse_args()
|
|
names = {args.project: [args.name]}
|
|
|
|
quiet = False
|
|
if args.json and not args.output:
|
|
# if we are writing json data to stdout
|
|
# we shouldn't be outputting anything else
|
|
quiet = True
|
|
|
|
if args.input:
|
|
with open(args.input) as f:
|
|
names = yaml.load(f)
|
|
|
|
if args.json:
|
|
if not quiet:
|
|
print "generating report %s" % args.json
|
|
print "report is over last %s comments" % args.count
|
|
report = {}
|
|
timestamp = epoch(datetime.datetime.utcnow())
|
|
report['timestamp'] = timestamp
|
|
report['rows'] = []
|
|
|
|
for project in names:
|
|
if not quiet:
|
|
print 'Checking project: %s' % project
|
|
for name in names[project]:
|
|
if name != 'Jenkins':
|
|
url = ("https://wiki.openstack.org/wiki/ThirdPartySystems/%s" %
|
|
name.replace(" ", "_"))
|
|
if not quiet:
|
|
print 'Checking name: %s - %s' % (name, url)
|
|
else:
|
|
if not quiet:
|
|
print('Checking name: %s' % name)
|
|
try:
|
|
report_result = generate_report(name, args.count, project,
|
|
quiet)
|
|
if args.json:
|
|
report['rows'].append(report_result)
|
|
except Exception as e:
|
|
print e
|
|
pass
|
|
|
|
if args.json:
|
|
if not args.output:
|
|
print(json.dumps(report))
|
|
else:
|
|
with open(args.output, 'w') as f:
|
|
json.dump(report, f)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|