changeset 11:a0f3609a6b9e

add a always-on system backend
author Omair Majid <omajid@redhat.com>
date Fri, 02 Dec 2011 12:37:01 -0500
parents 02c81fea0d80
children fdec0f43fe00
files src/com/redhat/thermostat/agent/Main.java src/com/redhat/thermostat/agent/MongoStorage.java src/com/redhat/thermostat/agent/Storage.java src/com/redhat/thermostat/agent/StorageConstants.java src/com/redhat/thermostat/agent/config/Configuration.java src/com/redhat/thermostat/backend/Backend.java src/com/redhat/thermostat/backend/BackendRegistry.java src/com/redhat/thermostat/backend/system/CpuStatBuilder.java src/com/redhat/thermostat/backend/system/DistributionIdentity.java src/com/redhat/thermostat/backend/system/HostInfoBuilder.java src/com/redhat/thermostat/backend/system/MemoryStatBuilder.java src/com/redhat/thermostat/backend/system/SystemBackend.java src/com/redhat/thermostat/common/Constants.java src/com/redhat/thermostat/common/CpuStat.java src/com/redhat/thermostat/common/HostInfo.java src/com/redhat/thermostat/common/MemoryStat.java src/com/redhat/thermostat/common/NotImplementedException.java
diffstat 17 files changed, 747 insertions(+), 20 deletions(-) [+]
line wrap: on
line diff
--- a/src/com/redhat/thermostat/agent/Main.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/agent/Main.java	Fri Dec 02 12:37:01 2011 -0500
@@ -58,14 +58,6 @@
 
         logger.setLevel(config.getLogLevel());
 
