/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.acs.testsupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import alma.acs.concurrent.DaemonThreadFactory;
import alma.acs.util.ProcessStreamGobbler;
/**
* Helper class to be used by tests which need to verify that some other process exists
* or does not exist, or that need to kill some process.
* <b>Don't use this class in operational code!</b>
*/
public class ProcessUtil
{
private final Logger logger;
private volatile boolean DEBUG = false;
public ProcessUtil(Logger logger) {
this.logger = logger;
}
/**
* Gets the process IDs of all java processes run by the current user on the local host,
* whose main class is given as the parameter.
* <p>
* The "jps" command is used via Runtime.exec, which should work not only on Linux
* but also on Windows.
*
* @throws IOException
*/
public List<String> getJavaPIDs(Class<?> mainClass) throws IOException {
Map<String, List<String>> pidMap = getJavaPIDs();
List<String> pidList = pidMap.get(mainClass.getName());
if (pidList == null) {
pidList = new ArrayList<String>();
}
return pidList;
}
/**
* Returns true if one or more JVMs are running the given Java main class
* on the local host as the current user.
* @throws IOException
*/
public boolean isJavaProcessRunning(Class<?> mainClass) throws IOException {
return ((getJavaPIDs(mainClass)).size() > 0);
}
/**
* Kills a process with a given PID, which could be obtained from {@link #getJavaPIDs(Class)}.
* <p>
* @TODO Currently this method works only on Linux because it uses the "kill -9" command.
*
* @TODO Currently the stdout and stderr from running "kill" are not gobbled because
* they are expected to be very small and thus won't block buffers etc.
* If they do, then use {@link ProcessStreamGobbler} also here.
*
* @param tough if true, a tough way of killing is used (kill -9)
* @return Exit value of the kill command
* @throws IOException
* @throws InterruptedException
*/
public int killProcess(String pid, boolean tough) throws IOException, InterruptedException {
String command = "kill ";
if (tough) {
command += "-9 ";
}
command += pid;
logger.info("Will kill process " + pid + " using command '" + command + "'.");
Process killProc = Runtime.getRuntime().exec(command);
return killProc.waitFor();
}
/**
* Gets a map with key=(running java main classes) and value=(list of the process IDs).
* Filters out sun.tools.jps.Jps which is the tool used to get the processes.
* @return Map<classname, pid-list>
* @throws IOException
* @throws InterruptedException
*/
protected Map<String, List<String>> getJavaPIDs() throws IOException {
// The following command returns lines of the format
// 23551 com.cosylab.acs.maci.manager.app.Manager
// 29113 sun.tools.jps.Jps
String command = "jps -l";
Process proc = Runtime.getRuntime().exec(command);
ProcessStreamGobbler gob = new ProcessStreamGobbler(proc, new DaemonThreadFactory(), true);
gob.setDebug(DEBUG);
try {
// read stdout and stderr
if (!gob.gobble(10, TimeUnit.SECONDS)) {
throw new IOException("Failed to execute command '" + command + "' within 10 seconds");
}
if (gob.hasStreamReadErrors()) {
throw new IOException("Failed to read output of command '" + command + "'");
}
} catch (InterruptedException ex) {
throw new IOException("Thread reading output of command '" + command + "' got interrupted.");
}
// evaluate jps output
Map<String, List<String>> pidMap = new HashMap<String, List<String>>();
List<String> outlines = gob.getStdout();
String[] splitLine = null;
for (String line : outlines) {
if (line.length() > 0 && (splitLine = line.split(" ")).length == 2) {
String cname = splitLine[1];
if (!"sun.tools.jps.Jps".equals(cname)) {
String pid = splitLine[0];
List<String> pidList = pidMap.containsKey(cname) ? pidMap.get(cname) : new ArrayList<String>();
pidList.add(pid);
pidMap.put(cname, pidList);
}
}
else {
logger.info("jps returned unexpected line '" + line + "'");
}
}
return pidMap;
}
public void setDebug(boolean DEBUG) {
this.DEBUG = DEBUG;
}
}