changeset 338:eae8d583ebd4

Show time and memory stats in vm-stat command. Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-May/001598.html PR 1000
author Roman Kennke <rkennke@redhat.com>
date Wed, 30 May 2012 20:14:55 +0200
parents f3f4efd17456
children cd786147af62
files client/core/src/main/java/com/redhat/thermostat/client/ui/DisplayableValues.java client/core/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java client/core/src/test/java/com/redhat/thermostat/client/ui/DisplayableValuesTest.java common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAO.java common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java common/src/main/java/com/redhat/thermostat/common/model/TimeStampedPojoCorrelator.java common/src/main/java/com/redhat/thermostat/common/utils/DisplayableValues.java common/src/test/java/com/redhat/thermostat/common/model/TimeStampedPojoCorrelatorTest.java common/src/test/java/com/redhat/thermostat/common/utils/DisplayableValuesTest.java tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatCommand.java tools/src/test/java/com/redhat/thermostat/tools/cli/VmStatCommandTest.java
diffstat 11 files changed, 729 insertions(+), 171 deletions(-) [+]
line wrap: on
line diff
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/DisplayableValues.java	Tue May 29 12:19:43 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.ui;
-
-public class DisplayableValues {
-
-    private static final long BYTES_IN_KB = 1024;
-    private static final long BYTES_IN_MB = 1024*BYTES_IN_KB;
-    private static final long BYTES_IN_GB = 1024*BYTES_IN_MB;
-    private static final long BYTES_IN_TB = 1024*BYTES_IN_GB;
-
-    private static final String BYTES_UNIT = "B";
-    private static final String KBYTES_UNIT = "KiB";
-    private static final String MBYTES_UNIT = "MiB";
-    private static final String GBYTES_UNIT = "GiB";
-    private static final String TBYTES_UNIT = "TiB";
-
-    private static final String DOUBLE_FORMAT_STRING = "%.1f";
-
-    private DisplayableValues() {} // Not to be instantiated.
-
-    public static String[] bytes(final long bytes) {
-        if (bytes < BYTES_IN_KB) {
-            return new String[] { String.valueOf(bytes), BYTES_UNIT };
-        } else if (bytes < BYTES_IN_MB) {
-            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_KB), KBYTES_UNIT };
-        } else if (bytes < BYTES_IN_GB) {
-            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_MB), MBYTES_UNIT };
-        } else if (bytes < BYTES_IN_TB) {
-            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_GB), GBYTES_UNIT };
-        } else {
-            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_TB), TBYTES_UNIT };
-        }
-    }
-}
--- a/client/core/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java	Tue May 29 12:19:43 2012 +0200
+++ b/client/core/src/main/java/com/redhat/thermostat/client/ui/VmMemoryController.java	Wed May 30 20:14:55 2012 +0200
@@ -56,6 +56,7 @@
 import com.redhat.thermostat.common.model.VmMemoryStat;
 import com.redhat.thermostat.common.model.VmMemoryStat.Generation;
 import com.redhat.thermostat.common.model.VmMemoryStat.Space;
