OLD | NEW |
1 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 # Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 # for details. All rights reserved. Use of this source code is governed by a | 2 # for details. All rights reserved. Use of this source code is governed by a |
3 # BSD-style license that can be found in the LICENSE file. | 3 # BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 # This file contains a set of utilities functions used by other Python-based | 5 # This file contains a set of utilities functions used by other Python-based |
6 # scripts. | 6 # scripts. |
7 | 7 |
8 import commands | 8 import commands |
| 9 import contextlib |
9 import datetime | 10 import datetime |
10 import glob | 11 import glob |
11 import imp | 12 import imp |
12 import json | 13 import json |
13 import os | 14 import os |
14 import platform | 15 import platform |
15 import re | 16 import re |
16 import shutil | 17 import shutil |
17 import subprocess | 18 import subprocess |
18 import sys | 19 import sys |
(...skipping 580 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
599 # https://github.com/dart-lang/sdk/wiki/The-checked-in-SDK-in-tools | 600 # https://github.com/dart-lang/sdk/wiki/The-checked-in-SDK-in-tools |
600 def CheckedInSdkPath(): | 601 def CheckedInSdkPath(): |
601 # We don't use the normal macos, linux, win32 directory names here, instead, | 602 # We don't use the normal macos, linux, win32 directory names here, instead, |
602 # we use the names that the download_from_google_storage script uses. | 603 # we use the names that the download_from_google_storage script uses. |
603 osdict = {'Darwin':'mac', 'Linux':'linux', 'Windows':'win'} | 604 osdict = {'Darwin':'mac', 'Linux':'linux', 'Windows':'win'} |
604 system = platform.system() | 605 system = platform.system() |
605 try: | 606 try: |
606 osname = osdict[system] | 607 osname = osdict[system] |
607 except KeyError: | 608 except KeyError: |
608 print >>sys.stderr, ('WARNING: platform "%s" not supported') % (system) | 609 print >>sys.stderr, ('WARNING: platform "%s" not supported') % (system) |
609 return None; | 610 return None |
610 tools_dir = os.path.dirname(os.path.realpath(__file__)) | 611 tools_dir = os.path.dirname(os.path.realpath(__file__)) |
611 return os.path.join(tools_dir, | 612 return os.path.join(tools_dir, |
612 'sdks', | 613 'sdks', |
613 osname, | 614 osname, |
614 'dart-sdk') | 615 'dart-sdk') |
615 | 616 |
616 | 617 |
617 def CheckedInSdkExecutable(): | 618 def CheckedInSdkExecutable(): |
618 name = 'dart' | 619 name = 'dart' |
619 if IsWindows(): | 620 if IsWindows(): |
(...skipping 22 matching lines...) Expand all Loading... |
642 'canary.dart') | 643 'canary.dart') |
643 try: | 644 try: |
644 with open(os.devnull, 'wb') as silent_sink: | 645 with open(os.devnull, 'wb') as silent_sink: |
645 if 0 == subprocess.call([executable, canary_script], stdout=silent_sink): | 646 if 0 == subprocess.call([executable, canary_script], stdout=silent_sink): |
646 return True | 647 return True |
647 except OSError as e: | 648 except OSError as e: |
648 pass | 649 pass |
649 return False | 650 return False |
650 | 651 |
651 | 652 |
| 653 def CheckLinuxCoreDumpPattern(fatal=False): |
| 654 core_pattern_file = '/proc/sys/kernel/core_pattern' |
| 655 core_pattern = open(core_pattern_file).read() |
| 656 |
| 657 expected_core_pattern = 'core.%p' |
| 658 if core_pattern.strip() != expected_core_pattern: |
| 659 if fatal: |
| 660 message = ('Invalid core_pattern configuration. ' |
| 661 'The configuration of core dump handling is *not* correct for ' |
| 662 'a buildbot. The content of {0} must be "{1}" instead of "{2}".' |
| 663 .format(core_pattern_file, expected_core_pattern, core_pattern)) |
| 664 raise Exception(message) |
| 665 else: |
| 666 return False |
| 667 return True |
| 668 |
| 669 |
652 class TempDir(object): | 670 class TempDir(object): |
653 def __init__(self, prefix=''): | 671 def __init__(self, prefix=''): |
654 self._temp_dir = None | 672 self._temp_dir = None |
655 self._prefix = prefix | 673 self._prefix = prefix |
656 | 674 |
657 def __enter__(self): | 675 def __enter__(self): |
658 self._temp_dir = tempfile.mkdtemp(self._prefix) | 676 self._temp_dir = tempfile.mkdtemp(self._prefix) |
659 return self._temp_dir | 677 return self._temp_dir |
660 | 678 |
661 def __exit__(self, *_): | 679 def __exit__(self, *_): |
662 shutil.rmtree(self._temp_dir, ignore_errors=True) | 680 shutil.rmtree(self._temp_dir, ignore_errors=True) |
663 | 681 |
664 class ChangedWorkingDirectory(object): | 682 class ChangedWorkingDirectory(object): |
665 def __init__(self, working_directory): | 683 def __init__(self, working_directory): |
666 self._working_directory = working_directory | 684 self._working_directory = working_directory |
667 | 685 |
668 def __enter__(self): | 686 def __enter__(self): |
669 self._old_cwd = os.getcwd() | 687 self._old_cwd = os.getcwd() |
670 print "Enter directory = ", self._working_directory | 688 print "Enter directory = ", self._working_directory |
671 os.chdir(self._working_directory) | 689 os.chdir(self._working_directory) |
672 | 690 |
673 def __exit__(self, *_): | 691 def __exit__(self, *_): |
674 print "Enter directory = ", self._old_cwd | 692 print "Enter directory = ", self._old_cwd |
675 os.chdir(self._old_cwd) | 693 os.chdir(self._old_cwd) |
676 | 694 |
677 class CoreDump(object): | 695 |
678 def __init__(self, test, core, binary): | 696 class UnexpectedCrash(object): |
| 697 def __init__(self, test, pid, binary): |
679 self.test = test | 698 self.test = test |
680 self.core = core | 699 self.pid = pid |
681 self.binary = binary | 700 self.binary = binary |
682 | 701 |
683 def __str__(self): | 702 def __str__(self): |
684 return "%s: %s %s" % (self.test, self.binary, self.core) | 703 return "%s: %s %s" % (self.test, self.binary, self.pid) |
685 | 704 |
686 class CoreDumpArchiver(object): | 705 |
| 706 class PosixCoredumpEnabler(object): |
| 707 def __init__(self): |
| 708 self._old_limits = None |
| 709 |
| 710 def __enter__(self): |
| 711 self._old_limits = resource.getrlimit(resource.RLIMIT_CORE) |
| 712 |
| 713 # Bump core limits to unlimited if core_pattern is correctly configured. |
| 714 if CheckLinuxCoreDumpPattern(fatal=False): |
| 715 resource.setrlimit(resource.RLIMIT_CORE, (-1, -1)) |
| 716 |
| 717 def __exit__(self, *_): |
| 718 resource.setrlimit(resource.RLIMIT_CORE, self._old_limits) |
| 719 CheckLinuxCoreDumpPattern(fatal=True) |
| 720 |
| 721 class WindowsCoredumpEnabler(object): |
| 722 """Configure Windows Error Reporting to store crash dumps. |
| 723 |
| 724 The documentation can be found here: |
| 725 https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181.aspx |
| 726 """ |
| 727 |
| 728 WINDOWS_COREDUMP_FOLDER = r'crashes' |
| 729 |
| 730 WER_NAME = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting' |
| 731 WER_LOCALDUMPS_NAME = r'%s\LocalDumps' % WER_NAME |
| 732 IMGEXEC_NAME = (r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' |
| 733 r'\Image File Execution Options\WerFault.exe') |
| 734 |
| 735 def __init__(self): |
| 736 # Depending on whether we're in cygwin or not we use a different import. |
| 737 try: |
| 738 import winreg |
| 739 except ImportError: |
| 740 import _winreg as winreg |
| 741 self.winreg = winreg |
| 742 |
| 743 def __enter__(self): |
| 744 # We want 32 and 64 bit coredumps to land in the same coredump directory. |
| 745 for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]: |
| 746 # In case WerFault.exe was prevented from executing, we fix it here. |
| 747 # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed. |
| 748 self._prune_existing_key( |
| 749 self.winreg.HKEY_LOCAL_MACHINE, self.IMGEXEC_NAME, sam) |
| 750 |
| 751 # Create (or open) the WER keys. |
| 752 with self.winreg.CreateKeyEx( |
| 753 self.winreg.HKEY_LOCAL_MACHINE, self.WER_NAME, 0, |
| 754 self.winreg.KEY_ALL_ACCESS | sam) as wer: |
| 755 with self.winreg.CreateKeyEx( |
| 756 self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0, |
| 757 self.winreg.KEY_ALL_ACCESS | sam) as wer_localdumps: |
| 758 # Prevent any modal UI dialog & disable normal windows error reporting |
| 759 # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed |
| 760 self.winreg.SetValueEx(wer, "DontShowUI", 0, self.winreg.REG_DWORD, 1) |
| 761 self.winreg.SetValueEx(wer, "Disabled", 0, self.winreg.REG_DWORD, 1) |
| 762 |
| 763 coredump_folder = os.path.join( |
| 764 os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER) |
| 765 |
| 766 # Create the directory which will contain the dumps |
| 767 if not os.path.exists(coredump_folder): |
| 768 os.mkdir(coredump_folder) |
| 769 |
| 770 # Do full dumps (not just mini dumps), keep max 100 dumps and specify |
| 771 # folder. |
| 772 self.winreg.SetValueEx( |
| 773 wer_localdumps, "DumpType", 0, self.winreg.REG_DWORD, 2) |
| 774 self.winreg.SetValueEx( |
| 775 wer_localdumps, "DumpCount", 0, self.winreg.REG_DWORD, 200) |
| 776 self.winreg.SetValueEx( |
| 777 wer_localdumps, "DumpFolder", 0, self.winreg.REG_EXPAND_SZ, |
| 778 coredump_folder) |
| 779 |
| 780 def __exit__(self, *_): |
| 781 # We remove the local dumps settings after running the tests. |
| 782 for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]: |
| 783 with self.winreg.CreateKeyEx( |
| 784 self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0, |
| 785 self.winreg.KEY_ALL_ACCESS | sam) as wer_localdumps: |
| 786 self.winreg.DeleteValue(wer_localdumps, 'DumpType') |
| 787 self.winreg.DeleteValue(wer_localdumps, 'DumpCount') |
| 788 self.winreg.DeleteValue(wer_localdumps, 'DumpFolder') |
| 789 |
| 790 def _prune_existing_key(self, key, subkey, wowbit): |
| 791 handle = None |
| 792 |
| 793 # If the open fails, the key doesn't exist and it's fine. |
| 794 try: |
| 795 handle = self.winreg.OpenKey( |
| 796 key, subkey, 0, self.winreg.KEY_READ | wowbit) |
| 797 except OSError: |
| 798 pass |
| 799 |
| 800 # If the key exists then we delete it. If the deletion does not work, we |
| 801 # let the exception through. |
| 802 if handle: |
| 803 handle.Close() |
| 804 self.winreg.DeleteKeyEx(key, subkey, wowbit, 0) |
| 805 |
| 806 class BaseCoreDumpArchiver(object): |
687 """This class reads coredumps file written by UnexpectedCrashDumpArchiver | 807 """This class reads coredumps file written by UnexpectedCrashDumpArchiver |
688 into the current working directory and uploads all cores and binaries | 808 into the current working directory and uploads all cores and binaries |
689 listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart). | 809 listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart). |
690 """ | 810 """ |
691 | 811 |
692 def __init__(self, args): | 812 # test.dart will write a line for each unexpected crash into this file. |
693 self._enabled = '--copy-coredumps' in args and GuessOS() == 'linux' | 813 _UNEXPECTED_CRASHES_FILE = "unexpected-crashes" |
694 self._search_dir = os.getcwd() | 814 |
| 815 def __init__(self): |
695 self._bucket = 'dart-temp-crash-archive' | 816 self._bucket = 'dart-temp-crash-archive' |
696 self._old_limits = None | 817 self._binaries_dir = os.getcwd() |
697 | 818 |
698 def __enter__(self): | 819 def __enter__(self): |
699 if not self._enabled: | 820 # Cleanup any stale files |
700 return | |
701 | |
702 # Cleanup any stale coredumps | |
703 if self._cleanup(): | 821 if self._cleanup(): |
704 print "WARNING: Found and removed stale coredumps" | 822 print "WARNING: Found and removed stale coredumps" |
705 | 823 |
706 self._old_limits = resource.getrlimit(resource.RLIMIT_CORE) | |
707 | |
708 # Bump core limits to unlimited if core_pattern is correctly configured. | |
709 if self._check_core_dump_pattern(fatal=False): | |
710 resource.setrlimit(resource.RLIMIT_CORE, (-1, -1)) | |
711 | |
712 def __exit__(self, *_): | 824 def __exit__(self, *_): |
713 if not self._enabled: | |
714 return | |
715 | |
716 try: | 825 try: |
717 # Restore old core limit. | 826 crashes = self._find_unexpected_crashes() |
718 resource.setrlimit(resource.RLIMIT_CORE, self._old_limits) | 827 if crashes: |
719 | |
720 # Check that kernel was correctly configured to use core.%p | |
721 # core_pattern. | |
722 self._check_core_dump_pattern(fatal=True) | |
723 | |
724 coredumps = self._find_coredumps() | |
725 if coredumps: | |
726 # If we get a ton of crashes, only archive 10 dumps. | 828 # If we get a ton of crashes, only archive 10 dumps. |
727 archive_coredumps = coredumps[:10] | 829 archive_crashes = crashes[:10] |
728 print 'Archiving coredumps:' | 830 print 'Archiving coredumps for crash (if possible):' |
729 for core in archive_coredumps: | 831 for crash in archive_crashes: |
730 print '----> %s' % core | 832 print '----> %s' % crash |
731 | 833 |
732 sys.stdout.flush() | 834 sys.stdout.flush() |
733 self._archive(archive_coredumps) | 835 self._archive(archive_crashes) |
734 | 836 |
735 finally: | 837 finally: |
736 self._cleanup() | 838 self._cleanup() |
737 | 839 |
738 def _cleanup(self): | 840 def _archive(self, crashes): |
739 found = False | |
740 for core in glob.glob(os.path.join(self._search_dir, 'core.*')): | |
741 found = True | |
742 os.unlink(core) | |
743 for binary in glob.glob(os.path.join(self._search_dir, 'binary.*')): | |
744 found = True | |
745 os.unlink(binary) | |
746 try: | |
747 os.unlink(os.path.join(self._search_dir, 'coredumps')) | |
748 found = True | |
749 except: | |
750 pass | |
751 | |
752 return found | |
753 | |
754 def _find_coredumps(self): | |
755 """Load coredumps file. Each line has the following format: | |
756 | |
757 test-name,core-file,binary-file | |
758 """ | |
759 try: | |
760 with open('coredumps') as f: | |
761 return [CoreDump(*ln.strip('\n').split(',')) for ln in f.readlines()] | |
762 except: | |
763 return [] | |
764 | |
765 def _archive(self, coredumps): | |
766 files = set() | 841 files = set() |
767 for core in coredumps: | 842 missing = [] |
768 files.add(core.core) | 843 for crash in crashes: |
769 files.add(core.binary) | 844 files.add(crash.binary) |
| 845 core = self._find_coredump_file(crash) |
| 846 if core: |
| 847 files.add(core) |
| 848 else: |
| 849 missing.append(crash) |
770 self._upload(files) | 850 self._upload(files) |
| 851 if missing: |
| 852 raise Exception('Missing crash dumps for: %s' % ', '.join(missing)) |
771 | 853 |
772 def _upload(self, files): | 854 def _upload(self, files): |
773 bot_utils = GetBotUtils() | 855 bot_utils = GetBotUtils() |
774 gsutil = bot_utils.GSUtil() | 856 gsutil = bot_utils.GSUtil() |
775 storage_path = '%s/%s/' % (self._bucket, uuid.uuid4()) | 857 storage_path = '%s/%s/' % (self._bucket, uuid.uuid4()) |
776 gs_prefix = 'gs://%s' % storage_path | 858 gs_prefix = 'gs://%s' % storage_path |
777 http_prefix = 'https://storage.cloud.google.com/%s' % storage_path | 859 http_prefix = 'https://storage.cloud.google.com/%s' % storage_path |
778 | 860 |
779 print '\n--- Uploading into %s (%s) ---' % (gs_prefix, http_prefix) | 861 print '\n--- Uploading into %s (%s) ---' % (gs_prefix, http_prefix) |
780 for file in files: | 862 for file in files: |
(...skipping 17 matching lines...) Expand all Loading... |
798 | 880 |
799 try: | 881 try: |
800 gsutil.upload(tarname, gs_url) | 882 gsutil.upload(tarname, gs_url) |
801 print '+++ Uploaded %s (%s)' % (gs_url, http_url) | 883 print '+++ Uploaded %s (%s)' % (gs_url, http_url) |
802 except Exception as error: | 884 except Exception as error: |
803 print '!!! Failed to upload %s, error: %s' % (tarname, error) | 885 print '!!! Failed to upload %s, error: %s' % (tarname, error) |
804 | 886 |
805 os.unlink(tarname) | 887 os.unlink(tarname) |
806 print '--- Done ---\n' | 888 print '--- Done ---\n' |
807 | 889 |
808 def _check_core_dump_pattern(self, fatal=False): | 890 def _find_unexpected_crashes(self): |
809 core_pattern_file = '/proc/sys/kernel/core_pattern' | 891 """Load coredumps file. Each line has the following format: |
810 core_pattern = open(core_pattern_file).read() | |
811 | 892 |
812 expected_core_pattern = 'core.%p' | 893 test-name,pid,binary-file |
813 if core_pattern.strip() != expected_core_pattern: | 894 """ |
814 if fatal: | 895 try: |
815 message = ('Invalid core_pattern configuration. ' | 896 with open(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE) as f: |
816 'The configuration of core dump handling is *not* correct for ' | 897 return [UnexpectedCrash(*ln.strip('\n').split(',')) for ln in f.readline
s()] |
817 'a buildbot. The content of {0} must be "{1}" instead of "{2}".' | 898 except: |
818 .format(core_pattern_file, expected_core_pattern, core_pattern)) | 899 return [] |
819 raise Exception(message) | 900 |
820 else: | 901 def _cleanup(self): |
821 return False | 902 found = False |
822 return True | 903 if os.path.exists(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE): |
| 904 os.unlink(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE) |
| 905 found = True |
| 906 for binary in glob.glob(os.path.join(self._binaries_dir, 'binary.*')): |
| 907 found = True |
| 908 os.unlink(binary) |
| 909 return found |
| 910 |
| 911 class LinuxCoreDumpArchiver(BaseCoreDumpArchiver): |
| 912 def __init__(self): |
| 913 super(self.__class__, self).__init__() |
| 914 self._search_dir = os.getcwd() |
| 915 |
| 916 def _cleanup(self): |
| 917 found = super(self.__class__, self)._cleanup() |
| 918 for core in glob.glob(os.path.join(self._search_dir, 'core.*')): |
| 919 found = True |
| 920 os.unlink(core) |
| 921 return found |
| 922 |
| 923 def _find_coredump_file(self, crash): |
| 924 core_filename = os.path.join(self._search_dir, 'core.%s' % crash.pid) |
| 925 if os.path.exists(core_filename): |
| 926 return core_filename |
| 927 |
| 928 class WindowsCoreDumpArchiver(BaseCoreDumpArchiver): |
| 929 def __init__(self): |
| 930 super(self.__class__, self).__init__() |
| 931 self._search_dir = os.path.join( |
| 932 os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER) |
| 933 |
| 934 def _cleanup(self): |
| 935 found = super(self.__class__, self)._cleanup() |
| 936 for core in glob.glob(os.path.join(self._search_dir, '*')): |
| 937 found = True |
| 938 os.unlink(core) |
| 939 return found |
| 940 |
| 941 def _find_coredump_file(self, crash): |
| 942 pattern = os.path.join(self._search_dir, '*.%s.*' % crash.pid) |
| 943 for core_filename in glob.glob(pattern): |
| 944 return core_filename |
| 945 |
| 946 @contextlib.contextmanager |
| 947 def NooptCoreDumpArchiver(): |
| 948 yield |
| 949 |
| 950 |
| 951 def CoreDumpArchiver(args): |
| 952 enabled = '--copy-coredumps' in args |
| 953 |
| 954 if not enabled: |
| 955 return NooptCoreDumpArchiver() |
| 956 |
| 957 osname = GuessOS() |
| 958 if osname == 'linux': |
| 959 return contextlib.nested(PosixCoredumpEnabler(), |
| 960 LinuxCoreDumpArchiver()) |
| 961 elif osname == 'win32': |
| 962 return contextlib.nested(WindowsCoredumpEnabler(), |
| 963 WindowsCoreDumpArchiver()) |
| 964 else: |
| 965 # We don't have support for MacOS yet. |
| 966 assert osname == 'macos' |
| 967 return NooptCoreDumpArchiver() |
823 | 968 |
824 if __name__ == "__main__": | 969 if __name__ == "__main__": |
825 import sys | 970 import sys |
826 Main() | 971 Main() |
OLD | NEW |