/* * 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.util.PluginLoader; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.ConfigSchema; public abstract class Collector implements Runnable { public static final String PROP_HOSTNAME = "hostname"; public static final String PROP_PORT = "port"; public static final String PROP_PROTOCOL = "protocol"; public static final String PROP_PATH = "path"; public static final String PROP_SSL = "ssl"; public static final String PROP_SSL_PROTOCOL = "sslprotocol"; public static final String PROP_USERNAME = "user"; public static final String PROP_PASSWORD = "pass"; public static final String PROP_REALM = "realm"; public static final String PROP_FOLLOW = "follow"; public static final String PROP_METHOD = "method"; public static final String PROP_SSLPORT = PROP_SSL + PROP_PORT; public static final String METHOD_HEAD = "HEAD"; public static final String METHOD_GET = "GET"; public static final String PROTOCOL_HTTP = "http"; public static final String PROTOCOL_HTTPS = "https"; public static final String PROTOCOL_FTP = "ftp"; public static final String PROTOCOL_SOCKET = "socket"; public static final String DEFAULT_HOSTNAME = "localhost"; public static final String DEFAULT_FTP_PORT = "21"; public static final String DEFAULT_HTTP_PORT = "80"; public static final String DEFAULT_HTTPS_PORT = "443"; public static final String PROP_TIMEOUT = "timeout"; public static final String ATTR_RESPONSE_TIME = "ResponseTime"; public static final String ATTR_RESPONSE_CODE = "ResponseCode"; public static final String LISTEN_PORTS = "listen.ports"; public static final String GUID = "GUID"; public static final String MAC = "Mac"; static Log log = LogFactory.getLog(Collector.class.getName()); static Map containers = new HashMap(); private GenericPlugin plugin; private Properties props; private boolean isRunning = false; private int timeout = -1; private long startTime, endTime; //use a ref to Metric: ScheduleThread.unscheduleMetric unsets interval private Metric intervalMetric; private long lastCollection = -1; private static Map compatAliases = new HashMap(); static { //maintain compat w/ old templates String[][] aliases = { //NetworkServer IP { "RequestTime", ATTR_RESPONSE_TIME }, //Script, Nagios Plugin { "ExecTime", ATTR_RESPONSE_TIME }, { "ReturnCode", ATTR_RESPONSE_CODE }, { "Arg", ExecutableProcess.PROP_FILE }, { "prefix", ExecutableProcess.PROP_EXEC }, { "Params", ExecutableProcess.PROP_ARGS }, //other { "availability", Metric.ATTR_AVAIL }, }; for (int i=0; i<aliases.length; i++) { compatAliases.put(aliases[i][0], aliases[i][1]); } } CollectorResult result = new CollectorResult(); protected void init() throws PluginException { setSource(getPlugin().getName()); } public abstract void collect(); /** * Initialize a Collector instance for use outside of MeasurementPlugin. * Collectors are generally used for metric collection, but can also be * used in some cases for inventory property discovery and/or control. * @param plugin A ServerDetector or ControlPlugin * @param config Resource configuration properties * @throws PluginException */ public void init(GenericPlugin plugin, ConfigResponse config) throws PluginException { this.plugin = plugin; setProperties(config.toProperties()); init(); } /** * Initialize and collect values for use outside of MeasurementPlugin. * This method is useful for inventory property discovery. * @param plugin A ServerDetector or ControlPlugin * @param config * @return Resource configuration properties * @throws PluginException */ public Map getValues(GenericPlugin plugin, ConfigResponse config) throws PluginException { init(plugin, config); collect(); return getResult().values; } public int getTimeout() { if (this.timeout == -1) { this.timeout = getIntegerProperty(getPropTimeout(), getDefaultTimeout()); } return this.timeout; } public int getTimeoutMillis() { return getTimeout() * 1000; } protected int getDefaultTimeout() { return 30; //30 seconds } protected String getPropTimeout() { return PROP_TIMEOUT; } protected int getIntegerProperty(String key, int defVal) { String val = getProperties().getProperty(key); if (val == null) { if (defVal == -1) { String msg = "Missing " + key + " property"; throw new IllegalArgumentException(msg); } return defVal; } try { return Integer.parseInt(val); } catch (NumberFormatException e) { String msg = "Invalid value (" + val + ")" + " for " + key; throw new IllegalArgumentException(msg); } } protected String getCollectorProperty(String key, String defVal) { return this.props.getProperty(key, defVal); } static Object getCompatValue(Map map, String key) { Object val = map.get(key); if (val == null) { Object alias = compatAliases.get(key); if (alias == null) { alias = key.toLowerCase(); } if (alias != null) { val = map.get(alias); } } return val; } protected String getCollectorProperty(String key) { return (String)getCompatValue(this.props, key); } protected void setProperties(Properties props) { this.props = props; } protected Properties getProperties() { return this.props; } protected GenericPlugin getPlugin() { return this.plugin; } protected void setSource(String value) { this.result.source = value; } protected String getSource() { return this.result.source; } protected void setLogLevel(int value) { this.result.level = value; } protected int getLogLevel() { return this.result.level; } protected void setMessage(String value) { this.result.message = value; } protected String getMessage() { return this.result.message; } private String composeMessage(String msg, Throwable t) { return msg + ": " + t.getMessage(); } protected void setMessage(String msg, Throwable t) { setMessage(composeMessage(msg, t)); } protected void setErrorMessage(String msg) { setLogLevel(LogTrackPlugin.LOGLEVEL_ERROR); setMessage(msg); } protected void setWarningMessage(String msg) { setLogLevel(LogTrackPlugin.LOGLEVEL_WARN); setMessage(msg); } protected void setInfoMessage(String msg) { setLogLevel(LogTrackPlugin.LOGLEVEL_INFO); setMessage(msg); } protected void setDebugMessage(String msg) { setLogLevel(LogTrackPlugin.LOGLEVEL_DEBUG); setMessage(msg); } protected void setErrorMessage(String msg, Throwable t) { setErrorMessage(composeMessage(msg, t)); } protected void setWarningMessage(String msg, Throwable t) { setWarningMessage(composeMessage(msg, t)); } protected void setInfoMessage(String msg, Throwable t) { setInfoMessage(composeMessage(msg, t)); } protected void setDebugMessage(String msg, Throwable t) { setDebugMessage(composeMessage(msg, t)); } protected void setValue(String key, String val) { this.result.setValue(key, val); } protected void addValues(Map values) { this.result.addValues(values); } protected CollectorResult getResult() { return this.result; } protected void setValue(String key, double val) { this.result.setValue(key, val); } protected void setAvailability(double val) { setValue(Metric.ATTR_AVAIL, val); } protected void setAvailability(boolean val) { setAvailability(val ? Metric.AVAIL_UP : Metric.AVAIL_DOWN); } protected void setResponseCode(int code) { setValue(ATTR_RESPONSE_CODE, code); } protected void setResponseTime(double value) { setValue(ATTR_RESPONSE_TIME, value); } protected void startTime() { this.startTime = System.currentTimeMillis(); } protected void endTime() { this.endTime = System.currentTimeMillis(); } String mapToString(Map map) { Map props = new HashMap(); for (Iterator it = map.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry)it.next(); String key = (String)entry.getKey(); String val = (String)entry.getValue(); if (ConfigSchema.isSecret(key)) { val = Metric.mask(val); } props.put(key, val); } return props.toString(); } public String toString() { return mapToString(this.props); } static class PluginContainer { String name; Map collectors = Collections.synchronizedMap(new HashMap()); Map results = Collections.synchronizedMap(new HashMap()); static PluginContainer get(GenericPlugin plugin) { String name = plugin.getName(); synchronized (containers) { PluginContainer container = (PluginContainer)containers.get(name); if (container == null) { container = new PluginContainer(); container.name = name; containers.put(name, container); } return container; } } static void setResult(Collector collector) { CollectorResult result = new CollectorResult(collector); if (log.isDebugEnabled()) { log.debug("name=" + collector.plugin.getName() + ", " + "thread=" + Thread.currentThread().getName() + ", result=" + result); } get(collector.plugin).results.put(collector.props, result); } } public MetricValue getValue(Metric metric, CollectorResult result) { return result.getMetricValue(metric.getAttributeName()); } private long getInterval() { if (this.intervalMetric == null) { return -1; } else { return this.intervalMetric.getInterval(); } } private void setInterval(Metric metric) { this.intervalMetric = metric; } //interval is used to make collection to happen 1 minute before //the Availability metric is scheduled to be collected protected void setInterval(MeasurementPlugin plugin, Metric metric) { if (!isPoolable()) { return; //XXX apply only to netservices and exec for now. } long itv = metric.getInterval(); boolean isAvail = metric.getAttributeName().equals(Metric.ATTR_AVAIL); if ((isAvail || (getInterval() == -1)) && (itv > 0) && (getInterval() != itv)) { setInterval(metric); if (log.isDebugEnabled()) { log.debug("Set itv=" + (getInterval() / MINUTE) + "min for " + plugin.getName() + " collector: " + this); } } } public static MetricValue getValue(MeasurementPlugin plugin, Metric metric) throws PluginException, MetricNotFoundException, MetricUnreachableException { CollectorResult result; Collector collector; Properties props = plugin.getCollectorProperties(metric); PluginContainer container = PluginContainer.get(plugin); collector = (Collector)container.collectors.get(props); result = (CollectorResult)container.results.get(props); if (result != null) { boolean isAvail = metric.getAttributeName().equals(Metric.ATTR_AVAIL); result.collected = true; if (!result.reported && (result.level != -1)) { plugin.getManager().reportEvent(metric, result.getTimeStamp(), result.getLevel(), result.getSource(), result.getMessage()); result.reported = true; } MetricValue value; boolean setClassLoader = PluginLoader.setClassLoader(collector); try { value = collector.getValue(metric, result); if (isAvail) { collector.lastCollection = value.getTimestamp(); } } finally { collector.setInterval(plugin, metric); //sync if (setClassLoader) { PluginLoader.resetClassLoader(collector); } } if (value == null) { throw new MetricNotFoundException(metric.toString()); } return value; } if (collector == null) { collector = plugin.getNewCollector(); collector.plugin = plugin; boolean setClassLoader = PluginLoader.setClassLoader(collector); try { collector.setProperties(props); collector.init(); collector.setInterval(plugin, metric); } finally { if (setClassLoader) { PluginLoader.resetClassLoader(collector); } } if (log.isDebugEnabled()) { log.debug("Adding " + plugin.getName() + " collector: " + collector); } container.collectors.put(props, collector); } //just added collector to the thread, //next time will pickup metrics. return MetricValue.FUTURE; } public boolean equals(Object obj) { if (!(obj instanceof Collector)) { return false; } return ((Collector)obj).props.equals(this.props); } public int hashCode() { return this.props.hashCode(); } public boolean isPoolable() { return false; } public void run() { this.isRunning = true; this.result.values.clear(); this.result.level = -1; this.startTime = this.endTime = -1; boolean setClassLoader = PluginLoader.setClassLoader(this); try { collect(); } catch (Throwable t) { log.error("Error running " + this.plugin.getName() + " collector: " + t, t); setErrorMessage("Error: " + t.getMessage(), t); setAvailability(false); } finally { if (setClassLoader) { PluginLoader.resetClassLoader(this); } } if (this.endTime != -1) { this.result.timestamp = this.endTime; setResponseTime(this.endTime-this.startTime); } else { this.result.timestamp = System.currentTimeMillis(); } PluginContainer.setResult(this); this.isRunning = false; } protected void parseResults(String message) { boolean hasResultValue = false; StringTokenizer st = new StringTokenizer(message, "\r\n,"); while (st.hasMoreTokens()) { String attr = st.nextToken(); int ix = attr.indexOf('='); if (ix == -1) { if (!hasResultValue) { StringTokenizer st2 = new StringTokenizer(attr, " \t"); while (st2.hasMoreTokens()) { String s = st2.nextToken(); if (Character.isDigit(s.charAt(0)) && Character.isDigit(s.charAt(s.length()-1))) { setValue("ResultValue", s); hasResultValue = true; //use the first number } } } continue; } String key = attr.substring(0, ix); String val = attr.substring(key.length()+1); if (key.equals("Message")) { setMessage(val); continue; } setValue(key, val); } } private static final long SECOND = 1 * 1000; private static final long MINUTE = 60 * SECOND; private static final long HOUR = 60 * MINUTE; private static final long DAY = 24 * HOUR; public static final String REMOVABLE = "removable"; public static final String ALLOW_REMOVE = "allow_remove"; private static String lastRun(long now, long time) { long delta = now - time; if ((delta / MINUTE) < 1) { long seconds = delta/SECOND; return seconds + " seconds ago"; } if ((delta / HOUR) < 1) { long minutes = delta/MINUTE; return minutes + " minutes ago"; } if ((delta / DAY) < 1) { long hours = delta/HOUR; return hours + " hours ago"; } return "on " + new Date(time); } private static Collection<Collector> getCollectorsToExecute(PluginContainer container) { final boolean debug = log.isDebugEnabled(); final Collection<Collector> rtn = new ArrayList<Collector>(); if (debug) log.debug("Running " + container.name + " collectors"); List pluginCollectors; //copy so we don't block PluginCollector.get() synchronized (container.collectors) { Collection values = container.collectors.values(); pluginCollectors = new ArrayList(values.size()); pluginCollectors.addAll(values); } for (int i=0; i<pluginCollectors.size(); i++) { Collector collector = (Collector)pluginCollectors.get(i); long interval = collector.getInterval(); long lastCollection = collector.lastCollection; if (collector.isRunning) { if (debug) log.debug(collector + " is running: deferring"); continue; } CollectorResult result = (CollectorResult)container.results.get(collector.props); if ((result != null) && (result.values.size() != 0)) { long now = System.currentTimeMillis(); boolean shouldSkip = true; if ((interval != -1) && (lastCollection != -1)) { if ((now - lastCollection) >= interval-MINUTE) { //ScheduleThread should be picking this up //within the next minute, so collect now. shouldSkip = false; } else if ((now - result.timestamp) >= interval) { //not in sync w/ ScheduleThread shouldSkip = false; } } else { shouldSkip = !result.collected; } String msg = null; if (debug) { String itv, coll; if (interval == -1) { itv = "unknown"; } else { itv = (interval / MINUTE) + "min"; } if (lastCollection == -1) { coll = "n/a"; } else { coll = lastRun(now, lastCollection); } msg = collector + " ran " + lastRun(now, result.timestamp) + "," + " consumed " + coll + ", " + itv + " itv: "; } if (shouldSkip) { if (debug) log.debug(msg + "deferring."); continue; } else { if (debug) log.debug(msg + "collecting."); } } rtn.add(collector); } return rtn; } public static Collection<Collector> getCollectorsToExecute() { Collection<Collector> rtn = new ArrayList<Collector>(); List pluginContainers; //copy so we don't block PluginCollector.get() synchronized (containers) { Collection values = containers.values(); pluginContainers = new ArrayList(values.size()); pluginContainers.addAll(values); } for (int i=0; i<pluginContainers.size(); i++) { PluginContainer container = (PluginContainer)pluginContainers.get(i); rtn.addAll(getCollectorsToExecute(container)); } return rtn; } public static void main(String[] args) throws Exception { Collector collector = new ExecutableProcess(); collector.setProperties(System.getProperties()); collector.init(); collector.run(); System.out.println(collector); } }