Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2014 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 import os | 5 import os |
| 6 from threading import Lock, Thread | 6 from threading import Lock, Thread |
| 7 | 7 |
| 8 import blame | 8 import blame |
| 9 from common import utils | 9 from common import utils |
| 10 import component_dictionary | 10 import component_dictionary |
| 11 import crash_utils | 11 import crash_utils |
| 12 import git_repository_parser | 12 import git_repository_parser |
| 13 import match_set | 13 import match_set |
| 14 import svn_repository_parser | 14 import svn_repository_parser |
| 15 import svn_repository_parser | |
|
stgao
2014/08/22 06:50:54
duplicate
jeun
2014/08/22 22:58:43
Done.
| |
| 15 | 16 |
| 16 | 17 |
| 17 LINE_CHANGE_PRIORITY = 1 | 18 LINE_CHANGE_PRIORITY = 1 |
| 18 FILE_CHANGE_PRIORITY = 2 | 19 FILE_CHANGE_PRIORITY = 2 |
| 19 _THIS_DIR = os.path.abspath(os.path.dirname(__file__)) | 20 _THIS_DIR = os.path.abspath(os.path.dirname(__file__)) |
| 20 CONFIG = crash_utils.ParseURLsFromConfig(os.path.join(_THIS_DIR, | 21 CONFIG = crash_utils.ParseURLsFromConfig(os.path.join(_THIS_DIR, |
| 21 'config.ini')) | 22 'config.ini')) |
| 22 | 23 |
| 23 | 24 |
| 24 def GenerateMatchEntry( | 25 def GenerateMatchEntry( |
| (...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 138 threads = [] | 139 threads = [] |
| 139 | 140 |
| 140 # Iterate through the crashed files in the stacktrace. | 141 # Iterate through the crashed files in the stacktrace. |
| 141 for crashed_file_path in file_to_crash_info: | 142 for crashed_file_path in file_to_crash_info: |
| 142 # Ignore header file. | 143 # Ignore header file. |
| 143 if crashed_file_path.endswith('.h'): | 144 if crashed_file_path.endswith('.h'): |
| 144 continue | 145 continue |
| 145 | 146 |
| 146 # If the file in the stacktrace is not changed in any commits, continue. | 147 # If the file in the stacktrace is not changed in any commits, continue. |
| 147 for changed_file_path in file_to_revision_info: | 148 for changed_file_path in file_to_revision_info: |
| 148 changed_file_name = changed_file_path.split('/')[-1] | 149 changed_file_name = changed_file_path.split('/')[-1].lower() |
|
stgao
2014/08/22 06:50:54
use os.path.basename instead.
jeun
2014/08/22 22:58:43
Changed to split('/') so that it would also work o
| |
| 149 crashed_file_name = crashed_file_path.split('/')[-1] | 150 crashed_file_name = crashed_file_path.split('/')[-1].lower() |
| 150 | |
| 151 if changed_file_name != crashed_file_name: | 151 if changed_file_name != crashed_file_name: |
| 152 continue | 152 continue |
| 153 | 153 |
| 154 if not crash_utils.GuessIfSameSubPath( | 154 if not crash_utils.GuessIfSameSubPath( |
| 155 changed_file_path, crashed_file_path): | 155 changed_file_path.lower(), crashed_file_path.lower()): |
| 156 continue | 156 continue |
| 157 | 157 |
| 158 crashed_line_numbers = file_to_crash_info.GetCrashedLineNumbers( | 158 crashed_line_numbers = file_to_crash_info.GetCrashedLineNumbers( |
| 159 crashed_file_path) | 159 crashed_file_path) |
| 160 stack_frame_nums = file_to_crash_info.GetCrashStackFrameIndices( | 160 stack_frame_nums = file_to_crash_info.GetCrashStackFrameIndices( |
| 161 crashed_file_path) | 161 crashed_file_path) |
| 162 functions = file_to_crash_info.GetCrashFunctions(crashed_file_path) | 162 functions = file_to_crash_info.GetCrashFunctions(crashed_file_path) |
| 163 | 163 |
| 164 # Iterate through the CLs that this file path is changed. | 164 # Iterate through the CLs that this file path is changed. |
| 165 for (cl, file_change_type) in file_to_revision_info[changed_file_path]: | 165 for (cl, file_change_type) in file_to_revision_info[changed_file_path]: |
| (...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 349 match_with_stack_priority: A match object, with the CL it is from and what | 349 match_with_stack_priority: A match object, with the CL it is from and what |
| 350 callstack it is from. | 350 callstack it is from. |
| 351 | 351 |
| 352 Returns: | 352 Returns: |
| 353 A sort key. | 353 A sort key. |
| 354 """ | 354 """ |
| 355 (stack_priority, _, match) = match_with_stack_priority | 355 (stack_priority, _, match) = match_with_stack_priority |
| 356 | 356 |
| 357 return (min(match.priorities), | 357 return (min(match.priorities), |
| 358 stack_priority, | 358 stack_priority, |
| 359 match.min_distance, | |
|
stgao
2014/08/22 06:50:53
Why we do this change?
jeun
2014/08/22 22:58:43
Abhishek suggested it.
| |
| 359 crash_utils.FindMinStackFrameNumber(match.stack_frame_indices, | 360 crash_utils.FindMinStackFrameNumber(match.stack_frame_indices, |
| 360 match.priorities), | 361 match.priorities)) |
| 361 -len(match.changed_files), match.min_distance) | |
| 362 | 362 |
| 363 | 363 |
| 364 def SortAndFilterMatches(matches, num_important_frames=5): | 364 def SortAndFilterMatches(matches, num_important_frames=5): |
| 365 """Filters the list of potential culprit CLs to remove noise. | 365 """Filters the list of potential culprit CLs to remove noise. |
| 366 | 366 |
| 367 Args: | 367 Args: |
| 368 matches: A list containing match results. | 368 matches: A list containing match results. |
| 369 num_important_frames: A number of frames on the top of the frame to Check | 369 num_important_frames: A number of frames on the top of the frame to Check |
| 370 for when filtering the results. A match with a file | 370 for when filtering the results. A match with a file |
| 371 that is in top num_important_frames of the stacktrace | 371 that is in top num_important_frames of the stacktrace |
| 372 is regarded more probable then others. | 372 is regarded more probable then others. |
| 373 | 373 |
| 374 Returns: | 374 Returns: |
| 375 Filtered match results. | 375 Filtered match results. |
| 376 """ | 376 """ |
| 377 new_matches = [] | 377 new_matches = [] |
| 378 line_changed = False | 378 line_changed = False |
| 379 is_important_frame = False | 379 is_important_frame = False |
| 380 highest_priority_stack = crash_utils.INFINITY | 380 highest_priority_stack = crash_utils.INFINITY |
| 381 matches.sort(key=SortMatchesFunction) | 381 matches.sort(key=SortMatchesFunction) |
| 382 | |
| 383 # Iterate through the matches to find out what results are significant. | 382 # Iterate through the matches to find out what results are significant. |
| 384 for stack_priority, cl, match in matches: | 383 for stack_priority, cl, match in matches: |
| 385 # Check if the current match changes crashed line. | 384 # Check if the current match changes crashed line. |
| 386 is_line_change = (min(match.priorities) == LINE_CHANGE_PRIORITY) | 385 is_line_change = (min(match.priorities) == LINE_CHANGE_PRIORITY) |
| 387 | 386 |
| 388 # Check which stack this match is from, and finds the highest priority | 387 # Check which stack this match is from, and finds the highest priority |
| 389 # callstack up to this point. | 388 # callstack up to this point. |
| 390 current_stack = stack_priority | 389 current_stack = stack_priority |
| 391 if current_stack < highest_priority_stack: | 390 if current_stack < highest_priority_stack: |
| 392 highest_priority_stack = current_stack | 391 highest_priority_stack = current_stack |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 426 Args: | 425 Args: |
| 427 matches: A list of match objects. | 426 matches: A list of match objects. |
| 428 """ | 427 """ |
| 429 # Iterate through the matches in the list. | 428 # Iterate through the matches in the list. |
| 430 for i, _, match in matches: | 429 for i, _, match in matches: |
| 431 reason = [] | 430 reason = [] |
| 432 | 431 |
| 433 # Zip the files in the match by the reason they are suspected | 432 # Zip the files in the match by the reason they are suspected |
| 434 # (how the file is modified). | 433 # (how the file is modified). |
| 435 match_by_priority = zip( | 434 match_by_priority = zip( |
| 436 match.priorities, match.crashed_line_numbers, match.changed_files, | 435 match.priorities, match.crashed_line_numbers, match.changed_files) |
| 437 match.changed_file_urls) | |
| 438 | 436 |
| 439 # Sort the zipped changed files in the match by their priority so that the | 437 # Sort the zipped changed files in the match by their priority so that the |
| 440 # changed lines comes first in the reason. | 438 # changed lines comes first in the reason. |
| 441 match_by_priority.sort( | 439 match_by_priority.sort( |
| 442 key=lambda (priority, crashed_line_number, file_name, url): priority) | 440 key=lambda (priority, crashed_line_numbers, file_name): priority) |
| 443 | 441 |
| 444 # Iterate through the sorted match. | 442 # Iterate through the sorted match. |
| 445 for i in range(len(match_by_priority)): | 443 for i in range(len(match_by_priority)): |
| 446 (priority, crashed_line_number, file_name, file_url) = \ | 444 (priority, crashed_line_numbers, file_name) = match_by_priority[i] |
| 447 match_by_priority[i] | |
| 448 | 445 |
| 449 # If the file in the match is a line change, append a explanation. | 446 # If the file in the match is a line change, append a explanation. |
| 450 if priority == LINE_CHANGE_PRIORITY: | 447 if priority == LINE_CHANGE_PRIORITY: |
| 448 crashed_line_numbers = [crashed_line_number | |
| 449 for lines in crashed_line_numbers | |
| 450 for crashed_line_number in lines] | |
| 451 reason.append( | 451 reason.append( |
| 452 'Line %s of file %s which potentially caused the crash ' | 452 'Line %s of file %s which potentially caused the crash ' |
| 453 'according to the stacktrace, is changed in this cl.\n' % | 453 'according to the stacktrace, is changed in this cl.\n' % |
| 454 (crash_utils.PrettifyList(crashed_line_number), | 454 (crash_utils.PrettifyList(crashed_line_numbers), |
| 455 crash_utils.PrettifyFiles([(file_name, file_url)]))) | 455 file_name)) |
| 456 | 456 |
| 457 else: | 457 else: |
| 458 # Get all the files that are not line change. | 458 # Get all the files that are not line change. |
| 459 rest_of_the_files = match_by_priority[i:] | 459 rest_of_the_files = match_by_priority[i:] |
| 460 | 460 |
| 461 if len(rest_of_the_files) == 1: | 461 if len(rest_of_the_files) == 1: |
| 462 file_string = 'File %s is changed in this cl.\n' | 462 file_string = 'File %s is changed in this cl.\n' |
| 463 else: | 463 else: |
| 464 file_string = 'Files %s are changed in this cl.\n' | 464 file_string = 'Files %s are changed in this cl.\n' |
| 465 | 465 |
| 466 # Create a list of file name and its url, and prettify the list. | 466 # Create a list of file names, and prettify the list. |
| 467 file_name_with_url = [(file_name, file_url) | 467 file_names = [file_name |
|
Martin Barbella
2014/08/22 02:24:14
Nit: just include all of this as a continuation on
jeun
2014/08/22 22:58:43
Done.
| |
| 468 for (_, _, file_name, file_url) | 468 for (_, _, file_name) in rest_of_the_files] |
| 469 in rest_of_the_files] | 469 pretty_file_names = crash_utils.PrettifyList(file_names) |
| 470 pretty_file_name_url = crash_utils.PrettifyFiles(file_name_with_url) | |
| 471 | 470 |
| 472 # Add the reason, break because we took care of the rest of the files. | 471 # Add the reason, break because we took care of the rest of the files. |
| 473 reason.append(file_string % pretty_file_name_url) | 472 reason.append(file_string % pretty_file_names) |
| 474 break | 473 break |
| 475 | 474 |
| 476 # Set the reason as string. | 475 # Set the reason as string. |
| 477 match.reason = ''.join(reason) | 476 match.reason = ''.join(reason) |
| 478 | 477 |
| 479 | 478 |
| 480 def CombineMatches(matches): | 479 def CombineMatches(matches): |
| 481 """Combine possible duplicates in matches. | 480 """Combine possible duplicates in matches. |
| 482 | 481 |
| 483 Args: | 482 Args: |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 528 | 527 |
| 529 | 528 |
| 530 def ParseCrashComponents(main_stack): | 529 def ParseCrashComponents(main_stack): |
| 531 """Parses the crashing component. | 530 """Parses the crashing component. |
| 532 | 531 |
| 533 Crashing components is a component that top_n_frames of the stacktrace is | 532 Crashing components is a component that top_n_frames of the stacktrace is |
| 534 from. | 533 from. |
| 535 | 534 |
| 536 Args: | 535 Args: |
| 537 main_stack: Main stack from the stacktrace. | 536 main_stack: Main stack from the stacktrace. |
| 538 top_n_frames: A number of frames to regard as crashing frame. | |
| 539 | 537 |
| 540 Returns: | 538 Returns: |
| 541 A set of components. | 539 A set of components. |
| 542 """ | 540 """ |
| 543 components = set() | 541 components = set() |
| 544 | 542 |
| 545 for frame in main_stack.frame_list: | 543 for frame in main_stack.frame_list: |
| 546 components.add(frame.component_path) | 544 components.add(frame.component_path) |
| 547 | 545 |
| 548 return components | 546 return components |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 595 no results for all stacktraces in the stacktrace_list. | 593 no results for all stacktraces in the stacktrace_list. |
| 596 component_to_regression_dict: A parsed regression information as a | 594 component_to_regression_dict: A parsed regression information as a |
| 597 result of parsing DEPS file. | 595 result of parsing DEPS file. |
| 598 component_to_crash_revision_dict: A parsed crash revision information. | 596 component_to_crash_revision_dict: A parsed crash revision information. |
| 599 | 597 |
| 600 Returns: | 598 Returns: |
| 601 A list of result objects, with the message how the result is created. | 599 A list of result objects, with the message how the result is created. |
| 602 """ | 600 """ |
| 603 # If regression information is not available, return blame information. | 601 # If regression information is not available, return blame information. |
| 604 if not component_to_regression_dict: | 602 if not component_to_regression_dict: |
| 605 return_message = ( | |
| 606 'Regression information is not available. The result is ' | |
| 607 'the blame information.') | |
| 608 result = GenerateAndFilterBlameList(callstack, | 603 result = GenerateAndFilterBlameList(callstack, |
| 609 component_to_crash_revision_dict, | 604 component_to_crash_revision_dict, |
| 610 component_to_regression_dict) | 605 component_to_regression_dict) |
| 606 if result: | |
| 607 return_message = ( | |
| 608 'Regression information is not available. The result is ' | |
| 609 'the blame information.') | |
| 610 else: | |
| 611 return_message = ('Findit could not find any suspected CLs.') | |
|
stgao
2014/08/22 06:50:54
Please add a comment on why we would reach this ca
jeun
2014/08/22 22:58:43
Done.
| |
| 612 | |
| 611 return (return_message, result) | 613 return (return_message, result) |
| 612 | 614 |
| 613 for stacktrace in stacktrace_list: | 615 for stacktrace in stacktrace_list: |
| 614 # Check the next stacktrace if current one is empty. | 616 # Check the next stacktrace if current one is empty. |
| 615 if not stacktrace.stack_list: | 617 if not stacktrace.stack_list: |
| 616 continue | 618 continue |
| 617 | 619 |
| 618 # Get the crash stack for this stacktrace, and extract crashing components | 620 # Get the crash stack for this stacktrace, and extract crashing components |
| 619 # from it. | 621 # from it. |
| 620 main_stack = stacktrace.GetCrashStack() | 622 main_stack = stacktrace.GetCrashStack() |
| 621 components = ParseCrashComponents(main_stack) | 623 components = ParseCrashComponents(main_stack) |
| 622 | 624 |
| 623 result_for_stacktrace = FindMatchForStacktrace( | 625 result_for_stacktrace = FindMatchForStacktrace( |
| 624 stacktrace, components, component_to_regression_dict) | 626 stacktrace, components, component_to_regression_dict) |
| 627 filtered_result = FilterAndGenerateReasonForMatches(result_for_stacktrace) | |
| 625 | 628 |
| 626 # If the result is empty, check the next stacktrace. Else, return the | 629 # If the result is empty, check the next stacktrace. Else, return the |
| 627 # filtered result. | 630 # filtered result. |
| 628 if not result_for_stacktrace: | 631 if not filtered_result: |
| 629 continue | 632 continue |
| 630 | 633 |
| 631 return_message = ( | 634 return_message = ( |
| 632 'The result is a list of CLs that change the crashed files.') | 635 'The result is a list of CLs that change the crashed files.') |
| 633 result = FilterAndGenerateReasonForMatches(result_for_stacktrace) | 636 return (return_message, filtered_result) |
| 634 return (return_message, result) | |
| 635 | 637 |
| 636 # If no match is found, return the blame information for the input | 638 # If no match is found, return the blame information for the input |
| 637 # callstack. | 639 # callstack. |
| 638 return_message = ( | |
| 639 'There are no CLs that change the crashed files. The result is the ' | |
| 640 'blame information.') | |
| 641 result = GenerateAndFilterBlameList( | 640 result = GenerateAndFilterBlameList( |
| 642 callstack, component_to_crash_revision_dict, | 641 callstack, component_to_crash_revision_dict, |
| 643 component_to_regression_dict) | 642 component_to_regression_dict) |
| 643 | |
| 644 if result: | |
| 645 return_message = ( | |
| 646 'No CL in the regression changes the crashed files. The result is ' | |
| 647 'the blame information.') | |
| 648 else: | |
| 649 return_message = ('Findit could not find any suspected CLs.') | |
| 650 | |
| 644 return (return_message, result) | 651 return (return_message, result) |
| 645 | 652 |
| OLD | NEW |