changeset 1278:341b73a2b354

Extend ThreadDao API Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-October/008431.html
author Omair Majid <omajid@redhat.com>
date Wed, 16 Oct 2013 12:40:04 -0400
parents 6f704bf1d5ac
children 7b91182a8455
files thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java
diffstat 5 files changed, 343 insertions(+), 154 deletions(-) [+]
line wrap: on
line diff
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java	Fri Oct 04 19:34:29 2013 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/ThreadDao.java	Wed Oct 16 12:40:04 2013 -0400
@@ -38,15 +38,15 @@
 
 import java.util.List;
 
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.thread.model.VmDeadLockData;
+import com.redhat.thermostat.thread.model.ThreadHarvestingStatus;
 import com.redhat.thermostat.thread.model.ThreadInfoData;
-import com.redhat.thermostat.thread.model.ThreadHarvestingStatus;
 import com.redhat.thermostat.thread.model.ThreadSummary;
 import com.redhat.thermostat.thread.model.VMThreadCapabilities;
+import com.redhat.thermostat.thread.model.VmDeadLockData;
 
 public interface ThreadDao {
 
@@ -121,15 +121,24 @@
     void saveSummary(ThreadSummary summary);
     ThreadSummary loadLastestSummary(VmRef ref);
     List<ThreadSummary> loadSummary(VmRef ref, long since);
+
+    /** Save the specified thread info */
     void saveThreadInfo(ThreadInfoData info);
+    /** Get the time interval for the entire data */
+    Range<Long> getThreadInfoTimeRange(VmRef ref);
+    /** Get the thread info data with a timestamp greated than the one specified */
     List<ThreadInfoData> loadThreadInfo(VmRef ref, long since);
+    /** Get the thread info data with a timestamp in the given time range (inclusive) */
+    List<ThreadInfoData> loadThreadInfo(VmRef ref, Range<Long> time);
+
     ThreadHarvestingStatus getLatestHarvestingStatus(VmRef vm);
     void saveHarvestingStatus(ThreadHarvestingStatus status);
+
     VMThreadCapabilities loadCapabilities(VmRef ref);
     void saveCapabilities(VMThreadCapabilities caps);
+
     void saveDeadLockStatus(VmDeadLockData deadLockInfo);
     VmDeadLockData loadLatestDeadLockStatus(VmRef ref);
-    Storage getStorage();
     
 }
 
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Fri Oct 04 19:34:29 2013 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImpl.java	Wed Oct 16 12:40:04 2013 -0400
@@ -42,6 +42,7 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
@@ -86,12 +87,29 @@
             + Key.AGENT_ID.getName() + "' = ?s AND '" 
             + Key.VM_ID.getName() + "' = ?s SORT '" 
             + Key.TIMESTAMP.getName() + "' DSC LIMIT 1";
-    static final String QUERY_THREAD_INFO = "QUERY "
+    static final String QUERY_THREAD_INFO_SINCE = "QUERY "
             + THREAD_INFO.getName() + " WHERE '"
             + Key.AGENT_ID.getName() + "' = ?s AND '" 
             + Key.VM_ID.getName() + "' = ?s AND '"
             + Key.TIMESTAMP.getName() + "' > ?l SORT '"
             + Key.TIMESTAMP.getName() + "' DSC";
+    static final String QUERY_THREAD_INFO_INTERVAL = "QUERY "
+            + THREAD_INFO.getName() + " WHERE '"
+            + Key.AGENT_ID.getName() + "' = ?s AND '"
+            + Key.VM_ID.getName() + "' = ?s AND '"
+            + Key.TIMESTAMP.getName() + "' > ?l AND '"
+            + Key.TIMESTAMP.getName() + "' < ?l SORT '"
+            + Key.TIMESTAMP.getName() + "' DSC";
+    static final String QUERY_OLDEST_THREAD_INFO = "QUERY "
+            + THREAD_INFO.getName() + " WHERE '"
+            + Key.AGENT_ID.getName() + "' = ?s AND '"
+            + Key.VM_ID.getName() + "' = ?s SORT '"
+            + Key.TIMESTAMP.getName() + "' ASC LIMIT 1";
+    static final String QUERY_LATEST_THREAD_INFO = "QUERY "
+            + THREAD_INFO.getName() + " WHERE '"
+            + Key.AGENT_ID.getName() + "' = ?s AND '"
+            + Key.VM_ID.getName() + "' = ?s SORT '"
+            + Key.TIMESTAMP.getName() + "' DSC LIMIT 1";
     static final String QUERY_LATEST_DEADLOCK_INFO = "QUERY "
             + DEADLOCK_INFO.getName() + " WHERE '"
             + Key.AGENT_ID.getName() + "' = ?s AND '" 
