#!/usr/bin/env 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 getpass import optparse import sys import logging import utils def sec_to_period_string(seconds): days = seconds / (3600 * 24) hours = (seconds / 3600) - (days * 24) minutes = (seconds / 60) - (days * 24 * 60) - (hours * 60) return '%d days, %d hours, %d minutes' % (days, hours, minutes) def average_age(changes, key='age'): if not changes: return 0 total_seconds = 0 for change in changes: total_seconds += change[key] avg_age = total_seconds / len(changes) return sec_to_period_string(avg_age) def quartile_age(changes, quartile=2, key='age'): """Quartile age quartile 1: 25% quartile 2: 50% (median) default quartile 3: 75% """ if not changes: return 0 changes = sorted(changes, key=lambda change: change[key]) quartile_age = changes[len(changes) * quartile/4][key] return sec_to_period_string(quartile_age) def number_waiting_more_than(changes, seconds, key='age'): index = 0 for change in changes: if change[key] < seconds: return index index += 1 return len(changes) def format_url(url, options): return '%s%s%s' % ('%s' % url) if options.html else '') def gen_stats(projects, waiting_on_reviewer, waiting_on_submitter, options): age_sorted = sorted(waiting_on_reviewer, key=lambda change: change['age'], reverse=True) age2_sorted = sorted(waiting_on_reviewer, key=lambda change: change['age2'], reverse=True) age3_sorted = sorted(waiting_on_reviewer, key=lambda change: change['age3'], reverse=True) result = [] result.append(('Projects', '%s' % [project['name'] for project in projects])) stats = [] stats.append(('Total Open Reviews', '%d' % (len(waiting_on_reviewer) + len(waiting_on_submitter)))) stats.append(('Waiting on Submitter', '%d' % len(waiting_on_submitter))) stats.append(('Waiting on Reviewer', '%d' % len(waiting_on_reviewer))) latest_rev_stats = [] latest_rev_stats.append(('Average wait time', '%s' % (average_age(waiting_on_reviewer)))) latest_rev_stats.append(('1rd quartile wait time', '%s' % (quartile_age(waiting_on_reviewer, quartile=1)))) latest_rev_stats.append(('Median wait time', '%s' % (quartile_age(waiting_on_reviewer)))) latest_rev_stats.append(('3rd quartile wait time', '%s' % (quartile_age(waiting_on_reviewer, quartile=3)))) latest_rev_stats.append(( 'Number waiting more than %i days' % options.waiting_more, '%i' % (number_waiting_more_than( age_sorted, 60 * 60 * 24 * options.waiting_more)))) stats.append(('Stats since the latest revision', latest_rev_stats)) last_without_nack_stats = [] last_without_nack_stats.append(('Average wait time', '%s' % (average_age(waiting_on_reviewer, key='age3')))) last_without_nack_stats.append(('1rd quartile wait time', '%s' % (quartile_age(waiting_on_reviewer, quartile=1, key='age3')))) last_without_nack_stats.append(('Median wait time', '%s' % (quartile_age(waiting_on_reviewer, key='age3')))) last_without_nack_stats.append(('3rd quartile wait time', '%s' % (quartile_age(waiting_on_reviewer, quartile=3, key='age3')))) stats.append(('Stats since the last revision without -1 or -2 ', last_without_nack_stats)) first_rev_stats = [] first_rev_stats.append(('Average wait time', '%s' % (average_age(waiting_on_reviewer, key='age2')))) first_rev_stats.append(('1st quartile wait time', '%s' % (quartile_age(waiting_on_reviewer, quartile=1, key='age2')))) first_rev_stats.append(('Median wait time', '%s' % (quartile_age(waiting_on_reviewer, key='age2')))) first_rev_stats.append(('3rd quartile wait time', '%s' % (quartile_age(waiting_on_reviewer, quartile=3, key='age2')))) stats.append(('Stats since the first revision (total age)', first_rev_stats)) changes = [] for change in age_sorted[:options.longest_waiting]: changes.append('%s %s (%s)' % (sec_to_period_string(change['age']), format_url(change['url'], options), change['subject'])) stats.append(('Longest waiting reviews (based on latest revision)', changes)) changes = [] for change in age3_sorted[:options.longest_waiting]: changes.append('%s %s (%s)' % (sec_to_period_string(change['age3']), format_url(change['url'], options), change['subject'])) stats.append(('Longest waiting reviews (based on oldest rev without -1 or' ' -2)', changes)) changes = [] for change in age2_sorted[:options.longest_waiting]: changes.append('%s %s (%s)' % (sec_to_period_string(change['age2']), format_url(change['url'], options), change['subject'])) stats.append(('Longest waiting reviews (since first revision, total age)', changes)) result.append(stats) return result def print_stats_txt(stats, f=sys.stdout): def print_list_txt(l, level): for item in l: if not isinstance(item, list): f.write('%s> ' % ('--' * level)) print_item_txt(item, level) def print_item_txt(item, level): if isinstance(item, basestring): f.write('%s\n' % item) elif isinstance(item, list): print_list_txt(item, level + 1) elif isinstance(item, tuple): f.write('%s: ' % item[0]) if isinstance(item[1], list): f.write('\n') print_item_txt(item[1], level) else: raise Exception('Unhandled type') print_list_txt(stats, 0) def print_stats_html(stats, f=sys.stdout): def print_list_html(l, level): if level: f.write('<%s>\n' % ('ul' if level == 1 else 'ol')) for item in l: if level: f.write('%s