changeset 24:3387781e66ff

Generic storage interface. - This unfortunately huge commit allows for addition of new Backends and/or changes to the data stored by a existing Backends, without needing to add or change any code behind the Storage interface. - Additional change that crept in incidentally: network-info is moved to separate collection.
author Jon VanAlten <jon.vanalten@redhat.com>
date Thu, 22 Dec 2011 18:45:12 -0500
parents 272e028f7dac
children 1943c1071d45
files src/com/redhat/thermostat/agent/storage/Category.java src/com/redhat/thermostat/agent/storage/Chunk.java src/com/redhat/thermostat/agent/storage/Key.java src/com/redhat/thermostat/agent/storage/MongoStorage.java src/com/redhat/thermostat/agent/storage/Storage.java src/com/redhat/thermostat/agent/storage/StorageConstants.java src/com/redhat/thermostat/backend/Backend.java src/com/redhat/thermostat/backend/sample/SampleBackend.java src/com/redhat/thermostat/backend/system/HostInfoBuilder.java src/com/redhat/thermostat/backend/system/JvmStatHostListener.java src/com/redhat/thermostat/backend/system/JvmStatVmListener.java src/com/redhat/thermostat/backend/system/NetworkInfoBuilder.java src/com/redhat/thermostat/backend/system/SystemBackend.java src/com/redhat/thermostat/common/Constants.java src/com/redhat/thermostat/common/HostInfo.java src/com/redhat/thermostat/common/NetworkInfo.java src/com/redhat/thermostat/common/NetworkInterfaceInfo.java
diffstat 17 files changed, 536 insertions(+), 219 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/agent/storage/Category.java	Thu Dec 22 18:45:12 2011 -0500
@@ -0,0 +1,36 @@
+package com.redhat.thermostat.agent.storage;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class Category {
+    private final String name;
+    private final List<Key> keys;
+    private boolean locked = false;
+
+    public Category(String name) {
+        this.name = name;
+        keys = new ArrayList<Key>();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public synchronized void lock() {
+        locked = true;
+    }
+
+    public synchronized void addKey(Key key) {
+        if (!locked) {
+            keys.add(key);
+        } else {
+            throw new IllegalStateException("Once locked, a category's keys may not be changed.");
+        }
+    }
+
+    public synchronized Iterator<Key> getEntryIterator() {
+        return keys.iterator();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/agent/storage/Chunk.java	Thu Dec 22 18:45:12 2011 -0500
@@ -0,0 +1,44 @@
+package com.redhat.thermostat.agent.storage;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A Chunk is a unit containing a set of data that can be added as a whole to the dataset
+ * that exists behind the storage layer.
+ */
+public class Chunk {
+    private final Category category;
+    private final boolean replace;
+
+    private Map<Key, String> values = new HashMap<Key, String>();
+
+    /**
+     * 
+     * @param timestamp The time that should be associated with the data in this nugget.
+     * @param category The {@link Category} of this data.  This should be a Category that the {@link Backend}
+     * who is producing this Chunk has registered via {@link Storage#registerCategory()}
+     * @param add whether this chunk should replace the values based on the keys for this category,
+     * or be added to a set of values in this category
+     */
+    public Chunk(Category category, boolean replace) {
+        this.category = category;
+        this.replace = replace;
+    }
+
+    public Category getCategory() {
+        return category;
+    }
+
+    public boolean getReplace() {
+        return replace;
+    }
+
+    public void put(Key entry, String value) {
+        values.put(entry, value);
+    }
+
+    public String get(Key entry) {
+        return values.get(entry);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/agent/storage/Key.java	Thu Dec 22 18:45:12 2011 -0500
@@ -0,0 +1,48 @@
+package com.redhat.thermostat.agent.storage;
+
+/**
+ * A Key is used to refer to data in a {@link Chunk}.  It may also be a partial key to the
+ * set of data represented by a {@link Chunk} in a category.
+ */
+public class Key {
+
+    // Key used by most Categories.
+    public static Key TIMESTAMP = new Key("timestamp", false);
+
+    private String name;
+    private boolean isPartialCategoryKey;
+
+    public Key(String name, boolean isPartialCategoryKey) {
+        this.name = name;
+        this.isPartialCategoryKey = isPartialCategoryKey;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isPartialCategoryKey() {
+        return isPartialCategoryKey;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if ((o == null) || (o.getClass() != this.getClass())) {
+            return false;
+        }
+        Key e = (Key) o;
+        return (isPartialCategoryKey == e.isPartialCategoryKey()) &&
+            name.equals(e.getName());
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 1867;
+        hash = hash * 37 + (isPartialCategoryKey ? 0 : 1);
+        hash = hash * 37 + (name == null ? 0 : name.hashCode());
+        return hash;
+    }
+}
--- a/src/com/redhat/thermostat/agent/storage/MongoStorage.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/agent/storage/MongoStorage.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,8 +1,9 @@
 package com.redhat.thermostat.agent.storage;
 
 import java.net.UnknownHostException;
-import java.util.List;
-import java.util.Map.Entry;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
 import java.util.UUID;
 
 import org.bson.BSONObject;
@@ -15,12 +16,15 @@
 import com.mongodb.MongoURI;
 import com.mongodb.WriteConcern;
 import com.redhat.thermostat.agent.config.StartupConfiguration;
-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 {
+/**
+ * Implementation of the Storage interface that uses MongoDB to store the instrumentation data.
+ * 
+ * In this implementation, each CATEGORY is given a distinct collection.
+ */
+public class MongoStorage extends Storage {
+
+    public static final String KEY_AGENT_ID = "agent-id";
 
     private Mongo mongo = null;
     private DB db = null;
@@ -34,7 +38,7 @@
 
     public void connect(MongoURI uri) throws UnknownHostException {
         mongo = new Mongo(uri);
-        db = mongo.getDB(StorageConstants.THERMOSTAT_DB);
+        db = mongo.getDB(StorageConstants.THERMOSTAT_DB_NAME);
     }
 
     @Override
@@ -44,7 +48,7 @@
 
     @Override
     public void addAgentInformation(StartupConfiguration config) {
-        DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
+        DBCollection configCollection = db.getCollection(StorageConstants.CATEGORY_AGENT_CONFIG);
         DBObject toInsert = config.toDBObject();
         /* cast required to disambiguate between putAll(BSONObject) and putAll(Map) */
         toInsert.putAll((BSONObject) getAgentDBObject());
@@ -53,14 +57,14 @@
 
     @Override
     public void removeAgentInformation() {
-        DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
+        DBCollection configCollection = db.getCollection(StorageConstants.CATEGORY_AGENT_CONFIG);
         BasicDBObject toRemove = getAgentDBObject();
         configCollection.remove(toRemove, WriteConcern.NORMAL);
     }
 
     @Override
     public String getBackendConfig(String backendName, String configurationKey) {
-        DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
+        DBCollection configCollection = db.getCollection(StorageConstants.CATEGORY_AGENT_CONFIG);
         BasicDBObject query = getAgentDBObject();
         query.put(StorageConstants.KEY_AGENT_CONFIG_BACKENDS + "." + backendName, new BasicDBObject("$exists", true));
         DBObject config = configCollection.findOne(query);
@@ -72,76 +76,59 @@
     }
 
     private BasicDBObject getAgentDBObject() {
-        return new BasicDBObject(StorageConstants.KEY_AGENT_ID, agentId.toString());
+        return new BasicDBObject(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);
+    protected void addChunkImpl(Chunk chunk) {
+        Category cat = chunk.getCategory();
+        DBCollection coll = db.getCollection(cat.getName());
+        BasicDBObject toInsert = getAgentDBObject();
+        BasicDBObject toDelete = null;
+        boolean replace = chunk.getReplace();
+        Map<String, BasicDBObject> nestedParts = new HashMap<String, BasicDBObject>();
+        Map<String, BasicDBObject> deleteNestedParts = null;
+        if (replace) {
+            toDelete = getAgentDBObject();
+            deleteNestedParts = new HashMap<String, BasicDBObject>();
         }
-        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);
+        for (Iterator<com.redhat.thermostat.agent.storage.Key> iter = cat.getEntryIterator(); iter.hasNext();) {
+            com.redhat.thermostat.agent.storage.Key key = iter.next();
+            boolean isKey = key.isPartialCategoryKey();
+            String[] entryParts = key.getName().split(".");
+            if (entryParts.length == 2) {
+                BasicDBObject nested = nestedParts.get(entryParts[0]);
+                if (nested == null) {
+                    nested = new BasicDBObject();
+                    nestedParts.put(entryParts[0], nested);
+                }
+                nested.append(entryParts[1], chunk.get(key));
+                if (replace && isKey) {
+                    BasicDBObject deleteNested = deleteNestedParts.get(entryParts[0]);
+                    if (deleteNested == null) {
+                        deleteNested = new BasicDBObject();
+                        deleteNestedParts.put(entryParts[0], deleteNested);
+                    }
+                    deleteNested.append(entryParts[1], deleteNested);
+                }
+            } else {
+                String mongoKey = key.getName();
+                String value = chunk.get(key);
+                toInsert.append(mongoKey, value);
+                if (replace && isKey) {
+                    toDelete.append(mongoKey, value);
+                }
+            }
+        }
+        for (String mongoKey : nestedParts.keySet()) {
+            toInsert.append(mongoKey, nestedParts.get(mongoKey));
+        }
+        if (replace) {
+            for (String mongoKey : deleteNestedParts.keySet()) {
+                toDelete.append(mongoKey, deleteNestedParts.get(mongoKey));
+            }
+            coll.remove(toDelete);
+        }
+        coll.insert(toInsert);
     }
 }
--- a/src/com/redhat/thermostat/agent/storage/Storage.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/agent/storage/Storage.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,31 +1,47 @@
 package com.redhat.thermostat.agent.storage;
 
 import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
 
 import com.redhat.thermostat.agent.config.StartupConfiguration;
-import com.redhat.thermostat.common.CpuStat;
-import com.redhat.thermostat.common.HostInfo;
-import com.redhat.thermostat.common.MemoryStat;
+import com.redhat.thermostat.backend.Backend;
 
-public interface Storage {
-    public void connect(String uri) throws UnknownHostException;
-
-    public void setAgentId(UUID id);
+public abstract class Storage {
+    private Map<String, Backend> categoryMap;
 
-    public void addAgentInformation(StartupConfiguration config);
+    public Storage() {
+        categoryMap = new HashMap<String, Backend>();
+    }
 
-    public void removeAgentInformation();
+    public abstract void connect(String uri) throws UnknownHostException;
 
-    public void addCpuStat(CpuStat stat);
+    public abstract void setAgentId(UUID id);
 
-    public void addMemoryStat(MemoryStat stat);
+    public abstract void addAgentInformation(StartupConfiguration config);
 
-    public void updateHostInfo(HostInfo hostInfo);
+    public abstract void removeAgentInformation();
 
     /**
      * @return {@code null} if the value is invalid or missing
      */
-    public String getBackendConfig(String backendName, String configurationKey);
+    public abstract String getBackendConfig(String backendName, String configurationKey);
+
+    public final void registerCategory(Category category, Backend backend) {
+        if (categoryMap.containsKey(category.getName())) {
+            throw new IllegalStateException("Category may only be associated with one backend.");
+        }
+        categoryMap.put(category.getName(), backend);
+    }
 
+    public final void putChunk(Chunk chunk, Backend backend) {
+        Category category = chunk.getCategory();
+        if (backend != categoryMap.get(category.getName())) { // This had better be not just equivalent, but actually the same object.
+            throw new IllegalArgumentException("Invalid category-backend combination while inserting data.  Category: " + category.getName() + "  Backend: " + backend.getName());
+        }
+        addChunkImpl(chunk);
+    }
+    
+    protected abstract void addChunkImpl(Chunk chunk);
 }
--- a/src/com/redhat/thermostat/agent/storage/StorageConstants.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/agent/storage/StorageConstants.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,39 +1,10 @@
 package com.redhat.thermostat.agent.storage;
 
 public class StorageConstants {
-    public static final String THERMOSTAT_DB = "thermostat";
+    public static final String THERMOSTAT_DB_NAME = "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_ID = "agent-id";
-    public static final String KEY_TIMESTAMP = "timestamp";
+    public static final String CATEGORY_AGENT_CONFIG = "agent-config";
 
     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/backend/Backend.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/backend/Backend.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,8 +1,11 @@
 package com.redhat.thermostat.backend;
 
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import com.redhat.thermostat.agent.storage.Category;
+import com.redhat.thermostat.agent.storage.Chunk;
 import com.redhat.thermostat.agent.storage.Storage;
 
 /**
@@ -12,7 +15,7 @@
 public abstract class Backend {
 
     private boolean initialConfigurationComplete = false;
-    protected Storage storage;
+    private Storage storage;
 
     /**
      * 
@@ -36,10 +39,15 @@
         initialConfigurationComplete = true;
     }
 
-    protected void setStorage(Storage storage) {
+    public final void setStorage(Storage storage) {
         this.storage = storage;
+        for (Iterator<Category> iter = getCategoryIterator(); iter.hasNext();) {
+            storage.registerCategory(iter.next(), this);
+        }
     }
 
+    protected abstract Iterator<Category> getCategoryIterator();
+
     /**
      * Set the named configuration to the given value.
      * @param name
@@ -107,4 +115,7 @@
      */
     public abstract boolean isActive();
 
+    public final void store(Chunk chunk) {
+        storage.putChunk(chunk, this);
+    }
 }
--- a/src/com/redhat/thermostat/backend/sample/SampleBackend.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/backend/sample/SampleBackend.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,10 +1,13 @@
 package com.redhat.thermostat.backend.sample;
 
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.redhat.thermostat.agent.storage.Category;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
@@ -77,4 +80,9 @@
         return currentlyActive;
     }
 
+    @Override
+    protected Iterator<Category> getCategoryIterator() {
+        return new HashSet<Category>().iterator();
+    }
+
 }
--- a/src/com/redhat/thermostat/backend/system/HostInfoBuilder.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/backend/system/HostInfoBuilder.java	Thu Dec 22 18:45:12 2011 -0500
@@ -3,24 +3,14 @@
 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;
-import com.redhat.thermostat.common.utils.StringUtils;
 
 public class HostInfoBuilder {
 
@@ -29,7 +19,7 @@
 
     private static final Logger logger = LoggingUtils.getLogger(HostInfoBuilder.class);
 
-    public HostInfo build() {
+    public static HostInfo build() {
         InetAddress localAddr;
         String hostname;
         try {
@@ -72,31 +62,11 @@
         }
         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);
+        return new HostInfo(hostname, osName, osKernel, cpuCount, totalMemory);
 
     }
 
-    private int getProcessorCountFromProc() {
+    private static int getProcessorCountFromProc() {
         final String KEY_PROCESSOR_ID = "processor";
         int totalCpus = 0;
         BufferedReader reader = null;
@@ -121,22 +91,4 @@
         }
         return totalCpus;
     }
-
-    /**
-     * 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;
-    }
 }
--- a/src/com/redhat/thermostat/backend/system/JvmStatHostListener.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/backend/system/JvmStatHostListener.java	Thu Dec 22 18:45:12 2011 -0500
@@ -2,6 +2,7 @@
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -17,7 +18,7 @@
 import sun.jvmstat.monitor.event.HostListener;
 import sun.jvmstat.monitor.event.VmStatusChangeEvent;
 
-import com.redhat.thermostat.agent.storage.Storage;
+import com.redhat.thermostat.agent.storage.Category;
 import com.redhat.thermostat.common.VmInfo;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
@@ -25,15 +26,18 @@
 
     private static final Logger logger = LoggingUtils.getLogger(JvmStatHostListener.class);
 
-    private Storage storage;
+    private SystemBackend backend;
 
     private Map<Integer, JvmStatVmListener> listenerMap = new HashMap<Integer, JvmStatVmListener>();
 
-    public void setStorage(Storage storage) {
-        if (storage == null) {
-            throw new NullPointerException();
-        }
-        this.storage = storage;
+    public static Collection<Category> getCategories() {
+        ArrayList<Category> categories = new ArrayList<Category>();
+        // TODO add appropriate categories
+        return categories;
+    }
+
+    public void setBackend(SystemBackend backend) {
+        this.backend = backend;
     }
 
     @Override
@@ -43,10 +47,6 @@
 
     @Override
     public void vmStatusChanged(VmStatusChangeEvent event) {
-        if (storage == null) {
-            throw new NullPointerException("null");
-        }
-
         MonitoredHost host = event.getMonitoredHost();
 
         Iterator<Integer> newActive = event.getStarted().iterator();
@@ -100,7 +100,7 @@
                 logger.log(Level.WARNING, "error getting vm info for " + vmId, me);
             }
 
-            JvmStatVmListener listener = new JvmStatVmListener(storage, vmId);
+            JvmStatVmListener listener = new JvmStatVmListener(backend, vmId);
             listenerMap.put(vmId, listener);
             vm.addVmListener(listener);
         }
--- a/src/com/redhat/thermostat/backend/system/JvmStatVmListener.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/backend/system/JvmStatVmListener.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,6 +1,7 @@
 package com.redhat.thermostat.backend.system;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -11,7 +12,7 @@
 import sun.jvmstat.monitor.event.VmEvent;
 import sun.jvmstat.monitor.event.VmListener;
 
-import com.redhat.thermostat.agent.storage.Storage;
+import com.redhat.thermostat.agent.storage.Category;
 import com.redhat.thermostat.common.VmGcStat;
 import com.redhat.thermostat.common.VmMemoryStat;
 import com.redhat.thermostat.common.VmMemoryStat.Generation;
@@ -23,13 +24,19 @@
     private static final Logger logger = LoggingUtils.getLogger(JvmStatVmListener.class);
 
     private final int vmId;
-    private final Storage storage;
+    private final SystemBackend backend;
 
-    public JvmStatVmListener(Storage storage, int vmId) {
-        this.storage = storage;
+    public JvmStatVmListener(SystemBackend backend, int vmId) {
+        this.backend = backend;
         this.vmId = vmId;
     }
 
+    public static Collection<Category> getCategories() {
+        ArrayList<Category> categories = new ArrayList<Category>();
+        // TODO add appropriate categories
+        return categories;
+    }
+
     @Override
     public void disconnected(VmEvent event) {
         /* nothing to do here */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/backend/system/NetworkInfoBuilder.java	Thu Dec 22 18:45:12 2011 -0500
@@ -0,0 +1,59 @@
+package com.redhat.thermostat.backend.system;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.NetworkInfo;
+import com.redhat.thermostat.common.NetworkInterfaceInfo;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class NetworkInfoBuilder {
+
+    private static final Logger logger = LoggingUtils.getLogger(NetworkInfoBuilder.class);
+
+    public static NetworkInfo build() {
+        NetworkInfo info = new NetworkInfo();
+        try {
+            Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
+            for (NetworkInterface iface : Collections.list(ifaces)) {
+                NetworkInterfaceInfo iInfo = new NetworkInterfaceInfo(iface.getName());
+                for (InetAddress addr : Collections.list(iface.getInetAddresses())) {
+                    if (addr instanceof Inet4Address) {
+                        iInfo.setIp4Addr(fixAddr(addr.toString()));
+                    } else if (addr instanceof Inet6Address) {
+                        iInfo.setIp6Addr(fixAddr(addr.toString()));
+                    }
+                }
+                info.addNetworkInterfaceInfo(iInfo);
+            }
+        } catch (SocketException e) {
+            logger.log(Level.WARNING, "error enumerating network interfaces");
+        }
+        return info;
+    }
+
+    /**
+     * Removes the "hostname/" and the "%scope_id" parts from the
+     * {@link InetAddress#toString()} output.
+     */
+    private static 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;
+    }
+}
--- a/src/com/redhat/thermostat/backend/system/SystemBackend.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/backend/system/SystemBackend.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,6 +1,9 @@
 package com.redhat.thermostat.backend.system;
 
 import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -11,10 +14,14 @@
 import sun.jvmstat.monitor.MonitorException;
 import sun.jvmstat.monitor.MonitoredHost;
 
+import com.redhat.thermostat.agent.storage.Category;
+import com.redhat.thermostat.agent.storage.Key;
+import com.redhat.thermostat.agent.storage.Chunk;
 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.NetworkInterfaceInfo;
 import com.redhat.thermostat.common.NotImplementedException;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
@@ -27,7 +34,7 @@
 
     private static final Logger logger = LoggingUtils.getLogger(SystemBackend.class);
 
-    private long procCheckInterval = 1000;
+    private long procCheckInterval = 1000; // TODO make this configurable.
 
     private Timer timer = null;
 
@@ -35,6 +42,76 @@
     private MonitoredHost host = null;
     private JvmStatHostListener hostListener = new JvmStatHostListener();
 
+    private List<Category> categories = new ArrayList<Category>();
+
+    private Category hostInfoCategory = new Category("host-info");
+    private Key hostNameKey = new Key("hostname", true);
+    private Key osNameKey = new Key("os_name", false);
+    private Key osKernelKey = new Key("os_kernel", false);
+    private Key cpuCountKey = new Key("cpu_num", false);
+    private Key hostMemoryTotalKey = new Key("memory_total", false);
+
+    private Category networkInfoCategory = new Category("network-info");
+    private Key ifaceKey = new Key("iface", true);
+    private Key ip4AddrKey = new Key("ipv4addr", false);
+    private Key ip6AddrKey = new Key("ipv6addr", false);
+
+    private Category cpuStatCategory = new Category("cpu-stats");
+    private Key cpu5LoadKey = new Key("5load", false);
+    private Key cpu10LoadKey = new Key("10load", false);
+    private Key cpu15LoadKey = new Key("15load", false);
+
+    private Category memoryStatCategory = new Category("memory-stats");
+    private Key memoryTotalKey = new Key("total", false);
+    private Key memoryFreeKey = new Key("free", false);
+    private Key memoryBuffersKey = new Key("buffers", false);
+    private Key memoryCachedKey = new Key("cached", false);
+    private Key memorySwapTotalKey = new Key("swap-total", false);
+    private Key memorySwapFreeKey = new Key("swap-free", false);
+    private Key memoryCommitLimitKey = new Key("commit-limit", false);
+
+    {
+        // Set up categories that will later be registered.
+        // host-info category.
+        hostInfoCategory.addKey(hostNameKey);
+        hostInfoCategory.addKey(osNameKey);
+        hostInfoCategory.addKey(osKernelKey);
+        hostInfoCategory.addKey(cpuCountKey);
+        hostInfoCategory.addKey(hostMemoryTotalKey);
+        hostInfoCategory.lock();
+        categories.add(hostInfoCategory);
+
+        //network-info category
+        networkInfoCategory.addKey(ifaceKey);
+        networkInfoCategory.addKey(ip4AddrKey);
+        networkInfoCategory.addKey(ip6AddrKey);
+        networkInfoCategory.lock();
+        categories.add(networkInfoCategory);
+        
+        // cpu-stats category.
+        cpuStatCategory.addKey(Key.TIMESTAMP);
+        cpuStatCategory.addKey(cpu5LoadKey);
+        cpuStatCategory.addKey(cpu10LoadKey);
+        cpuStatCategory.addKey(cpu15LoadKey);
+        cpuStatCategory.lock();
+        categories.add(cpuStatCategory);
+
+        // memory-stat category.
+        memoryStatCategory.addKey(Key.TIMESTAMP);
+        memoryStatCategory.addKey(memoryTotalKey);
+        memoryStatCategory.addKey(memoryFreeKey);
+        memoryStatCategory.addKey(memoryBuffersKey);
+        memoryStatCategory.addKey(memoryCachedKey);
+        memoryStatCategory.addKey(memorySwapTotalKey);
+        memoryStatCategory.addKey(memorySwapFreeKey);
+        memoryStatCategory.addKey(memoryCommitLimitKey);
+        memoryStatCategory.lock();
+        categories.add(memoryStatCategory);
+
+        categories.addAll(JvmStatHostListener.getCategories());
+        categories.addAll(JvmStatVmListener.getCategories());
+    }
+
 
     @Override
     protected void setConfigurationValue(String name, String value) {
@@ -72,30 +149,29 @@
             return true;
         }
 
-        HostInfo hostInfo = new HostInfoBuilder().build();
-        storage.updateHostInfo(hostInfo);
+        store(makeHostChunk(HostInfoBuilder.build()));
 
         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);
+                store(makeCpuChunk(new CpuStatBuilder().build()));
+                for (Iterator<NetworkInterfaceInfo> iter = NetworkInfoBuilder.build().getInterfacesIterator(); iter.hasNext();) {
+                    store(makeNetworkChunk(iter.next()));
+                }
+                store(makeMemoryChunk(new MemoryStatBuilder().build()));
             }
         }, 0, procCheckInterval);
 
         try {
             hostId = new HostIdentifier((String) null);
             host = MonitoredHost.getMonitoredHost(hostId);
-            hostListener.setStorage(storage);
+            hostListener.setBackend(this);
             host.addHostListener(hostListener);
         } catch (MonitorException me) {
-            logger.log(Level.WARNING , "problems with connecting jvmstat to local machien" , me);
+            logger.log(Level.WARNING , "problems with connecting jvmstat to local machine" , me);
         } catch (URISyntaxException use) {
-            logger.log(Level.WARNING , "problems with connecting jvmstat to local machien" , use);
+            logger.log(Level.WARNING , "problems with connecting jvmstat to local machine" , use);
         }
 
         return true;
@@ -132,4 +208,54 @@
         return null;
     }
 
+    @Override
+    protected Iterator<Category> getCategoryIterator() {
+        return categories.iterator();
+    }
+
+    private Chunk makeCpuChunk(CpuStat cpuStat) {
+        Chunk chunk = new Chunk(/*cpuStat.getTimeStamp(), */cpuStatCategory, false);
+        chunk.put(Key.TIMESTAMP, Long.toString(cpuStat.getTimeStamp()));
+        chunk.put(cpu5LoadKey, Double.toString(cpuStat.getLoad5()));
+        chunk.put(cpu10LoadKey, Double.toString(cpuStat.getLoad10()));
+        chunk.put(cpu15LoadKey, Double.toString(cpuStat.getLoad15()));
+        return chunk;
+    }
+
+    private Chunk makeHostChunk(HostInfo hostInfo) {
+        Chunk chunk = new Chunk(hostInfoCategory, false);
+        chunk.put(hostNameKey, hostInfo.getHostname());
+        chunk.put(osNameKey, hostInfo.getOsName());
+        chunk.put(osKernelKey, hostInfo.getOsKernel());
+        chunk.put(cpuCountKey, Integer.toString(hostInfo.getCpuCount()));
+        chunk.put(hostMemoryTotalKey, Long.toString(hostInfo.getTotalMemory()));
+        return chunk;
+    }
+
+    private Chunk makeNetworkChunk(NetworkInterfaceInfo info) {
+        Chunk chunk = new Chunk(networkInfoCategory, true);
+        chunk.put(ifaceKey, info.getInterfaceName());
+        String ip4 = info.getIp4Addr();
+        if (ip4 != null) {
+            chunk.put(ip4AddrKey, ip4);
+        }
+        String ip6 = info.getIp6Addr();
+        if (ip6 != null) {
+            chunk.put(ip6AddrKey, ip6);
+        }
+        return chunk;
+    }
+
+    private Chunk makeMemoryChunk(MemoryStat mem) {
+        Chunk chunk = new Chunk(memoryStatCategory, false);
+        chunk.put(Key.TIMESTAMP, Long.toString(mem.getTimeStamp()));
+        chunk.put(memoryTotalKey, Long.toString(mem.getTotal()));
+        chunk.put(memoryFreeKey, Long.toString(mem.getFree()));
+        chunk.put(memoryBuffersKey, Long.toString(mem.getBuffers()));
+        chunk.put(memoryCachedKey, Long.toString(mem.getCached()));
+        chunk.put(memorySwapTotalKey, Long.toString(mem.getSwapTotal()));
+        chunk.put(memorySwapFreeKey, Long.toString(mem.getSwapFree()));
+        chunk.put(memoryCommitLimitKey, Long.toString(mem.getCommitLimit()));
+        return chunk;
+    }
 }
--- a/src/com/redhat/thermostat/common/Constants.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/common/Constants.java	Thu Dec 22 18:45:12 2011 -0500
@@ -6,8 +6,6 @@
  */
 public class Constants {
 
-    public static final String LOG_LEVEL_CONFIG = "com.redhat.thermostat.level=";
-
     public static final String AGENT_PROPERTIES_FILE = "config/agent.properties";
 
     public static final int EXIT_UNKNOWN_ERROR = 1;
--- a/src/com/redhat/thermostat/common/HostInfo.java	Fri Dec 16 17:33:54 2011 -0500
+++ b/src/com/redhat/thermostat/common/HostInfo.java	Thu Dec 22 18:45:12 2011 -0500
@@ -1,8 +1,5 @@
 package com.redhat.thermostat.common;
 
-import java.util.List;
-import java.util.Map;
-
 public class HostInfo {
 
     private final String hostname;
@@ -10,15 +7,13 @@
     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) {
+    public HostInfo(String hostname, String osName, String osKernel, int cpuCount, long totalMemory) {
         this.hostname = hostname;
         this.osName = osName;
         this.osKernel = osKernel;
         this.cpuCount = cpuCount;
         this.totalMemory = totalMemory;
-        this.networkInfo = networkInfo;
     }
 
     public String getHostname() {
@@ -43,13 +38,4 @@
     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/NetworkInfo.java	Thu Dec 22 18:45:12 2011 -0500
@@ -0,0 +1,26 @@
+package com.redhat.thermostat.common;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+public class NetworkInfo {
+
+    private Map<String, NetworkInterfaceInfo> interfaces = new HashMap<String, NetworkInterfaceInfo>();
+
+    public NetworkInfo() {
+        
+    }
+
+    public synchronized void addNetworkInterfaceInfo(NetworkInterfaceInfo info) {
+        interfaces.put(info.getInterfaceName(), info);
+    }
+
+    public synchronized void removeNetworkInterfaceInfo(NetworkInterfaceInfo info) {
+        interfaces.remove(info.getInterfaceName());
+    }
+
+    public Iterator<NetworkInterfaceInfo> getInterfacesIterator() {
+        return interfaces.values().iterator();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/common/NetworkInterfaceInfo.java	Thu Dec 22 18:45:12 2011 -0500
@@ -0,0 +1,42 @@
+package com.redhat.thermostat.common;
+
+public class NetworkInterfaceInfo {
+
+    private String iFace;
+    private String ip4Addr;
+    private String ip6Addr;
+
+    public NetworkInterfaceInfo(String iFace) {
+        this.iFace = iFace;
+        this.ip4Addr = null;
+        this.ip6Addr = null;
+    }
+
+    public String getInterfaceName() {
+        return iFace;
+    }
+
+    public String getIp4Addr() {
+        return ip4Addr;
+    }
+
+    public void setIp4Addr(String newAddr) {
+        ip4Addr = newAddr;
+    }
+
+    public void clearIp4Addr() {
+        ip4Addr = null;
+    }
+
+    public String getIp6Addr() {
+        return ip6Addr;
+    }
+
+    public void setIp6Addr(String newAddr) {
+        ip6Addr = newAddr;
+    }
+
+    public void clearIp6Addr() {
+        ip6Addr = null;
+    }
+}