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

Side by Side 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, 2 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Get rietveld stats about the review you done, or forgot to do. 6 """Get rietveld stats about the review you done, or forgot to do.
7 7
8 Example: 8 Example:
9 - my_reviews.py -r me@chromium.org -Q for stats for last quarter. 9 - my_reviews.py -r me@chromium.org -Q for stats for last quarter.
10 """ 10 """
11 import datetime 11 import datetime
12 import math
12 import optparse 13 import optparse
13 import os 14 import os
14 import sys 15 import sys
15 16
16 import rietveld 17 import rietveld
17 18
18 19
19 def username(email): 20 def username(email):
21 """Keeps the username of an email address."""
20 return email.split('@', 1)[0] 22 return email.split('@', 1)[0]
21 23
22 24
25 def to_datetime(string):
26 """Load UTC time as a string into a datetime object."""
27 try:
28 # Format is 2011-07-05 01:26:12.084316
29 return datetime.datetime.strptime(
30 string.split('.', 1)[0], '%Y-%m-%d %H:%M:%S')
31 except ValueError:
32 return datetime.datetime.strptime(string, '%Y-%m-%d')
33
34
35 def to_time(seconds):
36 """Convert a number of seconds into human readable compact string."""
37 prefix = ''
38 if seconds < 0:
39 prefix = '-'
40 seconds *= -1
41 minutes = math.floor(seconds / 60)
42 seconds -= minutes * 60
43 hours = math.floor(minutes / 60)
44 minutes -= hours * 60
45 days = math.floor(hours / 24)
46 hours -= days * 24
47 out = []
48 if days > 0:
49 out.append('%dd' % days)
50 if hours > 0 or days > 0:
51 out.append('%02dh' % hours)
52 if minutes > 0 or hours > 0 or days > 0:
53 out.append('%02dm' % minutes)
54 if seconds > 0 and not out:
55 # Skip seconds unless there's only seconds.
56 out.append('%02ds' % seconds)
57 return prefix + ''.join(out)
58
59
60 class Stats(object):
61 def __init__(self):
62 self.total = 0
63 self.actually_reviewed = 0
64 self.average_latency = 0.
65 self.number_latency = 0
66 self.lgtms = 0
67 self.multiple_lgtms = 0
68 self.drive_by = 0
69 self.not_requested = 0
70
71 self.percent_done = 0.
72 self.percent_lgtm = 0.
73 self.percent_drive_by = 0.
74 self.percent_not_requested = 0.
75 self.days = None
76 self.review_per_day = 0.
77 self.review_done_per_day = 0.
78
79 def add_latency(self, latency):
80 self.average_latency = (
81 (self.average_latency * self.number_latency + latency) /
82 (self.number_latency + 1.))
83 self.number_latency += 1
84
85 def finalize(self, first_day, last_day):
86 if self.total:
87 self.percent_done = (self.actually_reviewed * 100. / self.total)
88 if self.actually_reviewed:
89 self.percent_lgtm = (self.lgtms * 100. / self.actually_reviewed)
90 self.percent_drive_by = (self.drive_by * 100. / self.actually_reviewed)
91 self.percent_not_requested = (
92 self.not_requested * 100. / self.actually_reviewed)
93 if first_day and last_day:
94 self.days = (to_datetime(last_day) - to_datetime(first_day)).days + 1
95 if self.days:
96 self.review_per_day = self.total * 1. / self.days
97 self.review_done_per_day = self.actually_reviewed * 1. / self.days
98
99
100 def _process_issue_lgtms(issue, reviewer, stats):
101 """Calculates LGTMs stats."""
102 stats.actually_reviewed += 1
103 reviewer_lgtms = len([
104 msg for msg in issue['messages']
105 if msg['approval'] and msg['sender'] == reviewer])
106 if reviewer_lgtms > 1:
107 stats.multiple_lgtms += 1
108 return ' X '
109 if reviewer_lgtms:
110 stats.lgtms += 1
111 return ' x '
112 else:
113 return ' o '
114
115
116 def _process_issue_latency(issue, reviewer, stats):
117 """Calculates latency for an issue that was actually reviewed."""
118 from_owner = [
119 msg for msg in issue['messages'] if msg['sender'] == issue['owner_email']
120 ]
121 if not from_owner:
122 # Probably requested by email.
123 stats.not_requested += 1
124 return '<no rqst sent>'
125
126 first_msg_from_owner = None
127 latency = None
128 received = False
129 for index, msg in enumerate(issue['messages']):
130 if not first_msg_from_owner and msg['sender'] == issue['owner_email']:
131 first_msg_from_owner = msg
132 if index and not received and msg['sender'] == reviewer:
133 # Not first email, reviewer never received one, reviewer sent a mesage.
134 stats.drive_by += 1
135 return '<drive-by>'
136 received |= reviewer in msg['recipients']
137
138 if first_msg_from_owner and msg['sender'] == reviewer:
139 delta = msg['date'] - first_msg_from_owner['date']
140 latency = delta.seconds + delta.days * 24 * 3600
141 break
142
143 if latency is None:
144 stats.not_requested += 1
145 return '<no rqst sent>'
146 if latency > 0:
147 stats.add_latency(latency)
148 else:
149 stats.not_requested += 1
150 return to_time(latency)
151
152
153 def _process_issue(issue):
154 """Preprocesses the issue to simplify the remaining code."""
155 issue['owner_email'] = username(issue['owner_email'])
156 issue['reviewers'] = set(username(r) for r in issue['reviewers'])
157 # By default, hide commit-bot.
158 issue['reviewers'] -= set(['commit-bot'])
159 for msg in issue['messages']:
160 msg['sender'] = username(msg['sender'])
161 msg['recipients'] = [username(r) for r in msg['recipients']]
162 # Convert all times to datetime instances.
163 msg['date'] = to_datetime(msg['date'])
164 issue['messages'].sort(key=lambda x: x['date'])
165
166
167 def print_issue(issue, reviewer, stats):
168 """Process an issue and prints stats about it."""
169 stats.total += 1
170 _process_issue(issue)
171 if any(msg['sender'] == reviewer for msg in issue['messages']):
172 reviewed = _process_issue_lgtms(issue, reviewer, stats)
173 latency = _process_issue_latency(issue, reviewer, stats)
174 else:
175 latency = 'N/A'
176 reviewed = ''
177
178 # More information is available, print issue.keys() to see them.
179 print '%7d %10s %3s %14s %-15s %s' % (
180 issue['issue'],
181 issue['created'][:10],
182 reviewed,
183 latency,
184 issue['owner_email'],
185 ', '.join(sorted(issue['reviewers'])))
186
187
23 def print_reviews(reviewer, created_after, created_before, instance_url): 188 def print_reviews(reviewer, created_after, created_before, instance_url):
24 """Prints issues the dude reviewed.""" 189 """Prints issues |reviewer| received and potentially reviewed."""
25 remote = rietveld.Rietveld(instance_url, None, None) 190 remote = rietveld.Rietveld(instance_url, None, None)
26 total = 0 191
27 actually_reviewed = 0 192 # The stats we gather. Feel free to send me a CL to get more stats.
193 stats = Stats()
194
195 last_issue = None
196 first_day = None
197 last_day = None
198
199 # Column sizes need to match print_issue() output.
200 print >> sys.stderr, (
201 'Issue Creation Did Latency Owner Reviewers')
28 202
29 # See def search() in rietveld.py to see all the filters you can use. 203 # See def search() in rietveld.py to see all the filters you can use.
30 for issue in remote.search( 204 for issue in remote.search(
31 reviewer=reviewer, 205 reviewer=reviewer,
32 created_after=created_after, 206 created_after=created_after,
33 created_before=created_before, 207 created_before=created_before,
34 with_messages=True, 208 with_messages=True):
35 ): 209 last_issue = issue
36 total += 1 210 if not first_day:
37 # By default, hide commit-bot and the domain. 211 first_day = issue['created'][:10]
38 reviewers = set(username(r) for r in issue['reviewers']) 212 print_issue(issue, username(reviewer), stats)
39 reviewers -= set(['commit-bot']) 213 if last_issue:
40 # Strip time. 214 last_day = last_issue['created'][:10]
41 timestamp = issue['created'][:10] 215 stats.finalize(first_day, last_day)
42 if any( 216
43 username(msg['sender']) == username(reviewer) 217 print >> sys.stderr, (
44 for msg in issue['messages']): 218 '%s reviewed %d issues out of %d (%1.1f%%).' %
45 reviewed = ' x ' 219 (reviewer, stats.actually_reviewed, stats.total, stats.percent_done))
46 actually_reviewed += 1 220 print >> sys.stderr, (
47 else: 221 '%4.1f review request/day during %3d days (%4.1f r/d done).' % (
48 reviewed = ' ' 222 stats.review_per_day, stats.days, stats.review_done_per_day))
49 223 print >> sys.stderr, (
50 # More information is available, print issue.keys() to see them. 224 '%4d were drive-bys (%5.1f%% of reviews done).' % (
51 print '%7d %s %s O:%-15s R:%s' % ( 225 stats.drive_by, stats.percent_drive_by))
52 issue['issue'], 226 print >> sys.stderr, (
53 timestamp, 227 '%4d were requested over IM or irc (%5.1f%% of reviews done).' % (
54 reviewed, 228 stats.not_requested, stats.percent_not_requested))
55 username(issue['owner_email']), 229 print >> sys.stderr, (
56 ', '.join(reviewers)) 230 ('%4d issues LGTM\'d (%5.1f%% of reviews done),'
57 percent = 0. 231 ' gave multiple LGTMs on %d issues.') % (
58 if total: 232 stats.lgtms, stats.percent_lgtm, stats.multiple_lgtms))
59 percent = (actually_reviewed * 100. / total) 233 print >> sys.stderr, (
60 print 'You actually reviewed %d issues out of %d (%1.1f%%)' % ( 234 'Average latency from request to first comment is %s.' %
61 actually_reviewed, total, percent) 235 to_time(stats.average_latency))
62 236
63 237
64 def print_count(reviewer, created_after, created_before, instance_url): 238 def print_count(reviewer, created_after, created_before, instance_url):
65 remote = rietveld.Rietveld(instance_url, None, None) 239 remote = rietveld.Rietveld(instance_url, None, None)
66 print len(list(remote.search( 240 print len(list(remote.search(
67 reviewer=reviewer, 241 reviewer=reviewer,
68 created_after=created_after, 242 created_after=created_after,
69 created_before=created_before, 243 created_before=created_before,
70 keys_only=True))) 244 keys_only=True)))
71 245
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 print_reviews( 318 print_reviews(
145 options.reviewer, 319 options.reviewer,
146 options.begin, 320 options.begin,
147 options.end, 321 options.end,
148 options.instance_url) 322 options.instance_url)
149 return 0 323 return 0
150 324
151 325
152 if __name__ == '__main__': 326 if __name__ == '__main__':
153 sys.exit(main()) 327 sys.exit(main())
OLDNEW
« 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