changeset 1948:131195abd5fc

Add additional class statistics. Backport includes two patches from HEAD in one commit. http://icedtea.classpath.org/hg/thermostat/rev/bf3bb0a9f9fa : Add additional class statistics http://icedtea.classpath.org/hg/thermostat/rev/746fd0b71d73 : Fix test PR3034 Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019863.html
author Jie Kang <jkang@redhat.com>
date Mon, 27 Jun 2016 12:24:03 -0400
parents 2c8adb63d39a
children 311aeb2c95aa
files integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTest.java vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatDataExtractor.java vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListener.java vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatView.java vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatController.java vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/locale/LocaleResources.java vm-classstat/client-core/src/main/resources/com/redhat/thermostat/vm/classstat/client/locale/strings.properties vm-classstat/client-core/src/test/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatControllerTest.java vm-classstat/client-swing/src/main/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanel.java vm-classstat/client-swing/src/test/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanelTest.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/model/VmClassStat.java vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOTest.java
diffstat 15 files changed, 388 insertions(+), 217 deletions(-) [+]
line wrap: on
line diff
--- a/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Mon Jun 27 12:24:03 2016 -0400
@@ -668,8 +668,12 @@
         // manually for this test.
         String strDesc = "ADD vm-class-stats SET 'agentId' = ?s , " +
                                 "'vmId' = ?s , " +
-                                "'timeStamp' = ?l , " + 
-                                "'loadedClasses' = ?l";
+                                "'timeStamp' = ?l , " +
+                                "'loadedClasses' = ?l , " +
+                                "'loadedBytes' = ?l , " +
+                                "'unloadedClasses' = ?l , " +
+                                "'unloadedBytes' = ?l , " +
+                                "'classLoadTime' = ?l";
         StatementDescriptor<VmClassStat> desc = new StatementDescriptor<>(VmClassStatDAO.vmClassStatsCategory, strDesc);
         VmClassStat pojo = new VmClassStat();
         pojo.setAgentId("fluff");
@@ -705,6 +709,10 @@
         add.setString(1, pojo.getVmId());
         add.setLong(2, pojo.getTimeStamp());
         add.setLong(3, pojo.getLoadedClasses());
+        add.setLong(4, pojo.getLoadedBytes());
+        add.setLong(5, pojo.getUnloadedClasses());
+        add.setLong(6, pojo.getUnloadedBytes());
+        add.setLong(7, pojo.getClassLoadTime());
         add.execute();
     }
     
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatDataExtractor.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatDataExtractor.java	Mon Jun 27 12:24:03 2016 -0400
@@ -59,6 +59,8 @@
      * http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#intern()
      */
 
+    // For the definitions of the values, see VmClassStat.
+
     private final VmUpdate update;
 
     public VmClassStatDataExtractor(VmUpdate update) {
@@ -66,8 +68,28 @@
     }
 
     public Long getLoadedClasses() throws VmUpdateException {
-        return update.getPerformanceCounterLong("java.cls.loadedClasses");
+        return update.getPerformanceCounterLong("java.cls.loadedClasses")
+                + update.getPerformanceCounterLong("java.cls.sharedLoadedClasses");
+    }
+
+    public Long getLoadedBytes() throws VmUpdateException {
+        return update.getPerformanceCounterLong("sun.cls.loadedBytes")
+                + update.getPerformanceCounterLong("sun.cls.sharedLoadedBytes");
+    }
+
+    public Long getUnloadedClasses() throws VmUpdateException {
+        return update.getPerformanceCounterLong("java.cls.unloadedClasses")
+                + update.getPerformanceCounterLong("java.cls.sharedUnloadedClasses");
+    }
+
+    public Long getUnloadedBytes() throws VmUpdateException {
+        return update.getPerformanceCounterLong("sun.cls.unloadedBytes")
+                + update.getPerformanceCounterLong("sun.cls.sharedUnloadedBytes");
+    }
+
+    public Long getClassLoadTime() throws VmUpdateException {
+        return update.getPerformanceCounterLong("sun.cls.time")
+                / update.getPerformanceCounterLong("sun.os.hrt.frequency");
     }
 
 }
-
--- a/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListener.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/agent/src/main/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListener.java	Mon Jun 27 12:24:03 2016 -0400
@@ -66,21 +66,32 @@
     public void countersUpdated(VmUpdate update) {
         VmClassStatDataExtractor extractor = new VmClassStatDataExtractor(update);
         try {
-            Long loadedClasses = extractor.getLoadedClasses();
-            if (loadedClasses != null) {
-                long timestamp = System.currentTimeMillis();
-                VmClassStat stat = new VmClassStat(writerId, vmId, timestamp, loadedClasses);
-                dao.putVmClassStat(stat);
-            }
-            else {
-                logWarningOnce("Unable to determine number of loaded classes for VM " 
-                        + vmId);
-            }
+            long loadedClasses = valueOrUnknown(extractor.getLoadedClasses(), "number of loaded classes", vmId);
+            long loadedBytes = valueOrUnknown(extractor.getLoadedBytes(), "number of loaded bytes", vmId);
+            long unloadedClasses = valueOrUnknown(extractor.getUnloadedClasses(), "number of unloaded", vmId);
+            long unloadedBytes = valueOrUnknown(extractor.getUnloadedBytes(), "number of unloaded bytes", vmId);
+            long classLoadTime = valueOrUnknown(extractor.getClassLoadTime(), "class load time", vmId);
+
+            long timestamp = System.currentTimeMillis();
+            VmClassStat stat = new VmClassStat(writerId, vmId, timestamp,
+                    loadedClasses, loadedBytes,
+                    unloadedClasses, unloadedBytes,
+                    classLoadTime);
+
+            dao.putVmClassStat(stat);
+
         } catch (VmUpdateException e) {
             logger.log(Level.WARNING, "Error gathering class info for VM " + vmId, e);
         }
     }
