def main(argv=None): if argv is None: argv = sys.argv optparser = optparse.OptionParser() optparser.add_option( '-p', '--project', default='projects/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=getpass.getuser(), help='gerrit user') optparser.add_option( '-k', '--key', default=None, help='ssh key for gerrit') optparser.add_option('-s', '--stable', action='store_true', help='Include stable branch commits') 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) approved_and_rebased = set() for change in changes: if 'rowCount' in change: continue if not options.stable and 'stable' in change['branch']: continue if change['status'] != 'NEW': # Filter out WORKINPROGRESS continue for patch_set in change['patchSets'][:-1]: if (utils.patch_set_approved(patch_set) and not utils.patch_set_approved(change['patchSets'][-1])): if has_negative_feedback(change['patchSets'][-1]): continue approved_and_rebased.add("%s %s" % (change['url'], change['subject'])) for x in approved_and_rebased: print x print "total %d" % len(approved_and_rebased)
def main(argv=None): if argv is None: argv = sys.argv optparser = optparse.OptionParser() optparser.add_option('-p', '--project', default='projects/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') optparser.add_option('-s', '--stable', action='store_true', help='Include stable branch commits') optparser.add_option('-l', '--longest-waiting', type='int', default=5, help='Show n changesets that have waited the longest)') optparser.add_option('-m', '--waiting-more', type='int', default=7, help='Show number of changesets that have waited more than n days)') optparser.add_option('-H', '--html', action='store_true', help='Use HTML output instead of plain text') 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 if not options.stable and 'stable' in change['branch']: continue if change['status'] != 'NEW': # Filter out WORKINPROGRESS continue latest_patch = change['patchSets'][-1] waiting_for_review = True approvals = latest_patch.get('approvals', []) approvals.sort(key=lambda a:a['grantedOn']) for review in approvals: if review['type'] not in ('CRVW', 'VRIF'): continue if review['value'] in ('-1', '-2'): waiting_for_review = False break change['age'] = get_age_of_patch(latest_patch, now_ts) change['age2'] = get_age_of_patch(change['patchSets'][0], now_ts) patch = find_oldest_no_nack(change) change['age3'] = get_age_of_patch(patch, now_ts) if patch else 0 if waiting_for_review: waiting_on_reviewer.append(change) else: waiting_on_submitter.append(change) age_sorted = sorted(waiting_on_reviewer, key=lambda change: change['age']) age2_sorted = sorted(waiting_on_reviewer, key=lambda change: change['age2']) age3_sorted = sorted(waiting_on_reviewer, key=lambda change: change['age3']) stats = gen_stats(projects, waiting_on_reviewer, waiting_on_submitter, age_sorted, age2_sorted, age3_sorted, options) if options.html: print_stats_html(stats) else: print_stats_txt(stats)
def main(argv=None): if argv is None: argv = sys.argv optparser = optparse.OptionParser() optparser.add_option('-p', '--project', default='projects/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('-d', '--days', type='int', default=14, help='Number of days to consider') optparser.add_option('-u', '--user', default=getpass.getuser(), 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) reviewers = {} cut_off = datetime.datetime.now() - datetime.timedelta(days=options.days) ts = calendar.timegm(cut_off.timetuple()) for project in projects: changes = utils.get_changes([project], options.user, options.key) for change in changes: for patchset in change.get('patchSets', []): process_patchset(project, patchset, reviewers, ts) reviewers = [(v, k) for k, v in reviewers.iteritems() if k.lower() not in ('jenkins', 'smokestack')] reviewers.sort(reverse=True, key=lambda r:r[0]['total']) if options.all: print 'Reviews for the last %d days in projects: %s' % (options.days, [project['name'] for project in projects]) else: print 'Reviews for the last %d days in %s' % (options.days, projects[0]['name']) if options.all: print '** -- Member of at least one core reviewer team' else: print '** -- %s-core team member' % projects[0]['name'] table = prettytable.PrettyTable( ('Reviewer', 'Reviews -2 -1 +1 +2 +/- %', 'Disagreements*')) total = 0 for k, v in reviewers: in_core_team = False for project in projects: if v in project['core-team']: in_core_team = True break name = '%s%s' % (v, ' **' if in_core_team else '') plus = float(k['votes']['2'] + k['votes']['1']) minus = float(k['votes']['-2'] + k['votes']['-1']) ratio = (plus / (plus + minus)) * 100 r = '%7d %3d %3d %3d %3d %5.1f%%' % (k['total'], k['votes']['-2'], k['votes']['-1'], k['votes']['1'], k['votes']['2'], ratio) dratio = ((float(k['disagreements']) / plus) * 100) if plus else 0.0 d = '%3d (%5.1f%%)' % (k['disagreements'], dratio) table.add_row((name, r, d)) total += k['total'] print table print '\nTotal reviews: %d' % total print 'Total reviewers: %d' % len(reviewers) print '\n(*) Disagreements are defined as a +1 or +2 vote on a patch ' \ 'where a core team member later gave a -1 or -2 vote, or a ' \ 'negative vote overridden with a postive one afterwards.' return 0
def main(): parser = ArgumentParser( description="Get reviews for open bugs against a milestone") parser.add_argument( '-p', '--project', default='projects/nova.json', help='JSON file describing the project to generate stats for') parser.add_argument( '-m', '--milestone', default='', help='Only show bugs targeted to a specified milestone') parser.add_argument( '-u', '--user', default=getpass.getuser(), help='gerrit user') parser.add_argument('-k', '--key', default=None, help='ssh key for gerrit') args = parser.parse_args() projects = utils.get_projects_info(args.project, False) project_name = projects[0]['name'] if not projects: print "Please specify a project." return 1 launchpad = Launchpad.login_with('openstack-releasing', 'production') proj = launchpad.projects[project_name] statuses = ['New', 'Incomplete', 'Confirmed', 'Triaged', 'In Progress'] if args.milestone: milestone = proj.getMilestone(name=args.milestone) bugtasks = proj.searchTasks(status=statuses, milestone=milestone) else: bugtasks = proj.searchTasks(status=statuses) bugs_by_id = {} for bt in bugtasks: bugs_by_id[str(bt.bug.id)] = bt milestones = {} changes = utils.get_changes(projects, args.user, args.key, only_open=True) bug_regex = re.compile('bug/(\d+)') for change in changes: if 'topic' not in change: continue match = bug_regex.match(change['topic']) if not match: continue bugid = match.group(1) try: bugtask = bugs_by_id[bugid] milestone = str(bugtask.milestone).split('/')[-1] if milestone == 'None': milestone = 'Untargeted' except KeyError: milestone = 'Bug does not exist for this project' milestones.setdefault(milestone, []) milestones[milestone].append((change['url'], bugid)) print 'Reviews for bugs grouped by milestone for project: %s\n' % ( project_name) for milestone, reviews in milestones.items(): if args.milestone and milestone != args.milestone: continue print 'Milestone: %s' % milestone for review, bugid in reviews: print '--> %s -- https://bugs.launchpad.net/%s/+bug/%s' \ % (review, project_name, bugid) print
def main(argv=None): if argv is None: argv = sys.argv optparser = optparse.OptionParser() optparser.add_option( '-p', '--project', default='projects/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=getpass.getuser(), help='gerrit user') optparser.add_option( '-k', '--key', default=None, help='ssh key for gerrit') optparser.add_option( '-s', '--stable', action='store_true', help='Include stable branch commits') optparser.add_option( '-l', '--longest-waiting', type='int', default=5, help='Show n changesets that have waited the longest)') optparser.add_option( '-m', '--waiting-more', type='int', default=7, help='Show number of changesets that have waited more than n days)') optparser.add_option( '-H', '--html', action='store_true', help='Use HTML output instead of plain text') optparser.add_option( '--server', default='review.openstack.org', help='Gerrit server to connect to') optparser.add_option( '--debug', action='store_true', help='Show extra debug output') optparser.add_option( '--projects-dir', default='./projects', help='Directory where to locate the project files') options, args = optparser.parse_args() logging.basicConfig(level=logging.ERROR) if options.debug: logging.root.setLevel(logging.DEBUG) projects = utils.get_projects_info(options.project, options.all, base_dir=options.projects_dir) if not projects: print "Please specify a project." sys.exit(1) changes = utils.get_changes(projects, options.user, options.key, only_open=True, server=options.server) 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 if not options.stable and 'stable' in change['branch']: continue if change['status'] != 'NEW': # Filter out WORKINPROGRESS continue latest_patch = change['patchSets'][-1] if utils.patch_set_approved(latest_patch): # Ignore patches already approved and just waiting to merge continue waiting_for_review = True approvals = latest_patch.get('approvals', []) approvals.sort(key=lambda a: a['grantedOn']) for review in approvals: if review['type'] not in ('CRVW', 'VRIF', 'Code-Review', 'Verified'): continue if review['value'] in ('-1', '-2'): waiting_for_review = False break change['age'] = get_age_of_patch(latest_patch, now_ts) change['age2'] = get_age_of_patch(change['patchSets'][0], now_ts) patch = find_oldest_no_nack(change) change['age3'] = get_age_of_patch(patch, now_ts) if patch else 0 if waiting_for_review: waiting_on_reviewer.append(change) else: waiting_on_submitter.append(change) stats = gen_stats(projects, waiting_on_reviewer, waiting_on_submitter, options) if options.html: print_stats_html(stats) else: print_stats_txt(stats)
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:
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('-d', '--days', type='int', default=14, help='Number of days to consider') 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) all_changes = utils.get_changes(projects, options.user, options.key) reviews = [] for change in all_changes: # print json.dumps(change, sort_keys=True, indent=4) for patchset in change.get('patchSets', []): for review in patchset.get('approvals', []): reviews += [review]
def main(argv=None): if argv is None: argv = sys.argv optparser = optparse.OptionParser() optparser.add_option( '-p', '--project', default='projects/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( '-o', '--output', default='-', help='Where to write output. If - stdout is used and only one output' 'format may be given. Otherwise the output format is appended to' 'the output parameter to generate file names.') optparser.add_option( '--outputs', default=['txt'], action='append', help='Select what outputs to generate. (txt,csv).') optparser.add_option( '-d', '--days', type='int', default=14, help='Number of days to consider') optparser.add_option( '-u', '--user', default=getpass.getuser(), 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) reviewers = {} cut_off = datetime.datetime.now() - datetime.timedelta(days=options.days) ts = calendar.timegm(cut_off.timetuple()) for project in projects: changes = utils.get_changes([project], options.user, options.key) for change in changes: for patchset in change.get('patchSets', []): process_patchset(project, patchset, reviewers, ts) reviewers = [(v, k) for k, v in reviewers.iteritems() if k.lower() not in ('jenkins', 'smokestack')] reviewers.sort(reverse=True, key=lambda r: r[0]['total']) # Do logical processing of reviewers. reviewer_data = [] total = 0 for k, v in reviewers: in_core_team = False for project in projects: if v in project['core-team']: in_core_team = True break name = '%s%s' % (v, ' **' if in_core_team else '') plus = float(k['votes']['2'] + k['votes']['1']) minus = float(k['votes']['-2'] + k['votes']['-1']) ratio = ((plus / (plus + minus)) * 100) if plus + minus > 0 else 0 r = (k['total'], k['votes']['-2'], k['votes']['-1'], k['votes']['1'], k['votes']['2'], k['votes']['A'], "%5.1f%%" % ratio) dratio = ((float(k['disagreements']) / plus) * 100) if plus else 0.0 d = (k['disagreements'], "%5.1f%%" % dratio) reviewer_data.append((name, r, d)) total += k['total'] # And output. writers = { 'csv': write_csv, 'txt': write_pretty, } if options.output == '-': if len(options.outputs) != 1: raise Exception("Can only output one format to stdout.") for output in options.outputs: if options.output == '-': file_obj = sys.stdout on_done = None else: file_obj = open(options.output + '.' + output, 'wt') on_done = file_obj.close try: writer = writers[output] if options.all: file_obj.write( 'Reviews for the last %d days in projects: %s\n' % (options.days, [project['name'] for project in projects])) else: file_obj.write('Reviews for the last %d days in %s\n' % (options.days, projects[0]['name'])) if options.all: file_obj.write( '** -- Member of at least one core reviewer team\n') else: file_obj.write( '** -- %s-core team member\n' % projects[0]['name']) writer(reviewer_data, file_obj) file_obj.write('\nTotal reviews: %d\n' % total) file_obj.write('Total reviewers: %d\n' % len(reviewers)) file_obj.write( '\n(*) Disagreements are defined as a +1 or +2 vote on a ' \ 'patch where a core team member later gave a -1 or -2 vote' \ ', or a negative vote overridden with a positive one ' \ 'afterwards.\n') finally: if on_done: on_done() return 0