/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, as * published by the Free Software Foundation, and/or the GNU Lesser * General Public License, version 2.1, also as published by the Free * Software Foundation. * * This program 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 General Public License and the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.rhq.core.system.pquery; import java.io.FileInputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.rhq.core.system.NativeSystemInfo; import org.rhq.core.system.ProcessInfo; import org.rhq.core.system.pquery.Conditional.Qualifier; /** * Performs a query over a set of {@link ProcessInfo#getCommandLine() command line strings}. The query strings are * written in the Process Info Query Language (PIQL, pronounced pickle).In effect, your PIQL will examine * a list of running processes whose command lines match a certain set of criteria. PIQL statements are formatted by a * series of criteria, with each criteria separated with a comma: * *
CRITERIA[,CRITERIA]*
* *

Criteria are formatted in the following manner:

* *
CONDITIONAL=VALUE
* *

VALUE is either a regular expression or a pid filename to compare a value obtained using the * CONDITIONAL. See the class javadoc for java.util.regex.Pattern to learn the syntax of valid regular * expressions. A CONDITIONAL is defined as:

* *
CATEGORY|ATTRIBUTE|OPERATOR[|QUALIFIER]
* *

where:

* * * *

The ATTRIBUTE can be one of the following:

* * * *

The OPERATOR can be one of the following:

* * * *

Some examples of PIQL are:

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
PIQLWhat is matched
process|pidfile|match=/etc/product/lock.pidthe process whose pid matches the number found in the lock.pid file
process|pidfile|match|parent=/etc/product/lock.pidchild processes of the parent process whose pid matches the number found in the lock.pid file
process|name|match=^/foo.*all processes whose executables are found under the root "foo" directory
process|basename|match=^java.*all processes whose executable file has "java" at the start of it
process|basename|match=(?i)^java.*all processes whose executable file has "java" at the start of it (case insensitive, so "JAVA" would also * match)
process|name|match=.*(product|java).*all processes whose executable paths have either "product" or "java" in them
process|name|match=^C:.*,process|basename|nomatch=java.exeall processes whose executables are found on the Windows C: drive but is not a "java.exe" process
arg|1|match=org\.jboss\.Mainall processes whose command line argument #1 has a value of "org.jboss.Main". This will NOT match a process * that does not have a command line argument at the given index.
arg|*|match=.*daemon.*all processes whose command lines have any argument with the substring "daemon" in them
arg|-b|nomatch=127\.0\.0\.1all processes whose command lines have any argument named "-b" whose value is not "127.0.0.1" (e.g. "-b * 192.168.0.5"). This will NOT match a process that does not have that argument at all.
arg|-Dbind.address|match=127.0.0.1all processes whose command lines have any argument named "bind.address" whose value is "127.0.0.1" (e.g. * "-Dbind.address=127.0.0.1"). This will NOT match a process that does not have that argument at all.
arg|-cp|match=.*org\.abc\.Class.*all processes whose command lines have any argument named "-cp" whose value contains "org.abc.Class". This * will NOT match a process that does not have that argument at all.
arg|org.jboss.Main|match=.*all processes whose command lines have any argument named "org.jboss.Main"
process|basename|match=(?i)Apache.exe,arg|-k|match|parent=runserviceall Apache processes that are running as child processes to the main Apache service.
process|basename|nomatch|parent=execall processes that have a parent whose basename is not exec. This will match all processes that do not have a * parent.
* process|basename|match=^(https?d.*|[Aa]pache)$,process|name|nomatch|parent=^(https?d.*|[Aa]pache)$ * all Apache processes that do not have a parent process that is also an Apache process (i.e. this eliminates * all of the httpd child processes and only returns the main Apache servers). This will match a process that does * not have a parent but has a basename of Apache.
process|pid|match=1016The process whose pid is 1016.
* * @author John Mazzitelli */ public class ProcessInfoQuery { /** * The map of all processes keyed on their pids. */ private Map allProcesses; /** * Constructor for {@link ProcessInfoQuery} given an collection of process information that represents the processes * currently running. Think of the processes data as coming from part of the output you see in the * typical UNIX "ps" command. * * @param processes * * @see NativeSystemInfo#getAllProcesses() */ public ProcessInfoQuery(List processes) { this.allProcesses = new HashMap(processes.size()); for (ProcessInfo process : processes) { this.allProcesses.put(process.getPid(), process); } } /** * Returns the list of all the {@link ProcessInfo processes} that this object will {@link #query(String) query} * against. * * @return all processes this object knows about */ public List getProcesses() { return new ArrayList(allProcesses.values()); } /** * Performs a query on the set of known processes where query defines the criteria. * * @param query the query string containing the criteria to match * * @return the matches processes' command lines * * @throws IllegalArgumentException if the query was invalid */ public List query(String query) { List criteriaList = getCriteriaList(query); // if we got an empty query - it means we match nothing so return an empty list immediately if (criteriaList.size() == 0) { return new ArrayList(); } // keyed on pid so we automatically avoid dups (in case more than one criteria matches) Map queryResults = new HashMap(this.allProcesses); Map criteriaResults; for (Criteria criteria : criteriaList) { if (criteria.getConditional().getCategory().equals(Conditional.Category.process)) { criteriaResults = doProcessCriteriaQuery(criteria); } else if (criteria.getConditional().getCategory().equals(Conditional.Category.arg)) { criteriaResults = doArgCriteriaQuery(criteria); } else { throw new IllegalArgumentException("Unknown category: " + criteria); // should never happen } // multiple criteria results are ANDed together // only retain those previously matched processes that were also matched in the latest criteria Set pids = new HashSet(queryResults.keySet()); // new set to avoid concurrent mod exceptions for (Long pid : pids) { if (!criteriaResults.containsKey(pid)) { // a previously matched process was not matched in the latest criteria, so removed it queryResults.remove(pid); } } if (queryResults.size() == 0) { // we've eliminated every possible process - don't bother running any more criteria break; } } List results = new ArrayList(queryResults.size()); results.addAll(queryResults.values()); return results; } /** * Runs the given criteria with the arg conditional and returns the processes that match. * * @param criteria the criteria with the arg conditional * * @return the matched processes keyed on the pids * * @throws IllegalArgumentException */ private Map doArgCriteriaQuery(Criteria criteria) { Map matches = new HashMap(); Attribute attribute = criteria.getConditional().getAttribute(); Operation op = new Operation(criteria.getConditional().getOperator()); Qualifier qualifier = criteria.getConditional().getQualifier(); String operand1 = null; String operand2 = criteria.getValue(); for (ProcessInfo process : getProcesses()) { ProcessInfo processToMatch; // will be the same as process unless the parent qualifier was provided if (qualifier.equals(Qualifier.parent)) { processToMatch = getParentProcess(process); } else { processToMatch = process; } String[] cmdline = (processToMatch != null) ? processToMatch.getCommandLine() : null; if ((cmdline == null) || (cmdline.length == 0)) { continue; // no sense continuing with this process - there are no command line arguments } if (attribute.getAttributeValue().equals("*")) { // * means see if any arg matches for (String arg : cmdline) { operand1 = arg; if (op.doOperation(operand1, operand2)) { matches.put(process.getPid(), process); break; // we got a match, don't bother looking at more args } } } else if (attribute.getAttributeValueAsInteger() != null) { // if we get here, it means the argument specified was a specific argument index number int attributeIndex = attribute.getAttributeValueAsInteger().intValue(); // an arg of -1 means the query wants to obtain the last argument in the command line if (attributeIndex < 0) { attributeIndex = cmdline.length - 1; } if ((cmdline.length - 1) < attributeIndex) { continue; // process doesn't have enough args - there is no command line argument with that index } operand1 = cmdline[attributeIndex]; if (op.doOperation(operand1, operand2)) { matches.put(process.getPid(), process); } } else { // if we get here, it means the attribute specified was the name of an argument String attributeName = attribute.getAttributeValue(); for (int i = 0; i < cmdline.length; i++) { String arg = cmdline[i]; // if the arg name doesn't even start with our attribute, then we continue on to the next if (arg.startsWith(attributeName)) { if (arg.equals(attributeName)) { // the full argument name is the attribute name, the command line was something like: // "exec.exe -arg value" or "exec.exe -arg" so the value is the next argument operand1 = ((i + 1) < cmdline.length) ? cmdline[i + 1] : ""; } else { // the command line was something like: "exec.exe -arg=value" so the value is after the equals side within the arg int equals = arg.indexOf('='); if (equals == -1) { continue; // the argument looked like what we were trying to find, but it really wasn't } operand1 = (arg.length() > (equals + 1)) ? arg.substring(equals + 1) : ""; } if (op.doOperation(operand1, operand2)) { matches.put(process.getPid(), process); break; // no need to continue, we've got the match we are looking for } } } } } return matches; } /** * Runs the given criteria with the process conditional and returns the processes that match. * * @param criteria the criteria with the process conditional * * @return the matched processes keyed on the pids * * @throws IllegalArgumentException */ private Map doProcessCriteriaQuery(Criteria criteria) { Map matches = new HashMap(); Attribute attribute = criteria.getConditional().getAttribute(); Operation op = new Operation(criteria.getConditional().getOperator()); Qualifier qualifier = criteria.getConditional().getQualifier(); String operand1; String operand2; String pidfileContentsCache = null; // so we avoid reading the file over and over again for (ProcessInfo process : getProcesses()) { ProcessInfo processToMatch; // will be the same as process unless the parent qualifier was provided if (qualifier.equals(Qualifier.parent)) { processToMatch = getParentProcess(process); } else { processToMatch = process; } if (attribute.getAttributeValue().equals(Attribute.ProcessCategoryAttributes.name.toString())) { operand1 = (processToMatch != null) ? processToMatch.getName() : ""; operand2 = criteria.getValue(); } else if (attribute.getAttributeValue().equals(Attribute.ProcessCategoryAttributes.basename.toString())) { operand1 = (processToMatch != null) ? processToMatch.getBaseName() : ""; operand2 = criteria.getValue(); } else if (attribute.getAttributeValue().equals(Attribute.ProcessCategoryAttributes.pid.toString())) { operand1 = (processToMatch != null) ? Long.toString(processToMatch.getPid()) : ""; operand2 = criteria.getValue(); } else if (attribute.getAttributeValue().equals(Attribute.ProcessCategoryAttributes.pidfile.toString())) { if (pidfileContentsCache == null) { pidfileContentsCache = getPidfileContents(criteria.getValue()); } operand1 = (processToMatch != null) ? String.valueOf(processToMatch.getPid()) : null; operand2 = pidfileContentsCache; } else { throw new IllegalArgumentException( "Criteria with 'process' category must have an attribute of either 'name' or 'basename': " + criteria); } if (op.doOperation(operand1, operand2)) { matches.put(process.getPid(), process); } } return matches; } /** * Gets the parent process for the given process. The parent will be searched for within the {@link #getProcesses()} * list. * * @param child * * @return the child's parent process or null if the child has no parent */ private ProcessInfo getParentProcess(ProcessInfo child) { ProcessInfo parent = null; if (child != null) { parent = this.allProcesses.get(child.getParentPid()); } return parent; } private List getCriteriaList(String query) { List criteria = new ArrayList(); if (query != null) { String[] tokens = query.split(","); for (String criteriaString : tokens) { Criteria c = new Criteria(criteriaString); criteria.add(c); } } return criteria; } private String getPidfileContents(String pidfileName) { String contents; try { byte[] bytes = new byte[64]; // a pid file should never come close to being 64 bytes big FileInputStream fis = new FileInputStream(pidfileName); try { int count = fis.read(bytes); contents = new String(bytes, 0, count); } finally { fis.close(); } } catch (Exception e) { contents = ""; } return contents; } }