diff options
Diffstat (limited to 'tools/drmemory/scripts/common.py')
-rw-r--r-- | tools/drmemory/scripts/common.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/tools/drmemory/scripts/common.py b/tools/drmemory/scripts/common.py new file mode 100644 index 0000000000..7e163e3c60 --- /dev/null +++ b/tools/drmemory/scripts/common.py @@ -0,0 +1,252 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import logging +import platform +import os +import signal +import subprocess +import sys +import time + + +class NotImplementedError(Exception): + pass + + +class TimeoutError(Exception): + pass + + +def RunSubprocessInBackground(proc): + """Runs a subprocess in the background. Returns a handle to the process.""" + logging.info("running %s in the background" % " ".join(proc)) + return subprocess.Popen(proc) + + +def RunSubprocess(proc, timeout=0): + """ Runs a subprocess, until it finishes or |timeout| is exceeded and the + process is killed with taskkill. A |timeout| <= 0 means no timeout. + + Args: + proc: list of process components (exe + args) + timeout: how long to wait before killing, <= 0 means wait forever + """ + + logging.info("running %s, timeout %d sec" % (" ".join(proc), timeout)) + sys.stdout.flush() + sys.stderr.flush() + + # Manually read and print out stdout and stderr. + # By default, the subprocess is supposed to inherit these from its parent, + # however when run under buildbot, it seems unable to read data from a + # grandchild process, so we have to read the child and print the data as if + # it came from us for buildbot to read it. We're not sure why this is + # necessary. + # TODO(erikkay): should we buffer stderr and stdout separately? + p = subprocess.Popen(proc, universal_newlines=True, + bufsize=0, # unbuffered + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + logging.info("started subprocess") + + did_timeout = False + if timeout > 0: + wait_until = time.time() + timeout + while p.poll() is None and not did_timeout: + # Have to use readline rather than readlines() or "for line in p.stdout:", + # otherwise we get buffered even with bufsize=0. + line = p.stdout.readline() + while line and not did_timeout: + sys.stdout.write(line) + sys.stdout.flush() + line = p.stdout.readline() + if timeout > 0: + did_timeout = time.time() > wait_until + + if did_timeout: + logging.info("process timed out") + else: + logging.info("process ended, did not time out") + + if did_timeout: + if IsWindows(): + subprocess.call(["taskkill", "/T", "/F", "/PID", str(p.pid)]) + else: + # Does this kill all children, too? + os.kill(p.pid, signal.SIGINT) + logging.error("KILLED %d" % p.pid) + # Give the process a chance to actually die before continuing + # so that cleanup can happen safely. + time.sleep(1.0) + logging.error("TIMEOUT waiting for %s" % proc[0]) + raise TimeoutError(proc[0]) + else: + for line in p.stdout: + sys.stdout.write(line) + if not IsMac(): # stdout flush fails on Mac + logging.info("flushing stdout") + sys.stdout.flush() + + logging.info("collecting result code") + result = p.poll() + if result: + logging.error("%s exited with non-zero result code %d" % (proc[0], result)) + return result + + +def IsLinux(): + return sys.platform.startswith('linux') + + +def IsMac(): + return sys.platform.startswith('darwin') + + +def IsWindows(): + return sys.platform == 'cygwin' or sys.platform.startswith('win') + + +def WindowsVersionName(): + """Returns the name of the Windows version if it is known, or None. + + Possible return values are: xp, vista, 7, 8, or None + """ + if sys.platform == 'cygwin': + # Windows version number is hiding in system name. Looks like: + # CYGWIN_NT-6.1-WOW64 + try: + version_str = platform.uname()[0].split('-')[1] + except: + return None + elif sys.platform.startswith('win'): + # Normal Windows version string. Mine: 6.1.7601 + version_str = platform.version() + else: + return None + + parts = version_str.split('.') + try: + major = int(parts[0]) + minor = int(parts[1]) + except: + return None # Can't parse, unknown version. + + if major == 5: + return 'xp' + elif major == 6 and minor == 0: + return 'vista' + elif major == 6 and minor == 1: + return '7' + elif major == 6 and minor == 2: + return '8' # Future proof. ;) + return None + + +def PlatformNames(): + """Return an array of string to be used in paths for the platform + (e.g. suppressions, gtest filters, ignore files etc.) + The first element of the array describes the 'main' platform + """ + if IsLinux(): + return ['linux'] + if IsMac(): + return ['mac'] + if IsWindows(): + names = ['win32'] + version_name = WindowsVersionName() + if version_name is not None: + names.append('win-%s' % version_name) + return names + raise NotImplementedError('Unknown platform "%s".' % sys.platform) + + +def PutEnvAndLog(env_name, env_value): + os.putenv(env_name, env_value) + logging.info('export %s=%s', env_name, env_value) + +def BoringCallers(mangled, use_re_wildcards): + """Return a list of 'boring' function names (optinally mangled) + with */? wildcards (optionally .*/.). + Boring = we drop off the bottom of stack traces below such functions. + """ + + need_mangling = [ + # Don't show our testing framework: + ("testing::Test::Run", "_ZN7testing4Test3RunEv"), + ("testing::TestInfo::Run", "_ZN7testing8TestInfo3RunEv"), + ("testing::internal::Handle*ExceptionsInMethodIfSupported*", + "_ZN7testing8internal3?Handle*ExceptionsInMethodIfSupported*"), + + # Depend on scheduling: + ("MessageLoop::Run", "_ZN11MessageLoop3RunEv"), + ("MessageLoop::RunTask", "_ZN11MessageLoop7RunTask*"), + ("RunnableMethod*", "_ZN14RunnableMethod*"), + ("DispatchToMethod*", "_Z*16DispatchToMethod*"), + ("base::internal::Invoker*::DoInvoke*", + "_ZN4base8internal8Invoker*DoInvoke*"), # Invoker{1,2,3} + ("base::internal::RunnableAdapter*::Run*", + "_ZN4base8internal15RunnableAdapter*Run*"), + ] + + ret = [] + for pair in need_mangling: + ret.append(pair[1 if mangled else 0]) + + ret += [ + # Also don't show the internals of libc/pthread. + "start_thread", + "main", + "BaseThreadInitThunk", + ] + + if use_re_wildcards: + for i in range(0, len(ret)): + ret[i] = ret[i].replace('*', '.*').replace('?', '.') + + return ret + +def NormalizeWindowsPath(path): + """If we're using Cygwin Python, turn the path into a Windows path. + + Don't turn forward slashes into backslashes for easier copy-pasting and + escaping. + + TODO(rnk): If we ever want to cut out the subprocess invocation, we can use + _winreg to get the root Cygwin directory from the registry key: + HKEY_LOCAL_MACHINE\SOFTWARE\Cygwin\setup\rootdir. + """ + if sys.platform.startswith("cygwin"): + p = subprocess.Popen(["cygpath", "-m", path], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (out, err) = p.communicate() + if err: + logging.warning("WARNING: cygpath error: %s", err) + return out.strip() + else: + return path + +############################ +# Common output format code + +def PrintUsedSuppressionsList(suppcounts): + """ Prints out the list of used suppressions in a format common to all the + memory tools. If the list is empty, prints nothing and returns False, + otherwise True. + + suppcounts: a dictionary of used suppression counts, + Key -> name, Value -> count. + """ + if not suppcounts: + return False + + print "-----------------------------------------------------" + print "Suppressions used:" + print " count name" + for (name, count) in sorted(suppcounts.items(), key=lambda (k,v): (v,k)): + print "%7d %s" % (count, name) + print "-----------------------------------------------------" + sys.stdout.flush() + return True |