aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/github
diff options
context:
space:
mode:
authorGravatar David Garcia Quintas <dgq@google.com>2017-09-14 14:52:37 -0700
committerGravatar David Garcia Quintas <dgq@google.com>2017-09-14 14:52:37 -0700
commite2d60524bdbd6d7f761d3dbce185d6f6b0dbbbcb (patch)
tree84b64b7aaa7e9ec74075c762e485e03477ea8579 /tools/github
parent79a89c9accae38cd74ead048c81c19e34b6feeab (diff)
Script for CI testing latency
Diffstat (limited to 'tools/github')
-rw-r--r--tools/github/pr_latency.py146
1 files changed, 146 insertions, 0 deletions
diff --git a/tools/github/pr_latency.py b/tools/github/pr_latency.py
new file mode 100644
index 0000000000..e773bda37a
--- /dev/null
+++ b/tools/github/pr_latency.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Measure the time between PR creation and completion of all tests"""
+
+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 # Ignore jenkins
+ 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']))
+ # First status is the most recent one.
+ 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')
+ parser.add_argument('--system', type=str, choices=['jenkins', 'kokoro'], required=True)
+ parser.add_argument('--token', type=str, default='')
+ 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'])
+ # PR with a single commit -> use the PRs creation time.
+ # else -> use the latest commit's date.
+ 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()