-        BackendRegistry backendRegistry = null;
-        try {
-            backendRegistry = new BackendRegistry(config);
-        } catch (BackendLoadException ble) {
-            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", ble);
-            System.exit(Constants.EXIT_BACKEND_LOAD_ERROR);
-        }
-
         Storage storage = new MongoStorage();
         try {
             storage.connect(config.getDatabaseURIAsString());
@@ -74,10 +66,18 @@
             logger.log(Level.SEVERE, "Could not initialize storage layer.", uhe);
             System.exit(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
         }
+        config.setStorage(storage);
+
+        BackendRegistry backendRegistry = null;
+        try {
+            backendRegistry = new BackendRegistry(config, storage);
+        } catch (BackendLoadException ble) {
+            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", ble);
+            System.exit(Constants.EXIT_BACKEND_LOAD_ERROR);
+        }
 
         Agent agent = new Agent(backendRegistry, config, storage);
         config.setAgent(agent);
-        config.setStorage(storage);
         storage.setAgentId(agent.getId());
         try {
             agent.start();
--- a/src/com/redhat/thermostat/agent/MongoStorage.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/agent/MongoStorage.java	Fri Dec 02 12:37:01 2011 -0500
@@ -1,8 +1,12 @@
 package com.redhat.thermostat.agent;
 
 import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map.Entry;
 import java.util.UUID;
 
+import org.bson.BSONObject;
+
 import com.mongodb.BasicDBObject;
 import com.mongodb.DB;
 import com.mongodb.DBCollection;
@@ -11,6 +15,10 @@
 import com.mongodb.MongoURI;
 import com.mongodb.WriteConcern;
 import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.CpuStat;
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.MemoryStat;
 
 public class MongoStorage implements Storage {
 
@@ -38,22 +46,22 @@
     public void addAgentInformation(Configuration config) {
         DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
         DBObject toInsert = config.toDBObject();
-        toInsert.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_ID, agentId.toString());
+        /* cast required to disambiguate between putAll(BSONObject) and putAll(Map) */
+        toInsert.putAll((BSONObject) getAgentDBObject());
         configCollection.insert(toInsert, WriteConcern.SAFE);
     }
 
     @Override
     public void removeAgentInformation() {
         DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
-        BasicDBObject toRemove = new BasicDBObject(StorageConstants.KEY_AGENT_CONFIG_AGENT_ID, agentId.toString());
+        BasicDBObject toRemove = getAgentDBObject();
         configCollection.remove(toRemove, WriteConcern.NORMAL);
     }
 
     @Override
     public String getBackendConfig(String backendName, String configurationKey) {
         DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
-        BasicDBObject query = new BasicDBObject();
-        query.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_ID, agentId.toString());
+        BasicDBObject query = getAgentDBObject();
         query.put(StorageConstants.KEY_AGENT_CONFIG_BACKENDS + "." + backendName, new BasicDBObject("$exists", true));
         DBObject config = configCollection.findOne(query);
         Object value = config.get(configurationKey);
@@ -63,4 +71,78 @@
         return null;
     }
 
+    private BasicDBObject getAgentDBObject() {
+        return new BasicDBObject(StorageConstants.KEY_AGENT_ID, agentId.toString());
+    }
+
+    @Override
+    public void updateHostInfo(HostInfo hostInfo) {
+        /*
+         * Host Info is determined (completely) on the agent side. No one else
+         * should be touching it. So let's overwrite any changes to it.
+         */
+        DBObject queryForOldObject = getAgentDBObject();
+
+        BasicDBObject toInsert = new BasicDBObject();
+        toInsert.put(StorageConstants.KEY_AGENT_ID, agentId.toString());
+        toInsert.put(StorageConstants.KEY_HOST_INFO_HOSTNAME, hostInfo.getHostname());
+
+        BasicDBObject osParts = new BasicDBObject();
+        osParts.put(StorageConstants.KEY_HOST_INFO_OS_NAME, hostInfo.getOsName());
+        osParts.put(StorageConstants.KEY_HOST_INFO_OS_KERNEL, hostInfo.getOsKernel());
+        toInsert.put(StorageConstants.KEY_HOST_INFO_OS, osParts);
+
+        BasicDBObject cpuParts = new BasicDBObject();
+        cpuParts.put(StorageConstants.KEY_HOST_INFO_CPU_COUNT, hostInfo.getCpuCount());
+        toInsert.put(StorageConstants.KEY_HOST_INFO_CPU, cpuParts);
+
+        BasicDBObject memoryParts = new BasicDBObject();
+        memoryParts.put(StorageConstants.KEY_HOST_INFO_MEMORY_TOTAL, hostInfo.getTotalMemory());
+        toInsert.put(StorageConstants.KEY_HOST_INFO_MEMORY, memoryParts);
+
+        BasicDBObject networkParts = new BasicDBObject();
+        for (Entry<String, List<String>> entry: hostInfo.getNetworkInfo().entrySet()) {
+            BasicDBObject details = new BasicDBObject();
+            details.put(StorageConstants.KEY_HOST_INFO_NETWORK_ADDR_IPV4,
+                    entry.getValue().get(Constants.HOST_INFO_NETWORK_IPV4_INDEX));
+            details.put(StorageConstants.KEY_HOST_INFO_NETWORK_ADDR_IPV6,
+                    entry.getValue().get(Constants.HOST_INFO_NETWORK_IPV6_INDEX));
+            networkParts.put(entry.getKey(), details);
+        }
+        toInsert.put(StorageConstants.KEY_HOST_INFO_NETWORK, networkParts);
+
+        DBCollection hostInfoCollection = db.getCollection(StorageConstants.COLLECTION_HOST_INFO);
+        hostInfoCollection.update(
+                queryForOldObject,
+                toInsert,
+                true,
+                false, /* doesnt matter should only have one object */
+                WriteConcern.NORMAL
+        );
+    }
+
+    @Override
+    public void addCpuStat(CpuStat stat) {
+        DBCollection cpuStatsCollection = db.getCollection(StorageConstants.COLLECTION_CPU_STATS);
+        BasicDBObject toInsert = getAgentDBObject();
+        toInsert.put(StorageConstants.KEY_TIMESTAMP, stat.getTimeStamp());
+        toInsert.put(StorageConstants.KEY_CPU_STATS_LOAD, stat.getLoad());
+        cpuStatsCollection.insert(toInsert, WriteConcern.NORMAL);
+    }
+
+    @Override
+    public void addMemoryStat(MemoryStat stat) {
+        DBCollection memoryStatsCollection = db.getCollection(StorageConstants.COLLECTION_MEMORY_STATS);
+        BasicDBObject toInsert = getAgentDBObject();
+        toInsert.put(StorageConstants.KEY_TIMESTAMP, stat.getTimeStamp());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_TOTAL, stat.getTotal());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_FREE, stat.getFree());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_BUFFERS, stat.getBuffers());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_CACHED, stat.getCached());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_SWAP_TOTAL, stat.getSwapTotal());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_SWAP_FREE, stat.getSwapFree());
+        toInsert.put(StorageConstants.KEY_MEMORY_STATS_COMMIT_LIMIT, stat.getCommitLimit());
+        memoryStatsCollection.insert(toInsert, WriteConcern.NORMAL);
+    }
+
 }
