Commit 34dd5ebb authored by maruel@chromium.org's avatar maruel@chromium.org

Add review latency and other juicy stats like the number of review per day.

R=dpranke@chromium.org
BUG=
TEST=


Review URL: http://codereview.chromium.org/8034001

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@102578 0039d316-1c4b-4281-b951-d872f2087c98
parent 49fcb0cb
......@@ -9,6 +9,7 @@ Example:
- my_reviews.py -r me@chromium.org -Q for stats for last quarter.
"""
import datetime
import math
import optparse
import os
import sys
......@@ -17,48 +18,221 @@ import rietveld
def username(email):
"""Keeps the username of an email address."""
return email.split('@', 1)[0]
def to_datetime(string):
"""Load UTC time as a string into a datetime object."""
try:
# Format is 2011-07-05 01:26:12.084316
return datetime.datetime.strptime(
string.split('.', 1)[0], '%Y-%m-%d %H:%M:%S')
except ValueError:
return datetime.datetime.strptime(string, '%Y-%m-%d')
def to_time(seconds):
"""Convert a number of seconds into human readable compact string."""
prefix = ''
if seconds < 0:
prefix = '-'
seconds *= -1
minutes = math.floor(seconds / 60)
seconds -= minutes * 60
hours = math.floor(minutes / 60)
minutes -= hours * 60
days = math.floor(hours / 24)
hours -= days * 24
out = []
if days > 0:
out.append('%dd' % days)
if hours > 0 or days > 0:
out.append('%02dh' % hours)
if minutes > 0 or hours > 0 or days > 0:
out.append('%02dm' % minutes)
if seconds > 0 and not out:
# Skip seconds unless there's only seconds.
out.append('%02ds' % seconds)
return prefix + ''.join(out)
class Stats(object):
def __init__(self):
self.total = 0
self.actually_reviewed = 0
self.average_latency = 0.
self.number_latency = 0
self.lgtms = 0
self.multiple_lgtms = 0
self.drive_by = 0
self.not_requested = 0
self.percent_done = 0.
self.percent_lgtm = 0.
self.percent_drive_by = 0.
self.percent_not_requested = 0.
self.days = None
self.review_per_day = 0.
self.review_done_per_day = 0.
def add_latency(self, latency):
self.average_latency = (
(self.average_latency * self.number_latency + latency) /
(self.number_latency + 1.))
self.number_latency += 1
def finalize(self, first_day, last_day):
if self.total:
self.percent_done = (self.actually_reviewed * 100. / self.total)
if self.actually_reviewed:
self.percent_lgtm = (self.lgtms * 100. / self.actually_reviewed)
self.percent_drive_by = (self.drive_by * 100. / self.actually_reviewed)
self.percent_not_requested = (
self.not_requested * 100. / self.actually_reviewed)
if first_day and last_day:
self.days = (to_datetime(last_day) - to_datetime(first_day)).days + 1
if self.days:
self.review_per_day = self.total * 1. / self.days
self.review_done_per_day = self.actually_reviewed * 1. / self.days
def _process_issue_lgtms(issue, reviewer, stats):
"""Calculates LGTMs stats."""
stats.actually_reviewed += 1
reviewer_lgtms = len([
msg for msg in issue['messages']
if msg['approval'] and msg['sender'] == reviewer])
if reviewer_lgtms > 1:
stats.multiple_lgtms += 1
return ' X '
if reviewer_lgtms:
stats.lgtms += 1
return ' x '
else:
return ' o '
def _process_issue_latency(issue, reviewer, stats):
"""Calculates latency for an issue that was actually reviewed."""
from_owner = [
msg for msg in issue['messages'] if msg['sender'] == issue['owner_email']
]
if not from_owner:
# Probably requested by email.
stats.not_requested += 1
return '<no rqst sent>'
first_msg_from_owner = None
latency = None
received = False
for index, msg in enumerate(issue['messages']):
if not first_msg_from_owner and msg['sender'] == issue['owner_email']:
first_msg_from_owner = msg
if index and not received and msg['sender'] == reviewer:
# Not first email, reviewer never received one, reviewer sent a mesage.
stats.drive_by += 1
return '<drive-by>'
received |= reviewer in msg['recipients']
if first_msg_from_owner and msg['sender'] == reviewer:
delta = msg['date'] - first_msg_from_owner['date']
latency = delta.seconds + delta.days * 24 * 3600
break
if latency is None:
stats.not_requested += 1
return '<no rqst sent>'
if latency > 0:
stats.add_latency(latency)
else:
stats.not_requested += 1
return to_time(latency)
def _process_issue(issue):
"""Preprocesses the issue to simplify the remaining code."""
issue['owner_email'] = username(issue['owner_email'])
issue['reviewers'] = set(username(r) for r in issue['reviewers'])
# By default, hide commit-bot.
issue['reviewers'] -= set(['commit-bot'])
for msg in issue['messages']:
msg['sender'] = username(msg['sender'])
msg['recipients'] = [username(r) for r in msg['recipients']]
# Convert all times to datetime instances.
msg['date'] = to_datetime(msg['date'])
issue['messages'].sort(key=lambda x: x['date'])
def print_issue(issue, reviewer, stats):
"""Process an issue and prints stats about it."""
stats.total += 1
_process_issue(issue)
if any(msg['sender'] == reviewer for msg in issue['messages']):
reviewed = _process_issue_lgtms(issue, reviewer, stats)
latency = _process_issue_latency(issue, reviewer, stats)
else:
latency = 'N/A'
reviewed = ''
# More information is available, print issue.keys() to see them.
print '%7d %10s %3s %14s %-15s %s' % (
issue['issue'],
issue['created'][:10],
reviewed,
latency,
issue['owner_email'],
', '.join(sorted(issue['reviewers'])))
def print_reviews(reviewer, created_after, created_before, instance_url):
"""Prints issues the dude reviewed."""
"""Prints issues |reviewer| received and potentially reviewed."""
remote = rietveld.Rietveld(instance_url, None, None)
total = 0
actually_reviewed = 0
# The stats we gather. Feel free to send me a CL to get more stats.
stats = Stats()
last_issue = None
first_day = None
last_day = None
# Column sizes need to match print_issue() output.
print >> sys.stderr, (
'Issue Creation Did Latency Owner Reviewers')
# See def search() in rietveld.py to see all the filters you can use.
for issue in remote.search(
reviewer=reviewer,
created_after=created_after,
created_before=created_before,
with_messages=True,
):
total += 1
# By default, hide commit-bot and the domain.
reviewers = set(username(r) for r in issue['reviewers'])
reviewers -= set(['commit-bot'])
# Strip time.
timestamp = issue['created'][:10]
if any(
username(msg['sender']) == username(reviewer)
for msg in issue['messages']):
reviewed = ' x '
actually_reviewed += 1
else:
reviewed = ' '
# More information is available, print issue.keys() to see them.
print '%7d %s %s O:%-15s R:%s' % (
issue['issue'],
timestamp,
reviewed,
username(issue['owner_email']),
', '.join(reviewers))
percent = 0.
if total:
percent = (actually_reviewed * 100. / total)
print 'You actually reviewed %d issues out of %d (%1.1f%%)' % (
actually_reviewed, total, percent)
with_messages=True):
last_issue = issue
if not first_day:
first_day = issue['created'][:10]
print_issue(issue, username(reviewer), stats)
if last_issue:
last_day = last_issue['created'][:10]
stats.finalize(first_day, last_day)
print >> sys.stderr, (
'%s reviewed %d issues out of %d (%1.1f%%).' %
(reviewer, stats.actually_reviewed, stats.total, stats.percent_done))
print >> sys.stderr, (
'%4.1f review request/day during %3d days (%4.1f r/d done).' % (
stats.review_per_day, stats.days, stats.review_done_per_day))
print >> sys.stderr, (
'%4d were drive-bys (%5.1f%% of reviews done).' % (
stats.drive_by, stats.percent_drive_by))
print >> sys.stderr, (
'%4d were requested over IM or irc (%5.1f%% of reviews done).' % (
stats.not_requested, stats.percent_not_requested))
print >> sys.stderr, (
('%4d issues LGTM\'d (%5.1f%% of reviews done),'
' gave multiple LGTMs on %d issues.') % (
stats.lgtms, stats.percent_lgtm, stats.multiple_lgtms))
print >> sys.stderr, (
'Average latency from request to first comment is %s.' %
to_time(stats.average_latency))
def print_count(reviewer, created_after, created_before, instance_url):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment