#!/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', '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', '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', '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("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())