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

Side by Side Diff: tools/utils.py

Issue 2692883002: Enable support for coredump archiving on windows (Closed)
Patch Set: Get rid of WerFault.exe execution prevention, ... Created 3 years, 10 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
« no previous file with comments | « tools/testing/dart/test_progress.dart ('k') | 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 # 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
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
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 wether we're in cygwin or not we use a different import.
Vyacheslav Egorov (Google) 2017/02/14 15:24:07 whether
kustermann 2017/02/14 15:34:28 Done.
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 wer = None
747 wer_localdumps = None
748 try:
749 # In case WerFault.exe was prevented from executing, we fix it here.
750 # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed.
751 self._prune_existing_key(
752 self.winreg.HKEY_LOCAL_MACHINE, self.IMGEXEC_NAME, sam)
753
754 # Create (or open) the WER keys.
755 wer = self.winreg.CreateKeyEx(
Vyacheslav Egorov (Google) 2017/02/14 15:24:07 maybe with CreateKeyEx() as wer: ...
kustermann 2017/02/14 15:34:28 Done.
756 self.winreg.HKEY_LOCAL_MACHINE, self.WER_NAME, 0,
757 self.winreg.KEY_ALL_ACCESS | sam)
758 wer_localdumps = self.winreg.CreateKeyEx(
759 self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0,
760 self.winreg.KEY_ALL_ACCESS | sam)
761
762 # Prevent any modal UI dialog & disable normal windows error reporting.
763 # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed.
764 self.winreg.SetValueEx(wer, "DontShowUI", 0, self.winreg.REG_DWORD, 1)
765 self.winreg.SetValueEx(wer, "Disabled", 0, self.winreg.REG_DWORD, 1)
766
767 coredump_folder = os.path.join(
768 os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER)
769
770 # Create the directory which will contain the dumps
771 if not os.path.exists(coredump_folder):
772 os.mkdir(coredump_folder)
773
774 # Do full dumps (not just mini dumps), keep max 100 dumps and specify
775 # folder.
776 self.winreg.SetValueEx(
777 wer_localdumps, "DumpType", 0, self.winreg.REG_DWORD, 2)
778 self.winreg.SetValueEx(
779 wer_localdumps, "DumpCount", 0, self.winreg.REG_DWORD, 200)
780 self.winreg.SetValueEx(
781 wer_localdumps, "DumpFolder", 0, self.winreg.REG_EXPAND_SZ,
782 coredump_folder)
783 finally:
784 if wer:
785 wer.Close()
786 if wer_localdumps:
787 wer_localdumps.Close()
788
789 def __exit__(self, *_):
790 # We remove the local dumps settings we have afte running the tests.
Vyacheslav Egorov (Google) 2017/02/14 15:24:07 after
kustermann 2017/02/14 15:34:28 Done.
791 for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]:
792 wer_localdumps = self.winreg.CreateKeyEx(
793 self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0,
794 self.winreg.KEY_ALL_ACCESS | sam)
795 try:
Vyacheslav Egorov (Google) 2017/02/14 15:24:07 maybe with ... as wer_localdumps: ...
kustermann 2017/02/14 15:34:28 Done.
796 self.winreg.DeleteValue(wer_localdumps, 'DumpType')
797 self.winreg.DeleteValue(wer_localdumps, 'DumpCount')
798 self.winreg.DeleteValue(wer_localdumps, 'DumpFolder')
799 finally:
800 wer_localdumps.Close()
801
802 def _prune_existing_key(self, key, subkey, wowbit):
803 handle = None
804
805 # If the open fails, the key doesn't exist and it's fine.
806 try:
807 handle = self.winreg.OpenKey(
808 key, subkey, 0, self.winreg.KEY_READ | wowbit)
809 except OSError:
810 pass
811
812 # If the key exists then we delete it. If the deletion does not work, we
813 # let the exception through.
814 if handle:
815 handle.Close()
816 self.winreg.DeleteKeyEx(key, subkey, wowbit, 0)
817
818 class BaseCoreDumpArchiver(object):
687 """This class reads coredumps file written by UnexpectedCrashDumpArchiver 819 """This class reads coredumps file written by UnexpectedCrashDumpArchiver
688 into the current working directory and uploads all cores and binaries 820 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). 821 listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart).
690 """ 822 """
691 823
692 def __init__(self, args): 824 # test.dart will write a line for each unexpected crash into this file.
693 self._enabled = '--copy-coredumps' in args and GuessOS() == 'linux' 825 _UNEXPECTED_CRASHES_FILE = "unexpected-crashes"
694 self._search_dir = os.getcwd() 826
827 def __init__(self):
695 self._bucket = 'dart-temp-crash-archive' 828 self._bucket = 'dart-temp-crash-archive'
696 self._old_limits = None 829 self._binaries_dir = os.getcwd()
697 830
698 def __enter__(self): 831 def __enter__(self):
699 if not self._enabled: 832 # Cleanup any stale files
700 return
701
702 # Cleanup any stale coredumps
703 if self._cleanup(): 833 if self._cleanup():
704 print "WARNING: Found and removed stale coredumps" 834 print "WARNING: Found and removed stale coredumps"
705 835
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, *_): 836 def __exit__(self, *_):
713 if not self._enabled:
714 return
715
716 try: 837 try:
717 # Restore old core limit. 838 crashes = self._find_unexpected_crashes()
718 resource.setrlimit(resource.RLIMIT_CORE, self._old_limits) 839 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. 840 # If we get a ton of crashes, only archive 10 dumps.
727 archive_coredumps = coredumps[:10] 841 archive_crashes = crashes[:10]
728 print 'Archiving coredumps:' 842 print 'Archiving coredumps for crash (if possible):'
729 for core in archive_coredumps: 843 for crash in archive_crashes:
730 print '----> %s' % core 844 print '----> %s' % crash
731 845
732 sys.stdout.flush() 846 sys.stdout.flush()
733 self._archive(archive_coredumps) 847 self._archive(archive_crashes)
734 848
735 finally: 849 finally:
736 self._cleanup() 850 self._cleanup()
737 851
738 def _cleanup(self): 852 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() 853 files = set()
767 for core in coredumps: 854 missing = []
768 files.add(core.core) 855 for crash in crashes:
769 files.add(core.binary) 856 files.add(crash.binary)
857 core = self._find_coredump_file(crash)
858 if core:
859 files.add(core)
860 else:
861 missing.append(crash)
770 self._upload(files) 862 self._upload(files)
863 if missing:
864 raise Exception('Missing crash dumps for: %s' % ', '.join(missing))
771 865
772 def _upload(self, files): 866 def _upload(self, files):
773 bot_utils = GetBotUtils() 867 bot_utils = GetBotUtils()
774 gsutil = bot_utils.GSUtil() 868 gsutil = bot_utils.GSUtil()
775 storage_path = '%s/%s/' % (self._bucket, uuid.uuid4()) 869 storage_path = '%s/%s/' % (self._bucket, uuid.uuid4())
776 gs_prefix = 'gs://%s' % storage_path 870 gs_prefix = 'gs://%s' % storage_path
777 http_prefix = 'https://storage.cloud.google.com/%s' % storage_path 871 http_prefix = 'https://storage.cloud.google.com/%s' % storage_path
778 872
779 print '\n--- Uploading into %s (%s) ---' % (gs_prefix, http_prefix) 873 print '\n--- Uploading into %s (%s) ---' % (gs_prefix, http_prefix)
780 for file in files: 874 for file in files:
(...skipping 17 matching lines...) Expand all
798 892
799 try: 893 try:
800 gsutil.upload(tarname, gs_url) 894 gsutil.upload(tarname, gs_url)
801 print '+++ Uploaded %s (%s)' % (gs_url, http_url) 895 print '+++ Uploaded %s (%s)' % (gs_url, http_url)
802 except Exception as error: 896 except Exception as error:
803 print '!!! Failed to upload %s, error: %s' % (tarname, error) 897 print '!!! Failed to upload %s, error: %s' % (tarname, error)
804 898
805 os.unlink(tarname) 899 os.unlink(tarname)
806 print '--- Done ---\n' 900 print '--- Done ---\n'
807 901
808 def _check_core_dump_pattern(self, fatal=False): 902 def _find_unexpected_crashes(self):
809 core_pattern_file = '/proc/sys/kernel/core_pattern' 903 """Load coredumps file. Each line has the following format:
810 core_pattern = open(core_pattern_file).read()
811 904
812 expected_core_pattern = 'core.%p' 905 test-name,pid,binary-file
813 if core_pattern.strip() != expected_core_pattern: 906 """
814 if fatal: 907 try:
815 message = ('Invalid core_pattern configuration. ' 908 with open(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE) as f:
816 'The configuration of core dump handling is *not* correct for ' 909 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}".' 910 except:
818 .format(core_pattern_file, expected_core_pattern, core_pattern)) 911 return []
819 raise Exception(message) 912
820 else: 913 def _cleanup(self):
821 return False 914 found = False
822 return True 915 if os.path.exists(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE):
916 os.unlink(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE)
917 found = True
918 for binary in glob.glob(os.path.join(self._binaries_dir, 'binary.*')):
919 found = True
920 os.unlink(binary)
921 return found
922
923 class LinuxCoreDumpArchiver(BaseCoreDumpArchiver):
924 def __init__(self):
925 super(self.__class__, self).__init__()
926 self._search_dir = os.getcwd()
927
928 def _cleanup(self):
929 found = super(self.__class__, self)._cleanup()
930 for core in glob.glob(os.path.join(self._search_dir, 'core.*')):
931 found = True
932 os.unlink(core)
933 return found
934
935 def _find_coredump_file(self, crash):
936 core_filename = os.path.join(self._search_dir, 'core.%s' % crash.pid)
937 if os.path.exists(core_filename):
938 return core_filename
939
940 class WindowsCoreDumpArchiver(BaseCoreDumpArchiver):
941 def __init__(self):
942 super(self.__class__, self).__init__()
943 self._search_dir = os.path.join(
944 os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER)
945
946 def _cleanup(self):
947 found = super(self.__class__, self)._cleanup()
948 for core in glob.glob(os.path.join(self._search_dir, '*')):
949 found = True
950 os.unlink(core)
951 return found
952
953 def _find_coredump_file(self, crash):
954 pattern = os.path.join(self._search_dir, '*.%s.*' % crash.pid)
955 for core_filename in glob.glob(pattern):
956 return core_filename
957
958 @contextlib.contextmanager
959 def NooptCoreDumpArchiver():
960 yield
961
962
963 def CoreDumpArchiver(args):
964 enabled = '--copy-coredumps' in args
965
966 if not enabled:
967 return NooptCoreDumpArchiver()
968
969 osname = GuessOS()
970 if osname == 'linux':
971 return contextlib.nested(PosixCoredumpEnabler(),
972 LinuxCoreDumpArchiver())
973 elif osname == 'win32':
974 return contextlib.nested(WindowsCoredumpEnabler(),
975 WindowsCoreDumpArchiver())
976 else:
977 # We don't have support for MacOS yet.
978 assert osname == 'macos'
979 return NooptCoreDumpArchiver()
823 980
824 if __name__ == "__main__": 981 if __name__ == "__main__":
825 import sys 982 import sys
826 Main() 983 Main()
OLDNEW
« no previous file with comments | « tools/testing/dart/test_progress.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698