diff --git a/README.md b/README.md index 7f85bb6..c9f97c0 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,5 @@ OpenStack Stats Utility scripts for generating stats about OpenStack development. + * `openreviews.py` - Get some stats on the number and age of open reviews. * `reviewers.py` - See how many reviews each person has done over a period of time. diff --git a/openreviews.py b/openreviews.py new file mode 100755 index 0000000..159ffd7 --- /dev/null +++ b/openreviews.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# +# Copyright (C) 2011 - Soren Hansen +# Copyright (C) 2013 - Red Hat, Inc. +# + +# 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. + +import calendar +import datetime +import optparse +import os +import os.path +import sys + +import utils + + +optparser = optparse.OptionParser() +optparser.add_option('-p', '--project', default='nova.json', + help='JSON file describing the project to generate stats for') +optparser.add_option('-a', '--all', action='store_true', + help='Generate stats across all known projects (*.json)') +optparser.add_option('-u', '--user', default='russellb', help='gerrit user') +optparser.add_option('-k', '--key', default=None, help='ssh key for gerrit') + +options, args = optparser.parse_args() + +projects = utils.get_projects_info(options.project, options.all) + +if not projects: + print "Please specify a project." + sys.exit(1) + +changes = utils.get_changes(projects, options.user, options.key, + only_open=True) + +waiting_on_submitter = [] +waiting_on_reviewer = [] + +now = datetime.datetime.utcnow() +now_ts = calendar.timegm(now.timetuple()) + +for change in changes: + if 'rowCount' in change: + continue + latest_patch = change['patchSets'][-1] + waiting_for_review = True + for review in latest_patch.get('approvals', []): + if review['type'] == 'CRVW' and (review['value'] != '-1' or + review['value'] == '-2'): + waiting_for_review = False + break + change['age'] = now_ts - latest_patch['createdOn'] + if waiting_for_review: + waiting_on_reviewer.append(change) + else: + waiting_on_submitter.append(change) + + +def average_age(changes): + total_seconds = 0 + for change in changes: + total_seconds += change['age'] + avg_age = total_seconds / len(changes) + days = avg_age / (3600 * 24) + hours = (avg_age / 3600) - (days * 24) + minutes = (avg_age / 60) - (days * 24 * 60) - (hours * 60) + return '%d days, %d hours, %d minutes' % (days, hours, minutes) + + +print 'Projects: %s' % [project['name'] for project in projects] +print 'Total Open Reviews: %d' % (len(waiting_on_reviewer) + + len(waiting_on_submitter)) +print 'Waiting on Submitter: %d' % len(waiting_on_submitter) +print 'Waiting on Reviewer: %d' % len(waiting_on_reviewer) +print ' --> Average wait time: %s' % average_age(waiting_on_reviewer) diff --git a/utils.py b/utils.py index 786546e..d3c8dae 100644 --- a/utils.py +++ b/utils.py @@ -49,7 +49,7 @@ def projects_q(project): ')') -def get_changes(projects, ssh_user, ssh_key): +def get_changes(projects, ssh_user, ssh_key, only_open=False): all_changes = [] client = paramiko.SSHClient() @@ -59,13 +59,17 @@ def get_changes(projects, ssh_user, ssh_key): for project in projects: changes = [] - pickle_fn = '%s-changes.pickle' % project['name'] + if not only_open: + # Only use the cache for *all* changes (the entire history). + # Requesting only the open changes isn't nearly as big of a deal, + # so just get the current data. + pickle_fn = '%s-changes.pickle' % project['name'] - if os.path.isfile(pickle_fn): - mtime = os.stat(pickle_fn).st_mtime - if (time.time() - mtime) <= CACHE_AGE: - with open(pickle_fn, 'r') as f: - changes = pickle.load(f) + if os.path.isfile(pickle_fn): + mtime = os.stat(pickle_fn).st_mtime + if (time.time() - mtime) <= CACHE_AGE: + with open(pickle_fn, 'r') as f: + changes = pickle.load(f) if len(changes) == 0: @@ -74,6 +78,8 @@ def get_changes(projects, ssh_user, ssh_key): key_filename=ssh_key, username=ssh_user) cmd = ('gerrit query %s --all-approvals --patch-sets --format JSON' % projects_q(project)) + if only_open: + cmd += ' status:open' if len(changes) > 0: cmd += ' resume_sortkey:%s' % changes[-2]['sortKey'] stdin, stdout, stderr = client.exec_command(cmd) @@ -82,8 +88,9 @@ def get_changes(projects, ssh_user, ssh_key): if changes[-1]['rowCount'] == 0: break - with open(pickle_fn, 'w') as f: - pickle.dump(changes, f) + if not only_open: + with open(pickle_fn, 'w') as f: + pickle.dump(changes, f) all_changes.extend(changes)