@@ -181,21 +199,7 @@
             return null;
         }
         
-        Cursor<VMThreadCapabilities> cursor;
-        try {
-            cursor = stmt.executeQuery();
-        } catch (StatementExecutionException e) {
-            // should not happen, but if it *does* happen, at least log it
-            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
-            return null;
-        }
-        
-        VMThreadCapabilities caps = null;
-        if (cursor.hasNext()) {
-            caps = cursor.next();
-        }
-        
-        return caps;
+        return getFirstResult(stmt);
     }
     
     @Override
@@ -243,46 +247,17 @@
             return null;
         }
         
-        Cursor<ThreadSummary> cursor;
-        try {
-            cursor = stmt.executeQuery();
-        } catch (StatementExecutionException e) {
-            // should not happen, but if it *does* happen, at least log it
-            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
-            return null;
-        }
-        
-        ThreadSummary summary = null;
-        if (cursor.hasNext()) {
-            summary = cursor.next();
-        }
-        
-        return summary;
+        return getFirstResult(stmt);
     }
     
     @Override
     public List<ThreadSummary> loadSummary(VmRef ref, long since) {
-        PreparedStatement<ThreadSummary> stmt = prepareQuery(THREAD_SUMMARY, QUERY_SUMMARY_SINCE, ref, since);
+        PreparedStatement<ThreadSummary> stmt = prepareQuery(THREAD_SUMMARY, QUERY_SUMMARY_SINCE, ref, since, null);
         if (stmt == null) {
             return Collections.emptyList();
         }
 
-        Cursor<ThreadSummary> cursor;
-        try {
-            cursor = stmt.executeQuery();
-        } catch (StatementExecutionException e) {
-            // should not happen, but if it *does* happen, at least log it
-            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
-            return Collections.emptyList();
-        }
-        
-        List<ThreadSummary> result = new ArrayList<>();
-        while (cursor.hasNext()) {
-            ThreadSummary summary = cursor.next();
-            result.add(summary);
-        }
-        
-        return result;
+        return getAllResults(stmt);
     }
 
     @Override
@@ -311,20 +286,7 @@
             return null;
         }
         
-        Cursor<ThreadHarvestingStatus> cursor;
-        try {
-            cursor = stmt.executeQuery();
-        } catch (StatementExecutionException e) {
-            // should not happen, but if it *does* happen, at least log it
-            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
-            return null;
-        }
-        
-        ThreadHarvestingStatus result = null;
-        if (cursor.hasNext()) {
-            result = cursor.next();
-        }
-        return result;
+        return getFirstResult(stmt);
     }
 
     @Override
@@ -353,28 +315,38 @@
     }
 
     @Override
+    public Range<Long> getThreadInfoTimeRange(VmRef ref) {
+        PreparedStatement<ThreadInfoData> stmt;
+
+        stmt = prepareQuery(THREAD_INFO, QUERY_OLDEST_THREAD_INFO, ref);
+        ThreadInfoData oldestData = getFirstResult(stmt);
+        long oldestTimeStamp = oldestData.getTimeStamp();
+
+        stmt = prepareQuery(THREAD_INFO, QUERY_LATEST_THREAD_INFO, ref);
+        ThreadInfoData latestData = getFirstResult(stmt);
+        long latestTimeStamp = latestData.getTimeStamp();
+
+        return new Range<Long>(oldestTimeStamp, latestTimeStamp);
+    }
+
+    @Override
     public List<ThreadInfoData> loadThreadInfo(VmRef ref, long since) {
-        PreparedStatement<ThreadInfoData> stmt = prepareQuery(THREAD_INFO, QUERY_THREAD_INFO, ref, since);
+        PreparedStatement<ThreadInfoData> stmt = prepareQuery(THREAD_INFO, QUERY_THREAD_INFO_SINCE, ref, since, null);
         if (stmt == null) {
             return Collections.emptyList();
         }
-        
-        Cursor<ThreadInfoData> cursor;
-        try {
-            cursor = stmt.executeQuery();
-        } catch (StatementExecutionException e) {
-            // should not happen, but if it *does* happen, at least log it
-            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
+
+        return getAllResults(stmt);
+    }
+
+    @Override
+    public List<ThreadInfoData> loadThreadInfo(VmRef ref, Range<Long> time) {
+        PreparedStatement<ThreadInfoData> stmt = prepareQuery(THREAD_INFO, QUERY_THREAD_INFO_INTERVAL, ref, time.getMin(), time.getMax());
+        if (stmt == null) {
             return Collections.emptyList();
         }
-        
-        List<ThreadInfoData> result = new ArrayList<>();
-        while (cursor.hasNext()) {
-            ThreadInfoData info = cursor.next();
-            result.add(info);
-        }
-        
-        return result;
+
+        return getAllResults(stmt);
     }
 
     @Override
@@ -384,21 +356,7 @@
             return null;
         }
         
