| Index: commit-queue/tools/count.py
|
| ===================================================================
|
| --- commit-queue/tools/count.py (revision 249146)
|
| +++ commit-queue/tools/count.py (working copy)
|
| @@ -1,217 +0,0 @@
|
| -#!/usr/bin/env python
|
| -# Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
| -# Use of this source code is governed by a BSD-style license that can be
|
| -# found in the LICENSE file.
|
| -"""Count commits by the commit queue."""
|
| -
|
| -import datetime
|
| -import json
|
| -import logging
|
| -import optparse
|
| -import os
|
| -import re
|
| -import sys
|
| -from xml.etree import ElementTree
|
| -
|
| -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| -
|
| -import find_depot_tools # pylint: disable=W0611
|
| -import subprocess2
|
| -
|
| -
|
| -def log(repo, args):
|
| - """If extra is True, grab one revision before and one after."""
|
| - args = args or []
|
| - out = subprocess2.check_output(
|
| - ['svn', 'log', '--with-all-revprops', '--xml', repo] + args)
|
| - data = {}
|
| - for logentry in ElementTree.XML(out).findall('logentry'):
|
| - date_str = logentry.find('date').text
|
| - date = datetime.datetime(*map(int, re.split('[^\d]', date_str)[:-1]))
|
| - entry = {
|
| - 'author': logentry.find('author').text,
|
| - 'date': date,
|
| - 'msg': logentry.find('msg').text,
|
| - 'revprops': {},
|
| - 'commit-bot': False,
|
| - }
|
| - revprops = logentry.find('revprops')
|
| - if revprops is not None:
|
| - for revprop in revprops.findall('property'):
|
| - entry['revprops'][revprop.attrib['name']] = revprop.text
|
| - if revprop.attrib['name'] == 'commit-bot':
|
| - entry['commit-bot'] = True
|
| - data[logentry.attrib['revision']] = entry
|
| - return data
|
| -
|
| -
|
| -def log_dates(repo, start_date, days):
|
| - """Formats dates so 'svn log' does the right thing. Fetches everything in UTC.
|
| - """
|
| - # http://svnbook.red-bean.com/nightly/en/svn-book.html#svn.tour.revs.dates
|
| - if not days:
|
| - end_inclusive = datetime.date.today()
|
| - else:
|
| - end_inclusive = start_date + datetime.timedelta(days=days)
|
| - actual_days = (end_inclusive - start_date).days
|
| - print('Getting data from %s for %s days' % (start_date, actual_days))
|
| - range_str = (
|
| - '{%s 00:00:00 +0000}:{%s 00:00:00 +0000}' % (start_date, end_inclusive))
|
| - data = log(repo, ['-r', range_str])
|
| - # Strip off everything outside the range.
|
| - start_date_time = datetime.datetime(*start_date.timetuple()[:6])
|
| - if data:
|
| - first = sorted(data.keys())[0]
|
| - if data[first]['date'] < start_date_time:
|
| - del data[first]
|
| - # Strip the commit message to save space.
|
| - for item in data.itervalues():
|
| - del item['msg']
|
| - return data
|
| -
|
| -
|
| -def monday_last_week():
|
| - """Returns Monday in 'date' object."""
|
| - today = datetime.date.today()
|
| - last_week = today - datetime.timedelta(days=7)
|
| - return last_week - datetime.timedelta(days=(last_week.isoweekday() - 1))
|
| -
|
| -
|
| -class JSONEncoder(json.JSONEncoder):
|
| - def default(self, o): # pylint: disable=E0202
|
| - if isinstance(o, datetime.datetime):
|
| - return str(o)
|
| - return super(JSONEncoder, self)
|
| -
|
| -
|
| -def print_aligned(zipped_list):
|
| - max_len = max(len(i[0]) for i in zipped_list)
|
| - for author, count in zipped_list:
|
| - print('%*s: %d' % (max_len, author, count))
|
| -
|
| -
|
| -def print_data(log_data, stats_only, top):
|
| - # Calculate stats.
|
| - num_commit_bot = len([True for v in log_data.itervalues() if v['commit-bot']])
|
| - num_total_commits = len(log_data)
|
| - pourcent = 0.
|
| - if num_total_commits:
|
| - pourcent = float(num_commit_bot) * 100. / float(num_total_commits)
|
| - users = {}
|
| - for i in log_data.itervalues():
|
| - if i['commit-bot']:
|
| - users.setdefault(i['author'], 0)
|
| - users[i['author']] += 1
|
| -
|
| - if not stats_only:
|
| - max_author_len = max(len(i['author']) for i in log_data.itervalues())
|
| - for revision in sorted(log_data.keys()):
|
| - entry = log_data[revision]
|
| - commit_bot = ' '
|
| - if entry['commit-bot']:
|
| - commit_bot = 'c'
|
| - print('%s %s %s %*s' % (
|
| - ('r%s' % revision).rjust(6),
|
| - commit_bot,
|
| - entry['date'].strftime('%Y-%m-%d %H:%M UTC'),
|
| - max_author_len,
|
| - entry['author']))
|
| - print('')
|
| -
|
| - if top:
|
| - top_users = sorted(
|
| - users.iteritems(), key=lambda x: x[1], reverse=True)[:top]
|
| - top_commits = sum(x[1] for x in top_users)
|
| - p_u = 100. * len(top_users) / len(users)
|
| - p_c = 100. * top_commits / num_commit_bot
|
| - print(
|
| - 'Top users: %6d out of %6d total users %6.2f%%' %
|
| - (len(top_users), len(users), p_u))
|
| - print(
|
| - ' Committed %6d out of %6d CQ\'ed commits %5.2f%%' %
|
| - (top_commits, num_commit_bot, p_c))
|
| - if not stats_only:
|
| - print_aligned(top_users)
|
| -
|
| - non_committers = sorted(
|
| - ( (u, c) for u, c in users.iteritems()
|
| - if not u.endswith('@chromium.org')),
|
| - key=lambda x: x[1], reverse=True)
|
| - if non_committers:
|
| - print('')
|
| - n_c_commits = sum(x[1] for x in non_committers)
|
| - p_u = 100. * len(non_committers) / len(users)
|
| - p_c = 100. * n_c_commits / num_commit_bot
|
| - print(
|
| - 'Non-committers: %6d out of %6d total users %6.2f%%' %
|
| - (len(non_committers), len(users), p_u))
|
| - print(
|
| - ' Committed %6d out of %6d CQ\'ed commits %5.2f%%' %
|
| - (n_c_commits, num_commit_bot, p_c))
|
| - print('')
|
| - print('Top domains')
|
| - domains = {}
|
| - for user, count in non_committers:
|
| - domain = user.split('@', 1)[1]
|
| - domains.setdefault(domain, 0)
|
| - domains[domain] += count
|
| - domains_stats = sorted(
|
| - ((k, v) for k, v in domains.iteritems()),
|
| - key=lambda x: x[1], reverse=True)
|
| - print_aligned(domains_stats)
|
| - if not stats_only:
|
| - print_aligned(non_committers)
|
| -
|
| - print('')
|
| - print('Total commits: %6d' % num_total_commits)
|
| - print(
|
| - 'Total commits by commit bot: %6d (%6.1f%%)' % (num_commit_bot, pourcent))
|
| -
|
| -
|
| -def main():
|
| - parser = optparse.OptionParser(
|
| - description=sys.modules['__main__'].__doc__)
|
| - parser.add_option('-v', '--verbose', action='store_true')
|
| - parser.add_option(
|
| - '-r', '--repo', default='http://src.chromium.org/svn/trunk')
|
| - parser.add_option('-s', '--since', action='store')
|
| - parser.add_option('-d', '--days', type=int, default=7)
|
| - parser.add_option('--all', action='store_true', help='Get ALL the revisions!')
|
| - parser.add_option('--dump', help='Dump json in file')
|
| - parser.add_option('--read', help='Read the data from a file')
|
| - parser.add_option('-o', '--stats_only', action='store_true')
|
| - parser.add_option('--top', default=20, type='int')
|
| - options, args = parser.parse_args(None)
|
| - if args:
|
| - parser.error('Unsupported args: %s' % args)
|
| - logging.basicConfig(
|
| - level=(logging.DEBUG if options.verbose else logging.ERROR))
|
| -
|
| - # By default, grab stats for last week.
|
| - if not options.since:
|
| - options.since = monday_last_week()
|
| - else:
|
| - options.since = datetime.date(*map(int, re.split('[^\d]', options.since)))
|
| -
|
| - if options.read:
|
| - if options.dump:
|
| - parser.error('Can\'t use --dump and --read simultaneously')
|
| - log_data = json.load(open(options.read, 'r'))
|
| - for entry in log_data.itervalues():
|
| - # Convert strings like "2012-09-04 01:14:43.785581" to a datetime object.
|
| - entry['date'] = datetime.datetime.strptime(
|
| - entry['date'], '%Y-%m-%d %H:%M:%S.%f')
|
| - else:
|
| - if options.all:
|
| - log_data = log(options.repo, [])
|
| - else:
|
| - log_data = log_dates(options.repo, options.since, options.days)
|
| - if options.dump:
|
| - json.dump(log_data, open(options.dump, 'w'), cls=JSONEncoder)
|
| -
|
| - print_data(log_data, options.stats_only, options.top)
|
| - return 0
|
| -
|
| -
|
| -if __name__ == '__main__':
|
| - sys.exit(main())
|
|
|