Mercurial > hg > release > thermostat-0.6
changeset 2:13a5cb11acd7
adding a very basic and very crappy agent
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/Agent.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,122 @@ +package com.redhat.thermostat.agent; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.WriteConcern; +import com.redhat.thermostat.backend.Backend; +import com.redhat.thermostat.backend.BackendRegistry; +import com.redhat.thermostat.common.Constants; + +/** + * Represents the Agent running on a host. + */ +public class Agent { + + private final UUID id; + private final BackendRegistry backendRegistry; + private final List<AgentStateListener> listeners; + private final RuntimeConfiguration runtimeConfig; + private final StartupConfiguration startupConfig; + + private AgentState currentState; + private DB database; + + public Agent(BackendRegistry backendRegistry, StartupConfiguration startupConfiguration) { + this(backendRegistry, + UUID.randomUUID(), + startupConfiguration, + new RuntimeConfiguration()); + } + + public Agent(BackendRegistry registry, UUID agentId, StartupConfiguration startup, RuntimeConfiguration runtime) { + this.id = agentId; + this.backendRegistry = registry; + this.listeners = new CopyOnWriteArrayList<AgentStateListener>(); + this.currentState = AgentState.DISCONNECTED; + this.startupConfig = startup; + this.runtimeConfig = runtime; + updateConfig(); + } + + /** + * Update the agent configuration from backends + */ + private void updateConfig() { + for (Backend backend : backendRegistry.getAll()) { + String isActive = Boolean.toString(Arrays.asList(startupConfig.getBackendsToStart()).contains(backend.getName())); + runtimeConfig.addConfig(backend.getName(), Constants.AGENT_CONFIG_KEY_BACKEND_ACTIVE, isActive); + Map<String, String> settings = backend.getSettings(); + for (Entry<String, String> e : settings.entrySet()) { + runtimeConfig.addConfig(backend.getName(), e.getKey(), e.getValue()); + } + } + } + + public void setDatabase(DB database) { + this.database = database; + } + + /** + * Advertises the agent as active to the world. + */ + public void publish() { + DBCollection configCollection = database.getCollection(Constants.AGENT_CONFIG_COLLECTION_NAME); + DBObject toInsert = runtimeConfig.toBson(); + toInsert.put(Constants.AGENT_ID, id.toString()); + configCollection.insert(toInsert, WriteConcern.SAFE); + setState(AgentState.ACTIVE); + } + + /** + * Removes the agent info published to the world + */ + public void unpublish() { + DBCollection configCollection = database.getCollection(Constants.AGENT_CONFIG_COLLECTION_NAME); + BasicDBObject toRemove = new BasicDBObject(Constants.AGENT_ID, id.toString()); + configCollection.remove(toRemove); + setState(AgentState.DISCONNECTED); + } + + public synchronized AgentState getState() { + return currentState; + } + + public synchronized void setState(AgentState newState) { + if (currentState != newState) { + currentState = newState; + emitStateChanged(); + } + } + + private void emitStateChanged() { + for (AgentStateListener listener : listeners) { + listener.stateChanged(this); + } + } + + public void addStateListener(AgentStateListener listener) { + listeners.add(listener); + } + + public void removeStateListener(AgentStateListener listener) { + listeners.remove(listener); + } + + public UUID getId() { + return id; + } + + public BackendRegistry getBackendRegistry() { + return backendRegistry; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/AgentState.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,19 @@ +package com.redhat.thermostat.agent; + +public enum AgentState { + /** + * the agent in the default state. It has no connection. It is not doing + * anything + */ + DISCONNECTED, + /** just connected. setting up data structures */ + CONNECTED, + /** collecting data and doing stuff */ + ACTIVE, + /** + * server was disconnected abruptly. cache data until we can send it over + * again + */ + CACHING, + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/AgentStateListener.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,5 @@ +package com.redhat.thermostat.agent; + +public interface AgentStateListener { + public void stateChanged(Agent agent); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/ArgumentParser.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,71 @@ +package com.redhat.thermostat.agent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; + +import com.redhat.thermostat.common.Constants; + +public class ArgumentParser { + + private final boolean inSingleMode; + private final String connectionURL; + private final Level logLevel; + + public ArgumentParser(StartupConfiguration startupConfig, String[] args) { + List<String> arguments = new ArrayList<String>(Arrays.asList(args)); + boolean single = false; + String url = Constants.MONGO_DEFAULT_URL; + Level level = Level.WARNING; + int port = -1; + + int index = 0; + while (index < arguments.size()) { + if (arguments.get(index).equals("--loglevel")) { + int next = index + 1; + if (next < arguments.size()) { + level = Level.parse(arguments.get(next).toUpperCase()); + System.out.println("log level is: " + level); + index++; + } + } else if (arguments.get(index).equals("--local")) { + single = true; + } else if (arguments.get(index).equals("--remote")) { + single = false; + int next = index + 1; + if (next < arguments.size()) { + port = Integer.parseInt(arguments.get(next)); + index++; + } + } + + index++; + } + + if (port != -1) { + url = url + ":" + port + "/"; + } else if (single) { + url = url + ":" + startupConfig.getPortForLocal() + "/"; + } else { + url = url + ":" + startupConfig.getPortForRemote() + "/"; + } + + inSingleMode = single; + connectionURL = url; + logLevel = level; + } + + public boolean inSingleMode() { + return inSingleMode; + } + + public String getConnectionURL() { + return connectionURL; + } + + public Level getLogLevel() { + return logLevel; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/Main.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,76 @@ +package com.redhat.thermostat.agent; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import com.mongodb.DB; +import com.mongodb.Mongo; +import com.mongodb.MongoURI; +import com.redhat.thermostat.backend.BackendRegistry; +import com.redhat.thermostat.common.Constants; +import com.redhat.thermostat.common.utils.LoggingUtils; +import com.redhat.thermostat.common.utils.StringUtils; + +public final class Main { + + private Main() { + throw new IllegalStateException("Should not be instantiated"); + } + + public static void main(String[] args) { + try { + LogManager.getLogManager().readConfiguration( + StringUtils.toInputStream(Constants.LOGGING_CONFIG)); + } catch (Exception e) { + e.printStackTrace(); + } + + LoggingUtils.resetAndGetRootLogger(); + Logger logger = LoggingUtils.getLogger(Main.class); + + StartupConfiguration startupConfig = null; + try { + startupConfig = new StartupConfiguration(new FileReader(Constants.AGENT_CONFIG_FILE_LOCATION)); + } catch (FileNotFoundException fnfe) { + System.err.println("unable to read config file at " + Constants.AGENT_CONFIG_FILE_LOCATION); + System.exit(Constants.EXIT_UNABLE_TO_READ_CONFIG); + } + + ArgumentParser argumentParser = new ArgumentParser(startupConfig, args); + + logger.setLevel(argumentParser.getLogLevel()); + + BackendRegistry backendRegistry = BackendRegistry.getInstance(); + + Mongo mongo = null; + try { + String uri = argumentParser.getConnectionURL(); + logger.fine("connecting to " + uri); + mongo = new Mongo(new MongoURI(uri)); + DB db = mongo.getDB(Constants.THERMOSTAT_DB); + logger.fine("connected"); + Agent agent = new Agent(backendRegistry, startupConfig); + agent.setDatabase(db); + agent.publish(); + logger.fine("agent published"); + + try { + System.in.read(); + } catch (IOException e) { + e.printStackTrace(); + } + + agent.unpublish(); + logger.fine("agent unpublished"); + } catch (UnknownHostException uhe) { + System.err.println("unknown host"); + uhe.printStackTrace(); + System.exit(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE); + } + + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/RuntimeConfiguration.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,125 @@ +package com.redhat.thermostat.agent; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; +import com.redhat.thermostat.common.Constants; + +/** + * The current run time configuration of the agent. + */ +public class RuntimeConfiguration { + + private String agentName; + private Backends backendConfig; + + public RuntimeConfiguration() { + agentName = "agent " + new Random().nextLong(); + backendConfig = new Backends(); + } + + public static RuntimeConfiguration fromBson(DBObject remote) { + RuntimeConfiguration config = new RuntimeConfiguration(); + BasicDBObject other = (BasicDBObject) remote; + // TODO deal with missing/bad keys/values + config.agentName = other.getString(Constants.AGENT_CONFIG_KEY_AGENT_NAME); + BasicDBObject remoteBackends = (BasicDBObject) other.get(Constants.AGENT_CONFIG_KEY_BACKENDS); + for (Entry<String, Object> remoteBackendEntry : remoteBackends.entrySet()) { + String backendName = remoteBackendEntry.getKey(); + BasicDBObject backendConfig = (BasicDBObject) remoteBackendEntry.getValue(); + for (Entry<String, Object> e : backendConfig.entrySet()) { + config.backendConfig.addConfig(backendName, e.getKey(), (String) e.getValue()); + } + } + return config; + } + + public DBObject toBson() { + BasicDBObject result = new BasicDBObject(); + result.put(Constants.AGENT_CONFIG_KEY_AGENT_NAME, agentName); + result.put(Constants.AGENT_CONFIG_KEY_BACKENDS, backendConfig.toDBObject()); + return result; + } + + public void setAvailableBackends(String[] backends) { + for (String backend : backends) { + backendConfig.addBackend(backend); + } + } + + public String[] getAllBackends() { + return backendConfig.getBackends().toArray(new String[0]); + } + + public String[] getActiveBackends() { + return backendConfig.getMatchingBackends(Constants.AGENT_CONFIG_KEY_BACKEND_ACTIVE, Boolean.TRUE.toString()).toArray(new String[0]); + } + + public void addConfig(String backend, String key, String value) { + backendConfig.addConfig(backend, key, value); + } + + public String getConfig(String backendName, String configKey) { + return backendConfig.getConfig(backendName).get(configKey); + } + + /** + * A wrapper around the backend-specific information + */ + private static class Backends { + /** {backend-name: { opt1: va1, opt2:val2, } } */ + private Map<String, Map<String, String>> info; + + public Backends() { + info = new HashMap<String, Map<String, String>>(); + } + + public void addBackend(String backendName) { + if (!info.containsKey(backendName)) { + info.put(backendName, new HashMap<String, String>()); + } + } + + public Object toDBObject() { + BasicDBObject result = new BasicDBObject(); + for (Entry<String, Map<String, String>> e : info.entrySet()) { + BasicDBObject config = new BasicDBObject(); + config.putAll(e.getValue()); + result.put(e.getKey(), config); + } + return result; + } + + public Set<String> getBackends() { + return info.keySet(); + } + + public Set<String> getMatchingBackends(String key, String value) { + // TODO perhaps extend this to regex? + Set<String> matched = new HashSet<String>(); + for (Entry<String, Map<String, String>> e : info.entrySet()) { + if (e.getValue().get(key) != null && e.getValue().get(key).equals(value)) { + matched.add(e.getKey()); + } + } + return matched; + } + + public void addConfig(String backend, String key, String value) { + addBackend(backend); + info.get(backend).put(key, value); + } + + public Map<String, String> getConfig(String backend) { + return info.get(backend); + } + + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/agent/StartupConfiguration.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,41 @@ +package com.redhat.thermostat.agent; + +import java.io.Reader; +import java.util.Properties; + +/** + * Configuration used by the agent during startup, initialization. As opposed to + * {@link RuntimeConfiguration}, this configuration is not exported. + */ +public class StartupConfiguration { + + private final int localPort; + private final int remotePort; + private final String[] backends; + + public StartupConfiguration(Reader configReader) { + Properties properties = new Properties(); + try { + properties.load(configReader); + } catch (Exception e) { + e.printStackTrace(); + } + localPort = Integer.valueOf(properties.getProperty("mongod_port")); + remotePort = Integer.valueOf(properties.getProperty("mongos_port")); + backends = properties.getProperty("backends").trim().split(","); + + } + + public int getPortForLocal() { + return localPort; + } + + public int getPortForRemote() { + return remotePort; + } + + public String[] getBackendsToStart() { + return backends; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/backend/Backend.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,56 @@ +package com.redhat.thermostat.backend; + +import java.util.Map; + +/** + * Represents a monitoring back-end. All the {@link Backend}s should be + * registered with the {@link BackendRegistry}. + */ +public interface Backend { + + /** Returns the name of the {@link Backend} */ + public String getName(); + + /** Returns the description of the {@link Backend} */ + public String getDescription(); + + /** Returns the vendor of the {@link Backend} */ + public String getVendor(); + + /** Returns the version of the {@link Backend} */ + public String getVersion(); + + /** + * Returns an array of {@link BackendFeature}s supported by this backend. To + * indicate no supported features, return an empty array, not null. + */ + public BackendFeature[] getSupportedFeatures(); + + /** + * Returns a map containing the settings of this backend + */ + public Map<String, String> getSettings(); + + /** + * Activate the {@link Backend}. The backend should start gathering general + * data (not specific to any vm) and start sending it + * + * @return true on success, false if there was an error + */ + public boolean activate(); + + /** + * Deactivate the {@link Backend}. The backend should release resources at + * this point. + * + * @return true on success + */ + public boolean deactivate(); + + /** + * Returns a boolean indicating if the backend is currently active on this + * host + */ + public boolean isActive(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/backend/BackendFeature.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,43 @@ +package com.redhat.thermostat.backend; + +/** + * A specific feature provided by a {@link Backend}. Each {@link Backend} + * provides a different set of features. Some may even provide none. + */ +public class BackendFeature { + public static final BackendFeature MXBEANS = new BackendFeature("mxbeans"); + public static final BackendFeature PERF_COUNTER = new BackendFeature("perfcounter"); + public static final BackendFeature PROC = new BackendFeature("proc"); + public static final BackendFeature STARTUP_ARGUMENTS = new BackendFeature("startup-arguments"); + public static final BackendFeature SYSTEM_PROPERTIES = new BackendFeature("system-properties"); + public static final BackendFeature HEAP = new BackendFeature("heap"); + + private String featureName; + + public BackendFeature(String featureName) { + this.featureName = featureName; + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (other == this) { + return true; + } + if (other.getClass() == this.getClass()) { + BackendFeature otherFeature = (BackendFeature) other; + if (otherFeature.featureName.equals(featureName)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return featureName.hashCode(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/backend/BackendRegistry.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,71 @@ +package com.redhat.thermostat.backend; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +/** + * A registry for {@link Backend}s. Each {@link Backend} should call + * {@link #register(Backend, BackendFeature[])} to register itself. + */ +public final class BackendRegistry { + + private static BackendRegistry INSTANCE = null; + + private static ServiceLoader<Backend> backendLoader = ServiceLoader.load(Backend.class); + + private final List<Backend> registeredBackends; + private final Map<BackendFeature, Set<Backend>> featureToBackend; + + private BackendRegistry() { + registeredBackends = new ArrayList<Backend>(); + featureToBackend = new HashMap<BackendFeature, Set<Backend>>(); + + for (Backend newBackend : backendLoader) { + register(newBackend, newBackend.getSupportedFeatures()); + } + } + + public static synchronized BackendRegistry getInstance() { + if (INSTANCE == null) { + INSTANCE = new BackendRegistry(); + } + return INSTANCE; + } + + public synchronized void register(Backend backend, BackendFeature[] features) { + registeredBackends.add(backend); + for (BackendFeature feature : features) { + if (!featureToBackend.containsKey(feature)) { + featureToBackend.put(feature, new HashSet<Backend>()); + } + featureToBackend.get(feature).add(backend); + } + } + + public synchronized void unregister(Backend backend) { + registeredBackends.remove(backend); + } + + public synchronized Backend[] getAll() { + return registeredBackends.toArray(new Backend[0]); + } + + public synchronized Backend getByName(String name) { + for (Backend backend : registeredBackends) { + if (backend.getName().equals((name))) { + return backend; + } + } + return null; + } + + public synchronized Backend[] getBackendsForFeature(BackendFeature feature) { + return featureToBackend.get(feature).toArray(new Backend[0]); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/common/Constants.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,29 @@ +package com.redhat.thermostat.common; + +/** + * A grab bag of constants. This could be cleaned up later, but lets throw + * things here for now. + */ +public class Constants { + + public static final String LOGGING_CONFIG = "com.redhat.thermostat.level=ALL"; + + public static final String AGENT_CONFIG_FILE_LOCATION = "config/agent.properties"; + + public static final int EXIT_UNKNOWN_ERROR = 1; + public static final int EXIT_UNABLE_TO_CONNECT_TO_DATABASE = 2; + public static final int EXIT_UNABLE_TO_READ_CONFIG = 3; + + public static final String THERMOSTAT_DB = "thermostat"; + public static final String MONGO_DEFAULT_URL = "mongodb://127.0.0.1"; + + public static final String AGENT_CONFIG_COLLECTION_NAME = "agent-configs"; + public static final String AGENT_ID = "agent-id"; + + public static final int SAMPLING_INTERVAL_UNKNOWN = -1; + + public static final String AGENT_CONFIG_KEY_AGENT_NAME = "agent-name"; + public static final String AGENT_CONFIG_KEY_BACKENDS = "backends"; + public static final String AGENT_CONFIG_KEY_BACKEND_ACTIVE = "active"; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/common/LogFormatter.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,17 @@ +package com.redhat.thermostat.common; + +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +public class LogFormatter extends Formatter { + + @Override + public synchronized String format(LogRecord record) { + StringBuilder sb = new StringBuilder(); + sb.append(record.getLevel()); + sb.append(": "); + sb.append(record.getMessage()); + sb.append("\n"); + return sb.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/common/utils/LoggingUtils.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,49 @@ +package com.redhat.thermostat.common.utils; + +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import com.redhat.thermostat.common.LogFormatter; + +/** + * A few helper functions to facilitate using loggers + * <p> + * It would be good idea to call {@link LogManager#readConfiguration()} with a + * properties file that sets an appropriate value for ".level" + */ +public final class LoggingUtils { + + private LoggingUtils() { + /* should not be instantiated */ + } + + /** + * Should be called once, very-early during program initialization. This + * works around http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4462908 + */ + public static Logger resetAndGetRootLogger() { + Logger root = Logger.getLogger(""); + Handler[] handlers = root.getHandlers(); + for (Handler handler : handlers) { + root.removeHandler(handler); + } + + ConsoleHandler handler = new ConsoleHandler(); + handler.setFormatter(new LogFormatter()); + handler.setLevel(Level.ALL); + root.addHandler(handler); + + return root; + } + + /** + * Returns an appropriate logger to be used by class klass. + */ + public static Logger getLogger(Class<?> klass) { + return Logger.getLogger(klass.getPackage().getName()); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/common/utils/StringUtils.java Mon Nov 21 18:22:13 2011 -0500 @@ -0,0 +1,20 @@ +package com.redhat.thermostat.common.utils; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +public class StringUtils { + + private StringUtils() { + /* should not be instantiated */ + } + + public static InputStream toInputStream(String toConvert) { + try { + return new ByteArrayInputStream(toConvert.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("UTF-8 not supported"); + } + } +}