+import com.redhat.thermostat.common.utils.DisplayableValues;
 
 class VmMemoryController {
 
--- a/client/core/src/test/java/com/redhat/thermostat/client/ui/DisplayableValuesTest.java	Tue May 29 12:19:43 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright 2012 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.ui;
-
-import static org.junit.Assert.assertEquals;
-
-import java.util.Locale;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-public class DisplayableValuesTest {
-
-    private static Locale defaultLocale;
-
-    @BeforeClass
-    public static void setUp() {
-        defaultLocale = Locale.getDefault();
-        Locale.setDefault(Locale.US);
-    }
-
-    @AfterClass
-    public static void tearDown() {
-        Locale.setDefault(defaultLocale);
-    }
-
-    @Test
-    public void testBytes() {
-        testBytesOutput("1", "B", DisplayableValues.bytes(1));
-        testBytesOutput("1023", "B", DisplayableValues.bytes(1023));
-        testBytesOutput("1.0", "KiB", DisplayableValues.bytes(1024));
-        testBytesOutput("1024.0", "KiB", DisplayableValues.bytes(1_048_575));
-        testBytesOutput("1.0", "MiB", DisplayableValues.bytes(1_048_576));
-        testBytesOutput("10.0", "MiB", DisplayableValues.bytes(10_480_000));
-        testBytesOutput("42.0", "MiB", DisplayableValues.bytes(44_040_000));
-        testBytesOutput("99.9", "MiB", DisplayableValues.bytes(104_752_742));
-        testBytesOutput("100.0", "MiB", DisplayableValues.bytes(104_857_600));
-        testBytesOutput("500.0", "MiB", DisplayableValues.bytes(524_288_000));
-        testBytesOutput("900.0", "MiB", DisplayableValues.bytes(943_718_400));
-        testBytesOutput("999.9", "MiB", DisplayableValues.bytes(1_048_471_000));
-        testBytesOutput("1.0", "GiB", DisplayableValues.bytes(1_073_741_824));
-        testBytesOutput("1.1", "GiB", DisplayableValues.bytes(1_181_116_000));
-        testBytesOutput("9.9", "GiB", DisplayableValues.bytes(10_630_044_000l));
-        testBytesOutput("99.9", "GiB", DisplayableValues.bytes(107_266_808_000l));
-        testBytesOutput("1.0", "TiB", DisplayableValues.bytes(1_099_511_627_776l));
-    }
-
-    private void testBytesOutput(String number, String units, String[] output) {
-        assertEquals(2, output.length);
-        assertEquals(number, output[0]);
-        assertEquals(units, output[1]);
-    }
-}
--- a/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAO.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAO.java	Wed May 30 20:14:55 2012 +0200
@@ -36,6 +36,8 @@
 
 package com.redhat.thermostat.common.dao;
 
+import java.util.List;
+
 import com.redhat.thermostat.common.model.VmMemoryStat;
 import com.redhat.thermostat.common.storage.Category;
 import com.redhat.thermostat.common.storage.Key;
@@ -92,6 +94,8 @@
 
     public VmMemoryStat getLatestMemoryStat(VmRef ref);
 
+    public List<VmMemoryStat> getLatestVmMemoryStats(VmRef vm);
+
     public void putVmMemoryStat(VmMemoryStat stat);
 
 }
--- a/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java	Tue May 29 12:19:43 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/dao/VmMemoryStatDAOImpl.java	Wed May 30 20:14:55 2012 +0200
@@ -36,6 +36,11 @@
 
 package com.redhat.thermostat.common.dao;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.redhat.thermostat.common.model.VmCpuStat;
 import com.redhat.thermostat.common.model.VmMemoryStat;
 import com.redhat.thermostat.common.storage.Chunk;
 import com.redhat.thermostat.common.storage.Cursor;
@@ -47,6 +52,8 @@
     private final Storage storage;
     private final VmMemoryStatConverter converter;
 
+    private Map<VmRef, VmLatestPojoListGetter<VmMemoryStat>> getters = new HashMap<>();
+
     VmMemoryStatDAOImpl(Storage storage) {
         this.storage = storage;
         converter = new VmMemoryStatConverter();
@@ -69,4 +76,13 @@
         storage.putChunk(converter.toChunk(stat));
     }
 