-    
+
+    private long valueOrUnknown(Long value, String valudDescription, String vmId) {
+        if (value == null) {
+            logWarningOnce("Unable to determine " + valudDescription + " for VM " + vmId);
+        }
+        return value == null ? VmClassStat.UNKNOWN : value;
+    }
+
     private void logWarningOnce(String message) {
         if (!error) {
             logger.log(Level.WARNING, message);
--- a/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/agent/src/test/java/com/redhat/thermostat/vm/classstat/agent/internal/VmClassStatVmListenerTest.java	Mon Jun 27 12:24:03 2016 -0400
@@ -58,22 +58,51 @@
 public class VmClassStatVmListenerTest {
 
     private static final String VM_ID = "vmId";
-    private static final Long LOADED_CLASSES = 1234L;
+    private static final Long LOADED_CLASSES_NON_SHARED = 1234L;
+    private static final Long LOADED_CLASSES_SHARED = 1234L;
+    private static final Long LOADED_CLASSES = LOADED_CLASSES_NON_SHARED + LOADED_CLASSES_SHARED;
+    private static final Long LOADED_BYTES_NON_SHARED = 1234L;
+    private static final Long LOADED_BYTES_SHARED = 1234L;
+    private static final Long LOADED_BYTES = LOADED_BYTES_NON_SHARED + LOADED_BYTES_SHARED;
+    private static final Long UNLOADED_CLASSES_NON_SHARED = 1234L;
+    private static final Long UNLOADED_CLASSES_SHARED = 1234L;
+    private static final Long UNLOADED_CLASSES = UNLOADED_CLASSES_NON_SHARED + UNLOADED_CLASSES_SHARED;
+    private static final Long UNLOADED_BYTES_NON_SHARED = 1234L;
+    private static final Long UNLOADED_BYTES_SHARED = 1234L;
+    private static final Long UNLOADED_BYTES = UNLOADED_BYTES_NON_SHARED + UNLOADED_BYTES_SHARED;
+    private static final Long CLASS_TIME_TICKS = 4242L;
+    private static final Long FREQUENCY = 2L;
+    private static final Long CLASS_TIME = CLASS_TIME_TICKS / FREQUENCY;
 
     private VmClassStatDAO dao;
     private VmClassStatVmListener listener;
 
+    private VmUpdate update;
+
     @Before
-    public void setUp() {
+    public void setUp() throws VmUpdateException {
         dao = mock(VmClassStatDAO.class);
         listener = new VmClassStatVmListener("foo-agent", dao, VM_ID);
+
+        update = mock(VmUpdate.class);
+        when(update.getPerformanceCounterLong("java.cls.loadedClasses")).thenReturn(LOADED_CLASSES_NON_SHARED);
+        when(update.getPerformanceCounterLong("java.cls.sharedLoadedClasses")).thenReturn(LOADED_CLASSES_SHARED);
+
+        when(update.getPerformanceCounterLong("sun.cls.loadedBytes")).thenReturn(LOADED_BYTES_NON_SHARED);
+        when(update.getPerformanceCounterLong("sun.cls.sharedLoadedBytes")).thenReturn(LOADED_BYTES_SHARED);
+
+        when(update.getPerformanceCounterLong("java.cls.unloadedClasses")).thenReturn(UNLOADED_CLASSES_NON_SHARED);
+        when(update.getPerformanceCounterLong("java.cls.sharedUnloadedClasses")).thenReturn(UNLOADED_CLASSES_SHARED);
+
+        when(update.getPerformanceCounterLong("sun.cls.unloadedBytes")).thenReturn(UNLOADED_BYTES_NON_SHARED);
+        when(update.getPerformanceCounterLong("sun.cls.sharedUnloadedBytes")).thenReturn(UNLOADED_BYTES_SHARED);
+
+        when(update.getPerformanceCounterLong("sun.cls.time")).thenReturn(CLASS_TIME_TICKS);
+        when(update.getPerformanceCounterLong("sun.os.hrt.frequency")).thenReturn(FREQUENCY);
     }
 
     @Test
     public void testMonitorUpdatedClassStat() throws Exception {
-        VmUpdate update = mock(VmUpdate.class);
-        when(update.getPerformanceCounterLong(eq("java.cls.loadedClasses"))).thenReturn(LOADED_CLASSES);
-
         listener.countersUpdated(update);
 
         ArgumentCaptor<VmClassStat> arg = ArgumentCaptor.forClass(VmClassStat.class);
@@ -85,9 +114,6 @@
 
     @Test
     public void testMonitorUpdatedClassStatTwice() throws Exception {
-        VmUpdate update = mock(VmUpdate.class);
-        when(update.getPerformanceCounterLong(eq("java.cls.loadedClasses"))).thenReturn(LOADED_CLASSES);
-
         listener.countersUpdated(update);
         listener.countersUpdated(update);
 
@@ -97,21 +123,11 @@
 
     @Test
     public void testMonitorUpdateFails() throws VmUpdateException {
-        VmUpdate update = mock(VmUpdate.class);
         when(update.getPerformanceCounterLong(anyString())).thenThrow(new VmUpdateException());
         listener.countersUpdated(update);
 
         verifyNoMoreInteractions(dao);
     }
-    
-    @Test
-    public void testMonitorUpdatedClassStatNoCounter() throws Exception {
-        VmUpdate update = mock(VmUpdate.class);
-        when(update.getPerformanceCounterLong(eq("java.cls.loadedClasses"))).thenReturn(null);
 
-        listener.countersUpdated(update);
-
-        verify(dao, never()).putVmClassStat(any(VmClassStat.class));
-    }
 }
 
--- a/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatView.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/VmClassStatView.java	Mon Jun 27 12:24:03 2016 -0400
@@ -37,34 +37,37 @@
 package com.redhat.thermostat.vm.classstat.client.core;
 
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.core.views.BasicView;
 import com.redhat.thermostat.client.core.views.UIComponent;
-import com.redhat.thermostat.client.core.experimental.Duration;
-import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 
 public abstract class VmClassStatView extends BasicView implements UIComponent {
 
-    public enum UserAction {
-        USER_CHANGED_TIME_RANGE,
+    public enum Group {
+        NUMBER,
+        SIZE,
+        ;
     }
 
-    public abstract void addUserActionListener(ActionListener<UserAction> listener);
-
-    public abstract void removeUserActionListener(ActionListener<UserAction> listener);
+    public static final String TAG_LOADED_CLASSES = "loadedClasses";
+    public static final String TAG_LOADED_BYTES = "loadedBytes";
+    public static final String TAG_UNLOADED_CLASSES = "unloadedClasses";
+    public static final String TAG_UNLOADED_BYTES = "unloadedBytes";
+    public static final String TAG_CLASS_LOAD_TIME= "classLoadTime";
 
     public abstract Duration getUserDesiredDuration();
 
-    public abstract void setVisibleDataRange(int time, TimeUnit unit);
-
     public abstract void setAvailableDataRange(Range<Long> availableDataRange);
 
-    public abstract void clearClassCount();
+    public abstract void addClassChart(Group group, String tag, LocalizedString name);
+    public abstract void removeClassChart(Group group, String tag);
 
-    public abstract void addClassCount(List<DiscreteTimeData<Long>> data);
+    public abstract void addClassData(String tag, List<DiscreteTimeData<? extends Number>> data);
+    public abstract void clearMemoryData(String tag);
 
 }
 
--- a/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatController.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatController.java	Mon Jun 27 12:24:03 2016 -0400
@@ -36,19 +36,22 @@
 
 package com.redhat.thermostat.vm.classstat.client.core.internal;
 
-import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
+
 import com.redhat.thermostat.client.core.controllers.InformationServiceController;
-import com.redhat.thermostat.client.core.views.UIComponent;
-import com.redhat.thermostat.client.core.views.BasicView.Action;
 import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.client.core.experimental.TimeRangeController;
+import com.redhat.thermostat.client.core.views.BasicView.Action;
+import com.redhat.thermostat.client.core.views.UIComponent;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
 import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.Size;
+import com.redhat.thermostat.common.Size.Unit;
 import com.redhat.thermostat.common.Timer;
 import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.common.model.Range;
@@ -71,11 +74,14 @@
     private class UpdateChartData implements Runnable {
         @Override
         public void run() {
-            final List<DiscreteTimeData<Long>> data = new ArrayList<>();
-
             VmClassStat oldest = dao.getOldest(ref);
             VmClassStat newest = dao.getNewest(ref);
 
+            final List<DiscreteTimeData<? extends Number>> loadedClasses = new LinkedList<>();
+            final List<DiscreteTimeData<? extends Number>> loadedBytes = new LinkedList<>();
+            final List<DiscreteTimeData<? extends Number>> unloadedClasses = new LinkedList<>();
+            final List<DiscreteTimeData<? extends Number>> unloadedBytes = new LinkedList<>();
+
             Range<Long> newAvailableRange = new Range<>(oldest.getTimeStamp(), newest.getTimeStamp());
 
             TimeRangeController.StatsSupplier<VmClassStat, VmRef> singleValueSupplier = new TimeRangeController.StatsSupplier<VmClassStat, VmRef>() {
@@ -87,15 +93,27 @@
 
             TimeRangeController.SingleArgRunnable<VmClassStat> runnable = new TimeRangeController.SingleArgRunnable<VmClassStat>() {
                 @Override
-                public void run(VmClassStat arg) {
-                    data.add(new DiscreteTimeData<>(arg.getTimeStamp(), arg.getLoadedClasses()));
+                public void run(VmClassStat stat) {
+                    long timeStamp = stat.getTimeStamp();
+
+                    loadedClasses.add(new DiscreteTimeData<Number>(timeStamp, stat.getLoadedClasses()));
+                    loadedBytes.add(new DiscreteTimeData<Number>(timeStamp, bytesToMegaBytes(stat.getLoadedBytes())));
+                    unloadedClasses.add(new DiscreteTimeData<Number>(timeStamp, stat.getUnloadedClasses()));
+                    unloadedBytes.add(new DiscreteTimeData<Number>(timeStamp, bytesToMegaBytes(stat.getUnloadedBytes())));
+                }
+
+                private long bytesToMegaBytes(long bytes) {
+                    return (long) Size.bytes(bytes).convertTo(Unit.MiB).getValue();
                 }
             };
 
             timeRangeController.update(userDesiredDuration, newAvailableRange, singleValueSupplier, ref, runnable);
             classesView.setAvailableDataRange(timeRangeController.getAvailableRange());
 
-            classesView.addClassCount(data);
+            classesView.addClassData(VmClassStatView.TAG_LOADED_CLASSES, loadedClasses);
+            classesView.addClassData(VmClassStatView.TAG_LOADED_BYTES, loadedBytes);
+            classesView.addClassData(VmClassStatView.TAG_UNLOADED_CLASSES, unloadedClasses);
+            classesView.addClassData(VmClassStatView.TAG_UNLOADED_BYTES, unloadedBytes);
         }
     }
 
@@ -118,6 +136,14 @@
         timer.setInitialDelay(0);
 
         classesView = viewProvider.createView();
+        classesView.addClassChart(VmClassStatView.Group.NUMBER, VmClassStatView.TAG_LOADED_CLASSES,
+                translator.localize(LocaleResources.VM_CLASSES_CHART_LOADED_CLASSES_LENGEND));
+        classesView.addClassChart(VmClassStatView.Group.SIZE, VmClassStatView.TAG_LOADED_BYTES,
+                translator.localize(LocaleResources.VM_CLASSES_CHART_LOADED_BYTES_LENGEND));
+        classesView.addClassChart(VmClassStatView.Group.NUMBER, VmClassStatView.TAG_UNLOADED_CLASSES,
+                translator.localize(LocaleResources.VM_CLASSES_CHART_UNLOADED_CLASSES_LENGEND));
+        classesView.addClassChart(VmClassStatView.Group.SIZE, VmClassStatView.TAG_UNLOADED_BYTES,
+                translator.localize(LocaleResources.VM_CLASSES_CHART_UNLOADED_BYTES_LENGEND));
 
         classesView.addActionListener(new ActionListener<VmClassStatView.Action>() {
             @Override
@@ -135,22 +161,6 @@
             }
         });
 
-        classesView.addUserActionListener(new ActionListener<VmClassStatView.UserAction>() {
-
-            @Override
-            public void actionPerformed(final ActionEvent<VmClassStatView.UserAction> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case USER_CHANGED_TIME_RANGE:
-                    Duration duration = classesView.getUserDesiredDuration();
-                    userDesiredDuration = duration;
-                    classesView.setVisibleDataRange(duration.value, duration.unit);
-                    break;
-                default:
-                    throw new AssertionError("Unhandled action type");
-                }
-            }
-        });
-
         userDesiredDuration = classesView.getUserDesiredDuration();
 
         timeRangeController = new TimeRangeController<>();
--- a/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/locale/LocaleResources.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-core/src/main/java/com/redhat/thermostat/vm/classstat/client/locale/LocaleResources.java	Mon Jun 27 12:24:03 2016 -0400
@@ -40,10 +40,17 @@
 
 public enum LocaleResources {
 
-    VM_LOADED_CLASSES,
-    VM_CLASSES_CHART_REAL_TIME_LABEL,
-    VM_CLASSES_CHART_LOADED_CLASSES_LABEL,
     VM_INFO_TAB_CLASSES,
+    VM_CLASSES_HEADER,
+
+    VM_CLASSES_CHART_TIME_LABEL,
+    VM_CLASSES_CHART_CLASSES_LABEL,
+    VM_CLASSES_CHART_SIZE_LABEL,
+
+    VM_CLASSES_CHART_LOADED_CLASSES_LENGEND,
+    VM_CLASSES_CHART_LOADED_BYTES_LENGEND,
+    VM_CLASSES_CHART_UNLOADED_CLASSES_LENGEND,
+    VM_CLASSES_CHART_UNLOADED_BYTES_LENGEND,
     ;
     
     public static final String RESOURCE_BUNDLE =
--- a/vm-classstat/client-core/src/main/resources/com/redhat/thermostat/vm/classstat/client/locale/strings.properties	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-core/src/main/resources/com/redhat/thermostat/vm/classstat/client/locale/strings.properties	Mon Jun 27 12:24:03 2016 -0400
@@ -1,4 +1,11 @@
-VM_LOADED_CLASSES = Loaded Classes
-VM_CLASSES_CHART_REAL_TIME_LABEL = Time
-VM_CLASSES_CHART_LOADED_CLASSES_LABEL = Number of loaded classes
-VM_INFO_TAB_CLASSES = Classes
\ No newline at end of file
+VM_INFO_TAB_CLASSES = Classes
+VM_CLASSES_HEADER = Classes
+
+VM_CLASSES_CHART_TIME_LABEL = Time
+VM_CLASSES_CHART_CLASSES_LABEL = Number of classes
+VM_CLASSES_CHART_SIZE_LABEL = Size (MiB)
+
+VM_CLASSES_CHART_LOADED_CLASSES_LENGEND = Loaded Classes
+VM_CLASSES_CHART_LOADED_BYTES_LENGEND = Loaded Size
+VM_CLASSES_CHART_UNLOADED_CLASSES_LENGEND = Unloaded Classes
+VM_CLASSES_CHART_UNLOADED_BYTES_LENGEND = Unloaded Size
--- a/vm-classstat/client-core/src/test/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatControllerTest.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-core/src/test/java/com/redhat/thermostat/vm/classstat/client/core/internal/VmClassStatControllerTest.java	Mon Jun 27 12:24:03 2016 -0400
@@ -37,8 +37,10 @@
 package com.redhat.thermostat.vm.classstat.client.core.internal;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -46,10 +48,10 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import com.redhat.thermostat.client.core.experimental.Duration;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import com.redhat.thermostat.client.core.experimental.Duration;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ApplicationService;
@@ -67,7 +69,11 @@
     @Test
     public void testChartUpdate() {
 
-        VmClassStat stat1 = new VmClassStat("foo-agent", "vmId", 12345, 1234);
+        final long SOME_TIMESTAMP = 12345;
+        final int SOME_VALUE = 1234;
+
+        VmClassStat stat1 = new VmClassStat("foo-agent", "vmId", SOME_TIMESTAMP,
+                SOME_VALUE, SOME_VALUE, SOME_VALUE, SOME_VALUE, SOME_VALUE);
         List<VmClassStat> stats = new ArrayList<VmClassStat>();
         stats.add(stat1);
 
@@ -106,7 +112,7 @@
 
         verify(timer).start();
         timerActionCaptor.getValue().run();
-        verify(view).addClassCount(any(List.class));
+        verify(view, times(4)).addClassData(isA(String.class), isA(List.class));
 
         l.actionPerformed(new ActionEvent<>(view, VmClassStatView.Action.HIDDEN));
 
--- a/vm-classstat/client-swing/src/main/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanel.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-swing/src/main/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanel.java	Mon Jun 27 12:24:03 2016 -0400
@@ -37,33 +37,22 @@
 package com.redhat.thermostat.vm.classstat.client.swing;
 
 import java.awt.Component;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
+import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.Callable;
 
 import javax.swing.SwingUtilities;
 
 import com.redhat.thermostat.client.core.experimental.Duration;
-import com.redhat.thermostat.client.swing.components.experimental.SingleValueChartPanel;
-import com.redhat.thermostat.common.model.Range;
-import org.jfree.chart.ChartFactory;
-import org.jfree.chart.JFreeChart;
-import org.jfree.chart.axis.NumberAxis;
-import org.jfree.chart.axis.NumberTickUnit;
-import org.jfree.chart.axis.TickUnits;
-import org.jfree.data.RangeType;
-import org.jfree.data.time.FixedMillisecond;
-import org.jfree.data.time.RegularTimePeriod;
-import org.jfree.data.time.TimeSeries;
-import org.jfree.data.time.TimeSeriesCollection;
-
+import com.redhat.thermostat.client.swing.EdtHelper;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.swing.components.experimental.MultiChartPanel;
 import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
 import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
 import com.redhat.thermostat.vm.classstat.client.core.VmClassStatView;
@@ -73,62 +62,33 @@
 
     private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
 
-    private static final int DEFAULT_VALUE = 10;
-    private static final TimeUnit DEFAULT_UNIT = TimeUnit.MINUTES;
-
-    private Duration duration;
+    private final HeaderPanel visiblePanel;
 
-    private HeaderPanel visiblePanel;
-    
-    private final TimeSeriesCollection dataset = new TimeSeriesCollection();
+    private final MultiChartPanel multiChartPanel;
 
-    private SingleValueChartPanel chartPanel;
-
-    private ActionNotifier<UserAction> userActionActionNotifier = new ActionNotifier<VmClassStatView.UserAction>(this);
-
-    private final ActionNotifier<Action> notifier = new ActionNotifier<Action>(this);
+    private MultiChartPanel.DataGroup GROUP_NUMBER;
+    private MultiChartPanel.DataGroup GROUP_SIZE;
 
     public VmClassStatPanel() {
         visiblePanel = new HeaderPanel();
-        // any name works
-        dataset.addSeries(new TimeSeries("class-stat"));
 
-        duration = new Duration(DEFAULT_VALUE, DEFAULT_UNIT);
-
-        visiblePanel.setHeader(t.localize(LocaleResources.VM_LOADED_CLASSES));
+        visiblePanel.setHeader(t.localize(LocaleResources.VM_CLASSES_HEADER));
 
-        JFreeChart chart = ChartFactory.createTimeSeriesChart(
-                null,
-                t.localize(LocaleResources.VM_CLASSES_CHART_REAL_TIME_LABEL).getContents(),
-                t.localize(LocaleResources.VM_CLASSES_CHART_LOADED_CLASSES_LABEL).getContents(),
-                dataset,
-                false, false, false);
+        String xAxisLabel = t.localize(LocaleResources.VM_CLASSES_CHART_TIME_LABEL).getContents();
+        String classesAxisLabel = t.localize(LocaleResources.VM_CLASSES_CHART_CLASSES_LABEL).getContents();
+        String sizeAxisLabel = t.localize(LocaleResources.VM_CLASSES_CHART_SIZE_LABEL).getContents();
+
+        multiChartPanel = new MultiChartPanel();
 
-        TickUnits tickUnits = new TickUnits();
-        tickUnits.add(new NumberTickUnit(1));
-        tickUnits.add(new NumberTickUnit(10));
-        tickUnits.add(new NumberTickUnit(100));
-        tickUnits.add(new NumberTickUnit(1000));
-        tickUnits.add(new NumberTickUnit(10000));
-        tickUnits.add(new NumberTickUnit(100000));
-        tickUnits.add(new NumberTickUnit(1000000));
+        GROUP_NUMBER = multiChartPanel.createGroup();
+        GROUP_SIZE = multiChartPanel.createGroup();
+
+        multiChartPanel.setDomainAxisLabel(xAxisLabel);
 
-        NumberAxis axis = (NumberAxis) chart.getXYPlot().getRangeAxis();
-        axis.setStandardTickUnits(tickUnits);
-        axis.setRangeType(RangeType.POSITIVE);
-        axis.setAutoRangeMinimumSize(10);
-
-        chartPanel = new SingleValueChartPanel(chart, duration);
+        multiChartPanel.getRangeAxis(GROUP_NUMBER).setLabel(classesAxisLabel);
+        multiChartPanel.getRangeAxis(GROUP_SIZE).setLabel(sizeAxisLabel);
 
-        visiblePanel.setContent(chartPanel);
-
-        chartPanel.addPropertyChangeListener(SingleValueChartPanel.PROPERTY_VISIBLE_TIME_RANGE, new PropertyChangeListener() {
-            @Override
-            public void propertyChange(final PropertyChangeEvent evt) {
-                duration = (Duration) evt.getNewValue();
-                userActionActionNotifier.fireAction(UserAction.USER_CHANGED_TIME_RANGE);
-            }
-        });
+        visiblePanel.setContent(multiChartPanel);
 
         new ComponentVisibilityNotifier().initialize(visiblePanel, notifier);
     }
@@ -144,42 +104,71 @@
     }
 
     @Override
-    public void addClassCount(List<DiscreteTimeData<Long>> data) {
-        final List<DiscreteTimeData<Long>> copy = new ArrayList<>(data);
+    public void addClassChart(final Group group, final String tag, final LocalizedString name) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                multiChartPanel.addChart(getGroup(group), tag, name);
+                multiChartPanel.showChart(getGroup(group), tag);
+            }
+        });
+    }
+    @Override
+    public void removeClassChart(final Group group, final String tag) {
         SwingUtilities.invokeLater(new Runnable() {
             @Override
             public void run() {
-                TimeSeries series = dataset.getSeries(0);
-                for (DiscreteTimeData<Long> data: copy) {
-                    RegularTimePeriod period = new FixedMillisecond(data.getTimeInMillis());
-                    if (series.getDataItem(period) == null) {
-                        series.add(period, data.getData(), false);
-                    }
-                }
-                series.fireSeriesChanged();
+                multiChartPanel.hideChart(getGroup(group), tag);
+                multiChartPanel.removeChart(getGroup(group), tag);
             }
         });
+    }
 
+    private MultiChartPanel.DataGroup getGroup(Group group) {
+        switch (group) {
+        case NUMBER:
+            return GROUP_NUMBER;
+        case SIZE:
+            return GROUP_SIZE;
+        default:
+            throw new AssertionError("Unknown data group");
+        }
     }
 
     @Override
-    public void addUserActionListener(final ActionListener<UserAction> listener) {
-        userActionActionNotifier.addActionListener(listener);
+    public void addClassData(final String tag, final List<DiscreteTimeData<? extends Number>> data) {
+        final List<DiscreteTimeData<? extends Number>> copy = new ArrayList<>(data);
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                multiChartPanel.addData(tag, copy);
+            }
+        });
     }
 
     @Override
-    public void removeUserActionListener(final ActionListener<UserAction> listener) {
-        userActionActionNotifier.removeActionListener(listener);
+    public void clearMemoryData(final String tag) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                multiChartPanel.clearData(tag);
+            }
+        });
     }
 
     @Override
     public Duration getUserDesiredDuration() {
-        return duration;
-    }
-
-    @Override
-    public void setVisibleDataRange(final int time, final TimeUnit unit) {
-        chartPanel.setTimeRangeToShow(time, unit);
+        try {
+            return new EdtHelper().callAndWait(new Callable<Duration>() {
+                @Override
+                public Duration call() throws Exception {
+                    return multiChartPanel.getUserDesiredDuration();
+                }
+            });
+        } catch (InvocationTargetException | InterruptedException e) {
+            e.printStackTrace();
+            return null;
+        }
     }
 
     @Override
