| #!/usr/bin/env python | 
 | # | 
 | # Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
 | # for details. All rights reserved. Use of this source code is governed by a | 
 | # BSD-style license that can be found in the LICENSE file. | 
 | # | 
 |  | 
 | # A script to kill hanging process. The tool will return non-zero if any | 
 | # process was actually found. | 
 | # | 
 |  | 
 | import optparse | 
 | import os | 
 | import signal | 
 | import subprocess | 
 | import sys | 
 |  | 
 | import utils | 
 |  | 
 |  | 
 | os_name = utils.GuessOS() | 
 |  | 
 | POSIX_INFO = 'ps -p %s -o args' | 
 |  | 
 | EXECUTABLE_NAMES = { | 
 |   'win32': { | 
 |     'chrome': 'chrome.exe', | 
 |     'content_shell': 'content_shell.exe', | 
 |     'dart_bootstrap': 'dart_bootstrap.exe', | 
 |     'dart': 'dart.exe', | 
 |     'dart_precompiled_runtime': 'dart_precompiled_runtime.exe', | 
 |     'firefox': 'firefox.exe', | 
 |     'gen_snapshot': 'gen_snapshot.exe', | 
 |     'git': 'git.exe', | 
 |     'iexplore': 'iexplore.exe', | 
 |     'vctip': 'vctip.exe', | 
 |     'mspdbsrv': 'mspdbsrv.exe', | 
 |   }, | 
 |   'linux': { | 
 |     'chrome': 'chrome', | 
 |     'content_shell': 'content_shell', | 
 |     'dart_bootstrap': 'dart_bootstrap', | 
 |     'dart': 'dart', | 
 |     'dart_precompiled_runtime': 'dart_precompiled_runtime', | 
 |     'firefox': 'firefox', | 
 |     'gen_snapshot': 'gen_snapshot', | 
 |     'git': 'git', | 
 |   }, | 
 |   'macos': { | 
 |     'chrome': 'Chrome', | 
 |     'chrome_helper': 'Chrome Helper', | 
 |     'content_shell': 'Content Shell', | 
 |     'dart_bootstrap': 'dart_bootstrap', | 
 |     'dart': 'dart', | 
 |     'dart_precompiled_runtime': 'dart_precompiled_runtime', | 
 |     'firefox': 'firefox', | 
 |     'gen_snapshot': 'gen_snapshot', | 
 |     'git': 'git', | 
 |     'safari': 'Safari', | 
 |   } | 
 | } | 
 |  | 
 | INFO_COMMAND = { | 
 |   'win32': 'wmic process where Processid=%s get CommandLine', | 
 |   'macos': POSIX_INFO, | 
 |   'linux': POSIX_INFO, | 
 | } | 
 |  | 
 | STACK_INFO_COMMAND = { | 
 |   'win32': None, | 
 |   'macos': '/usr/bin/sample %s 1 4000 -mayDie', | 
 |   'linux': '/usr/bin/eu-stack -p %s', | 
 | } | 
 |  | 
 | def GetOptions(): | 
 |   parser = optparse.OptionParser('usage: %prog [options]') | 
 |   true_or_false = ['True', 'False'] | 
 |   parser.add_option("--kill_dart", default='True', type='choice', | 
 |                     choices=true_or_false, | 
 |                     help="Kill all dart processes") | 
 |   parser.add_option("--kill_vc", default='True', type='choice', | 
 |                     choices=true_or_false, | 
 |                     help="Kill all git processes") | 
 |   parser.add_option("--kill_vsbuild", default='False', type='choice', | 
 |                     choices=true_or_false, | 
 |                     help="Kill all visual studio build related processes") | 
 |   parser.add_option("--kill_browsers", default='False', type='choice', | 
 |                     choices=true_or_false, | 
 |                     help="Kill all browser processes") | 
 |   (options, args) = parser.parse_args() | 
 |   return options | 
 |  | 
 |  | 
 | def GetPidsPosix(process_name): | 
 |   # This is to have only one posix command, on linux we could just do: | 
 |   # pidof process_name | 
 |   cmd = 'ps -e -o pid= -o comm=' | 
 |   # Sample output: | 
 |   # 1 /sbin/launchd | 
 |   # 80943 /Applications/Safari.app/Contents/MacOS/Safari | 
 |   p = subprocess.Popen(cmd, | 
 |                        stdout=subprocess.PIPE, | 
 |                        stderr=subprocess.PIPE, | 
 |                        shell=True) | 
 |   output, stderr = p.communicate() | 
 |   results = [] | 
 |   lines = output.splitlines() | 
 |   for line in lines: | 
 |     split = line.split() | 
 |     # On mac this ps commands actually gives us the full path to non | 
 |     # system binaries. | 
 |     if len(split) >= 2 and " ".join(split[1:]).endswith(process_name): | 
 |       results.append(split[0]) | 
 |   return results | 
 |  | 
 |  | 
 | def GetPidsWindows(process_name): | 
 |   cmd = 'tasklist /FI "IMAGENAME eq %s" /NH' % process_name | 
 |   # Sample output: | 
 |   # dart.exe    4356 Console            1      6,800 K | 
 |   p = subprocess.Popen(cmd, | 
 |                        stdout=subprocess.PIPE, | 
 |                        stderr=subprocess.PIPE, | 
 |                        shell=True) | 
 |   output, stderr = p.communicate() | 
 |   results = [] | 
 |   lines = output.splitlines() | 
 |  | 
 |   for line in lines: | 
 |     split = line.split() | 
 |     if len(split) > 2 and split[0] == process_name: | 
 |       results.append(split[1]) | 
 |   return results | 
 |  | 
 | def GetPids(process_name): | 
 |   if os_name == "win32": | 
 |     return GetPidsWindows(process_name) | 
 |   else: | 
 |     return GetPidsPosix(process_name) | 
 |  | 
 | def PrintPidStackInfo(pid): | 
 |   command_pattern = STACK_INFO_COMMAND.get(os_name, False) | 
 |   if command_pattern: | 
 |     p = subprocess.Popen(command_pattern % pid, | 
 |                          stdout=subprocess.PIPE, | 
 |                          stderr=subprocess.PIPE, | 
 |                          shell=True) | 
 |     stdout, stderr = p.communicate() | 
 |     stdout = stdout.splitlines() | 
 |     stderr = stderr.splitlines() | 
 |  | 
 |     print "  Stack:" | 
 |     for line in stdout: | 
 |       print "    %s" % line | 
 |     if stderr: | 
 |       print "  Stack (stderr):" | 
 |       for line in stderr: | 
 |         print "    %s" % line | 
 |  | 
 | def PrintPidInfo(pid, dump_stacks): | 
 |   # We assume that the list command will return lines in the format: | 
 |   # EXECUTABLE_PATH ARGS | 
 |   # There may be blank strings in the output | 
 |   p = subprocess.Popen(INFO_COMMAND[os_name] % pid, | 
 |                        stdout=subprocess.PIPE, | 
 |                        stderr=subprocess.PIPE, | 
 |                        shell=True) | 
 |   output, stderr = p.communicate() | 
 |   lines = output.splitlines() | 
 |  | 
 |   # Pop the header | 
 |   lines.pop(0) | 
 |  | 
 |   print "Hanging process info:" | 
 |   print "  PID: %s" % pid | 
 |   for line in lines: | 
 |     # wmic will output a bunch of empty strings, we ignore these | 
 |     if line: print "  Command line: %s" % line | 
 |  | 
 |   if dump_stacks: | 
 |     PrintPidStackInfo(pid) | 
 |  | 
 | def KillPosix(pid): | 
 |   try: | 
 |     os.kill(int(pid), signal.SIGKILL) | 
 |   except: | 
 |     # Ignore this, the process is already dead from killing another process. | 
 |     pass | 
 |  | 
 | def KillWindows(pid): | 
 |   # os.kill is not available until python 2.7 | 
 |   cmd = "taskkill /F /PID %s" % pid | 
 |   p = subprocess.Popen(cmd, | 
 |                        stdout=subprocess.PIPE, | 
 |                        stderr=subprocess.PIPE, | 
 |                        shell=True) | 
 |   p.communicate() | 
 |  | 
 | def Kill(name, dump_stacks=False): | 
 |   if name not in EXECUTABLE_NAMES[os_name]: | 
 |     return 0 | 
 |   print("***************** Killing %s *****************" % name) | 
 |   platform_name = EXECUTABLE_NAMES[os_name][name] | 
 |   pids = GetPids(platform_name) | 
 |   for pid in pids: | 
 |     PrintPidInfo(pid, dump_stacks) | 
 |     if os_name == "win32": | 
 |       KillWindows(pid) | 
 |     else: | 
 |       KillPosix(pid) | 
 |     print("Killed pid: %s" % pid) | 
 |   if len(pids) == 0: | 
 |     print("  No %s processes found." % name) | 
 |   return len(pids) | 
 |  | 
 | def KillBrowsers(): | 
 |   status = Kill('firefox') | 
 |   # We don't give error on killing chrome. It happens quite often that the | 
 |   # browser controller fails in killing chrome, so we silently do it here. | 
 |   Kill('chrome') | 
 |   status += Kill('chrome_helper') | 
 |   status += Kill('iexplore') | 
 |   status += Kill('safari') | 
 |   if os_name == "win32": | 
 |     # TODO(29599): Let content_shell affect exit code on windows, | 
 |     # once issue with zombie renderers is fixed. | 
 |     Kill('content_shell') | 
 |   else: | 
 |     status += Kill('content_shell') | 
 |   return status | 
 |  | 
 | def KillVCSystems(): | 
 |   status = Kill('git') | 
 |   return status | 
 |  | 
 | def KillVSBuild(): | 
 |   status = Kill('vctip') | 
 |   status += Kill('mspdbsrv') | 
 |   return status | 
 |  | 
 | def KillDart(): | 
 |   status = Kill("dart", dump_stacks=True) | 
 |   status += Kill("dart_bootstrap", dump_stacks=True) | 
 |   status += Kill("gen_snapshot", dump_stacks=True) | 
 |   status += Kill("dart_precompiled_runtime", dump_stacks=True) | 
 |   return status | 
 |  | 
 | def Main(): | 
 |   options = GetOptions() | 
 |   status = 0 | 
 |   if options.kill_dart == 'True': | 
 |     if os_name == "win32": | 
 |       # TODO(24086): Add result of KillDart into status once pub hang is fixed. | 
 |       KillDart() | 
 |     else: | 
 |       status += KillDart() | 
 |   if options.kill_vc == 'True': | 
 |     status += KillVCSystems() | 
 |   if options.kill_vsbuild == 'True' and os_name == 'win32': | 
 |     status += KillVSBuild() | 
 |   if options.kill_browsers == 'True': | 
 |     status += KillBrowsers() | 
 |   return status | 
 |  | 
 | if __name__ == '__main__': | 
 |   sys.exit(Main()) |