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

Side by Side Diff: tools/utils.py

Issue 2692883002: Enable support for coredump archiving on windows (Closed)
Patch Set: Addressed comments 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 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
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()
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