ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # |
| 3 | # Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 4 | # for details. All rights reserved. Use of this source code is governed by a |
| 5 | # BSD-style license that can be found in the LICENSE file. |
| 6 | # |
| 7 | |
| 8 | # A script to kill hanging processs. The tool will return non-zero if any |
| 9 | # process was actually found. |
| 10 | # |
| 11 | |
| 12 | import optparse |
| 13 | import os |
| 14 | import signal |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 15 | import subprocess |
| 16 | import sys |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 17 | |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 18 | import utils |
| 19 | |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 20 | |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 21 | os_name = utils.GuessOS() |
| 22 | |
| 23 | POSIX_INFO = 'ps -p %s -o args' |
| 24 | |
| 25 | EXECUTABLE_NAMES = { |
kustermann@google.com | 6b21986 | 2014-01-09 14:07:20 +0000 | [diff] [blame] | 26 | 'win32': { |
| 27 | 'chrome': 'chrome.exe', |
| 28 | 'content_shell': 'content_shell.exe', |
| 29 | 'dart': 'dart.exe', |
| 30 | 'iexplore': 'iexplore.exe', |
ricow@google.com | 1aba267 | 2014-04-28 07:32:45 +0000 | [diff] [blame] | 31 | 'firefox': 'firefox.exe', |
| 32 | 'git': 'git.exe', |
Rico Wind | 171ca68 | 2015-08-20 09:32:59 +0200 | [diff] [blame] | 33 | 'svn': 'svn.exe', |
| 34 | 'fletch': 'fletch.exe', |
| 35 | 'fletch-vm': 'fletch-vm.exe', |
kustermann@google.com | 6b21986 | 2014-01-09 14:07:20 +0000 | [diff] [blame] | 36 | }, |
| 37 | 'linux': { |
| 38 | 'chrome': 'chrome', |
| 39 | 'content_shell': 'content_shell', |
| 40 | 'dart': 'dart', |
ricow@google.com | 1aba267 | 2014-04-28 07:32:45 +0000 | [diff] [blame] | 41 | 'firefox': 'firefox.exe', |
| 42 | 'git': 'git', |
Rico Wind | 171ca68 | 2015-08-20 09:32:59 +0200 | [diff] [blame] | 43 | 'svn': 'svn', |
| 44 | 'fletch': 'fletch', |
| 45 | 'fletch-vm': 'fletch-vm', |
kustermann@google.com | 6b21986 | 2014-01-09 14:07:20 +0000 | [diff] [blame] | 46 | }, |
| 47 | 'macos': { |
| 48 | 'chrome': 'Chrome', |
| 49 | 'content_shell': 'Content Shell', |
| 50 | 'dart': 'dart', |
| 51 | 'firefox': 'firefox', |
ricow@google.com | 1aba267 | 2014-04-28 07:32:45 +0000 | [diff] [blame] | 52 | 'safari': 'Safari', |
| 53 | 'git': 'git', |
Rico Wind | 171ca68 | 2015-08-20 09:32:59 +0200 | [diff] [blame] | 54 | 'svn': 'svn', |
| 55 | 'fletch': 'fletch', |
| 56 | 'fletch-vm': 'fletch-vm', |
kustermann@google.com | 6b21986 | 2014-01-09 14:07:20 +0000 | [diff] [blame] | 57 | } |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 58 | } |
| 59 | |
| 60 | INFO_COMMAND = { |
| 61 | 'win32': 'wmic process where Processid=%s get CommandLine', |
| 62 | 'macos': POSIX_INFO, |
| 63 | 'linux': POSIX_INFO, |
| 64 | } |
| 65 | |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 66 | STACK_INFO_COMMAND = { |
| 67 | 'win32': None, |
| 68 | 'macos': '/usr/bin/sample %s 1 4000 -mayDie', |
| 69 | 'linux': '/usr/bin/eu-stack -p %s', |
| 70 | } |
| 71 | |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 72 | def GetOptions(): |
| 73 | parser = optparse.OptionParser("usage: %prog [options]") |
| 74 | parser.add_option("--kill_dart", default=True, |
| 75 | help="Kill all dart processes") |
Rico Wind | 171ca68 | 2015-08-20 09:32:59 +0200 | [diff] [blame] | 76 | parser.add_option("--kill_fletch", default=True, |
| 77 | help="Kill all fletch and fletch-vm processes") |
ricow@google.com | 1aba267 | 2014-04-28 07:32:45 +0000 | [diff] [blame] | 78 | parser.add_option("--kill_vc", default=True, |
| 79 | help="Kill all git and svn processes") |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 80 | parser.add_option("--kill_browsers", default=False, |
| 81 | help="Kill all browser processes") |
| 82 | (options, args) = parser.parse_args() |
| 83 | return options |
| 84 | |
| 85 | |
| 86 | def GetPidsPosix(process_name): |
| 87 | # This is to have only one posix command, on linux we could just do: |
| 88 | # pidof process_name |
ricow@google.com | 63eaf52 | 2013-09-04 08:52:20 +0000 | [diff] [blame] | 89 | cmd = 'ps -e -o pid= -o comm=' |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 90 | # Sample output: |
| 91 | # 1 /sbin/launchd |
| 92 | # 80943 /Applications/Safari.app/Contents/MacOS/Safari |
| 93 | p = subprocess.Popen(cmd, |
| 94 | stdout=subprocess.PIPE, |
| 95 | stderr=subprocess.PIPE, |
| 96 | shell=True) |
| 97 | output, stderr = p.communicate() |
| 98 | results = [] |
| 99 | lines = output.splitlines() |
| 100 | for line in lines: |
| 101 | split = line.split() |
| 102 | # On mac this ps commands actually gives us the full path to non |
| 103 | # system binaries. |
| 104 | if len(split) >= 2 and " ".join(split[1:]).endswith(process_name): |
| 105 | results.append(split[0]) |
| 106 | return results |
| 107 | |
| 108 | |
| 109 | def GetPidsWindows(process_name): |
| 110 | cmd = 'tasklist /FI "IMAGENAME eq %s" /NH' % process_name |
| 111 | # Sample output: |
| 112 | # dart.exe 4356 Console 1 6,800 K |
| 113 | p = subprocess.Popen(cmd, |
| 114 | stdout=subprocess.PIPE, |
| 115 | stderr=subprocess.PIPE, |
| 116 | shell=True) |
| 117 | output, stderr = p.communicate() |
| 118 | results = [] |
| 119 | lines = output.splitlines() |
| 120 | |
| 121 | for line in lines: |
| 122 | split = line.split() |
| 123 | if len(split) > 2 and split[0] == process_name: |
| 124 | results.append(split[1]) |
| 125 | return results |
| 126 | |
| 127 | def GetPids(process_name): |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 128 | if os_name == "win32": |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 129 | return GetPidsWindows(process_name) |
| 130 | else: |
| 131 | return GetPidsPosix(process_name) |
| 132 | |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 133 | def PrintPidStackInfo(pid): |
| 134 | command_pattern = STACK_INFO_COMMAND.get(os_name, False) |
| 135 | if command_pattern: |
| 136 | p = subprocess.Popen(command_pattern % pid, |
| 137 | stdout=subprocess.PIPE, |
| 138 | stderr=subprocess.PIPE, |
| 139 | shell=True) |
| 140 | stdout, stderr = p.communicate() |
| 141 | stdout = stdout.splitlines() |
| 142 | stderr = stderr.splitlines() |
| 143 | |
| 144 | print " Stack:" |
| 145 | for line in stdout: |
| 146 | print " %s" % line |
| 147 | if stderr: |
| 148 | print " Stack (stderr):" |
| 149 | for line in stderr: |
| 150 | print " %s" % line |
| 151 | |
| 152 | def PrintPidInfo(pid, dump_stacks): |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 153 | # We assume that the list command will return lines in the format: |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 154 | # EXECUTABLE_PATH ARGS |
| 155 | # There may be blank strings in the output |
| 156 | p = subprocess.Popen(INFO_COMMAND[os_name] % pid, |
| 157 | stdout=subprocess.PIPE, |
| 158 | stderr=subprocess.PIPE, |
| 159 | shell=True) |
| 160 | output, stderr = p.communicate() |
| 161 | lines = output.splitlines() |
| 162 | |
| 163 | # Pop the header |
| 164 | lines.pop(0) |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 165 | |
| 166 | print "Hanging process info:" |
| 167 | print " PID: %s" % pid |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 168 | for line in lines: |
| 169 | # wmic will output a bunch of empty strings, we ignore these |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 170 | if line: print " Command line: %s" % line |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 171 | |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 172 | if dump_stacks: |
| 173 | PrintPidStackInfo(pid) |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 174 | |
| 175 | def KillPosix(pid): |
| 176 | try: |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 177 | os.kill(int(pid), signal.SIGKILL) |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 178 | except: |
| 179 | # Ignore this, the process is already dead from killing another process. |
| 180 | pass |
| 181 | |
| 182 | def KillWindows(pid): |
| 183 | # os.kill is not available until python 2.7 |
| 184 | cmd = "taskkill /F /PID %s" % pid |
| 185 | p = subprocess.Popen(cmd, |
| 186 | stdout=subprocess.PIPE, |
| 187 | stderr=subprocess.PIPE, |
| 188 | shell=True) |
| 189 | p.communicate() |
| 190 | |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 191 | def Kill(name, dump_stacks=False): |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 192 | if name not in EXECUTABLE_NAMES[os_name]: |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 193 | return 0 |
| 194 | print("***************** Killing %s *****************" % name) |
| 195 | platform_name = EXECUTABLE_NAMES[os_name][name] |
| 196 | pids = GetPids(platform_name) |
| 197 | for pid in pids: |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 198 | PrintPidInfo(pid, dump_stacks) |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 199 | if os_name == "win32": |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 200 | KillWindows(pid) |
| 201 | else: |
| 202 | KillPosix(pid) |
| 203 | print("Killed pid: %s" % pid) |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 204 | if len(pids) == 0: |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 205 | print(" No %s processes found." % name) |
| 206 | return len(pids) |
| 207 | |
| 208 | def KillBrowsers(): |
| 209 | status = Kill('firefox') |
Rico Wind | 6d4aea8 | 2015-06-04 12:43:33 +0200 | [diff] [blame] | 210 | # We don't give error on killing chrome. It happens quite often that the |
| 211 | # browser controller fails in killing chrome, so we silently do it here. |
| 212 | Kill('chrome') |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 213 | status += Kill('iexplore') |
| 214 | status += Kill('safari') |
kustermann@google.com | 6b21986 | 2014-01-09 14:07:20 +0000 | [diff] [blame] | 215 | status += Kill('content_shell') |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 216 | return status |
| 217 | |
ricow@google.com | 1aba267 | 2014-04-28 07:32:45 +0000 | [diff] [blame] | 218 | def KillVCSystems(): |
| 219 | status = Kill('git') |
| 220 | status += Kill('svn') |
| 221 | return status |
| 222 | |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 223 | def KillDart(): |
Martin Kustermann | 8347d26 | 2017-01-04 16:59:54 +0100 | [diff] [blame] | 224 | status = Kill("dart", dump_stacks=True) |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 225 | return status |
| 226 | |
Rico Wind | 171ca68 | 2015-08-20 09:32:59 +0200 | [diff] [blame] | 227 | def KillFletch(): |
| 228 | status = Kill("fletch") |
| 229 | status += Kill("fletch-vm") |
| 230 | return status |
| 231 | |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 232 | def Main(): |
| 233 | options = GetOptions() |
| 234 | status = 0 |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 235 | if options.kill_dart: |
William Hesse | cd21da1 | 2016-01-13 11:17:18 +0100 | [diff] [blame] | 236 | if os_name == "win32": |
| 237 | # TODO(24086): Add result of KillDart into status once pub hang is fixed. |
| 238 | KillDart() |
| 239 | else: |
| 240 | status += KillDart() |
Rico Wind | 171ca68 | 2015-08-20 09:32:59 +0200 | [diff] [blame] | 241 | if options.kill_fletch: |
| 242 | status += KillFletch() |
whesse@google.com | c7d5711 | 2014-07-25 11:47:59 +0000 | [diff] [blame] | 243 | if options.kill_vc: |
| 244 | status += KillVCSystems() |
| 245 | if options.kill_browsers: |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 246 | status += KillBrowsers() |
ricow@google.com | 0ea1b3e | 2013-09-06 10:26:13 +0000 | [diff] [blame] | 247 | return status |
ricow@google.com | 18504fd | 2013-08-28 11:08:02 +0000 | [diff] [blame] | 248 | |
| 249 | if __name__ == '__main__': |
| 250 | sys.exit(Main()) |