--- a/src/com/redhat/thermostat/agent/Storage.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/agent/Storage.java	Fri Dec 02 12:37:01 2011 -0500
@@ -4,6 +4,9 @@
 import java.util.UUID;
 
 import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.common.CpuStat;
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.MemoryStat;
 
 public interface Storage {
     public void connect(String uri) throws UnknownHostException;
@@ -14,6 +17,12 @@
 
     public void removeAgentInformation();
 
+    public void addCpuStat(CpuStat stat);
+
+    public void addMemoryStat(MemoryStat stat);
+
+    public void updateHostInfo(HostInfo hostInfo);
+
     /**
      * @return {@code null} if the value is invalid or missing
      */
--- a/src/com/redhat/thermostat/agent/StorageConstants.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/agent/StorageConstants.java	Fri Dec 02 12:37:01 2011 -0500
@@ -4,10 +4,36 @@
     public static final String THERMOSTAT_DB = "thermostat";
 
     public static final String COLLECTION_AGENT_CONFIG = "agent-config";
+    public static final String COLLECTION_HOST_INFO = "host-info";
+    public static final String COLLECTION_CPU_STATS = "cpu-stats";
+    public static final String COLLECTION_MEMORY_STATS = "memory-stats";
 
-    public static final String KEY_AGENT_CONFIG_AGENT_ID = "agent-id";
+    public static final String KEY_AGENT_ID = "agent-id";
+    public static final String KEY_TIMESTAMP = "timestamp";
+
     public static final String KEY_AGENT_CONFIG_BACKENDS = "backends";
-
     public static final String KEY_AGENT_CONFIG_AGENT_START_TIME = "start-time";
 
+    public static final String KEY_HOST_INFO_HOSTNAME = "hostname";
+    public static final String KEY_HOST_INFO_OS = "os";
+    public static final String KEY_HOST_INFO_OS_NAME = "name";
+    public static final String KEY_HOST_INFO_OS_KERNEL = "kernel";
+    public static final String KEY_HOST_INFO_CPU = "cpu";
+    public static final String KEY_HOST_INFO_CPU_COUNT = "num";
+    public static final String KEY_HOST_INFO_MEMORY = "memory";
+    public static final String KEY_HOST_INFO_MEMORY_TOTAL = "total";
+    public static final String KEY_HOST_INFO_NETWORK = "network";
+    public static final String KEY_HOST_INFO_NETWORK_ADDR_IPV4 = "ipv4addr";
+    public static final String KEY_HOST_INFO_NETWORK_ADDR_IPV6 = "ipv6addr";
+
+    public static final String KEY_CPU_STATS_LOAD = "load";
+
+    public static final String KEY_MEMORY_STATS_TOTAL = "total";
+    public static final String KEY_MEMORY_STATS_FREE = "free";
+    public static final String KEY_MEMORY_STATS_BUFFERS = "buffers";
+    public static final String KEY_MEMORY_STATS_CACHED = "cached";
+    public static final String KEY_MEMORY_STATS_SWAP_TOTAL = "swap-total";
+    public static final String KEY_MEMORY_STATS_SWAP_FREE = "swap-free";
+    public static final String KEY_MEMORY_STATS_COMMIT_LIMIT = "commit-limit";
+
 }
