/*******************************************************************************
* Copyright (c) 2012 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*******************************************************************************/
package org.cloudifysource.usm.locator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.cloudifysource.usm.USMException;
import org.cloudifysource.usm.UniversalServiceManagerBean;
import org.cloudifysource.usm.events.AbstractUSMEventListener;
import org.cloudifysource.usm.events.EventResult;
import org.cloudifysource.usm.events.PreStartListener;
import org.cloudifysource.usm.events.StartReason;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarException;
import com.gigaspaces.internal.sigar.SigarHolder;
/************
* A process locator implementation that is executed if no other process locator is defined. It scans the process tree
* under the current process, comparing the processes running before the start command was called, and after start
* detection passed successfully. Then it select the 'leaf' nodes of this tree. This gives us the 'interesting'
* processes, assuming the service process runs in the foreground.
*
* This implementation is a heuristic, and works best when executing a single process in the foreground, typical for
* multi-threaded processes like java application servers. It is generally a good idea for a process to explicitly
* define its process locator, so that this locator is not used.
*
*
* @author barakme
*
*/
public class DefaultProcessLocator extends AbstractUSMEventListener implements ProcessLocator, PreStartListener {
private static final java.util.logging.Logger logger =
java.util.logging.Logger.getLogger(DefaultProcessLocator.class.getName());
// process names for well-known shell
// used to check if the monitored process is a shell, and not an
// 'interesting' process
private static final String[] SHELL_PROCESS_NAMES = { "cmd.exe", "bash", "/bin/sh" };
private Sigar sigar;
private long myPid;
private Set<Long> childrenBeforeStart;
private long childProcessID;
private List<Long> serviceProcesses;
@Override
public List<Long> getProcessIDs()
throws USMException {
this.findProcessIDs();
return this.serviceProcesses;
}
@Override
public EventResult onPreStart(final StartReason reason) {
this.myPid = this.sigar.getPid();
try {
this.childrenBeforeStart = getChildProcesses(this.myPid);
} catch (final USMException e) {
throw new IllegalStateException("Failed to read child processes", e);
}
return EventResult.SUCCESS;
}
/**********
* Creates a process tree of the given Process IDs. Each entry in the map maps a PID to the set of its child PIDs.
*
* @param pids
* @return
*/
private Map<Long, Set<Long>> createProcessTree(final long[] pids) {
final HashMap<Long, Set<Long>> map = new HashMap<Long, Set<Long>>();
for (final long pid : pids) {
final Set<Long> childSet = map.get(pid);
if (childSet == null) {
map.put(pid, new HashSet<Long>());
}
try {
final long ppid = this.sigar.getProcState(pid).getPpid();
Set<Long> set = map.get(ppid);
if (set == null) {
set = new HashSet<Long>();
map.put(ppid, set);
}
set.add(pid);
} catch (final SigarException e) {
logger.log(Level.WARNING, "Failed to get Parent Process for process: " + pid, e);
}
}
return map;
}
private long findNewChildProcessID(final Set<Long> childrenBefore, final Map<Long, Set<Long>> procTree)
throws USMException {
final Set<Long> childrenAfter = procTree.get(this.myPid);
if (childrenAfter == null) {
throw new USMException("Could not find container process (" + this.myPid + ") in generated process tree");
}
childrenAfter.removeAll(childrenBefore);
if (childrenAfter.isEmpty()) {
logger.warning("Default process locator could not find a new process! "
+ "Are you running your service as a background process or a system service?");
return 0;
} else if (childrenAfter.size() > 1) {
logger.warning("Multiple new processes have been found: " + childrenAfter.toString()
+ ". Using the first as child process ID!");
}
final long newChildProcessID = childrenAfter.iterator().next();
return newChildProcessID;
}
/**********
* Recursive function that scans the given process tree, rooted at the given parent PID, and add all leaf pids to
* the result list.
*
* @param parentProcessID
* the root of the tree.
* @param procTree
* the full process tree.
* @param leafPids
* the result leaf pids list.
*/
private void findLeafProcessIDs(final long parentProcessID, final Map<Long, Set<Long>> procTree,
final List<Long> leafPids) {
final Set<Long> pids = procTree.get(parentProcessID);
if (pids == null || pids.isEmpty()) {
leafPids.add(parentProcessID);
return;
}
for (final Long pid : pids) {
// Recursive call
findLeafProcessIDs(pid, procTree, leafPids);
}
}
private void findProcessIDs()
throws USMException {
final long[] allPids = getAllPids();
final Map<Long, Set<Long>> procTree = createProcessTree(allPids);
this.childProcessID = findNewChildProcessID(childrenBeforeStart, procTree);
if (this.childProcessID == 0) {
logger.warning("Default foreground process locator was unable to locate a new child process. "
+ "The default implementation can only locate foreground processes. "
+ "If you are running backgorund processes or OS services, you must "
+ "set a process locator to get process level metrics and monitoring");
this.serviceProcesses = new ArrayList<Long>(0);
} else {
logger.info("Looking for actual process ID in process tree");
final List<Long> resultList = new LinkedList<Long>();
findLeafProcessIDs(this.childProcessID, procTree, resultList);
if (resultList.isEmpty()) {
logger.warning("Default process locator was unable to locate service processes. "
+ "The default implementation can only locate foreground processes. "
+ "If you are running backgorund processes or OS services, you must "
+ "set a process locator to get process level metrics and monitoring");
}
this.serviceProcesses = resultList;
// also logs process details to logger.
checkForConsoleProcess();
}
}
private void checkForConsoleProcess() {
final List<Long> pids = this.serviceProcesses;
for (final Long pid : pids) {
try {
String procName = this.sigar.getProcExe(pid).getName();
String[] procArgs = this.sigar.getProcArgs(pid);
// sigar could return anything...
if (procName == null) {
procName = "Unknown";
}
if (procArgs == null) {
procArgs = new String[0];
}
logger.info("Located process (" + pid + "): " + procName + " " + Arrays.toString(procArgs));
for (final String shellName : SHELL_PROCESS_NAMES) {
if (procName.contains(shellName)) {
logger.warning("A monitored process(" + pid + " - " + procName + ") may be a console process. "
+ "This is usually a configuration problem. "
+ "USM Statistics will be collected for this process, "
+ "and not for the child process it probably has. Are you missing a Start Detector?");
}
}
} catch (final SigarException e) {
logger.log(Level.SEVERE,
"While checking if process is a console, failed to read the process name for process: " + pid,
e);
}
}
}
private Set<Long> getChildProcesses(final long ppid)
throws USMException {
final long[] pids = getAllPids();
return getChildProcesses(ppid, pids);
}
private long[] getAllPids()
throws USMException {
long[] pids;
try {
pids = this.sigar.getProcList();
} catch (final SigarException se) {
throw new USMException("Failed to look up process IDs. Error was: " + se.getMessage(), se);
}
return pids;
}
private Set<Long> getChildProcesses(final long ppid, final long[] pids) {
final Set<Long> children = new HashSet<Long>();
for (final long pid : pids) {
try {
if (ppid == this.sigar.getProcState(pid).getPpid()) {
children.add(pid);
}
} catch (final SigarException e) {
logger.log(Level.WARNING, "While scanning for child processes of process " + ppid
+ ", could not read process state of Process: " + pid + ". Ignoring.", e);
}
}
return children;
}
@Override
public void init(final UniversalServiceManagerBean usm) {
super.init(usm);
this.sigar = SigarHolder.getSigar();
}
}