| #!/usr/bin/env python3 |
| # |
| # 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', |
| 'dart': 'dart.exe', |
| 'dartaotruntime': 'dartaotruntime.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', |
| 'dart': 'dart', |
| 'dartaotruntime': 'dartaotruntime', |
| 'dart_precompiled_runtime': 'dart_precompiled_runtime', |
| 'firefox': 'firefox', |
| 'gen_snapshot': 'gen_snapshot', |
| 'flutter_tester': 'flutter_tester', |
| 'git': 'git', |
| }, |
| 'macos': { |
| 'chrome': 'Chrome', |
| 'chrome_helper': 'Chrome Helper', |
| 'dart': 'dart', |
| 'dartaotruntime': 'dartaotruntime', |
| '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, |
| universal_newlines=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, |
| universal_newlines=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, |
| universal_newlines=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, |
| universal_newlines=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, |
| universal_newlines=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') |
| 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("gen_snapshot", dump_stacks=True) |
| status += Kill("dartaotruntime", dump_stacks=True) |
| status += Kill("dart_precompiled_runtime", dump_stacks=True) |
| status += Kill("flutter_tester", 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()) |