+    @Override
+    public List<VmMemoryStat> getLatestVmMemoryStats(VmRef ref) {
+        VmLatestPojoListGetter<VmMemoryStat> getter = getters.get(ref);
+        if (getter == null) {
+            getter = new VmLatestPojoListGetter<VmMemoryStat>(storage, vmMemoryStatsCategory, converter, ref);
+            getters.put(ref, getter);
+        }
+        return getter.getLatest();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/model/TimeStampedPojoCorrelator.java	Wed May 30 20:14:55 2012 +0200
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+public class TimeStampedPojoCorrelator {
+
+    public static class Correlation {
+
+        private long timestamp;
+        private TimeStampedPojo[] correlation;
+
+        private Correlation(long timestamp, TimeStampedPojo[] correlation) {
+            this.timestamp = timestamp;
+            this.correlation = correlation;
+        }
+
+        public TimeStampedPojo get(int i) {
+            return correlation[i];
+        }
+
+        public long getTimeStamp() {
+            return timestamp;
+        }
+        
+    }
+
+    private static class TimeStampedPojoComparator implements Comparator<TimeStampedPojo> {
+
+        @Override
+        public int compare(TimeStampedPojo o1, TimeStampedPojo o2) {
+            return Long.compare(o1.getTimeStamp(), o2.getTimeStamp());
+        }
+
+        
+    }
+
+    private class Correlator implements Iterator<Correlation> {
+
+        private TimeStampedPojo[] current;
+        private Correlation last;
+        private List<Iterator<TimeStampedPojo>> seriesIterators;
+
+        private Correlator() {
+            current = new TimeStampedPojo[numSeries];
+            seriesIterators = new ArrayList<>();
+            int index = 0;
+            for (List<TimeStampedPojo> series : seriesList) {
+                Iterator<TimeStampedPojo> seriesIterator = series.iterator();
+                seriesIterators.add(seriesIterator);
+                if (seriesIterator.hasNext()) {
+                    current[index] = seriesIterator.next();
+                }
+                index++;
+            }
+            
+        }
+
+        @Override
+        public boolean hasNext() {
+            boolean hasNext = false;
+            for (TimeStampedPojo pojo : current) {
+                hasNext |= pojo != null;
+            }
+            return hasNext;
+        }
+
+        @Override
+        public Correlation next() {
+            long minTimestamp = Long.MAX_VALUE;
+            for (TimeStampedPojo pojo : current) {
+                if (pojo != null) {
+                    minTimestamp = Math.min(minTimestamp, pojo.getTimeStamp());
+                }
+            }
+            TimeStampedPojo[] next = new TimeStampedPojo[numSeries];
+            for (int i = 0; i < numSeries; i++) {
+                if (current[i] != null && current[i].getTimeStamp() == minTimestamp) {
+                    next[i] = current[i];
+                    Iterator<TimeStampedPojo> iterator = seriesIterators.get(i);
+                    current[i] = iterator.hasNext() ? iterator.next() : null;
+                } else {
+                    next[i] = last != null ? last.get(i) : null;
+                }
+            }
+            last = new Correlation(minTimestamp, next);
+            return last;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+        
+    }
+
+    private int numSeries;
+
+    private List<List<TimeStampedPojo>> seriesList;
+
+    public TimeStampedPojoCorrelator(int numSeries) {
+        this.numSeries = numSeries;
+        seriesList = new ArrayList<>();
+        for (int i = 0; i < numSeries; i++) {
+            seriesList.add(new ArrayList<TimeStampedPojo>());
+        }
+    }
+
+    public void add(int seriesIndex, TimeStampedPojo timeStampedPojo) {
+        List<? extends TimeStampedPojo> series = seriesList.get(seriesIndex);
+        int insertIdx = Collections.binarySearch(series, timeStampedPojo, new TimeStampedPojoComparator());
+        if (insertIdx < 0) {
+            insertIdx = -(insertIdx + 1);
+        }
+        seriesList.get(seriesIndex).add(insertIdx, timeStampedPojo);
+    }
+
+    public Iterator<Correlation> iterator() {
+        return new Correlator();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/utils/DisplayableValues.java	Wed May 30 20:14:55 2012 +0200
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.utils;
+
+public class DisplayableValues {
+
+    private static final long BYTES_IN_KB = 1024;
+    private static final long BYTES_IN_MB = 1024*BYTES_IN_KB;
+    private static final long BYTES_IN_GB = 1024*BYTES_IN_MB;
+    private static final long BYTES_IN_TB = 1024*BYTES_IN_GB;
+
+    private static final String BYTES_UNIT = "B";
+    private static final String KBYTES_UNIT = "KiB";
+    private static final String MBYTES_UNIT = "MiB";
+    private static final String GBYTES_UNIT = "GiB";
+    private static final String TBYTES_UNIT = "TiB";
+
+    private static final String DOUBLE_FORMAT_STRING = "%.1f";
+
+    private DisplayableValues() {} // Not to be instantiated.
+
+    public static String[] bytes(final long bytes) {
+        if (bytes < BYTES_IN_KB) {
+            return new String[] { String.valueOf(bytes), BYTES_UNIT };
+        } else if (bytes < BYTES_IN_MB) {
+            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_KB), KBYTES_UNIT };
+        } else if (bytes < BYTES_IN_GB) {
+            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_MB), MBYTES_UNIT };
+        } else if (bytes < BYTES_IN_TB) {
+            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_GB), GBYTES_UNIT };
+        } else {
+            return new String[] { String.format(DOUBLE_FORMAT_STRING, (double) bytes/BYTES_IN_TB), TBYTES_UNIT };
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/common/model/TimeStampedPojoCorrelatorTest.java	Wed May 30 20:14:55 2012 +0200
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.model.TimeStampedPojoCorrelator.Correlation;
+
+public class TimeStampedPojoCorrelatorTest {
+
+    private static class TestTimeStampedPojo implements TimeStampedPojo {
+
+        private long timestamp;
+
+        private TestTimeStampedPojo(long timestamp) {
+            this.timestamp = timestamp;
+        }
+
+        @Override
+        public long getTimeStamp() {
+            return timestamp;
+        }
+        
+    }
+
+    @Test
+    public void testOneSeries() {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(1);
+        correlator.add(0, new TestTimeStampedPojo(3));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(0, new TestTimeStampedPojo(2));
+
+        Iterator<Correlation> i = correlator.iterator();
+
+        assertTrue(i.hasNext());
+        Correlation correlation1 = i.next();
+        assertEquals(1, correlation1.get(0).getTimeStamp());
+        assertEquals(1, correlation1.getTimeStamp());
+        assertTrue(i.hasNext());
+        Correlation correlation2 = i.next();
+        assertEquals(2, correlation2.get(0).getTimeStamp());
+        assertEquals(2, correlation2.getTimeStamp());
+        assertTrue(i.hasNext());
+        Correlation correlation3 = i.next();
+        assertEquals(3, correlation3.get(0).getTimeStamp());
+        assertEquals(3, correlation3.getTimeStamp());
+        assertFalse(i.hasNext());
+    }
+
+    @Test(expected=UnsupportedOperationException.class)
+    public void testOneSeriesRemove() {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(1);
+        correlator.add(0, new TestTimeStampedPojo(3));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(0, new TestTimeStampedPojo(2));
+
+        Iterator<Correlation> i = correlator.iterator();
+        i.next();
+        i.remove();
+    }
+
+    @Test
+    public void test3SeriesInterleaving() {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(3);
+        correlator.add(0, new TestTimeStampedPojo(9));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(0, new TestTimeStampedPojo(4));
+        correlator.add(1, new TestTimeStampedPojo(8));
+        correlator.add(1, new TestTimeStampedPojo(2));
+        correlator.add(1, new TestTimeStampedPojo(5));
+        correlator.add(2, new TestTimeStampedPojo(7));
+        correlator.add(2, new TestTimeStampedPojo(3));
+        correlator.add(2, new TestTimeStampedPojo(6));
+
+        Iterator<Correlation> i = correlator.iterator();
+        assertNextCorrelation(i, 1, 1l, null, null);
+        assertNextCorrelation(i, 2, 1l, 2l, null);
+        assertNextCorrelation(i, 3, 1l, 2l, 3l);
+        assertNextCorrelation(i, 4, 4l, 2l, 3l);
+        assertNextCorrelation(i, 5, 4l, 5l, 3l);
+        assertNextCorrelation(i, 6, 4l, 5l, 6l);
+        assertNextCorrelation(i, 7, 4l, 5l, 7l);
+        assertNextCorrelation(i, 8, 4l, 8l, 7l);
+        assertNextCorrelation(i, 9, 9l, 8l, 7l);
+        assertFalse(i.hasNext());
+    }
+
+    @Test
+    public void test3SeriesColliding() {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(3);
+        correlator.add(0, new TestTimeStampedPojo(3));
+        correlator.add(0, new TestTimeStampedPojo(2));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(1, new TestTimeStampedPojo(2));
+        correlator.add(1, new TestTimeStampedPojo(1));
+        correlator.add(1, new TestTimeStampedPojo(3));
+        correlator.add(2, new TestTimeStampedPojo(1));
+        correlator.add(2, new TestTimeStampedPojo(3));
+        correlator.add(2, new TestTimeStampedPojo(2));
+
+        Iterator<Correlation> i = correlator.iterator();
+        assertNextCorrelation(i, 1, 1l, 1l, 1l);
+        assertNextCorrelation(i, 2, 2l, 2l, 2l);
+        assertNextCorrelation(i, 3, 3l, 3l, 3l);
+        assertFalse(i.hasNext());
+    }
+
+    @Test
+    public void test3SeriesMissing1() {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(3);
+        correlator.add(0, new TestTimeStampedPojo(3));
+        correlator.add(0, new TestTimeStampedPojo(2));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(1, new TestTimeStampedPojo(2));
+        correlator.add(1, new TestTimeStampedPojo(1));
+        correlator.add(1, new TestTimeStampedPojo(3));
+
+        Iterator<Correlation> i = correlator.iterator();
+        assertNextCorrelation(i, 1, 1l, 1l, null);
+        assertNextCorrelation(i, 2, 2l, 2l, null);
+        assertNextCorrelation(i, 3, 3l, 3l, null);
+        assertFalse(i.hasNext());
+    }
+
+    @Test
+    public void test3SeriesEquals() {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(3);
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(0, new TestTimeStampedPojo(1));
+        correlator.add(1, new TestTimeStampedPojo(2));
+        correlator.add(1, new TestTimeStampedPojo(2));
+        correlator.add(1, new TestTimeStampedPojo(2));
+        correlator.add(2, new TestTimeStampedPojo(3));
+        correlator.add(2, new TestTimeStampedPojo(3));
+        correlator.add(2, new TestTimeStampedPojo(3));
+
+        Iterator<Correlation> i = correlator.iterator();
+        assertNextCorrelation(i, 1, 1l, null, null);
+        assertNextCorrelation(i, 1, 1l, null, null);
+        assertNextCorrelation(i, 1, 1l, null, null);
+        assertNextCorrelation(i, 2, 1l, 2l, null);
+        assertNextCorrelation(i, 2, 1l, 2l, null);
+        assertNextCorrelation(i, 2, 1l, 2l, null);
+        assertNextCorrelation(i, 3, 1l, 2l, 3l);
+        assertNextCorrelation(i, 3, 1l, 2l, 3l);
+        assertNextCorrelation(i, 3, 1l, 2l, 3l);
+        assertFalse(i.hasNext());
+    }
+
+    private void assertNextCorrelation(Iterator<Correlation> iter, long timestamp, Long... timestamps) {
+        assertTrue(iter.hasNext());
+        Correlation correlation = iter.next();
+        assertEquals(timestamp, correlation.getTimeStamp());
+        for (int i = 0 ; i < timestamps.length; i++) {
+            if (timestamps[i] == null) {
+                assertEquals(null, correlation.get(i));
+            } else {
+                assertEquals(timestamps[i].longValue(), correlation.get(i).getTimeStamp());
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/test/java/com/redhat/thermostat/common/utils/DisplayableValuesTest.java	Wed May 30 20:14:55 2012 +0200
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.common.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Locale;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class DisplayableValuesTest {
+
+    private static Locale defaultLocale;
+
+    @BeforeClass
+    public static void setUp() {
+        defaultLocale = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+    }
+
+    @AfterClass
+    public static void tearDown() {
+        Locale.setDefault(defaultLocale);
+    }
+
+    @Test
+    public void testBytes() {
+        testBytesOutput("1", "B", DisplayableValues.bytes(1));
+        testBytesOutput("1023", "B", DisplayableValues.bytes(1023));
+        testBytesOutput("1.0", "KiB", DisplayableValues.bytes(1024));
+        testBytesOutput("1024.0", "KiB", DisplayableValues.bytes(1_048_575));
+        testBytesOutput("1.0", "MiB", DisplayableValues.bytes(1_048_576));
+        testBytesOutput("10.0", "MiB", DisplayableValues.bytes(10_480_000));
+        testBytesOutput("42.0", "MiB", DisplayableValues.bytes(44_040_000));
+        testBytesOutput("99.9", "MiB", DisplayableValues.bytes(104_752_742));
+        testBytesOutput("100.0", "MiB", DisplayableValues.bytes(104_857_600));
+        testBytesOutput("500.0", "MiB", DisplayableValues.bytes(524_288_000));
+        testBytesOutput("900.0", "MiB", DisplayableValues.bytes(943_718_400));
+        testBytesOutput("999.9", "MiB", DisplayableValues.bytes(1_048_471_000));
+        testBytesOutput("1.0", "GiB", DisplayableValues.bytes(1_073_741_824));
+        testBytesOutput("1.1", "GiB", DisplayableValues.bytes(1_181_116_000));
+        testBytesOutput("9.9", "GiB", DisplayableValues.bytes(10_630_044_000l));
+        testBytesOutput("99.9", "GiB", DisplayableValues.bytes(107_266_808_000l));
+        testBytesOutput("1.0", "TiB", DisplayableValues.bytes(1_099_511_627_776l));
+    }
+
+    private void testBytesOutput(String number, String units, String[] output) {
+        assertEquals(2, output.length);
+        assertEquals(number, output[0]);
+        assertEquals(units, output[1]);
+    }
+}
--- a/tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatCommand.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/main/java/com/redhat/thermostat/tools/cli/VMStatCommand.java	Wed May 30 20:14:55 2012 +0200
@@ -37,8 +37,12 @@
 package com.redhat.thermostat.tools.cli;
 
 import java.io.PrintStream;
+import java.text.DateFormat;
 import java.text.DecimalFormat;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
 import java.util.List;
 
 import com.redhat.thermostat.common.appctx.ApplicationContext;
@@ -48,8 +52,12 @@
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.dao.DAOFactory;
 import com.redhat.thermostat.common.dao.VmCpuStatDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.common.model.TimeStampedPojoCorrelator;
 import com.redhat.thermostat.common.model.VmCpuStat;
+import com.redhat.thermostat.common.model.VmMemoryStat;
+import com.redhat.thermostat.common.utils.DisplayableValues;
 
 public class VMStatCommand implements Command {
 
@@ -57,25 +65,118 @@
     private static final String CMD_DESCRIPTION = "show various statistics about a VM";
 
     private static final String CPU_PERCENT = "%CPU";
+    private static final String MEM_PREFIX = "MEM.";
+    private static final String TIME = "TIME";
 
     @Override
     public void run(CommandContext ctx) throws CommandException {
         DAOFactory daoFactory = ApplicationContext.getInstance().getDAOFactory();
         VmCpuStatDAO vmCpuStatDAO = daoFactory.getVmCpuStatDAO();
+        VmMemoryStatDAO vmMemoryStatDAO = daoFactory.getVmMemoryStatDAO();
+
         HostVMArguments hostVMArgs = new HostVMArguments(ctx.getArguments());
         VmRef vm = hostVMArgs.getVM();
         List<VmCpuStat> cpuStats = vmCpuStatDAO.getLatestVmCpuStats(vm);
-        printStats(ctx.getConsole().getOutput(), cpuStats);
+        List<VmMemoryStat> memStats = vmMemoryStatDAO.getLatestVmMemoryStats(vm);
+        printStats(ctx.getConsole().getOutput(), cpuStats, memStats);
+    }
+
+    private void printStats(PrintStream out, List<VmCpuStat> cpuStats, List<VmMemoryStat> memStats) {
+        TimeStampedPojoCorrelator correlator = correlate(cpuStats, memStats);
+        int numSpaces = getNumSpaces(memStats);
+        int numColumns = numSpaces + 2;
+        TableRenderer table = new TableRenderer(numColumns);
+        printHeaders(memStats, numSpaces, numColumns, table);
+        Iterator<TimeStampedPojoCorrelator.Correlation> i = correlator.iterator();
+        while (i.hasNext()) {
+            printStats(numSpaces, table, i);
+        }
+        table.render(out);
+    }
+
+    private void printStats(int numSpaces, TableRenderer table, Iterator<TimeStampedPojoCorrelator.Correlation> i) {
+
+        TimeStampedPojoCorrelator.Correlation correlation = i.next();
+
+        VmCpuStat cpuStat = (VmCpuStat) correlation.get(0);
+        DecimalFormat format = new DecimalFormat("#0.0");
+        String cpuLoad = cpuStat != null ? format.format(cpuStat.getCpuLoad()) : "";
+
+        DateFormat dateFormat = DateFormat.getTimeInstance();
+        String time = dateFormat.format(new Date(correlation.getTimeStamp()));
+
+        String[] memoryUsage = getMemoryUsage((VmMemoryStat) correlation.get(1), numSpaces);
+
+        String[] line = new String[numSpaces + 2];
+        System.arraycopy(memoryUsage, 0, line, 2, numSpaces);
+        line[0] = time;
+        line[1] = cpuLoad;
+        table.printLine(line);
+    }
+
+    private void printHeaders(List<VmMemoryStat> memStats, int numSpaces, int numColumns, TableRenderer table) {
+        String[] spacesNames = getSpacesNames(memStats, numSpaces);
+        String[] headers = new String[numColumns];
+        headers[0] = TIME;
+        headers[1] = CPU_PERCENT;
+        System.arraycopy(spacesNames, 0, headers, 2, numSpaces);
+        table.printLine(headers);
     }
 
-    private void printStats(PrintStream out, List<VmCpuStat> cpuStats) {
-        TableRenderer table = new TableRenderer(1);
-        table.printLine(CPU_PERCENT);
-        for (VmCpuStat cpuStat : cpuStats) {
-            DecimalFormat format = new DecimalFormat("#0.0");
-            table.printLine(format.format(cpuStat.getCpuLoad()));
+    private String[] getMemoryUsage(VmMemoryStat vmMemoryStat, int numSpaces) {
+        String[] memoryUsage = new String[numSpaces];
+        if (vmMemoryStat == null) {
+            Arrays.fill(memoryUsage, "");
+            return memoryUsage;
+        }
+        int i = 0;
+        for (VmMemoryStat.Generation gen : vmMemoryStat.getGenerations()) {
+            for (VmMemoryStat.Space space : gen.spaces) {
+                String[] displayableSize = DisplayableValues.bytes(space.used);
+                memoryUsage[i] = displayableSize[0] + " " + displayableSize[1];
+                i++;
+            }
+        }
+        return memoryUsage;
+    }
+
+    private String[] getSpacesNames(List<VmMemoryStat> memStats, int numSpaces) {
+        if (numSpaces < 1) {
+            return new String[0];
         }
-        table.render(out);
+        String[] spacesNames = new String[numSpaces];
+        VmMemoryStat stat = memStats.get(0);
+        int i = 0;
+        for (VmMemoryStat.Generation gen : stat.getGenerations()) {
+            for (VmMemoryStat.Space space : gen.spaces) {
+                spacesNames[i] = MEM_PREFIX + space.name;
+                i++;
+            }
+        }
+        return spacesNames;
+    }
+
+    private int getNumSpaces(List<VmMemoryStat> memStats) {
+        if (memStats.size() < 1) {
+            return 0;
+        }
+        VmMemoryStat stat = memStats.get(0);
+        int numSpaces = 0;
+        for (VmMemoryStat.Generation gen : stat.getGenerations()) {
+            numSpaces += gen.spaces.size();
+        }
+        return numSpaces;
+    }
+
+    private TimeStampedPojoCorrelator correlate(List<VmCpuStat> cpuStats, List<VmMemoryStat> memStats) {
+        TimeStampedPojoCorrelator correlator = new TimeStampedPojoCorrelator(2);
+        for(VmCpuStat cpuStat : cpuStats) {
+            correlator.add(0, cpuStat);
+        }
+        for (VmMemoryStat memStat : memStats) {
+            correlator.add(1, memStat);
+        }
+        return correlator;
     }
 
     @Override
--- a/tools/src/test/java/com/redhat/thermostat/tools/cli/VmStatCommandTest.java	Tue May 29 12:19:43 2012 +0200
+++ b/tools/src/test/java/com/redhat/thermostat/tools/cli/VmStatCommandTest.java	Wed May 30 20:14:55 2012 +0200
@@ -63,8 +63,12 @@
 import com.redhat.thermostat.common.dao.DAOFactory;
 import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.common.dao.VmCpuStatDAO;
+import com.redhat.thermostat.common.dao.VmMemoryStatDAO;
 import com.redhat.thermostat.common.dao.VmRef;
 import com.redhat.thermostat.common.model.VmCpuStat;
+import com.redhat.thermostat.common.model.VmMemoryStat;
+import com.redhat.thermostat.common.model.VmMemoryStat.Generation;
+import com.redhat.thermostat.common.model.VmMemoryStat.Space;
 import com.redhat.thermostat.test.TestCommandContextFactory;
 
 public class VmStatCommandTest {
@@ -86,6 +90,7 @@
     private VmCpuStatDAO vmCpuStatDAO;
     private AppContextSetup appContextSetup;
     private TestCommandContextFactory cmdCtxFactory;
+    private VmMemoryStatDAO vmMemoryStatDAO;
 
     @Before
     public void setUp() {
@@ -123,23 +128,90 @@
         int vmId = 234;
         HostRef host = new HostRef("123", "dummy");
         VmRef vm = new VmRef(host, 234, "dummy");
-        VmCpuStat cpustat1 = new VmCpuStat(123, vmId, 50.123454);
-        VmCpuStat cpustat2 = new VmCpuStat(123, vmId, 65);
-        VmCpuStat cpustat3 = new VmCpuStat(123, vmId, 70);
-        List<VmCpuStat> cpuStats = Arrays.asList(cpustat1, cpustat2, cpustat3);
+        VmCpuStat cpustat1 = new VmCpuStat(2, vmId, 65);
+        VmCpuStat cpustat2 = new VmCpuStat(3, vmId, 70);
+        List<VmCpuStat> cpuStats = Arrays.asList(cpustat1, cpustat2);
         when(vmCpuStatDAO.getLatestVmCpuStats(vm)).thenReturn(cpuStats);
         DAOFactory daoFactory = mock(DAOFactory.class);
         when(daoFactory.getVmCpuStatDAO()).thenReturn(vmCpuStatDAO);
         ApplicationContext.getInstance().setDAOFactory(daoFactory);
+
+        VmMemoryStat.Space space1_1_1 = newSpace("space1", 123456, 12345, 1, 0);
+        VmMemoryStat.Space space1_1_2 = newSpace("space2", 123456, 12345, 1, 0);
+        List<VmMemoryStat.Space> spaces1_1 = Arrays.asList(space1_1_1, space1_1_2);
+        VmMemoryStat.Generation gen1_1 = newGeneration("gen1", "col1", 123456, 12345, spaces1_1);
+
+        VmMemoryStat.Space space1_2_1 = newSpace("space3", 123456, 12345, 1, 0);
+        VmMemoryStat.Space space1_2_2 = newSpace("space4", 123456, 12345, 1, 0);
+        List<VmMemoryStat.Space> spaces1_2 = Arrays.asList(space1_2_1, space1_2_2);
+        VmMemoryStat.Generation gen1_2 = newGeneration("gen2", "col1", 123456, 12345, spaces1_2);
+
+        List<VmMemoryStat.Generation> gens1 = Arrays.asList(gen1_1, gen1_2);
+
+        VmMemoryStat memStat1 = new VmMemoryStat(1, vmId, gens1);
+
+        VmMemoryStat.Space space2_1_1 = newSpace("space1", 123456, 12345, 2, 0);
+        VmMemoryStat.Space space2_1_2 = newSpace("space2", 123456, 12345, 2, 0);
+        List<VmMemoryStat.Space> spaces2_1 = Arrays.asList(space2_1_1, space2_1_2);
+        VmMemoryStat.Generation gen2_1 = newGeneration("gen1", "col1", 123456, 12345, spaces2_1);
+
+        VmMemoryStat.Space space2_2_1 = newSpace("space3", 123456, 12345, 3, 0);
+        VmMemoryStat.Space space2_2_2 = newSpace("space4", 123456, 12345, 4, 0);
+        List<VmMemoryStat.Space> spaces2_2 = Arrays.asList(space2_2_1, space2_2_2);
+        VmMemoryStat.Generation gen2_2 = newGeneration("gen2", "col1", 123456, 12345, spaces2_2);
+
+        List<VmMemoryStat.Generation> gens2 = Arrays.asList(gen2_1, gen2_2);
+
+        VmMemoryStat memStat2 = new VmMemoryStat(2, vmId, gens2);
+
+        VmMemoryStat.Space space3_1_1 = newSpace("space1", 123456, 12345, 4, 0);
+        VmMemoryStat.Space space3_1_2 = newSpace("space2", 123456, 12345, 5, 0);
+        List<VmMemoryStat.Space> spaces3_1 = Arrays.asList(space3_1_1, space3_1_2);
+        VmMemoryStat.Generation gen3_1 = newGeneration("gen1", "col1", 123456, 12345, spaces3_1);
+
+        VmMemoryStat.Space space3_2_1 = newSpace("space3", 123456, 12345, 6, 0);
+        VmMemoryStat.Space space3_2_2 = newSpace("space4", 123456, 12345, 7, 0);
+        List<VmMemoryStat.Space> spaces3_2 = Arrays.asList(space3_2_1, space3_2_2);
+        VmMemoryStat.Generation gen3_2 = newGeneration("gen2", "col1", 123456, 12345, spaces3_2);
+
+        List<VmMemoryStat.Generation> gens3 = Arrays.asList(gen3_1, gen3_2);
+
+        VmMemoryStat memStat3 = new VmMemoryStat(3, vmId, gens3);
+
+        vmMemoryStatDAO = mock(VmMemoryStatDAO.class);
+        when(vmMemoryStatDAO.getLatestVmMemoryStats(vm)).thenReturn(Arrays.asList(memStat1, memStat2, memStat3));
+        when(daoFactory.getVmMemoryStatDAO()).thenReturn(vmMemoryStatDAO);
+    }
+
+    private Space newSpace(String name, long maxCapacity, long capacity, long used, int index) {
+        VmMemoryStat.Space space = new VmMemoryStat.Space();
+        space.name = name;
+        space.maxCapacity = maxCapacity;
+        space.capacity = capacity;
+        space.used = used;
+        space.index = index;
+        return space;
+    }
+
+    private Generation newGeneration(String name, String collector, long maxCapacity, long capacity, List<Space> spaces) {
+        VmMemoryStat.Generation gen = new VmMemoryStat.Generation();
+        gen.name = name;
+        gen.collector = collector;
+        gen.maxCapacity = capacity;
+        gen.spaces = spaces;
+        return gen;
     }
 
     @Test
-    public void testBasicCPU() throws CommandException {
+    public void testBasicCPUMemory() throws CommandException {
         SimpleArguments args = new SimpleArguments();
         args.addArgument("vmId", "234");
         args.addArgument("hostId", "123");
         cmd.run(cmdCtxFactory.createContext(args));
-        String expected = "%CPU\n50.1\n65.0\n70.0\n";
+        String expected = "TIME       %CPU MEM.space1 MEM.space2 MEM.space3 MEM.space4\n" +
+                          "1:00:00 AM      1 B        1 B        1 B        1 B\n" +
+                          "1:00:00 AM 65.0 2 B        2 B        3 B        4 B\n" +
+                          "1:00:00 AM 70.0 4 B        5 B        6 B        7 B\n";
         assertEquals(expected, cmdCtxFactory.getOutput());
 
     }