| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Pulls historical try job metadata from Findit and prints a report.""" | 5 """Pulls historical try job metadata from Findit and prints a report.""" |
| 6 | 6 |
| 7 import argparse | 7 import argparse |
| 8 from collections import defaultdict | 8 from collections import defaultdict |
| 9 import datetime | 9 import datetime |
| 10 import json | 10 import json |
| 11 import numpy | 11 import numpy |
| 12 import os | 12 import os |
| 13 import sys | 13 import sys |
| 14 | 14 |
| 15 try: |
| 16 from matplotlib import pyplot |
| 17 except ImportError: |
| 18 pyplot = None |
| 19 |
| 15 _REMOTE_API_DIR = os.path.join(os.path.dirname(__file__), os.path.pardir) | 20 _REMOTE_API_DIR = os.path.join(os.path.dirname(__file__), os.path.pardir) |
| 16 sys.path.insert(1, _REMOTE_API_DIR) | 21 sys.path.insert(1, _REMOTE_API_DIR) |
| 17 | 22 |
| 18 import remote_api | 23 import remote_api |
| 19 | 24 |
| 20 from model.wf_try_job_data import WfTryJobData | 25 from model.wf_try_job_data import WfTryJobData |
| 21 | 26 |
| 22 | 27 |
| 23 NOT_AVAILABLE = 'N/A' | 28 NOT_AVAILABLE = 'N/A' |
| 24 | 29 |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 88 def _FormatSecondsAsHMS(seconds): | 93 def _FormatSecondsAsHMS(seconds): |
| 89 """Formats the number of seconds into hours, minutes, seconds.""" | 94 """Formats the number of seconds into hours, minutes, seconds.""" |
| 90 if seconds == NOT_AVAILABLE: | 95 if seconds == NOT_AVAILABLE: |
| 91 return NOT_AVAILABLE | 96 return NOT_AVAILABLE |
| 92 | 97 |
| 93 minutes, seconds = divmod(seconds, 60) | 98 minutes, seconds = divmod(seconds, 60) |
| 94 hours, minutes = divmod(minutes, 60) | 99 hours, minutes = divmod(minutes, 60) |
| 95 return '%d:%02d:%02d' % (hours, minutes, seconds) | 100 return '%d:%02d:%02d' % (hours, minutes, seconds) |
| 96 | 101 |
| 97 | 102 |
| 103 def _GetRequestSpikes(request_times, time_window_seconds=30*60, |
| 104 minimum_spike_size=3, show_plot=False): |
| 105 """Calculates and plots try jobs by request time. |
| 106 |
| 107 Args: |
| 108 request_time: List of datetime objects representing try job request times. |
| 109 time_window_seconds: Maximum number of seconds between requests to count |
| 110 as a spike. |
| 111 minimum_spike_size: Minimum number of requests within the specified time |
| 112 window needed to count as a spike. |
| 113 show_plot: Boolean whether to display visual graphs of the request times. |
| 114 |
| 115 Returns: |
| 116 spike_count: The number of spikes found. |
| 117 average_spike_size: The average number of requests in each spike. |
| 118 maximum_spike_size: The number of requests in the biggest spike. |
| 119 """ |
| 120 request_times = sorted(request_times) |
| 121 |
| 122 if show_plot: |
| 123 if pyplot: |
| 124 pyplot.plot(request_times, [i for i in range(len(request_times))], 'x') |
| 125 pyplot.show() |
| 126 else: |
| 127 print ('In order to show plots, matplotlib needs to be installed. To ' |
| 128 'install, please run \'sudo pip install matplotlib\'') |
| 129 |
| 130 candidate_spike_start = request_times[0] |
| 131 points_in_spike = 1 |
| 132 spike_count = 0 |
| 133 spike_sizes = [] |
| 134 |
| 135 for point_being_examined in request_times[1:]: |
| 136 if ((point_being_examined - candidate_spike_start).total_seconds() < |
| 137 time_window_seconds): |
| 138 points_in_spike += 1 |
| 139 else: |
| 140 # The time window has passed. Need a new starting point. |
| 141 if points_in_spike >= minimum_spike_size: |
| 142 spike_count += 1 |
| 143 spike_sizes.append(points_in_spike) |
| 144 |
| 145 candidate_spike_start = point_being_examined |
| 146 points_in_spike = 1 # Start over. |
| 147 |
| 148 return (spike_count, _GetAverageOfNumbersInList(spike_sizes), |
| 149 max(spike_sizes) if spike_sizes else 0) |
| 150 |
| 151 |
| 98 def _GetReportInformation(try_job_data_list, start_date, end_date): | 152 def _GetReportInformation(try_job_data_list, start_date, end_date): |
| 99 """Computes and returns try job metadata. | 153 """Computes and returns try job metadata. |
| 100 | 154 |
| 101 Args: | 155 Args: |
| 102 try_job_data_list: A list of WfTryJobData entities. | 156 try_job_data_list: A list of WfTryJobData entities. |
| 103 start_date: The earliest request date to compute data. | 157 start_date: The earliest request date to compute data. |
| 104 end_date: The latest request date to compute data. | 158 end_date: The latest request date to compute data. |
| 105 | 159 |
| 106 Returns: | 160 Returns: |
| 107 A dict in the following format: | 161 A dict in the following format: |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 149 longest_execution_time = NOT_AVAILABLE | 203 longest_execution_time = NOT_AVAILABLE |
| 150 shortest_execution_time = NOT_AVAILABLE | 204 shortest_execution_time = NOT_AVAILABLE |
| 151 detection_rate = NOT_AVAILABLE | 205 detection_rate = NOT_AVAILABLE |
| 152 error_rate = NOT_AVAILABLE | 206 error_rate = NOT_AVAILABLE |
| 153 number_of_try_jobs = len(try_job_data_list) if try_job_data_list else 0 | 207 number_of_try_jobs = len(try_job_data_list) if try_job_data_list else 0 |
| 154 time_per_revision = NOT_AVAILABLE | 208 time_per_revision = NOT_AVAILABLE |
| 155 under_five_minutes_rate = NOT_AVAILABLE | 209 under_five_minutes_rate = NOT_AVAILABLE |
| 156 under_fifteen_minutes_rate = NOT_AVAILABLE | 210 under_fifteen_minutes_rate = NOT_AVAILABLE |
| 157 under_thirty_minutes_rate = NOT_AVAILABLE | 211 under_thirty_minutes_rate = NOT_AVAILABLE |
| 158 over_thirty_minutes_rate = NOT_AVAILABLE | 212 over_thirty_minutes_rate = NOT_AVAILABLE |
| 213 spike_count = NOT_AVAILABLE |
| 214 average_spike_size = NOT_AVAILABLE |
| 215 maximum_spike_size = NOT_AVAILABLE |
| 159 | 216 |
| 160 if try_job_data_list: | 217 if try_job_data_list: |
| 161 try_jobs_per_day = ( | 218 try_jobs_per_day = ( |
| 162 len(try_job_data_list) / float((end_date - start_date).days)) | 219 len(try_job_data_list) / float((end_date - start_date).days)) |
| 163 regression_range_sizes = [] | 220 regression_range_sizes = [] |
| 164 execution_times_seconds = [] | 221 execution_times_seconds = [] |
| 222 request_times = [] |
| 165 in_queue_times = [] | 223 in_queue_times = [] |
| 166 end_to_end_times = [] | 224 end_to_end_times = [] |
| 167 commits_analyzed = [] | 225 commits_analyzed = [] |
| 168 culprits_detected = 0 | 226 culprits_detected = 0 |
| 169 errors_detected = 0 | 227 errors_detected = 0 |
| 170 number_under_five_minutes = 0 | 228 number_under_five_minutes = 0 |
| 171 number_under_fifteen_minutes = 0 | 229 number_under_fifteen_minutes = 0 |
| 172 number_under_thirty_minutes = 0 | 230 number_under_thirty_minutes = 0 |
| 173 number_over_thirty_minutes = 0 | 231 number_over_thirty_minutes = 0 |
| 174 total_number_of_try_jobs = len(try_job_data_list) | 232 total_number_of_try_jobs = len(try_job_data_list) |
| (...skipping 11 matching lines...) Expand all Loading... |
| 186 execution_times_seconds.append(execution_time) | 244 execution_times_seconds.append(execution_time) |
| 187 | 245 |
| 188 # In-queue time. | 246 # In-queue time. |
| 189 if try_job_data.start_time and try_job_data.request_time: | 247 if try_job_data.start_time and try_job_data.request_time: |
| 190 in_queue_time_delta = ( | 248 in_queue_time_delta = ( |
| 191 try_job_data.start_time - try_job_data.request_time) | 249 try_job_data.start_time - try_job_data.request_time) |
| 192 in_queue_time = in_queue_time_delta.total_seconds() | 250 in_queue_time = in_queue_time_delta.total_seconds() |
| 193 in_queue_times.append(in_queue_time) | 251 in_queue_times.append(in_queue_time) |
| 194 | 252 |
| 195 # Total time end-to-end. | 253 # Total time end-to-end. |
| 196 if try_job_data.request_time and try_job_data.end_time: | 254 if try_job_data.request_time: |
| 197 total_time_delta = try_job_data.end_time - try_job_data.start_time | 255 request_times.append(try_job_data.request_time) |
| 198 total_time_seconds = total_time_delta.total_seconds() | |
| 199 end_to_end_times.append(total_time_seconds) | |
| 200 | 256 |
| 201 if total_time_seconds < 300: # Under 5 minutes. | 257 if try_job_data.end_time: |
| 202 number_under_five_minutes += 1 | 258 total_time_delta = try_job_data.end_time - try_job_data.start_time |
| 203 elif total_time_seconds < 900: # Under 15 minutes. | 259 total_time_seconds = total_time_delta.total_seconds() |
| 204 number_under_fifteen_minutes += 1 | 260 end_to_end_times.append(total_time_seconds) |
| 205 elif total_time_seconds < 1800: # Under 30 minutes. | 261 |
| 206 number_under_thirty_minutes += 1 | 262 if total_time_seconds < 300: # Under 5 minutes. |
| 207 else: # Over 30 minutes. | 263 number_under_five_minutes += 1 |
| 208 number_over_thirty_minutes += 1 | 264 elif total_time_seconds < 900: # Under 15 minutes. |
| 265 number_under_fifteen_minutes += 1 |
| 266 elif total_time_seconds < 1800: # Under 30 minutes. |
| 267 number_under_thirty_minutes += 1 |
| 268 else: # Over 30 minutes. |
| 269 number_over_thirty_minutes += 1 |
| 209 | 270 |
| 210 # Number of commits analyzed. | 271 # Number of commits analyzed. |
| 211 if try_job_data.number_of_commits_analyzed: | 272 if try_job_data.number_of_commits_analyzed: |
| 212 commits_analyzed.append(try_job_data.number_of_commits_analyzed) | 273 commits_analyzed.append(try_job_data.number_of_commits_analyzed) |
| 213 | 274 |
| 214 # Culprit detection rate. | 275 # Culprit detection rate. |
| 215 if try_job_data.culprits: | 276 if try_job_data.culprits: |
| 216 culprits_detected += 1 | 277 culprits_detected += 1 |
| 217 | 278 |
| 218 if try_job_data.error: | 279 if try_job_data.error: |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 258 | 319 |
| 259 under_five_minutes_rate = ( | 320 under_five_minutes_rate = ( |
| 260 float(number_under_five_minutes) / total_number_of_try_jobs) | 321 float(number_under_five_minutes) / total_number_of_try_jobs) |
| 261 under_fifteen_minutes_rate = ( | 322 under_fifteen_minutes_rate = ( |
| 262 float(number_under_fifteen_minutes) / total_number_of_try_jobs) | 323 float(number_under_fifteen_minutes) / total_number_of_try_jobs) |
| 263 under_thirty_minutes_rate = ( | 324 under_thirty_minutes_rate = ( |
| 264 float(number_under_thirty_minutes) / total_number_of_try_jobs) | 325 float(number_under_thirty_minutes) / total_number_of_try_jobs) |
| 265 over_thirty_minutes_rate = ( | 326 over_thirty_minutes_rate = ( |
| 266 float(number_over_thirty_minutes) / total_number_of_try_jobs) | 327 float(number_over_thirty_minutes) / total_number_of_try_jobs) |
| 267 | 328 |
| 329 # Calculate try job spikes. |
| 330 spike_count, average_spike_size, maximum_spike_size = _GetRequestSpikes( |
| 331 request_times, time_window_seconds=30*60, minimum_spike_size=3, |
| 332 show_plot=False) |
| 333 |
| 268 return { | 334 return { |
| 269 'try_jobs_per_day': _FormatDigits(try_jobs_per_day), | 335 'try_jobs_per_day': _FormatDigits(try_jobs_per_day), |
| 270 'average_regression_range_size': _FormatDigits( | 336 'average_regression_range_size': _FormatDigits( |
| 271 average_regression_range_size), | 337 average_regression_range_size), |
| 272 'median_regression_range_size': median_regression_range_size, | 338 'median_regression_range_size': median_regression_range_size, |
| 273 'average_execution_time': _FormatSecondsAsHMS(_FormatDigits( | 339 'average_execution_time': _FormatSecondsAsHMS(_FormatDigits( |
| 274 average_execution_time)), | 340 average_execution_time)), |
| 275 'median_execution_time': _FormatSecondsAsHMS(_FormatDigits( | 341 'median_execution_time': _FormatSecondsAsHMS(_FormatDigits( |
| 276 median_execution_time)), | 342 median_execution_time)), |
| 277 'average_end_to_end_time': _FormatSecondsAsHMS(_FormatDigits( | 343 'average_end_to_end_time': _FormatSecondsAsHMS(_FormatDigits( |
| 278 average_end_to_end_time)), | 344 average_end_to_end_time)), |
| 279 'median_end_to_end_time': _FormatSecondsAsHMS(_FormatDigits( | 345 'median_end_to_end_time': _FormatSecondsAsHMS(_FormatDigits( |
| 280 median_end_to_end_time)), | 346 median_end_to_end_time)), |
| 281 'average_time_in_queue': _FormatSecondsAsHMS( | 347 'average_time_in_queue': _FormatSecondsAsHMS( |
| 282 _FormatDigits(average_time_in_queue)), | 348 _FormatDigits(average_time_in_queue)), |
| 283 'median_time_in_queue': _FormatSecondsAsHMS(_FormatDigits( | 349 'median_time_in_queue': _FormatSecondsAsHMS(_FormatDigits( |
| 284 median_time_in_queue)), | 350 median_time_in_queue)), |
| 285 'average_commits_analyzed': _FormatDigits(average_commits_analyzed), | 351 'average_commits_analyzed': _FormatDigits(average_commits_analyzed), |
| 286 'median_commits_analyzed': median_commits_analyzed, | 352 'median_commits_analyzed': median_commits_analyzed, |
| 287 'longest_execution_time': longest_execution_time, | 353 'longest_execution_time': longest_execution_time, |
| 288 'shortest_execution_time': shortest_execution_time, | 354 'shortest_execution_time': shortest_execution_time, |
| 289 'number_of_try_jobs': number_of_try_jobs, | 355 'number_of_try_jobs': number_of_try_jobs, |
| 290 'detection_rate': _FormatDigits(detection_rate), | 356 'detection_rate': _FormatDigits(detection_rate), |
| 291 'error_rate': _FormatDigits(error_rate), | 357 'error_rate': _FormatDigits(error_rate), |
| 292 'time_per_revision': _FormatSecondsAsHMS( | 358 'time_per_revision': _FormatSecondsAsHMS( |
| 293 _FormatDigits(time_per_revision)), | 359 _FormatDigits(time_per_revision)), |
| 294 'under_five_minutes_rate': _FormatDigits(under_five_minutes_rate), | 360 'under_five_minutes_rate': _FormatDigits(under_five_minutes_rate), |
| 295 'under_fifteen_minutes_rate': _FormatDigits(under_fifteen_minutes_rate), | 361 'under_fifteen_minutes_rate': _FormatDigits(under_fifteen_minutes_rate), |
| 296 'under_thirty_minutes_rate': _FormatDigits(under_thirty_minutes_rate), | 362 'under_thirty_minutes_rate': _FormatDigits(under_thirty_minutes_rate), |
| 297 'over_thirty_minutes_rate': _FormatDigits(over_thirty_minutes_rate) | 363 'over_thirty_minutes_rate': _FormatDigits(over_thirty_minutes_rate), |
| 364 'request_spike_count': spike_count, |
| 365 'request_spike_average_size': average_spike_size, |
| 366 'request_spike_maximum_size': maximum_spike_size, |
| 298 } | 367 } |
| 299 | 368 |
| 300 | 369 |
| 301 def PrintCommonStats(try_job_data_list, start_date, end_date, indent): | 370 def PrintCommonStats(try_job_data_list, start_date, end_date, indent): |
| 302 """Takes a list of WfTryJobData entities and prints their stats.""" | 371 """Takes a list of WfTryJobData entities and prints their stats.""" |
| 303 spaces = '' | 372 spaces = '' |
| 304 for _ in range(indent): | 373 for _ in range(indent): |
| 305 spaces += ' ' | 374 spaces += ' ' |
| 306 | 375 |
| 307 report_info = _GetReportInformation(try_job_data_list, start_date, end_date) | 376 report_info = _GetReportInformation(try_job_data_list, start_date, end_date) |
| (...skipping 208 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 516 if args_dict[parsed_arg]: | 585 if args_dict[parsed_arg]: |
| 517 ordered_args.append(parsed_arg) | 586 ordered_args.append(parsed_arg) |
| 518 | 587 |
| 519 return ordered_args | 588 return ordered_args |
| 520 | 589 |
| 521 | 590 |
| 522 if __name__ == '__main__': | 591 if __name__ == '__main__': |
| 523 # Set up the Remote API to use services on the live App Engine. | 592 # Set up the Remote API to use services on the live App Engine. |
| 524 remote_api.EnableRemoteApi(app_id='findit-for-me') | 593 remote_api.EnableRemoteApi(app_id='findit-for-me') |
| 525 | 594 |
| 526 START_DATE = datetime.datetime(2016, 5, 1) | 595 START_DATE = datetime.datetime(2016, 4, 17) |
| 527 END_DATE = datetime.datetime(2016, 6, 23) | 596 END_DATE = datetime.datetime(2016, 7, 15) |
| 528 | 597 |
| 529 try_job_data_query = WfTryJobData.query( | 598 try_job_data_query = WfTryJobData.query( |
| 530 WfTryJobData.request_time >= START_DATE, | 599 WfTryJobData.request_time >= START_DATE, |
| 531 WfTryJobData.request_time < END_DATE) | 600 WfTryJobData.request_time < END_DATE) |
| 532 categorized_data = try_job_data_query.fetch() | 601 categorized_data = try_job_data_query.fetch() |
| 533 | 602 |
| 534 args = GetArgsInOrder() | 603 args = GetArgsInOrder() |
| 535 for arg in args: | 604 for arg in args: |
| 536 categorized_data = SplitStructByOption(categorized_data, arg) | 605 categorized_data = SplitStructByOption(categorized_data, arg) |
| 537 | 606 |
| 538 # TODO(lijeffrey): Display data in an html page instead of printing. | 607 # TODO(lijeffrey): Display data in an html page instead of printing. |
| 539 PrettyPrint(categorized_data, START_DATE, END_DATE) | 608 PrettyPrint(categorized_data, START_DATE, END_DATE) |
| OLD | NEW |