/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License 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 for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.product;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.hyperic.sigar.Sigar;
import org.hyperic.sigar.SigarProxy;
import org.hyperic.sigar.SigarProxyCache;
import org.hyperic.sigar.SigarException;
import org.hyperic.sigar.SigarNotImplementedException;
import org.hyperic.sigar.jmx.SigarInvokerJMX;
import org.hyperic.sigar.ptql.ProcessQuery;
import org.hyperic.sigar.ptql.ProcessQueryFactory;
import org.hyperic.sigar.ptql.ProcessFinder;
import org.hyperic.sigar.ptql.MalformedQueryException;
import org.hyperic.sigar.ptql.QueryLoadException;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigResponse;
public class SigarMeasurementPlugin extends MeasurementPlugin {
public static final String DOMAIN = "sigar";
public static final String PTQL_DOMAIN = "sigar.ptql";
public static final String PTQL_CONFIG = "process.query";
private Sigar sigar = null;
private SigarProxy sigarProxy = null;
private ProcessFinder processFinder = null;
private static final Map AVAIL_ATTRS = new HashMap();
//Availability helpers. Assume resource is available if
//we can collect the given attribute.
static {
AVAIL_ATTRS.put("DirUsage", "Total");
AVAIL_ATTRS.put("FileInfo", "Type");
AVAIL_ATTRS.put("MultiProcCpu", "Processes");
AVAIL_ATTRS.put("MultiProcMem", "Size");
}
protected Sigar getSigar() throws PluginException {
if (this.sigar != null) {
return this.sigar;
}
try {
this.sigar = new Sigar();
this.sigarProxy = SigarProxyCache.newInstance(sigar);
} catch (UnsatisfiedLinkError le) {
//XXX ok for now; sigar is not loaded in the server
//getValue will fail in the agent in sigar was not properly
//installed.
getLog().warn("unable to load sigar: " + le.getMessage());
return null;
}
return this.sigar;
}
public void shutdown() throws PluginException {
if (this.sigar != null) {
this.sigar.close();
}
super.shutdown();
}
public String translate(String template, ConfigResponse config) {
if (template.indexOf(PTQL_DOMAIN + ":") > 0) {
//do not encode the query input.
String newTemplate =
StringUtil.replace(template,
"%" + PTQL_CONFIG + "%",
config.getValue(PTQL_CONFIG));
//yes, this is '!=', not '.equals'
//StringUtil.replace returns the original if unchanged.
if (newTemplate != template) {
return newTemplate;
}
//fallthru e.g. Win32MeasurementPlugin uses sigar.ptql
//domain but not %process.query%
}
return super.translate(template, config);
}
private String translatePTQL(Metric jdsn)
throws MetricNotFoundException {
String qs = jdsn.getPropString();
if (this.processFinder == null) {
this.processFinder =
new ProcessFinder(this.sigarProxy);
}
ProcessQuery query;
try {
query = ProcessQueryFactory.getInstance(qs);
} catch (MalformedQueryException e) {
throw new MetricInvalidException(e.getMessage(), e);
} catch (QueryLoadException e) {
throw new MetricInvalidException(e.getMessage(), e);
}
long pid;
try {
pid = processFinder.findSingleProcess(query);
} catch (SigarNotImplementedException e) {
throw new MetricInvalidException(e.getMessage(), e);
} catch (MalformedQueryException e) {
throw new MetricNotFoundException(e.getMessage(), e);
} catch (SigarException e) {
throw new MetricNotFoundException(e.getMessage(), e);
}
final String type = SigarInvokerJMX.PROP_TYPE;
final String arg = SigarInvokerJMX.PROP_ARG;
//rewrite template with found pid.
return new StringBuffer().
append(type).append('=').
append(jdsn.getObjectProperty(type)).append(',').
append(arg).append('=').append(pid).
toString();
}
private boolean isProcessState(String attr) {
return attr.equals("State");
}
private MetricValue getSwapPercentage(Metric m)
throws MetricNotFoundException
{
String attribute = m.getAttributeName();
Object total, free, used;
SigarInvokerJMX invoker =
SigarInvokerJMX.getInstance(sigarProxy, m.getObjectName());
synchronized (sigar) {
try {
total = invoker.invoke("Total");
free = invoker.invoke("Free");
used = invoker.invoke("Used");
} catch (SigarNotImplementedException e) {
throw new MetricInvalidException("Unable to gather swap metrics", e);
} catch (SigarException e) {
throw new MetricInvalidException("Unable to gather swap metrics", e);
}
}
Double returnVal;
if (convertToDouble(total, false) == 0) {
// Avoid potential div by 0.
returnVal = Double.NaN;
} else if (attribute.equals("UsedPercent")) {
returnVal = convertToDouble(used, false)/convertToDouble(total, false);
} else if (attribute.equals("FreePercent")) {
returnVal = convertToDouble(free, false)/convertToDouble(total, false);
} else {
throw new MetricNotFoundException("Unhandled attribute " + attribute);
}
return new MetricValue(returnVal, System.currentTimeMillis());
}
private MetricValue getTimeSinceMetrics(Metric m)
throws MetricNotFoundException
{
String attribute = m.getAttributeName();
Object mtime, ctime, atime;
SigarInvokerJMX invoker =
SigarInvokerJMX.getInstance(sigarProxy, m.getObjectName());
synchronized (sigar) {
try {
mtime = invoker.invoke("Mtime");
ctime = invoker.invoke("Ctime");
atime = invoker.invoke("Atime");
} catch (SigarNotImplementedException e) {
throw new MetricInvalidException("Unable to gather FileInfo metrics", e);
} catch (SigarException e) {
throw new MetricInvalidException("Unable to gather FileInfo metrics", e);
}
}
Double returnVal;
Long now = System.currentTimeMillis();
if (attribute.equals("SMtime")) {
returnVal = now - convertToDouble(mtime, false);
} else if (attribute.equals("SAtime")) {
returnVal = now - convertToDouble(atime, false);
} else if (attribute.equals("SCtime")) {
returnVal = now - convertToDouble(ctime, false);
} else {
throw new MetricNotFoundException("Unhandled attribute " + attribute);
}
return new MetricValue(returnVal, System.currentTimeMillis());
}
public MetricValue getValue(Metric metric)
throws PluginException,
MetricNotFoundException,
MetricUnreachableException
{
Object systemValue;
Double useVal;
String domain = metric.getDomainName();
String name = metric.getObjectName();
String attr = metric.getAttributeName();
if (this.sigar == null) {
getSigar();
}
//XXX: Until these are supported within SIGAR
Properties props = metric.getObjectProperties();
String mType = props.getProperty("Type");
if (mType.equals("Swap") && (attr.equals("UsedPercent") || attr.equals("FreePercent"))) {
return getSwapPercentage(metric);
}
if (mType.equals("FileInfo") && attr.equals("SMtime") || (attr.equals("SCtime") || attr.equals("SAtime"))) {
return getTimeSinceMetrics(metric);
}
if (domain.equals(PTQL_DOMAIN)) {
name = translatePTQL(metric);
}
else {
//backcompat for metrics still scheduled w/ old template
name = StringUtil.replace(name,
"Type=FileSystemUsage",
"Type=MountedFileSystemUsage");
}
boolean isAvailabilityAttribute = false;
boolean isStateAttribute = false;
//check for Availability attribute aliases
if (attr.equals(Metric.ATTR_AVAIL)) {
String type =
metric.getObjectProperty(SigarInvokerJMX.PROP_TYPE);
String alias = (String)AVAIL_ATTRS.get(type);
if (alias != null) {
attr = alias;
isAvailabilityAttribute = true;
}
}
else {
isAvailabilityAttribute = isStateAttribute = isProcessState(attr);
}
SigarInvokerJMX invoker =
SigarInvokerJMX.getInstance(sigarProxy, name);
try {
synchronized (sigar) {
systemValue = invoker.invoke(attr);
}
} catch (SigarNotImplementedException e) {
return MetricValue.NONE;
} catch (SigarException e) {
if (isAvailabilityAttribute) {
return new MetricValue(Metric.AVAIL_DOWN);
}
else {
throw new MetricNotFoundException(e.getMessage(), e);
}
}
/* If the availability metric was accessible then it is considered
* "up" for certain cases (File, FileServer, NetworkInterface...), otherwise
* the metric would be set to down in the above SigarException catch statement.
*
* We should also see the value returned from SIGAR to be the number of
* processes found with the specified ptql. So if it is greater then 0 it
* is Available.
*/
if (isAvailabilityAttribute && !isStateAttribute) {
if (systemValue instanceof Number && ((Number)systemValue).intValue() > 0){
useVal = new Double(Metric.AVAIL_UP);
} else {
useVal = new Double(Metric.AVAIL_DOWN);
}
}else {
useVal = convertToDouble(systemValue, isStateAttribute);
}
if (useVal.doubleValue() == Sigar.FIELD_NOTIMPL) {
return MetricValue.NONE;
}
return new MetricValue(useVal, System.currentTimeMillis());
}
private Double convertToDouble(Object systemValue, boolean isProcessState) throws MetricNotFoundException{
Double useVal;
if (systemValue instanceof Double) {
useVal = (Double)systemValue;
}
else if (systemValue instanceof Long) {
useVal = new Double(((Long)systemValue).longValue());
}
else if (systemValue instanceof Integer) {
useVal = new Double(((Integer)systemValue).intValue());
}
else if (systemValue instanceof Character) {
char c = ((Character)systemValue).charValue();
//process state
if (isProcessState) {
double avail;
switch (c) {
case 'Z':
avail = Metric.AVAIL_DOWN;
break;
case 'T':
avail = Metric.AVAIL_PAUSED;
break;
default:
avail = Metric.AVAIL_UP;
break;
}
useVal = new Double(avail);
}
else {
useVal = new Double((int)c);
}
}
else {
//wont happen.
throw new MetricNotFoundException("System plugin returned a " +
systemValue.getClass() +
" object, which could not " +
" be handled");
}
return useVal;
}
}