Index: tools/drmemory/scripts/common.py |
diff --git a/tools/drmemory/scripts/common.py b/tools/drmemory/scripts/common.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..7e163e3c6025354c11092f6939509c999b4f733b |
--- /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 |