| Index: tools/utils.py
|
| diff --git a/tools/utils.py b/tools/utils.py
|
| index 87ee9075e51f6f8cb749008076bab3a14717bcd5..da2cd28892b25e9d8eb5eb306e86d623bd5a6a76 100644
|
| --- a/tools/utils.py
|
| +++ b/tools/utils.py
|
| @@ -6,6 +6,7 @@
|
| # scripts.
|
|
|
| import commands
|
| +import contextlib
|
| import datetime
|
| import glob
|
| import imp
|
| @@ -606,7 +607,7 @@ def CheckedInSdkPath():
|
| osname = osdict[system]
|
| except KeyError:
|
| print >>sys.stderr, ('WARNING: platform "%s" not supported') % (system)
|
| - return None;
|
| + return None
|
| tools_dir = os.path.dirname(os.path.realpath(__file__))
|
| return os.path.join(tools_dir,
|
| 'sdks',
|
| @@ -649,6 +650,23 @@ def CheckedInSdkCheckExecutable():
|
| return False
|
|
|
|
|
| +def CheckLinuxCoreDumpPattern(fatal=False):
|
| + core_pattern_file = '/proc/sys/kernel/core_pattern'
|
| + core_pattern = open(core_pattern_file).read()
|
| +
|
| + expected_core_pattern = 'core.%p'
|
| + if core_pattern.strip() != expected_core_pattern:
|
| + if fatal:
|
| + message = ('Invalid core_pattern configuration. '
|
| + 'The configuration of core dump handling is *not* correct for '
|
| + 'a buildbot. The content of {0} must be "{1}" instead of "{2}".'
|
| + .format(core_pattern_file, expected_core_pattern, core_pattern))
|
| + raise Exception(message)
|
| + else:
|
| + return False
|
| + return True
|
| +
|
| +
|
| class TempDir(object):
|
| def __init__(self, prefix=''):
|
| self._temp_dir = None
|
| @@ -674,100 +692,164 @@ class ChangedWorkingDirectory(object):
|
| print "Enter directory = ", self._old_cwd
|
| os.chdir(self._old_cwd)
|
|
|
| -class CoreDump(object):
|
| - def __init__(self, test, core, binary):
|
| +
|
| +class UnexpectedCrash(object):
|
| + def __init__(self, test, pid, binary):
|
| self.test = test
|
| - self.core = core
|
| + self.pid = pid
|
| self.binary = binary
|
|
|
| def __str__(self):
|
| - return "%s: %s %s" % (self.test, self.binary, self.core)
|
| + return "%s: %s %s" % (self.test, self.binary, self.pid)
|
|
|
| -class CoreDumpArchiver(object):
|
| - """This class reads coredumps file written by UnexpectedCrashDumpArchiver
|
| - into the current working directory and uploads all cores and binaries
|
| - listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart).
|
| - """
|
|
|
| - def __init__(self, args):
|
| - self._enabled = '--copy-coredumps' in args and GuessOS() == 'linux'
|
| - self._search_dir = os.getcwd()
|
| - self._bucket = 'dart-temp-crash-archive'
|
| +class PosixCoredumpEnabler(object):
|
| + def __init__(self):
|
| self._old_limits = None
|
|
|
| def __enter__(self):
|
| - if not self._enabled:
|
| - return
|
| -
|
| - # Cleanup any stale coredumps
|
| - if self._cleanup():
|
| - print "WARNING: Found and removed stale coredumps"
|
| -
|
| self._old_limits = resource.getrlimit(resource.RLIMIT_CORE)
|
|
|
| # Bump core limits to unlimited if core_pattern is correctly configured.
|
| - if self._check_core_dump_pattern(fatal=False):
|
| + if CheckLinuxCoreDumpPattern(fatal=False):
|
| resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
|
|
|
| def __exit__(self, *_):
|
| - if not self._enabled:
|
| - return
|
| + resource.setrlimit(resource.RLIMIT_CORE, self._old_limits)
|
| + CheckLinuxCoreDumpPattern(fatal=True)
|
|
|
| - try:
|
| - # Restore old core limit.
|
| - resource.setrlimit(resource.RLIMIT_CORE, self._old_limits)
|
| +class WindowsCoredumpEnabler(object):
|
| + """Configure Windows Error Reporting to store crash dumps.
|
|
|
| - # Check that kernel was correctly configured to use core.%p
|
| - # core_pattern.
|
| - self._check_core_dump_pattern(fatal=True)
|
| + The documentation can be found here:
|
| + https://msdn.microsoft.com/en-us/library/windows/desktop/bb787181.aspx
|
| + """
|
|
|
| - coredumps = self._find_coredumps()
|
| - if coredumps:
|
| - # If we get a ton of crashes, only archive 10 dumps.
|
| - archive_coredumps = coredumps[:10]
|
| - print 'Archiving coredumps:'
|
| - for core in archive_coredumps:
|
| - print '----> %s' % core
|
| + WINDOWS_COREDUMP_FOLDER = r'crashes'
|
|
|
| - sys.stdout.flush()
|
| - self._archive(archive_coredumps)
|
| + WER_NAME = r'SOFTWARE\Microsoft\Windows\Windows Error Reporting'
|
| + WER_LOCALDUMPS_NAME = r'%s\LocalDumps' % WER_NAME
|
| + IMGEXEC_NAME = (r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
|
| + r'\Image File Execution Options\WerFault.exe')
|
|
|
| - finally:
|
| - self._cleanup()
|
| + def __init__(self):
|
| + # Depending on whether we're in cygwin or not we use a different import.
|
| + try:
|
| + import winreg
|
| + except ImportError:
|
| + import _winreg as winreg
|
| + self.winreg = winreg
|
|
|
| - def _cleanup(self):
|
| - found = False
|
| - for core in glob.glob(os.path.join(self._search_dir, 'core.*')):
|
| - found = True
|
| - os.unlink(core)
|
| - for binary in glob.glob(os.path.join(self._search_dir, 'binary.*')):
|
| - found = True
|
| - os.unlink(binary)
|
| + def __enter__(self):
|
| + # We want 32 and 64 bit coredumps to land in the same coredump directory.
|
| + for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]:
|
| + # In case WerFault.exe was prevented from executing, we fix it here.
|
| + # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed.
|
| + self._prune_existing_key(
|
| + self.winreg.HKEY_LOCAL_MACHINE, self.IMGEXEC_NAME, sam)
|
| +
|
| + # Create (or open) the WER keys.
|
| + with self.winreg.CreateKeyEx(
|
| + self.winreg.HKEY_LOCAL_MACHINE, self.WER_NAME, 0,
|
| + self.winreg.KEY_ALL_ACCESS | sam) as wer:
|
| + with self.winreg.CreateKeyEx(
|
| + self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0,
|
| + self.winreg.KEY_ALL_ACCESS | sam) as wer_localdumps:
|
| + # Prevent any modal UI dialog & disable normal windows error reporting
|
| + # TODO(kustermann): Remove this once https://crbug.com/691971 is fixed
|
| + self.winreg.SetValueEx(wer, "DontShowUI", 0, self.winreg.REG_DWORD, 1)
|
| + self.winreg.SetValueEx(wer, "Disabled", 0, self.winreg.REG_DWORD, 1)
|
| +
|
| + coredump_folder = os.path.join(
|
| + os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER)
|
| +
|
| + # Create the directory which will contain the dumps
|
| + if not os.path.exists(coredump_folder):
|
| + os.mkdir(coredump_folder)
|
| +
|
| + # Do full dumps (not just mini dumps), keep max 100 dumps and specify
|
| + # folder.
|
| + self.winreg.SetValueEx(
|
| + wer_localdumps, "DumpType", 0, self.winreg.REG_DWORD, 2)
|
| + self.winreg.SetValueEx(
|
| + wer_localdumps, "DumpCount", 0, self.winreg.REG_DWORD, 200)
|
| + self.winreg.SetValueEx(
|
| + wer_localdumps, "DumpFolder", 0, self.winreg.REG_EXPAND_SZ,
|
| + coredump_folder)
|
| +
|
| + def __exit__(self, *_):
|
| + # We remove the local dumps settings after running the tests.
|
| + for sam in [self.winreg.KEY_WOW64_64KEY, self.winreg.KEY_WOW64_32KEY]:
|
| + with self.winreg.CreateKeyEx(
|
| + self.winreg.HKEY_LOCAL_MACHINE, self.WER_LOCALDUMPS_NAME, 0,
|
| + self.winreg.KEY_ALL_ACCESS | sam) as wer_localdumps:
|
| + self.winreg.DeleteValue(wer_localdumps, 'DumpType')
|
| + self.winreg.DeleteValue(wer_localdumps, 'DumpCount')
|
| + self.winreg.DeleteValue(wer_localdumps, 'DumpFolder')
|
| +
|
| + def _prune_existing_key(self, key, subkey, wowbit):
|
| + handle = None
|
| +
|
| + # If the open fails, the key doesn't exist and it's fine.
|
| try:
|
| - os.unlink(os.path.join(self._search_dir, 'coredumps'))
|
| - found = True
|
| - except:
|
| + handle = self.winreg.OpenKey(
|
| + key, subkey, 0, self.winreg.KEY_READ | wowbit)
|
| + except OSError:
|
| pass
|
|
|
| - return found
|
| + # If the key exists then we delete it. If the deletion does not work, we
|
| + # let the exception through.
|
| + if handle:
|
| + handle.Close()
|
| + self.winreg.DeleteKeyEx(key, subkey, wowbit, 0)
|
|
|
| - def _find_coredumps(self):
|
| - """Load coredumps file. Each line has the following format:
|
| +class BaseCoreDumpArchiver(object):
|
| + """This class reads coredumps file written by UnexpectedCrashDumpArchiver
|
| + into the current working directory and uploads all cores and binaries
|
| + listed in it into Cloud Storage (see tools/testing/dart/test_progress.dart).
|
| + """
|
|
|
| - test-name,core-file,binary-file
|
| - """
|
| + # test.dart will write a line for each unexpected crash into this file.
|
| + _UNEXPECTED_CRASHES_FILE = "unexpected-crashes"
|
| +
|
| + def __init__(self):
|
| + self._bucket = 'dart-temp-crash-archive'
|
| + self._binaries_dir = os.getcwd()
|
| +
|
| + def __enter__(self):
|
| + # Cleanup any stale files
|
| + if self._cleanup():
|
| + print "WARNING: Found and removed stale coredumps"
|
| +
|
| + def __exit__(self, *_):
|
| try:
|
| - with open('coredumps') as f:
|
| - return [CoreDump(*ln.strip('\n').split(',')) for ln in f.readlines()]
|
| - except:
|
| - return []
|
| + crashes = self._find_unexpected_crashes()
|
| + if crashes:
|
| + # If we get a ton of crashes, only archive 10 dumps.
|
| + archive_crashes = crashes[:10]
|
| + print 'Archiving coredumps for crash (if possible):'
|
| + for crash in archive_crashes:
|
| + print '----> %s' % crash
|
| +
|
| + sys.stdout.flush()
|
| + self._archive(archive_crashes)
|
| +
|
| + finally:
|
| + self._cleanup()
|
|
|
| - def _archive(self, coredumps):
|
| + def _archive(self, crashes):
|
| files = set()
|
| - for core in coredumps:
|
| - files.add(core.core)
|
| - files.add(core.binary)
|
| + missing = []
|
| + for crash in crashes:
|
| + files.add(crash.binary)
|
| + core = self._find_coredump_file(crash)
|
| + if core:
|
| + files.add(core)
|
| + else:
|
| + missing.append(crash)
|
| self._upload(files)
|
| + if missing:
|
| + raise Exception('Missing crash dumps for: %s' % ', '.join(missing))
|
|
|
| def _upload(self, files):
|
| bot_utils = GetBotUtils()
|
| @@ -805,21 +887,84 @@ class CoreDumpArchiver(object):
|
| os.unlink(tarname)
|
| print '--- Done ---\n'
|
|
|
| - def _check_core_dump_pattern(self, fatal=False):
|
| - core_pattern_file = '/proc/sys/kernel/core_pattern'
|
| - core_pattern = open(core_pattern_file).read()
|
| -
|
| - expected_core_pattern = 'core.%p'
|
| - if core_pattern.strip() != expected_core_pattern:
|
| - if fatal:
|
| - message = ('Invalid core_pattern configuration. '
|
| - 'The configuration of core dump handling is *not* correct for '
|
| - 'a buildbot. The content of {0} must be "{1}" instead of "{2}".'
|
| - .format(core_pattern_file, expected_core_pattern, core_pattern))
|
| - raise Exception(message)
|
| - else:
|
| - return False
|
| - return True
|
| + def _find_unexpected_crashes(self):
|
| + """Load coredumps file. Each line has the following format:
|
| +
|
| + test-name,pid,binary-file
|
| + """
|
| + try:
|
| + with open(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE) as f:
|
| + return [UnexpectedCrash(*ln.strip('\n').split(',')) for ln in f.readlines()]
|
| + except:
|
| + return []
|
| +
|
| + def _cleanup(self):
|
| + found = False
|
| + if os.path.exists(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE):
|
| + os.unlink(BaseCoreDumpArchiver._UNEXPECTED_CRASHES_FILE)
|
| + found = True
|
| + for binary in glob.glob(os.path.join(self._binaries_dir, 'binary.*')):
|
| + found = True
|
| + os.unlink(binary)
|
| + return found
|
| +
|
| +class LinuxCoreDumpArchiver(BaseCoreDumpArchiver):
|
| + def __init__(self):
|
| + super(self.__class__, self).__init__()
|
| + self._search_dir = os.getcwd()
|
| +
|
| + def _cleanup(self):
|
| + found = super(self.__class__, self)._cleanup()
|
| + for core in glob.glob(os.path.join(self._search_dir, 'core.*')):
|
| + found = True
|
| + os.unlink(core)
|
| + return found
|
| +
|
| + def _find_coredump_file(self, crash):
|
| + core_filename = os.path.join(self._search_dir, 'core.%s' % crash.pid)
|
| + if os.path.exists(core_filename):
|
| + return core_filename
|
| +
|
| +class WindowsCoreDumpArchiver(BaseCoreDumpArchiver):
|
| + def __init__(self):
|
| + super(self.__class__, self).__init__()
|
| + self._search_dir = os.path.join(
|
| + os.getcwd(), WindowsCoredumpEnabler.WINDOWS_COREDUMP_FOLDER)
|
| +
|
| + def _cleanup(self):
|
| + found = super(self.__class__, self)._cleanup()
|
| + for core in glob.glob(os.path.join(self._search_dir, '*')):
|
| + found = True
|
| + os.unlink(core)
|
| + return found
|
| +
|
| + def _find_coredump_file(self, crash):
|
| + pattern = os.path.join(self._search_dir, '*.%s.*' % crash.pid)
|
| + for core_filename in glob.glob(pattern):
|
| + return core_filename
|
| +
|
| +@contextlib.contextmanager
|
| +def NooptCoreDumpArchiver():
|
| + yield
|
| +
|
| +
|
| +def CoreDumpArchiver(args):
|
| + enabled = '--copy-coredumps' in args
|
| +
|
| + if not enabled:
|
| + return NooptCoreDumpArchiver()
|
| +
|
| + osname = GuessOS()
|
| + if osname == 'linux':
|
| + return contextlib.nested(PosixCoredumpEnabler(),
|
| + LinuxCoreDumpArchiver())
|
| + elif osname == 'win32':
|
| + return contextlib.nested(WindowsCoredumpEnabler(),
|
| + WindowsCoreDumpArchiver())
|
| + else:
|
| + # We don't have support for MacOS yet.
|
| + assert osname == 'macos'
|
| + return NooptCoreDumpArchiver()
|
|
|
| if __name__ == "__main__":
|
| import sys
|
|
|