--- a/src/com/redhat/thermostat/agent/config/Configuration.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/agent/config/Configuration.java	Fri Dec 02 12:37:01 2011 -0500
@@ -120,7 +120,7 @@
     public DBObject toDBObject() {
         BasicDBObject result = new BasicDBObject();
         // TODO explicit exception if agent not yet set.
-        result.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_ID, agent.getId().toString());
+        result.put(StorageConstants.KEY_AGENT_ID, agent.getId().toString());
         result.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_START_TIME, startTimestamp);
         // TODO create nested backend config parts
         return result;
--- a/src/com/redhat/thermostat/backend/Backend.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/backend/Backend.java	Fri Dec 02 12:37:01 2011 -0500
@@ -3,14 +3,15 @@
 import java.util.Map;
 import java.util.Map.Entry;
 
+import com.redhat.thermostat.agent.Storage;
+
 /**
  * Represents a monitoring back-end. All the {@link Backend}s should be
  * registered with the {@link BackendRegistry}.
  */
 public abstract class Backend {
 
-    public Backend() {
-    }
+    protected Storage storage;
 
     public final void setInitialConfiguration(Map<String, String> configMap) {
         for (Entry<String, String> e : configMap.entrySet()) {
@@ -20,6 +21,8 @@
 
     protected abstract void setConfigurationValue(String name, String value);
 
+
+
     /** Returns the name of the {@link Backend} */
     public abstract String getName();
 
@@ -37,6 +40,10 @@
      */
     public abstract Map<String, String> getConfigurationMap();
 
+    protected void setStorage(Storage storage) {
+        this.storage = storage;
+    }
+
     /**
      * Activate the {@link Backend}.  Based on the current configuration,
      * begin pushing data to the Storage layer.  If the {@link Backend} is
--- a/src/com/redhat/thermostat/backend/BackendRegistry.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/backend/BackendRegistry.java	Fri Dec 02 12:37:01 2011 -0500
@@ -7,7 +7,9 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.redhat.thermostat.agent.Storage;
 import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.backend.system.SystemBackend;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
 /**
@@ -20,9 +22,21 @@
 
     private final Map<String, Backend> registeredBackends;
 
-    public BackendRegistry(Configuration config) throws BackendLoadException {
+    public BackendRegistry(Configuration config, Storage storage) throws BackendLoadException {
         registeredBackends = new HashMap<String, Backend>();
-        
+
+        /*
+         * Configure the always-on backends
+         */
+        Backend systemBackend = new SystemBackend();
+        logger.log(Level.FINE, "Initializing backend: \"" + systemBackend.getClass().getCanonicalName() + "\"");
+        systemBackend.setInitialConfiguration(config.getStartupBackendConfigMap(systemBackend.getName()));
+        systemBackend.setStorage(storage);
+        register(systemBackend);
+
+        /*
+         * Configure the dynamic/custom backends
+         */
         for (String backendClassName : config.getStartupBackendClassNames()) {
             logger.log(Level.FINE, "Initializing backend: \"" + backendClassName + "\"");
             Backend backend = null;
@@ -32,6 +46,7 @@
                 Constructor<? extends Backend> backendConstructor = narrowed.getConstructor();
                 backend = backendConstructor.newInstance();
                 backend.setInitialConfiguration(config.getStartupBackendConfigMap(backend.getName()));
+                backend.setStorage(storage);
             } catch (Exception e) {
                 throw new BackendLoadException("Could not instantiate configured backend class: " + backendClassName, e);
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/backend/system/CpuStatBuilder.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,43 @@
+package com.redhat.thermostat.backend.system;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.CpuStat;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+/**
+ * Implementation note: uses information from /proc/
+ */
+public class CpuStatBuilder {
+
+    private static final String LOAD_FILE = "/proc/loadavg";
+
+    private static final Logger logger = LoggingUtils.getLogger(CpuStatBuilder.class);
+
+    public CpuStat build() {
+        long timestamp = System.currentTimeMillis();
+        double load5 = CpuStat.INVALID_LOAD;
+        double load10 = CpuStat.INVALID_LOAD;
+        double load15 = CpuStat.INVALID_LOAD;
+
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(LOAD_FILE));
+            String[] loadAvgParts = reader.readLine().split(" +");
+            if (loadAvgParts.length >= 3) {
+                load5 = Double.valueOf(loadAvgParts[0]);
+                load10 = Double.valueOf(loadAvgParts[1]);
+                load15 = Double.valueOf(loadAvgParts[2]);
+            }
+        } catch (NumberFormatException nfe) {
+            logger.log(Level.WARNING, "error extracting load from " + LOAD_FILE);
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "unable to read " + LOAD_FILE);
+        }
+
+        return new CpuStat(timestamp, load5, load10, load15);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/backend/system/DistributionIdentity.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,70 @@
+package com.redhat.thermostat.backend.system;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+/**
+ * Implementation note: this relies on the {@code lsb_release} program to work.
+ */
+public class DistributionIdentity {
+
+    private static final String DISTRIBUTION_NAME = "distributor id";
+    private static final String DISTRIBUTION_VERSION = "release";
+
+    private static final Logger logger = LoggingUtils.getLogger(DistributionIdentity.class);
+
+    private final String name;
+    private final String version;
+
+    public DistributionIdentity() {
+        String tempName = "Unknown Distribution";
+        String tempVersion = "Unknown";
+        try {
+            Process lsbProc = Runtime.getRuntime().exec(new String[] { "lsb_release", "-a" });
+            InputStream progOutput = lsbProc.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(progOutput));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                int sepLocation = line.indexOf(":");
+                if (sepLocation != -1) {
+                    String key = line.substring(0, sepLocation).toLowerCase();
+                    if (key.equals(DISTRIBUTION_NAME)) {
+                        tempName = line.substring(sepLocation + 1).trim();
+                    } else if (key.equals(DISTRIBUTION_VERSION)) {
+                        tempVersion = line.substring(sepLocation + 1).trim();
+                    }
+                }
+            }
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "unable to identify distribution");
+        }
+        name = tempName;
+        version = tempVersion;
+
+        logger.log(Level.FINE, "distro-name: " + name);
+        logger.log(Level.FINE, "distro-version: " + version);
+    }
+
+    /**
+     * @return the name of the distrbution, or {@code null} if it can not be
+     * identified
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return the release of the distribution or {@code null} if it can not be
+     * identified
+     */
+    public String getVersion() {
+        return version;
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/backend/system/HostInfoBuilder.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,105 @@
+package com.redhat.thermostat.backend.system;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class HostInfoBuilder {
+
+    private static final Logger logger = LoggingUtils.getLogger(HostInfoBuilder.class);
+
+    public HostInfo build() {
+        InetAddress localAddr;
+        String hostname;
+        try {
+            localAddr = InetAddress.getLocalHost();
+            hostname = localAddr.getCanonicalHostName();
+        } catch (UnknownHostException e) {
+            hostname = Constants.AGENT_LOCAL_HOSTNAME;
+        }
+        logger.log(Level.FINEST, "hostname: " + hostname);
+        DistributionIdentity identifier = new DistributionIdentity();
+        String osName = identifier.getName() + " " + identifier.getVersion();
+        logger.log(Level.FINEST, "osName: " + osName);
+
+        String osKernel = System.getProperty("os.name") + " " + System.getProperty("os.version");
+        logger.log(Level.FINEST, "osKernel: " + osKernel);
+
+        // FIXME replace with a real implementation. This is the number of
+        // processors available to the _JVM_
+        int cpuCount = Runtime.getRuntime().availableProcessors();
+        logger.log(Level.FINEST, "cpuCount: " + cpuCount);
+
+        long totalMemory = -1;
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader("/proc/meminfo"));
+            String[] memTotalParts = reader.readLine().split(" +");
+            long data = Long.valueOf(memTotalParts[1]);
+            String units = memTotalParts[2];
+            if (units.equals("kB")) {
+                totalMemory = data * Constants.KILOBYTES_TO_BYTES;
+            }
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "unable to read /proc/meminfo");
+        }
+        logger.log(Level.FINEST, "totalMemory: " + totalMemory + " bytes");
+
+        HashMap<String, List<String>> networkInfo = new HashMap<String, List<String>>();
+        try {
+            Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
+            for (NetworkInterface iface : Collections.list(ifaces)) {
+                List<String> ipAddresses = new ArrayList<String>(2);
+                ipAddresses.add(Constants.HOST_INFO_NETWORK_IPV4_INDEX, null);
+                ipAddresses.add(Constants.HOST_INFO_NETWORK_IPV6_INDEX, null);
+                for (InetAddress addr : Collections.list(iface.getInetAddresses())) {
+                    if (addr instanceof Inet4Address) {
+                        ipAddresses.set(Constants.HOST_INFO_NETWORK_IPV4_INDEX, fixAddr(addr.toString()));
+                    } else if (addr instanceof Inet6Address) {
+                        ipAddresses.set(Constants.HOST_INFO_NETWORK_IPV6_INDEX, fixAddr(addr.toString()));
+                    }
+                }
+                networkInfo.put(iface.getName(), ipAddresses);
+            }
+        } catch (SocketException e) {
+            logger.log(Level.WARNING, "error enumerating network interfaces");
+        }
+
+        return new HostInfo(hostname, osName, osKernel, cpuCount, totalMemory, networkInfo);
+
+    }
+
+    /**
+     * Removes the "hostname/" and the "%scope_id" parts from the
+     * {@link InetAddress#toString()} output.
+     */
+    private String fixAddr(String addr) {
+        int slashPos = addr.indexOf("/");
+        if (slashPos == -1) {
+            return addr;
+        }
+        String fixed = addr.substring(slashPos + 1);
+        int percentPos = fixed.indexOf("%");
+        if (percentPos == -1) {
+            return fixed;
+        }
+        fixed = fixed.substring(0, percentPos);
+        return fixed;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/backend/system/MemoryStatBuilder.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,100 @@
+package com.redhat.thermostat.backend.system;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.MemoryStat;
+import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+/**
+ * Implementation note: uses information from /proc/
+ */
+public class MemoryStatBuilder {
+
+    private static final long UNAVAILABLE = -1;
+
+    private static final String MEMINFO_FILE = "/proc/meminfo";
+
+    private static final String KEY_MEMORY_TOTAL = "MemTotal";
+    private static final String KEY_MEMORY_FREE = "MemFree";
+    private static final String KEY_BUFFERS = "Buffers";
+    private static final String KEY_CACHED = "Cached";
+    private static final String KEY_SWAP_TOTAL = "SwapTotal";
+    private static final String KEY_SWAP_FREE = "SwapFree";
+    private static final String KEY_COMMIT_LIMIT = "CommitLimit";
+
+    private static final Logger logger = LoggingUtils.getLogger(MemoryStatBuilder.class);
+
+    public MemoryStat build() {
+        long timestamp = System.currentTimeMillis();
+
+        long total = UNAVAILABLE;
+        long free = UNAVAILABLE;
+        long swapTotal = UNAVAILABLE;
+        long swapFree = UNAVAILABLE;
+        long buffers = UNAVAILABLE;
+        long cached = UNAVAILABLE;
+        long commitLimit = UNAVAILABLE;
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(MEMINFO_FILE));
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                String[] parts = line.split(":");
+                if (parts.length == 2) {
+                    String key = parts[0].trim();
+                    long value = getValue(parts[1].trim());
+                    if (key.equals(KEY_MEMORY_TOTAL)) {
+                        total = value;
+                    } else if (key.equals(KEY_MEMORY_FREE)) {
+                        free = value;
+                    } else if (key.equals(KEY_SWAP_TOTAL)) {
+                        swapTotal = value;
+                    } else if (key.equals(KEY_SWAP_FREE)) {
+                        swapFree = value;
+                    } else if (key.equals(KEY_BUFFERS)) {
+                        buffers = value;
+                    } else if (key.equals(KEY_CACHED)) {
+                        cached = value;
+                    } else if (key.equals(KEY_COMMIT_LIMIT)) {
+                        commitLimit = value;
+                    }
+
+                }
+            }
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "unable to read " + MEMINFO_FILE);
+        }
+        return new MemoryStat(timestamp, total, free, buffers, cached, swapTotal, swapFree, commitLimit);
+    }
+
+    private long getValue(String rawValue) {
+        String[] parts = rawValue.split(" +");
+        String value = rawValue;
+        String units = null;
+        if (parts.length > 1) {
+            value = parts[0];
+            units = parts[1];
+        }
+
+        long result = UNAVAILABLE;
+        try {
+            result = Long.parseLong(value);
+            if (units != null) {
+                if (units.equals("kB") || units.equals("KB")) {
+                    result = result * Constants.KILOBYTES_TO_BYTES;
+                } else {
+                    throw new NotImplementedException("unit conversion from " + units + " not implemented");
+                }
+            }
+        } catch (NumberFormatException nfe) {
+            logger.log(Level.WARNING, "error extracting memory info from " + MEMINFO_FILE);
+        }
+
+        return result;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/backend/system/SystemBackend.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,100 @@
+package com.redhat.thermostat.backend.system;
+
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.backend.Backend;
+import com.redhat.thermostat.common.CpuStat;
+import com.redhat.thermostat.common.HostInfo;
+import com.redhat.thermostat.common.MemoryStat;
+import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class SystemBackend extends Backend {
+
+    private static final String NAME = "system";
+    private static final String DESCRIPTION = "gathers basic information from the system";
+    private static final String VENDOR = "thermostat project";
+    private static final String VERSION = "0.01";
+
+    private static final Logger logger = LoggingUtils.getLogger(SystemBackend.class);
+
+    private long procCheckInterval = 1000;
+
+    private Timer timer = null;
+
+    @Override
+    protected void setConfigurationValue(String name, String value) {
+        logger.log(Level.INFO, "configuring " + NAME + " not supported");
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
+    }
+
+    @Override
+    public String getVendor() {
+        return VENDOR;
+    }
+
+    @Override
+    public String getVersion() {
+        return VERSION;
+    }
+
+    @Override
+    public Map<String, String> getConfigurationMap() {
+        throw new NotImplementedException("get configuration");
+    }
+
+    @Override
+    public synchronized boolean activate() {
+        if (timer != null) {
+            return true;
+        }
+
+        HostInfo hostInfo = new HostInfoBuilder().build();
+        storage.updateHostInfo(hostInfo);
+
+        timer = new Timer();
+        timer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                CpuStat cpuStat = new CpuStatBuilder().build();
+                storage.addCpuStat(cpuStat);
+
+                MemoryStat memoryStat = new MemoryStatBuilder().build();
+                storage.addMemoryStat(memoryStat);
+            }
+        }, 0, procCheckInterval);
+
+        return true;
+    }
+
+    @Override
+    public synchronized boolean deactivate() {
+        if (timer == null) {
+            return true;
+        }
+
+        timer.cancel();
+        timer = null;
+
+        return true;
+    }
+
+    @Override
+    public synchronized boolean isActive() {
+        return (timer != null);
+    }
+
+}
--- a/src/com/redhat/thermostat/common/Constants.java	Tue Nov 29 11:58:37 2011 -0500
+++ b/src/com/redhat/thermostat/common/Constants.java	Fri Dec 02 12:37:01 2011 -0500
@@ -29,4 +29,10 @@
 
     public static final String AGENT_LOCAL_HOSTNAME = "localhost";
 