-        Cursor<VmDeadLockData> cursor;
-        try {
-            cursor = stmt.executeQuery();
-        } catch (StatementExecutionException e) {
-            // should not happen, but if it *does* happen, at least log it
-            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
-            return null;
-        }
-        
-        VmDeadLockData result = null;
-        if (cursor.hasNext()) {
-            result = cursor.next();
-        }
-
-        return result;
+        return getFirstResult(stmt);
     }
 
     @Override
@@ -420,29 +378,66 @@
     }
     
     private <T extends Pojo> PreparedStatement<T> prepareQuery(Category<T> category, String query, VmRef ref) {
-        return prepareQuery(category, query, ref, null);
+        return prepareQuery(category, query, ref, null, null);
     }
     
-    private <T extends Pojo> PreparedStatement<T> prepareQuery(Category<T> category, String query, VmRef ref, Long since) {
+    private <T extends Pojo> PreparedStatement<T> prepareQuery(Category<T> category, String query, VmRef ref, Long since, Long to) {
         StatementDescriptor<T> desc = new StatementDescriptor<>(category, query);
         PreparedStatement<T> stmt = null;
         try {
             stmt = storage.prepareStatement(desc);
             stmt.setString(0, ref.getHostRef().getAgentId());
             stmt.setString(1, ref.getVmId());
+            // assume: the format of the query is such that 2nd and 3rd arguments (if any) are longs
             if (since != null) {
                 stmt.setLong(2, since);
             }
+            if (to != null) {
+                stmt.setLong(3, to);
+            }
         } catch (DescriptorParsingException e) {
             // should not happen, but if it *does* happen, at least log it
             logger.log(Level.SEVERE, "Preparing query '" + desc + "' failed!", e);
         }
         return stmt;
     }
-    
-    @Override
-    public Storage getStorage() {
-        return storage;
+
+    private <T extends Pojo> List<T> getAllResults(PreparedStatement<T> stmt) {
+        Cursor<T> cursor;
+        try {
+            cursor = stmt.executeQuery();
+        } catch (StatementExecutionException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
+            return Collections.emptyList();
+        }
+
+        List<T> result = new ArrayList<>();
+        while (cursor.hasNext()) {
+            T info = cursor.next();
+            result.add(info);
+        }
+
+        return result;
     }
+
+    private <T extends Pojo> T getFirstResult(PreparedStatement<T> stmt) {
+        Cursor<T> cursor;
+        try {
+            cursor = stmt.executeQuery();
+        } catch (StatementExecutionException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.log(Level.SEVERE, "Executing query '" + stmt + "' failed!", e);
+            return null;
+        }
+
+        T result = null;
+        if (cursor.hasNext()) {
+            result = cursor.next();
+        }
+
+        return result;
+    }
+
 }
 
--- a/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java	Fri Oct 04 19:34:29 2013 +0200
+++ b/thread/collector/src/main/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplStatementDescriptorRegistration.java	Wed Oct 16 12:40:04 2013 -0400
@@ -61,7 +61,10 @@
         descs.add(ThreadDaoImpl.QUERY_LATEST_SUMMARY);
         descs.add(ThreadDaoImpl.QUERY_SUMMARY_SINCE);
         descs.add(ThreadDaoImpl.QUERY_THREAD_CAPS);
-        descs.add(ThreadDaoImpl.QUERY_THREAD_INFO);
+        descs.add(ThreadDaoImpl.QUERY_THREAD_INFO_SINCE);
+        descs.add(ThreadDaoImpl.QUERY_THREAD_INFO_INTERVAL);
+        descs.add(ThreadDaoImpl.QUERY_OLDEST_THREAD_INFO);
+        descs.add(ThreadDaoImpl.QUERY_LATEST_THREAD_INFO);
         descs.add(ThreadDaoImpl.DESC_ADD_THREAD_DEADLOCK_DATA);
         descs.add(ThreadDaoImpl.DESC_ADD_THREAD_HARVESTING_STATUS);
         descs.add(ThreadDaoImpl.DESC_ADD_THREAD_INFO);
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java	Fri Oct 04 19:34:29 2013 +0200
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDAOImplStatementDescriptorRegistrationTest.java	Wed Oct 16 12:40:04 2013 -0400
@@ -76,7 +76,7 @@
     public void registersAllDescriptors() {
         ThreadDaoImplStatementDescriptorRegistration reg = new ThreadDaoImplStatementDescriptorRegistration();
         Set<String> descriptors = reg.getStatementDescriptors();
-        assertEquals(11, descriptors.size());
+        assertEquals(14, descriptors.size());
         assertFalse("null descriptor not allowed", descriptors.contains(null));
     }
     
@@ -104,7 +104,7 @@
         // storage-core + this module
         assertEquals(2, registrations.size());
         assertNotNull(threadDaoReg);
-        assertEquals(11, threadDaoReg.getStatementDescriptors().size());
+        assertEquals(14, threadDaoReg.getStatementDescriptors().size());
     }
     
     private Triple<String, String, PreparedParameter[]> setupForMetaDataTest() {
@@ -165,11 +165,38 @@
     }
     
     @Test
