Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Unified Diff: my_reviews.py

Issue 8034001: Add review latency and other juicy stats like the number of review per day. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: . Created 9 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: my_reviews.py
diff --git a/my_reviews.py b/my_reviews.py
index 3e1ab73251d7f008288e879362be2e0047680134..1d2377278ee3a9493b56cddcd97cfcd73749720d 100755
--- a/my_reviews.py
+++ b/my_reviews.py
@@ -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):
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698