+    public static final long KILOBYTES_TO_BYTES = 1000;
+
+    public static final int HOST_INFO_NETWORK_IPV4_INDEX = 0;
+    public static final int HOST_INFO_NETWORK_IPV6_INDEX = 1;
+
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/common/CpuStat.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,38 @@
+package com.redhat.thermostat.common;
+
+public class CpuStat {
+
+    public static final double INVALID_LOAD = Double.MIN_VALUE;
+
+    private final double load5;
+    private final double load10;
+    private final double load15;
+    private final long timeStamp;
+
+    public CpuStat(long timestamp, double load5, double load10, double load15) {
+        this.timeStamp = timestamp;
+        this.load5 = load5;
+        this.load10 = load10;
+        this.load15 = load15;
+    }
+
+    public double getLoad5() {
+        return load5;
+    }
+
+    public double getLoad10() {
+        return load10;
+    }
+
+    public double getLoad15() {
+        return load15;
+    }
+
+    public double[] getLoad() {
+        return new double[] { load5, load10, load15 };
+    }
+
+    public long getTimeStamp() {
+        return timeStamp;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/common/HostInfo.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,55 @@
+package com.redhat.thermostat.common;
+
+import java.util.List;
+import java.util.Map;
+
+public class HostInfo {
+
+    private final String hostname;
+    private final String osName;
+    private final String osKernel;
+    private final int cpuCount;
+    private final long totalMemory;
+    private final Map<String, List<String>> networkInfo;
+
+    public HostInfo(String hostname, String osName, String osKernel, int cpuCount, long totalMemory, Map<String, List<String>> networkInfo) {
+        this.hostname = hostname;
+        this.osName = osName;
+        this.osKernel = osKernel;
+        this.cpuCount = cpuCount;
+        this.totalMemory = totalMemory;
+        this.networkInfo = networkInfo;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public String getOsName() {
+        return osName;
+    }
+
+    public String getOsKernel() {
+        return osKernel;
+    }
+
+    public int getCpuCount() {
+        return cpuCount;
+    }
+
+    /**
+     * Total memory in bytes
+     */
+    public long getTotalMemory() {
+        return totalMemory;
+    }
+
+    /**
+     * @returns a map of the following form: {iface-name: [ipv4_addr,
+     * ipv6_addr]}
+     */
+    public Map<String, List<String>> getNetworkInfo() {
+        return networkInfo;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/common/MemoryStat.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,56 @@
+package com.redhat.thermostat.common;
+
+public class MemoryStat {
+    private final long timestamp;
+    private final long total;
+    private final long free;
+    private final long buffers;
+    private final long cached;
+    private final long swapTotal;
+    private final long swapFree;
+    private final long commitLimit;
+
+    public MemoryStat(long timestamp, long total, long free, long buffers, long cached, long swapTotal, long swapFree, long commitLimit) {
+        this.timestamp = timestamp;
+        this.total = total;
+        this.free = free;
+        this.buffers = buffers;
+        this.cached = cached;
+        this.swapTotal = swapTotal;
+        this.swapFree = swapFree;
+        this.commitLimit = commitLimit;
+    }
+
+    public long getTimeStamp() {
+        return timestamp;
+    }
+
+    public long getTotal() {
+        return total;
+    }
+
+    public long getFree() {
+        return free;
+    }
+
+    public long getBuffers() {
+        return buffers;
+    }
+
+    public long getCached() {
+        return cached;
+    }
+
+    public long getSwapTotal() {
+        return swapTotal;
+    }
+
+    public long getSwapFree() {
+        return swapFree;
+    }
+
+    public long getCommitLimit() {
+        return commitLimit;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/common/NotImplementedException.java	Fri Dec 02 12:37:01 2011 -0500
@@ -0,0 +1,15 @@
+package com.redhat.thermostat.common;
+
+public class NotImplementedException extends RuntimeException {
+
+    private static final long serialVersionUID = -1620198443624195618L;
+
+    public NotImplementedException(String message) {
+        super(message);
+    }
+
+    public NotImplementedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}