-    public void canGetMetadataThreadInfoQuery() {
+    public void canGetMetadataOldestThreadInfo() {
         Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest(); 
-        
+
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_OLDEST_THREAD_INFO, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+
+    @Test
+    public void canGetMetadataLatestThreadInfo() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest();
+
         StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
-        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_THREAD_INFO, triple.third);
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_LATEST_THREAD_INFO, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+
+    @Test
+    public void canGetMetadataThreadInfoQuerySince() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest();
+
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_THREAD_INFO_SINCE, triple.third);
+        assertThreadMetadata(triple, data);
+    }
+
+    @Test
+    public void canGetMetadataThreadInfoQueryInterval() {
+        Triple<String, String, PreparedParameter[]> triple = setupForMetaDataTest();
+
+        StatementDescriptorMetadataFactory factory = new ThreadDaoImplStatementDescriptorRegistration();
+        DescriptorMetadata data = factory.getDescriptorMetadata(ThreadDaoImpl.QUERY_THREAD_INFO_INTERVAL, triple.third);
         assertThreadMetadata(triple, data);
     }
     
--- a/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Fri Oct 04 19:34:29 2013 +0200
+++ b/thread/collector/src/test/java/com/redhat/thermostat/thread/dao/impl/ThreadDaoImplTest.java	Wed Oct 16 12:40:04 2013 -0400
@@ -40,18 +40,23 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import java.util.List;
 import java.util.NoSuchElementException;
 
