from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import json
import logging
import pprint
import urllib2
from datetime import datetime, timedelta
logging.basicConfig(format='%(asctime)s %(message)s')
PRS = 'https://api.github.com/repos/grpc/grpc/pulls?state=open&per_page=100'
COMMITS = 'https://api.github.com/repos/grpc/grpc/pulls/{pr_number}/commits'
def gh(url):
request = urllib2.Request(url)
if TOKEN:
request.add_header('Authorization', 'token {}'.format(TOKEN))
response = urllib2.urlopen(request)
return response.read()
def print_csv_header():
print('pr,base_time,test_time,latency_seconds,successes,failures,errors')
def output(pr,
base_time,
test_time,
diff_time,
successes,
failures,
errors,
mode='human'):
if mode == 'human':
print(
"PR #{} base time: {} UTC, Tests completed at: {} UTC. Latency: {}."
"\n\tSuccesses: {}, Failures: {}, Errors: {}".format(
pr, base_time, test_time, diff_time, successes, failures,
errors))
elif mode == 'csv':
print(','.join([
str(pr),
str(base_time),
str(test_time),
str(int((test_time - base_time).total_seconds())),
str(successes),
str(failures),
str(errors)
]))
def parse_timestamp(datetime_str):
return datetime.strptime(datetime_str, '%Y-%m-%dT%H:%M:%SZ')
def to_posix_timestamp(dt):
return str((dt - datetime(1970, 1, 1)).total_seconds())
def get_pr_data():
latest_prs = json.loads(gh(PRS))
res = [{
'number': pr['number'],
'created_at': parse_timestamp(pr['created_at']),
'updated_at': parse_timestamp(pr['updated_at']),
'statuses_url': pr['statuses_url']
} for pr in latest_prs]
return res
def get_commits_data(pr_number):
commits = json.loads(gh(COMMITS.format(pr_number=pr_number)))
return {
'num_commits': len(commits),
'most_recent_date':
parse_timestamp(commits[-1]['commit']['author']['date'])
}
def get_status_data(statuses_url, system):
status_url = statuses_url.replace('statuses', 'status')
statuses = json.loads(gh(status_url + '?per_page=100'))
successes = 0
failures = 0
errors = 0
latest_datetime = None
if not statuses: return None
if system == 'kokoro': string_in_target_url = 'kokoro'
elif system == 'jenkins': string_in_target_url = 'grpc-testing'
for status in statuses['statuses']:
if not status['target_url'] or string_in_target_url not in status['target_url']:
continue if status['state'] == 'pending': return None
elif status['state'] == 'success': successes += 1
elif status['state'] == 'failure': failures += 1
elif status['state'] == 'error': errors += 1
if not latest_datetime:
latest_datetime = parse_timestamp(status['updated_at'])
else:
latest_datetime = max(latest_datetime,
parse_timestamp(status['updated_at']))
if any([successes, failures, errors
]) and sum([successes, failures, errors]) > 15:
return {
'latest_datetime': latest_datetime,
'successes': successes,
'failures': failures,
'errors': errors
}
else:
return None
def build_args_parser():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
'--format',
type=str,
choices=['human', 'csv'],
default='human',
help='Output format: are you a human or a machine?')
parser.add_argument(
'--system',
type=str,
choices=['jenkins', 'kokoro'],
required=True,
help='Consider only the given CI system')
parser.add_argument(
'--token',
type=str,
default='',
help='GitHub token to use its API with a higher rate limit')
return parser
def main():
import sys
global TOKEN
args_parser = build_args_parser()
args = args_parser.parse_args()
TOKEN = args.token
if args.format == 'csv': print_csv_header()
for pr_data in get_pr_data():
commit_data = get_commits_data(pr_data['number'])
base_timestamp = pr_data['updated_at']
if commit_data['num_commits'] > 1:
base_timestamp = commit_data['most_recent_date']
else:
base_timestamp = pr_data['created_at']
last_status = get_status_data(pr_data['statuses_url'], args.system)
if last_status:
diff = last_status['latest_datetime'] - base_timestamp
if diff < timedelta(hours=5):
output(
pr_data['number'],
base_timestamp,
last_status['latest_datetime'],
diff,
last_status['successes'],
last_status['failures'],
last_status['errors'],
mode=args.format)
if __name__ == '__main__':
main()