@@ -188,17 +177,6 @@
     }
 
     @Override
-    public void clearClassCount() {
-        SwingUtilities.invokeLater(new Runnable() {
-            @Override
-            public void run() {
-                TimeSeries series = dataset.getSeries(0);
-                series.clear();
-            }
-        });
-    }
-
-    @Override
     public Component getUiComponent() {
         return visiblePanel;
     }
--- a/vm-classstat/client-swing/src/test/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanelTest.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/client-swing/src/test/java/com/redhat/thermostat/vm/classstat/client/swing/VmClassStatPanelTest.java	Mon Jun 27 12:24:03 2016 -0400
@@ -36,51 +36,47 @@
 
 package com.redhat.thermostat.vm.classstat.client.swing;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
-
-import javax.swing.JPanel;
-
-import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+import java.util.concurrent.TimeUnit;
 
-import org.fest.swing.edt.FailOnThreadViolationRepaintManager;
-import org.fest.swing.edt.GuiActionRunner;
-import org.fest.swing.edt.GuiTask;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
 
-import com.redhat.thermostat.annotations.internal.CacioTest;
+import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.storage.model.DiscreteTimeData;
+import com.redhat.thermostat.vm.classstat.client.core.VmClassStatView;
 
-@Category(CacioTest.class)
-@RunWith(CacioFESTRunner.class)
 public class VmClassStatPanelTest {
 
-    @BeforeClass
-    public static void setUpOnce() {
-        FailOnThreadViolationRepaintManager.install();
-    }
-
-    @Test
-    public void testAddDataTwice() {
-        GuiActionRunner.execute(new GuiTask() {
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(new Runnable() {
             @Override
-            protected void executeInEDT() throws Throwable {
-                VmClassStatPanel panel = new VmClassStatPanel();
-                List<DiscreteTimeData<Long>> data = new ArrayList<>();
-                panel.addClassCount(data);
-                int numComponents = ((JPanel)panel.getUiComponent()).getComponentCount();
-                assertTrue(numComponents > 0);
-                panel.addClassCount(data);
-                assertEquals(numComponents, ((JPanel)panel.getUiComponent()).getComponentCount());
+            public void run() {
+                JFrame frame = new JFrame("Test");
+
+                VmClassStatPanel classPanel = new VmClassStatPanel();
+
+                List<DiscreteTimeData<? extends Number>> data = new ArrayList<DiscreteTimeData<? extends Number>>();
+                data.add(new DiscreteTimeData<>(new Date().getTime(), 10l));
+                data.add(new DiscreteTimeData<>(new Date().getTime() + TimeUnit.MINUTES.toMillis(1), 1000l));
+
+                classPanel.addClassChart(VmClassStatView.Group.NUMBER, "classes", new LocalizedString("classes"));
+                classPanel.addClassData("classes", data);
+
+                List<DiscreteTimeData<? extends Number>> data2 = new ArrayList<DiscreteTimeData<? extends Number>>();
+                data2.add(new DiscreteTimeData<Number>(new Date().getTime(), 10));
+                data2.add(new DiscreteTimeData<Number>(new Date().getTime() + TimeUnit.MINUTES.toMillis(1), 20));
+                classPanel.addClassChart(VmClassStatView.Group.SIZE, "size", new LocalizedString("size"));
+                classPanel.addClassData("size", data2);
+
+                frame.add(classPanel.getUiComponent());
+                frame.pack();
+                frame.setVisible(true);
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             }
         });
     }
-
 }
 
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/VmClassStatDAO.java	Mon Jun 27 12:24:03 2016 -0400
@@ -49,10 +49,19 @@
 public interface VmClassStatDAO {
 
     static final Key<Long> loadedClassesKey = new Key<>("loadedClasses");
+    static final Key<Long> loadedBytesKey = new Key<>("loadedBytes");
+    static final Key<Long> unloadedClassesKey = new Key<>("unloadedClasses");
+    static final Key<Long> unloadedBytesKey = new Key<>("unloadedBytes");
+    static final Key<Long> classLoadTimeKey = new Key<>("classLoadTime");
 
     static final Category<VmClassStat> vmClassStatsCategory = new Category<>(
             "vm-class-stats", VmClassStat.class,
-            Arrays.<Key<?>>asList(Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, loadedClassesKey), Arrays.<Key<?>>asList(Key.TIMESTAMP));
+            Arrays.<Key<?>>asList(
+                    Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP,
+                    loadedClassesKey, loadedBytesKey,
+                    unloadedClassesKey, unloadedBytesKey,
+                    classLoadTimeKey),
+            Arrays.<Key<?>>asList(Key.TIMESTAMP));
 
     public List<VmClassStat> getLatestClassStats(VmRef ref, long since);
 
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOImpl.java	Mon Jun 27 12:24:03 2016 -0400
@@ -60,12 +60,20 @@
     // ADD vm-class-stats SET 'agentId' = ?s , \
     //                        'vmId' = ?s , \
     //                        'timeStamp' = ?l , \ 
-    //                        'loadedClasses' = ?l
+    //                        'loadedClasses' = ?l , \
+    //                        'loadedBytes' = ?l , \
+    //                        'unloadedClasses' = ?l , \
+    //                        'unloadedBytes' = ?l , \
+    //                        'classLoadTime' = ?l
     static final String DESC_ADD_VM_CLASS_STAT = "ADD " + vmClassStatsCategory.getName() +
             " SET '" + Key.AGENT_ID.getName() + "' = ?s , " +
                  "'" + Key.VM_ID.getName() + "' = ?s , " +
                  "'" + Key.TIMESTAMP.getName() + "' = ?l , " +
-                 "'" + loadedClassesKey.getName() + "' = ?l";
+                 "'" + loadedClassesKey.getName() + "' = ?l , " +
+                 "'" + loadedBytesKey.getName() + "' = ?l , " +
+                 "'" + unloadedClassesKey.getName() + "' = ?l , " +
+                 "'" + unloadedBytesKey.getName() + "' = ?l , " +
+                 "'" + classLoadTimeKey.getName() + "' = ?l";
 
     private final Storage storage;
     private final VmLatestPojoListGetter<VmClassStat> latestGetter;
@@ -100,6 +108,10 @@
             prepared.setString(1, stat.getVmId());
             prepared.setLong(2, stat.getTimeStamp());
             prepared.setLong(3, stat.getLoadedClasses());
+            prepared.setLong(4, stat.getLoadedBytes());
+            prepared.setLong(5, stat.getUnloadedClasses());
+            prepared.setLong(6, stat.getUnloadedBytes());
+            prepared.setLong(7, stat.getClassLoadTime());
             prepared.execute();
         } catch (DescriptorParsingException e) {
             logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
--- a/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/model/VmClassStat.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/common/src/main/java/com/redhat/thermostat/vm/classstat/common/model/VmClassStat.java	Mon Jun 27 12:24:03 2016 -0400
@@ -44,19 +44,39 @@
 @Entity
 public class VmClassStat extends BasePojo implements TimeStampedPojo {
 
+    // See the jdk sources for more information:
+    // - jdk/src/share/classes/sun/tools/jstat/resources/jstat_options
+    // - jdk/src/share/classes/sun/tools/jstat/resources/jstat_unsupported_options
+
+    public static final int UNKNOWN = -1;
+
     private String vmId;
     private long timestamp;
     private long loadedClasses;
+    private long loadedBytes;
+    private long unloadedClasses;
+    private long unloadedBytes;
+    private long classLoadTime;
 
     public VmClassStat() {
-        this(null, null, -1, -1);
+        this(null, null, UNKNOWN,
+                UNKNOWN, UNKNOWN,
+                UNKNOWN, UNKNOWN,
+                UNKNOWN);
     }
 
-    public VmClassStat(String writerId, String vmId, long timestamp, long loadedClasses) {
+    public VmClassStat(String writerId, String vmId, long timestamp,
+            long loadedClasses, long loadedBytes,
+            long unloadedClasses, long unloadedBytes,
+            long classLoadTime) {
         super(writerId);
         this.vmId = vmId;
         this.timestamp = timestamp;
         this.loadedClasses = loadedClasses;
+        this.loadedBytes = loadedBytes;
+        this.unloadedClasses = unloadedClasses;
+        this.unloadedBytes = unloadedBytes;
+        this.classLoadTime = classLoadTime;
     }
 
     @Persist
@@ -80,6 +100,7 @@
         this.timestamp = timestamp;
     }
 
+    /** Number of classes loaded */
     @Persist
     public long getLoadedClasses() {
         return loadedClasses;
@@ -89,5 +110,49 @@
     public void setLoadedClasses(long loadedClasses) {
         this.loadedClasses = loadedClasses;
     }
+
+    /** Accumulated Size of classes loaded */
+    @Persist
+    public long getLoadedBytes() {
+        return loadedBytes;
+    }
+
+    @Persist
+    public void setLoadedBytes(long bytes) {
+        this.loadedBytes = bytes;
+    }
+
+    /** Number of classes unloaded */
+    @Persist
+    public long getUnloadedClasses() {
+        return unloadedClasses;
+    }
+
+    @Persist
+    public void setUnloadedClasses(long classes) {
+        this.unloadedClasses = classes;
+    }
+
+    /** Accumulated size of classes unloaded */
+    @Persist
+    public long getUnloadedBytes() {
+        return unloadedBytes;
+    }
+
+    @Persist
+    public void setUnloadedBytes(long bytes) {
+        this.unloadedBytes = bytes;
+    }
+
+    /** Accumulated time for class loading. In seconds */
+    @Persist
+    public long getClassLoadTime() {
+        return classLoadTime;
+    }
+
+    @Persist
+    public void setClassLoadTime(long seconds) {
+        this.classLoadTime = seconds;
+    }
+
 }
-
--- a/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOTest.java	Fri Jun 24 12:17:11 2016 -0400
+++ b/vm-classstat/common/src/test/java/com/redhat/thermostat/vm/classstat/common/internal/VmClassStatDAOTest.java	Mon Jun 27 12:24:03 2016 -0400
@@ -67,13 +67,22 @@
     private static final Long TIMESTAMP = 1234L;
     private static final String VM_ID = "vmId";
     private static final Long LOADED_CLASSES = 12345L;
+    private static final Long LOADED_BYTES = 2345L;
+    private static final Long UNLOADED_CLASSES = 3456L;
+    private static final Long UNLOADED_BYTES = 4567L;
+    private static final Long CLASS_LOAD_TIME = 5678L;
 
     @Test
     public void testStatementDescriptorsAreSane() {
         String addVmClassStat = "ADD vm-class-stats SET 'agentId' = ?s , " +
                                                 "'vmId' = ?s , " +
                                                 "'timeStamp' = ?l , " +
-                                                "'loadedClasses' = ?l";
+                                                "'loadedClasses' = ?l , " +
+                                                "'loadedBytes' = ?l , " +
+                                                "'unloadedClasses' = ?l , " +
+                                                "'unloadedBytes' = ?l , " +
+                                                "'classLoadTime' = ?l";
+
         assertEquals(addVmClassStat, VmClassStatDAOImpl.DESC_ADD_VM_CLASS_STAT);
     }
     
@@ -85,7 +94,11 @@
         assertTrue(keys.contains(new Key<Integer>("vmId")));
         assertTrue(keys.contains(new Key<Long>("timeStamp")));
         assertTrue(keys.contains(new Key<Long>("loadedClasses")));
-        assertEquals(4, keys.size());
+        assertTrue(keys.contains(new Key<Long>("loadedBytes")));
+        assertTrue(keys.contains(new Key<Long>("unloadedClasses")));
+        assertTrue(keys.contains(new Key<Long>("unloadedBytes")));
+        assertTrue(keys.contains(new Key<Long>("classLoadTime")));
+        assertEquals(8, keys.size());
     }
 
     @Test
@@ -134,7 +147,10 @@
     }
 
     private VmClassStat getClassStat() {
-        return new VmClassStat("foo-agent", VM_ID, TIMESTAMP, LOADED_CLASSES);
+        return new VmClassStat("foo-agent", VM_ID, TIMESTAMP,
+                LOADED_CLASSES, LOADED_BYTES,
+                UNLOADED_CLASSES, UNLOADED_BYTES,
+                CLASS_LOAD_TIME);
     }
 
     @SuppressWarnings("unchecked")
@@ -145,7 +161,8 @@
         PreparedStatement<VmClassStat> add = mock(PreparedStatement.class);
         when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(add);
 
-        VmClassStat stat = new VmClassStat("foo-agent", VM_ID, TIMESTAMP, LOADED_CLASSES);
+        VmClassStat stat = new VmClassStat("foo-agent", VM_ID, TIMESTAMP,
+                LOADED_CLASSES, LOADED_BYTES, UNLOADED_CLASSES, UNLOADED_BYTES, CLASS_LOAD_TIME);
         VmClassStatDAO dao = new VmClassStatDAOImpl(storage);
         dao.putVmClassStat(stat);
         
@@ -160,6 +177,10 @@
         verify(add).setString(1, stat.getVmId());
         verify(add).setLong(2, stat.getTimeStamp());
         verify(add).setLong(3, stat.getLoadedClasses());
+        verify(add).setLong(4, stat.getLoadedBytes());
+        verify(add).setLong(5, stat.getUnloadedClasses());
+        verify(add).setLong(6, stat.getUnloadedBytes());
+        verify(add).setLong(7, stat.getClassLoadTime());
         verify(add).execute();
         verifyNoMoreInteractions(add);
     }