blob: c645cc0bae8e405404eb52800fe9f941020cc0be [file] [log] [blame]
#!/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())