+import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.HostRef;
@@ -64,11 +69,28 @@
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.thread.dao.ThreadDao;
 import com.redhat.thermostat.thread.model.ThreadHarvestingStatus;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
 import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 import com.redhat.thermostat.thread.model.VmDeadLockData;
 
 public class ThreadDaoImplTest {
 
+    private static final String AGENT_ID = "0xcafe";
+    private static final String VM_ID = "VM42";
+
+    private VmRef vmRef;
+    private HostRef hostRef;
+
+    @Before
+    public void setUp() {
+        hostRef = mock(HostRef.class);
+        when(hostRef.getAgentId()).thenReturn(AGENT_ID);
+
+        vmRef = mock(VmRef.class);
+        when(vmRef.getHostRef()).thenReturn(hostRef);
+        when(vmRef.getVmId()).thenReturn(VM_ID);
+    }
+
     @Test
     public void preparedQueryDescriptorsAreSane() {
         String expectedQueryThreadCaps = "QUERY vm-thread-capabilities WHERE 'agentId' = ?s AND 'vmId' = ?s LIMIT 1";
@@ -79,8 +101,14 @@
         assertEquals(expectedQuerySummarySince, ThreadDaoImpl.QUERY_SUMMARY_SINCE);
         String expectedQueryLatestHarvestingStatus = "QUERY vm-thread-harvesting WHERE 'agentId' = ?s AND 'vmId' = ?s SORT 'timeStamp' DSC LIMIT 1";
         assertEquals(expectedQueryLatestHarvestingStatus, ThreadDaoImpl.QUERY_LATEST_HARVESTING_STATUS);
-        String expectedQueryThreadInfo = "QUERY vm-thread-info WHERE 'agentId' = ?s AND 'vmId' = ?s AND 'timeStamp' > ?l SORT 'timeStamp' DSC";
-        assertEquals(expectedQueryThreadInfo, ThreadDaoImpl.QUERY_THREAD_INFO);
+        String expectedQueryThreadInfoSince = "QUERY vm-thread-info WHERE 'agentId' = ?s AND 'vmId' = ?s AND 'timeStamp' > ?l SORT 'timeStamp' DSC";
+        assertEquals(expectedQueryThreadInfoSince, ThreadDaoImpl.QUERY_THREAD_INFO_SINCE);
+        String expectedQueryThreadInfoInterval = "QUERY vm-thread-info WHERE 'agentId' = ?s AND 'vmId' = ?s AND 'timeStamp' > ?l AND 'timeStamp' < ?l SORT 'timeStamp' DSC";
+        assertEquals(expectedQueryThreadInfoInterval, ThreadDaoImpl.QUERY_THREAD_INFO_INTERVAL);
+        String expectedQueryLatestThreadInfo = "QUERY vm-thread-info WHERE 'agentId' = ?s AND 'vmId' = ?s SORT 'timeStamp' DSC LIMIT 1";
+        assertEquals(expectedQueryLatestThreadInfo, ThreadDaoImpl.QUERY_LATEST_THREAD_INFO);
+        String expectedQueryOldestThreadInfo = "QUERY vm-thread-info WHERE 'agentId' = ?s AND 'vmId' = ?s SORT 'timeStamp' ASC LIMIT 1";
+        assertEquals(expectedQueryOldestThreadInfo, ThreadDaoImpl.QUERY_OLDEST_THREAD_INFO);
         String expectedQueryThreadLatestDeadlockInfo = "QUERY vm-deadlock-data WHERE 'agentId' = ?s AND 'vmId' = ?s SORT 'timeStamp' DSC LIMIT 1";
         assertEquals(expectedQueryThreadLatestDeadlockInfo, ThreadDaoImpl.QUERY_LATEST_DEADLOCK_INFO);
         String addThreadSummary = "ADD vm-thread-summary SET 'agentId' = ?s , " +
@@ -137,16 +165,8 @@
         PreparedStatement<VMThreadCapabilities> stmt = (PreparedStatement<VMThreadCapabilities>) mock(PreparedStatement.class);
         Storage storage = mock(Storage.class);
         when(storage.prepareStatement(anyDescriptor(VMThreadCapabilities.class))).thenReturn(stmt);
-        VmRef ref = mock(VmRef.class);
-        when(ref.getVmId()).thenReturn("VM42");
-        
-        String agentId = "0xcafe";
-        HostRef agent = mock(HostRef.class);
-        when(agent.getAgentId()).thenReturn(agentId);
-        
-        when(ref.getHostRef()).thenReturn(agent);
 
-        VMThreadCapabilities expected = new VMThreadCapabilities(agentId);
+        VMThreadCapabilities expected = new VMThreadCapabilities(AGENT_ID);
         expected.setSupportedFeaturesList(new String[] { ThreadDao.CPU_TIME, ThreadDao.THREAD_ALLOCATED_MEMORY });
         @SuppressWarnings("unchecked")
         Cursor<VMThreadCapabilities> cursor = (Cursor<VMThreadCapabilities>) mock(Cursor.class);
@@ -155,11 +175,11 @@
         when(stmt.executeQuery()).thenReturn(cursor);
         
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
-        VMThreadCapabilities caps = dao.loadCapabilities(ref);
+        VMThreadCapabilities caps = dao.loadCapabilities(vmRef);
 
         verify(storage).prepareStatement(anyDescriptor(VMThreadCapabilities.class));
-        verify(stmt).setString(0, "0xcafe");
-        verify(stmt).setString(1, "VM42");
+        verify(stmt).setString(0, AGENT_ID);
+        verify(stmt).setString(1, VM_ID);
         verify(stmt).executeQuery();
         verifyNoMoreInteractions(stmt);
 
@@ -179,16 +199,8 @@
         PreparedStatement<VMThreadCapabilities> stmt = (PreparedStatement<VMThreadCapabilities>) mock(PreparedStatement.class);
         Storage storage = mock(Storage.class);
         when(storage.prepareStatement(anyDescriptor(VMThreadCapabilities.class))).thenReturn(stmt);
-        VmRef ref = mock(VmRef.class);
-        when(ref.getVmId()).thenReturn("VM42");
 
-        String agentId = "0xcafe";
-        HostRef agent = mock(HostRef.class);
-        when(agent.getAgentId()).thenReturn(agentId);
-
-        when(ref.getHostRef()).thenReturn(agent);
-
-        VMThreadCapabilities expected = new VMThreadCapabilities(agentId);
+        VMThreadCapabilities expected = new VMThreadCapabilities(AGENT_ID);
         expected.setSupportedFeaturesList(new String[] { ThreadDao.CPU_TIME, ThreadDao.THREAD_ALLOCATED_MEMORY });
         @SuppressWarnings("unchecked")
         Cursor<VMThreadCapabilities> cursor = (Cursor<VMThreadCapabilities>) mock(Cursor.class);
@@ -197,11 +209,11 @@
         when(stmt.executeQuery()).thenReturn(cursor);
 
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
-        VMThreadCapabilities caps = dao.loadCapabilities(ref);
+        VMThreadCapabilities caps = dao.loadCapabilities(vmRef);
 
         verify(storage).prepareStatement(anyDescriptor(VMThreadCapabilities.class));
-        verify(stmt).setString(0, "0xcafe");
-        verify(stmt).setString(1, "VM42");
+        verify(stmt).setString(0, AGENT_ID);
+        verify(stmt).setString(1, VM_ID);
         verify(stmt).executeQuery();
         verifyNoMoreInteractions(stmt);
 
@@ -211,13 +223,11 @@
     @SuppressWarnings("unchecked")
     @Test
     public void testSaveVMCapabilities() throws DescriptorParsingException, StatementExecutionException {
-        String agentId = "fooAgent";
         Storage storage = mock(Storage.class);
         PreparedStatement<AgentInformation> replace = mock(PreparedStatement.class);
         when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(replace);
-        
-        String vmId = "VM42";
-        VMThreadCapabilities caps = new VMThreadCapabilities(agentId);
+
+        VMThreadCapabilities caps = new VMThreadCapabilities(AGENT_ID);
         String[] capsFeatures = new String[] {
                 ThreadDao.CONTENTION_MONITOR,
                 ThreadDao.CPU_TIME,
@@ -227,7 +237,7 @@
         assertTrue(caps.supportContentionMonitor());
         assertTrue(caps.supportCPUTime());
         assertTrue(caps.supportThreadAllocatedMemory());
-        caps.setVmId(vmId);
+        caps.setVmId(VM_ID);
         
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
         dao.saveCapabilities(caps);
@@ -251,10 +261,10 @@
     @Test
     public void testLoadLatestDeadLockStatus() throws DescriptorParsingException, StatementExecutionException {
         VmRef vm = mock(VmRef.class);
-        when(vm.getVmId()).thenReturn("VM42");
+        when(vm.getVmId()).thenReturn(VM_ID);
 
         HostRef agent = mock(HostRef.class);
-        when(agent.getAgentId()).thenReturn("0xcafe");
+        when(agent.getAgentId()).thenReturn(AGENT_ID);
         when(vm.getHostRef()).thenReturn(agent);
 
         Storage storage = mock(Storage.class);
@@ -275,8 +285,8 @@
         assertSame(data, result);
 
         verify(storage).prepareStatement(anyDescriptor(VmDeadLockData.class));
-        verify(stmt).setString(0, "0xcafe");
-        verify(stmt).setString(1, "VM42");
+        verify(stmt).setString(0, AGENT_ID);
+        verify(stmt).setString(1, VM_ID);
         verify(stmt).executeQuery();
         verifyNoMoreInteractions(stmt);
     }
@@ -311,13 +321,6 @@
     @Test
     public void testGetLatestHarvestingStatus()
             throws DescriptorParsingException, StatementExecutionException {
-        VmRef vm = mock(VmRef.class);
-        when(vm.getVmId()).thenReturn("VM42");
-
-        HostRef agent = mock(HostRef.class);
-        when(agent.getAgentId()).thenReturn("0xcafe");
-        when(vm.getHostRef()).thenReturn(agent);
-
         Storage storage = mock(Storage.class);
         @SuppressWarnings("unchecked")
         PreparedStatement<ThreadHarvestingStatus> stmt = (PreparedStatement<ThreadHarvestingStatus>) mock(PreparedStatement.class);
@@ -331,11 +334,11 @@
         when(stmt.executeQuery()).thenReturn(cursor);
 
         ThreadDaoImpl dao = new ThreadDaoImpl(storage);
-        ThreadHarvestingStatus result = dao.getLatestHarvestingStatus(vm);
+        ThreadHarvestingStatus result = dao.getLatestHarvestingStatus(vmRef);
 
         verify(storage).prepareStatement(anyDescriptor(ThreadHarvestingStatus.class));
-        verify(stmt).setString(0, "0xcafe");
-        verify(stmt).setString(1, "VM42");
+        verify(stmt).setString(0, AGENT_ID);
+        verify(stmt).setString(1, VM_ID);
         verify(stmt).executeQuery();
         verifyNoMoreInteractions(stmt);
 
@@ -368,5 +371,157 @@
         verify(add).execute();
         verifyNoMoreInteractions(add);
     }
+
+    @Test
+    public void testSaveThreadInfo() throws DescriptorParsingException, StatementExecutionException {
+        final String THREAD_NAME = "name of a thread";
+        final long THREAD_ID = 0xcafebabe;
+        final String THREAD_STATE = Thread.State.RUNNABLE.toString();
+        final long ALLOCATED_BYTES = 0xbadbeef;
+        final long TIMESTAMP = 0xdeadbeef;
+        final long CPU_TIME = 42;
+        final long USER_TIME = 24;
+        final long BLOCKED_COUNT = 22;
+        final long WAIT_COUNT = 33;
+
+        Storage storage = mock(Storage.class);
+        PreparedStatement<ThreadInfoData> add = mock(PreparedStatement.class);
+        when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(add);
+
+        // not using mocks because ThreadInfoData is really a data holder (no logic)
+        ThreadInfoData threadInfo = new ThreadInfoData();
+        threadInfo.setAgentId(AGENT_ID);
+        threadInfo.setVmId(VM_ID);
+        threadInfo.setThreadName(THREAD_NAME);
+        threadInfo.setThreadId(THREAD_ID);
+        threadInfo.setThreadState(THREAD_STATE);
+        threadInfo.setAllocatedBytes(ALLOCATED_BYTES);
+        threadInfo.setTimeStamp(TIMESTAMP);
+        threadInfo.setThreadCpuTime(CPU_TIME);
+        threadInfo.setThreadUserTime(USER_TIME);
+        threadInfo.setThreadBlockedCount(BLOCKED_COUNT);
+        threadInfo.setThreadWaitCount(WAIT_COUNT);
+
+        ThreadDaoImpl dao = new ThreadDaoImpl(storage);
+        dao.saveThreadInfo(threadInfo);
+
+        verify(add).setString(0, AGENT_ID);
+        verify(add).setString(1, VM_ID);
+        verify(add).setString(2, THREAD_NAME);
+        verify(add).setLong(3, THREAD_ID);
+        verify(add).setString(4, THREAD_STATE);
+        verify(add).setLong(5, ALLOCATED_BYTES);
+        verify(add).setLong(6, TIMESTAMP);
+        verify(add).setLong(7, CPU_TIME);
+        verify(add).setLong(8, USER_TIME);
+        verify(add).setLong(9, BLOCKED_COUNT);
+        verify(add).setLong(10, WAIT_COUNT);
+        verify(add).execute();
+        verifyNoMoreInteractions(add);
+    }
+
+    @Test
+    public void testThreadInfoTimeRange() throws DescriptorParsingException, StatementExecutionException {
+        final long OLDEST_TIMESTAMP = 0;
+        final long LATEST_TIMESTAMP = 1999;
+
+        ThreadInfoData oldestData = new ThreadInfoData();
+        oldestData.setTimeStamp(OLDEST_TIMESTAMP);
+
+        ThreadInfoData latestData = new ThreadInfoData();
+        latestData.setTimeStamp(LATEST_TIMESTAMP);
+
+        StatementDescriptor<ThreadInfoData> oldestThreadQueryDescriptor
+                = new StatementDescriptor<>(ThreadDaoImpl.THREAD_INFO, ThreadDaoImpl.QUERY_OLDEST_THREAD_INFO);
+
+        StatementDescriptor<ThreadInfoData> latestThreadQueryDescriptor
+                = new StatementDescriptor<>(ThreadDaoImpl.THREAD_INFO, ThreadDaoImpl.QUERY_LATEST_THREAD_INFO);
+
+        Cursor<ThreadInfoData> oldestThreadQueryCursor = mock(Cursor.class);
+        when(oldestThreadQueryCursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(oldestThreadQueryCursor.next()).thenReturn(oldestData).thenThrow(IllegalStateException.class);
+
+        Cursor<ThreadInfoData> latestThreadQueryCursor = mock(Cursor.class);
+        when(latestThreadQueryCursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(latestThreadQueryCursor.next()).thenReturn(latestData).thenThrow(IllegalStateException.class);
+
+        PreparedStatement<ThreadInfoData> oldestThreadQueryStatement = mock(PreparedStatement.class);
+        when(oldestThreadQueryStatement.executeQuery()).thenReturn(oldestThreadQueryCursor);
+
+        PreparedStatement<ThreadInfoData> latestThreadQueryStatement = mock(PreparedStatement.class);
+        when(latestThreadQueryStatement.executeQuery()).thenReturn(latestThreadQueryCursor);
+
+        Storage storage = mock(Storage.class);
+        when(storage.prepareStatement(any(StatementDescriptor.class)))
+            .thenReturn(oldestThreadQueryStatement)
+            .thenReturn(latestThreadQueryStatement);
+
+        ThreadDaoImpl dao = new ThreadDaoImpl(storage);
+
+        Range<Long> result = dao.getThreadInfoTimeRange(vmRef);
+
+        assertEquals((Long) OLDEST_TIMESTAMP, result.getMin());
+        assertEquals((Long) LATEST_TIMESTAMP, result.getMax());
+    }
+
+    @Test
+    public void testLoadThreadInfoSince() throws DescriptorParsingException, StatementExecutionException {
+        final long SINCE_TIMESTAMP = 100;
+
+        Storage storage = mock(Storage.class);
+        PreparedStatement<ThreadInfoData> query = mock(PreparedStatement.class);
+        when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(query);
+
+        @SuppressWarnings("unchecked")
+        Cursor<ThreadInfoData> cursor = (Cursor<ThreadInfoData>) mock(Cursor.class);
+        ThreadInfoData info = mock(ThreadInfoData.class);
+
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(info).thenReturn(null);
+        when(query.executeQuery()).thenReturn(cursor);
+
+        ThreadDaoImpl dao = new ThreadDaoImpl(storage);
+        List<ThreadInfoData> result = dao.loadThreadInfo(vmRef, SINCE_TIMESTAMP);
+
+        verify(query).setString(0, AGENT_ID);
+        verify(query).setString(1, VM_ID);
+        verify(query).setLong(2, SINCE_TIMESTAMP);
+        verify(query).executeQuery();
+        verifyNoMoreInteractions(query);
+
+        assertEquals(1, result.size());
+        assertEquals(info, result.get(0));
+    }
+
+    @Test
+    public void testLoadThreadInfoForRange() throws StatementExecutionException, DescriptorParsingException {
+        final long SINCE_TIMESTAMP = 100;
+        final long TO_TIMESTAMP = 10000;
+
+        Storage storage = mock(Storage.class);
+        PreparedStatement<ThreadInfoData> query = mock(PreparedStatement.class);
+        when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(query);
+
+        @SuppressWarnings("unchecked")
+        Cursor<ThreadInfoData> cursor = (Cursor<ThreadInfoData>) mock(Cursor.class);
+        ThreadInfoData info = mock(ThreadInfoData.class);
+
+        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
+        when(cursor.next()).thenReturn(info).thenReturn(null);
+        when(query.executeQuery()).thenReturn(cursor);
+
+        ThreadDaoImpl dao = new ThreadDaoImpl(storage);
+        List<ThreadInfoData> result = dao.loadThreadInfo(vmRef, new Range<Long>(SINCE_TIMESTAMP, TO_TIMESTAMP));
+
+        verify(query).setString(0, AGENT_ID);
+        verify(query).setString(1, VM_ID);
+        verify(query).setLong(2, SINCE_TIMESTAMP);
+        verify(query).setLong(3, TO_TIMESTAMP);
+        verify(query).executeQuery();
+        verifyNoMoreInteractions(query);
+
+        assertEquals(1, result.size());
+        assertEquals(info, result.get(0));
+    }
 }