def main(): parser = ArgumentParser( description="Calculate some statistics about project bugs.", epilog=dedent("""\ Known caveats: Historical data uses the current task metadata rather than historical values. This is primarily due to performance considerations with the LP API and may be rectified in future (e.g. by mirroring the data persistently). As an example, if a bug is currently set to 'critical', it will show as critical in all time periods rather than progressing through different categories as it is triaged. """)) parser.add_argument( '-p', '--project', default='projects/nova.json', help='JSON file describing the project to generate stats for.') args = parser.parse_args() projects = utils.get_projects_info(args.project, False) lp_project_listeners = {} listeners = set() if not projects: sys.stderr.write('No projects found: please specify one or more.\n') return 1 launchpad = Launchpad.login_with( 'openstack-releasing', 'production', credentials_file='.lpcreds') for project in projects: lp_projects = project.get('lp_projects', []) if not lp_projects: print "Please specify a project." return 1 listener = Listener(project['name'], lp_projects) listeners.add(listener) for lp_project in project.get('lp_projects', []): lp_project_listeners.setdefault(lp_project, []).append(listener) statuses = ['New', 'Incomplete', 'Opinion', 'Invalid', "Won't Fix", 'Confirmed', 'Triaged', 'In Progress', "Fix Committed", "Fix Released"] bugs_by_bug_link = {} for lp_project, receivers in lp_project_listeners.items(): proj = launchpad.projects[lp_project] # Sort by id to make creating time periods easy. bugtasks = proj.searchTasks(status=statuses, order_by="id") for task in bugtasks: if task.bug_link not in bugs_by_bug_link: bugs_by_bug_link[task.bug_link] = task.bug bug = bugs_by_bug_link[task.bug_link] for receiver in receivers: receiver.categorise_task(task, bug) for listener in listeners: sys.stdout.write("Project: %s\n" % listener.name) sys.stdout.write("LP Projects: %s\n" % listener.lp_projects) table = prettytable.PrettyTable( ('Period', 'critical', 'high', 'undecided', 'other', 'total', 'created', 'closed', 'critical-tags')) for period in listener.summarise(): table.add_row(period) sys.stdout.write("%s\n" % table)
def get_changes(): #with open('changes.json') as f: # return json.loads(f.read()) projects = utils.get_projects_info(CONF.project_file) return utils.get_changes(projects, CONF.gerrit_user, CONF.ssh_key_file, only_open=True)
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( '--server', default='review.opendev.org', help='Gerrit server to connect to') 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, server=options.server) 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=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'] = utils.get_age_of_patch(latest_patch, now_ts) change['age2'] = utils.get_age_of_patch(change['patchSets'][0], now_ts) patch = find_oldest_no_nack(change) change['age3'] = utils.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)
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') optparser.add_option( '--output', '-o', default='-', help="Where to write output. - for stdout. The file will be appended" " if it exists.") 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 utils.is_workinprogress(change): # 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'] = utils.get_age_of_patch(latest_patch, now_ts) change['age2'] = utils.get_age_of_patch(change['patchSets'][0], now_ts) patch = find_oldest_no_nack(change) change['age3'] = utils.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.output == '-': output = sys.stdout else: output = open(options.output, 'at') try: if options.html: print_stats_html(stats, f=output) else: print_stats_txt(stats, f=output) finally: if output is not sys.stdout: output.close()
def test_get_projects_info_single_name_projects_prefixed(self): projects = utils.get_projects_info('projects/stable.json') self.assertEqual(1, len(projects))
def test_get_projects_info_single_name(self): projects = utils.get_projects_info('nova') self.assertEqual(1, len(projects))
def test_project_definitions_load(self): utils.get_projects_info('', True)
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( "-s", "--stable", default="", metavar="BRANCH", help='Generate stats for the specified stable BRANCH ("havana") ' "across all integrated projects", ) 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("-P", "--password", default=getpass.getuser(), help="gerrit HTTP password") optparser.add_option("-k", "--key", default=None, help="ssh key for gerrit") optparser.add_option("-r", "--csv-rows", default=0, help="Max rows for CSV output", type="int", dest="csv_rows") optparser.add_option("--server", default="review.openstack.org", help="Gerrit server to connect to") options, args = optparser.parse_args() if options.stable: projects = utils.get_projects_info("projects/stable.json", False) else: projects = utils.get_projects_info(options.project, options.all) if not projects: print "Please specify a project." sys.exit(1) reviewers = {} now = datetime.datetime.utcnow() cut_off = now - datetime.timedelta(days=options.days) ts = calendar.timegm(cut_off.timetuple()) now_ts = calendar.timegm(now.timetuple()) change_stats = {"patches": 0, "created": 0, "involved": 0, "merged": 0, "abandoned": 0, "wip": 0} for project in projects: changes = utils.get_changes([project], options.user, options.key, stable=options.stable, server=options.server) for change in changes: patch_for_change = False first_patchset = True for patchset in change.get("patchSets", []): process_patchset(project, patchset, reviewers, ts, options) age = utils.get_age_of_patch(patchset, now_ts) if (now_ts - age) > ts: change_stats["patches"] += 1 patch_for_change = True if first_patchset: change_stats["created"] += 1 first_patchset = False if patch_for_change: change_stats["involved"] += 1 if change["status"] == "MERGED": change_stats["merged"] += 1 elif change["status"] == "ABANDONED": change_stats["abandoned"] += 1 elif change["status"] == "WORKINPROGRESS": change_stats["wip"] += 1 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 = [] totals = {"all": 0, "core": 0} for k, v in reviewers: in_core_team = False for project in projects: if v in utils.get_core_team(project, options.server, options.user, options.password): 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"]) all_reviews = plus + minus ratio = ((plus / (all_reviews)) * 100) if all_reviews > 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"]) / all_reviews) * 100) if all_reviews else 0.0 d = (k["disagreements"], "%5.1f%%" % dratio) sratio = (float(k["total"]) / k["received"]) * 100 if k["received"] else 0 s = (k["received"], "%5.1f%%" % sratio if k["received"] else "inf") reviewer_data.append((name, r, d, s)) totals["all"] += k["total"] if in_core_team: totals["core"] += 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] writer(reviewer_data, file_obj, options, reviewers, projects, totals, change_stats) finally: if on_done: on_done() 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(): 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(): parser = ArgumentParser( description="Calculate some statistics about project bugs.", epilog=dedent("""\ Known caveats: Historical data uses the current task metadata rather than historical values. This is primarily due to performance considerations with the LP API and may be rectified in future (e.g. by mirroring the data persistently). As an example, if a bug is currently set to 'critical', it will show as critical in all time periods rather than progressing through different categories as it is triaged. """)) parser.add_argument( '-p', '--project', default='projects/nova.json', help='JSON file describing the project to generate stats for.') args = parser.parse_args() projects = utils.get_projects_info(args.project, False) lp_project_listeners = {} listeners = set() if not projects: sys.stderr.write('No projects found: please specify one or more.\n') return 1 launchpad = Launchpad.login_with('openstack-releasing', 'production', credentials_file='.lpcreds') for project in projects: lp_projects = project.get('lp_projects', []) if not lp_projects: print "Please specify a project." return 1 listener = Listener(project['name'], lp_projects) listeners.add(listener) for lp_project in project.get('lp_projects', []): lp_project_listeners.setdefault(lp_project, []).append(listener) statuses = [ 'New', 'Incomplete', 'Opinion', 'Invalid', "Won't Fix", 'Confirmed', 'Triaged', 'In Progress', "Fix Committed", "Fix Released" ] bugs_by_bug_link = {} for lp_project, receivers in lp_project_listeners.items(): proj = launchpad.projects[lp_project] # Sort by id to make creating time periods easy. bugtasks = proj.searchTasks(status=statuses, order_by="id") for task in bugtasks: if task.bug_link not in bugs_by_bug_link: bugs_by_bug_link[task.bug_link] = task.bug bug = bugs_by_bug_link[task.bug_link] for receiver in receivers: receiver.categorise_task(task, bug) for listener in listeners: sys.stdout.write("Project: %s\n" % listener.name) sys.stdout.write("LP Projects: %s\n" % listener.lp_projects) table = prettytable.PrettyTable( ('Period', 'critical', 'high', 'undecided', 'other', 'total', 'created', 'closed', 'critical-tags')) for period in listener.summarise(): table.add_row(period) sys.stdout.write("%s\n" % table)
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( '-s', '--stable', default='', metavar='BRANCH', help='Generate stats for the specified stable BRANCH ("havana") ' 'across all integrated projects') 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('-P', '--password', default=getpass.getuser(), help='gerrit HTTP password') optparser.add_option('-k', '--key', default=None, help='ssh key for gerrit') optparser.add_option('-r', '--csv-rows', default=0, help='Max rows for CSV output', type='int', dest='csv_rows') optparser.add_option('--server', default='review.openstack.org', help='Gerrit server to connect to') options, args = optparser.parse_args() if options.stable: projects = utils.get_projects_info('projects/stable.json', False) else: projects = utils.get_projects_info(options.project, options.all) if not projects: print "Please specify a project." sys.exit(1) reviewers = {} now = datetime.datetime.utcnow() cut_off = now - datetime.timedelta(days=options.days) ts = calendar.timegm(cut_off.timetuple()) now_ts = calendar.timegm(now.timetuple()) change_stats = { 'patches': 0, 'created': 0, 'involved': 0, 'merged': 0, 'abandoned': 0, 'wip': 0, } for project in projects: changes = utils.get_changes([project], options.user, options.key, stable=options.stable, server=options.server) for change in changes: patch_for_change = False first_patchset = True for patchset in change.get('patchSets', []): process_patchset(project, patchset, reviewers, ts, options) age = utils.get_age_of_patch(patchset, now_ts) if (now_ts - age) > ts: change_stats['patches'] += 1 patch_for_change = True if first_patchset: change_stats['created'] += 1 first_patchset = False if patch_for_change: change_stats['involved'] += 1 if change['status'] == 'MERGED': change_stats['merged'] += 1 elif change['status'] == 'ABANDONED': change_stats['abandoned'] += 1 elif change['status'] == 'WORKINPROGRESS': change_stats['wip'] += 1 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 = [] totals = { 'all': 0, 'core': 0, } for k, v in reviewers: in_core_team = False for project in projects: if v in utils.get_core_team(project, options.server, options.user, options.password): 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']) all_reviews = plus + minus ratio = ((plus / (all_reviews)) * 100) if all_reviews > 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']) / all_reviews) * 100) if all_reviews else 0.0) d = (k['disagreements'], "%5.1f%%" % dratio) sratio = ((float(k['total']) / k['received']) * 100 if k['received'] else 0) s = (k['received'], "%5.1f%%" % sratio if k['received'] else 'inf') reviewer_data.append((name, r, d, s)) totals['all'] += k['total'] if in_core_team: totals['core'] += 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] writer(reviewer_data, file_obj, options, reviewers, projects, totals, change_stats) finally: if on_done: on_done() return 0
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( '-s', '--stable', default='', metavar='BRANCH', help='Generate stats for the specified stable BRANCH ("havana") ' 'across all integrated projects') 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') optparser.add_option( '-r', '--csv-rows', default=0, help='Max rows for CSV output', type='int', dest='csv_rows') options, args = optparser.parse_args() if options.stable: projects = utils.get_projects_info('projects/stable.json', False) else: projects = utils.get_projects_info(options.project, options.all) if not projects: print "Please specify a project." sys.exit(1) reviewers = {} now = datetime.datetime.utcnow() cut_off = now - datetime.timedelta(days=options.days) ts = calendar.timegm(cut_off.timetuple()) now_ts = calendar.timegm(now.timetuple()) change_stats = { 'patches': 0, 'created': 0, 'involved': 0, 'merged': 0, 'abandoned': 0, 'wip': 0, } for project in projects: changes = utils.get_changes([project], options.user, options.key, stable=options.stable) for change in changes: patch_for_change = False first_patchset = True for patchset in change.get('patchSets', []): process_patchset(project, patchset, reviewers, ts) age = utils.get_age_of_patch(patchset, now_ts) if (now_ts - age) > ts: change_stats['patches'] += 1 patch_for_change = True if first_patchset: change_stats['created'] += 1 first_patchset = False if patch_for_change: change_stats['involved'] += 1 if change['status'] == 'MERGED': change_stats['merged'] += 1 elif change['status'] == 'ABANDONED': change_stats['abandoned'] += 1 elif change['status'] == 'WORKINPROGRESS': change_stats['wip'] += 1 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 = [] totals = { 'all': 0, 'core': 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']) all_reviews = plus + minus ratio = ((plus / (all_reviews)) * 100) if all_reviews > 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']) / all_reviews) * 100) if all_reviews else 0.0) d = (k['disagreements'], "%5.1f%%" % dratio) sratio = ((float(k['total']) / k['received']) * 100 if k['received'] else 0) s = (k['received'], "%5.1f%%" % sratio if k['received'] else 'inf') reviewer_data.append((name, r, d, s)) totals['all'] += k['total'] if in_core_team: totals['core'] += 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] writer(reviewer_data, file_obj, options, reviewers, projects, totals, change_stats) finally: if on_done: on_done() return 0
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('--server', default='review.openstack.org', help='Gerrit server to connect to') 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, server=options.server) 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)