changeset 156:ddb36e8b9995

Tests for VmCpuStatBuilder Reviewed-by: rkennke, vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-March/000488.html
author Omair Majid <omajid@redhat.com>
date Thu, 29 Mar 2012 11:31:43 -0400
parents 08829d9943de
children a9f2b8a4dbbb
files agent/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java agent/src/main/java/com/redhat/thermostat/backend/system/VmCpuStatBuilder.java agent/src/test/java/com/redhat/thermostat/backend/system/VmCpuStatBuilderTest.java common/src/main/java/com/redhat/thermostat/common/Clock.java common/src/main/java/com/redhat/thermostat/common/SystemClock.java common/src/main/java/com/redhat/thermostat/common/model/VmCpuStat.java
diffstat 6 files changed, 279 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/agent/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Thu Mar 29 13:15:28 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/backend/system/SystemBackend.java	Thu Mar 29 11:31:43 2012 -0400
@@ -55,6 +55,8 @@
 import com.redhat.thermostat.agent.JvmStatusListener;
 import com.redhat.thermostat.agent.JvmStatusNotifier;
 import com.redhat.thermostat.backend.Backend;
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.common.SystemClock;
 import com.redhat.thermostat.common.dao.CpuStatConverter;
 import com.redhat.thermostat.common.dao.CpuStatDAO;
 import com.redhat.thermostat.common.dao.HostInfoConverter;
@@ -92,8 +94,16 @@
 
     private Set<Integer> pidsToMonitor = new CopyOnWriteArraySet<Integer>();
 
+    private final VmCpuStatBuilder vmCpuBuilder;
+
     private static List<Category> categories = new ArrayList<Category>();
 
+    public SystemBackend() {
+        Clock clock = new SystemClock();
+        ProcessStatusInfoBuilder builder = new ProcessStatusInfoBuilder(new ProcDataSource());
+        long ticksPerSecond = SysConf.getClockTicksPerSecond();
+        vmCpuBuilder = new VmCpuStatBuilder(clock, ticksPerSecond, builder);
+    }
 
     static {
         // Set up categories that will later be registered.
@@ -153,8 +163,11 @@
                 store(new MemoryStatConverter().memoryStatToChunk(new MemoryStatBuilder(dataSource).build()));
 
                 for (Integer pid : pidsToMonitor) {
-                    new VmCpuStatBuilder();
-                    store(new VmCpuStatConverter().vmCpuStatToChunk(VmCpuStatBuilder.build(pid)));
+                    if (vmCpuBuilder.knowsAbout(pid)) {
+                        store(new VmCpuStatConverter().vmCpuStatToChunk(vmCpuBuilder.build(pid)));
+                    } else {
+                        vmCpuBuilder.learnAbout(pid);
+                    }
                 }
             }
         }, 0, procCheckInterval);
@@ -234,5 +247,6 @@
     @Override
     public void jvmStopped(int vmId) {
         pidsToMonitor.remove(vmId);
+        vmCpuBuilder.forgetAbout(vmId);
     }
 }
--- a/agent/src/main/java/com/redhat/thermostat/backend/system/VmCpuStatBuilder.java	Thu Mar 29 13:15:28 2012 +0200
+++ b/agent/src/main/java/com/redhat/thermostat/backend/system/VmCpuStatBuilder.java	Thu Mar 29 11:31:43 2012 -0400
@@ -39,16 +39,25 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import com.redhat.thermostat.common.Clock;
 import com.redhat.thermostat.common.model.VmCpuStat;
 
 public class VmCpuStatBuilder {
 
     // pid -> ticks
-    private static Map<Integer, Long> lastProcessTicks = new HashMap<Integer, Long>();
+    private final Map<Integer, Long> lastProcessTicks = new HashMap<Integer, Long>();
     // pid -> last time the ticks were updated
-    private static Map<Integer, Long> lastProcessTickTime = new HashMap<Integer, Long>();
+    private final Map<Integer, Long> lastProcessTickTime = new HashMap<Integer, Long>();
+
+    private final Clock clock;
+    private final long ticksPerSecond;
+    private final ProcessStatusInfoBuilder statusBuilder;
 
-    private static long clockTicksPerSecond = SysConf.getClockTicksPerSecond();
+    public VmCpuStatBuilder(Clock clock, long ticksPerSecond, ProcessStatusInfoBuilder statusBuilder) {
+        this.clock = clock;
+        this.ticksPerSecond = ticksPerSecond;
+        this.statusBuilder = statusBuilder;
+    }
 
     /**
      * To build the stat, this method needs to be called repeatedly. The first
@@ -58,26 +67,43 @@
      * @param pid
      * @return
      */
-    public static synchronized VmCpuStat build(Integer pid) {
+    public synchronized VmCpuStat build(Integer pid) {
+        if (!lastProcessTicks.containsKey(pid) || !lastProcessTickTime.containsKey(pid)) {
+            throw new IllegalArgumentException("unknown pid");
+        }
 
-        ProcDataSource dataSource = new ProcDataSource();
-        ProcessStatusInfo info = new ProcessStatusInfoBuilder(dataSource).build(pid);
-        long miliTime = System.currentTimeMillis();
-        long time = System.nanoTime();
+        ProcessStatusInfo info = statusBuilder.build(pid);
+        long miliTime = clock.getRealTimeMillis();
+        long time = clock.getMonotonicTimeNanos();
         long programTicks = (info.getKernelTime() + info.getUserTime());
         double cpuLoad = 0.0;
 
-        if (lastProcessTicks.containsKey(pid)) {
-            double timeDelta = (time - lastProcessTickTime.get(pid)) * 1E-9;
-            long programTicksDelta = programTicks - lastProcessTicks.get(pid);
-            cpuLoad = programTicksDelta * (100.0 / timeDelta / clockTicksPerSecond);
-        }
+        double timeDelta = (time - lastProcessTickTime.get(pid)) * 1E-9;
+        long programTicksDelta = programTicks - lastProcessTicks.get(pid);
+        // 100 as in 100 percent.
+        cpuLoad = programTicksDelta * (100.0 / timeDelta / ticksPerSecond);
 
         lastProcessTicks.put(pid, programTicks);
         lastProcessTickTime.put(pid, time);
 
-
         return new VmCpuStat(miliTime, pid, cpuLoad);
     }
 
+    public synchronized boolean knowsAbout(int pid) {
+        return (lastProcessTickTime.containsKey(pid) && lastProcessTicks.containsKey(pid));
+    }
+
+    public synchronized void learnAbout(int pid) {
+        long time = clock.getMonotonicTimeNanos();
+        ProcessStatusInfo info = statusBuilder.build(pid);
+
+        lastProcessTickTime.put(pid, time);
+        lastProcessTicks.put(pid, info.getUserTime()+ info.getKernelTime());
+    }
+
+    public synchronized void forgetAbout(int pid) {
+        lastProcessTicks.remove(pid);
+        lastProcessTickTime.remove(pid);
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/test/java/com/redhat/thermostat/backend/system/VmCpuStatBuilderTest.java	Thu Mar 29 11:31:43 2012 -0400
@@ -0,0 +1,117 @@
+/*
+ * 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.backend.system;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.common.model.VmCpuStat;
+
+public class VmCpuStatBuilderTest {
+
+    @Test
+    public void testBuilderKnowsNothing() {
+        Clock clock = mock(Clock.class);
+        ProcessStatusInfoBuilder statusBuilder = mock(ProcessStatusInfoBuilder.class);
+        long ticksPerSecond = 0;
+        VmCpuStatBuilder builder = new VmCpuStatBuilder(clock, ticksPerSecond, statusBuilder);
+
+        assertFalse(builder.knowsAbout(0));
+        assertFalse(builder.knowsAbout(1));
+        assertFalse(builder.knowsAbout(Integer.MIN_VALUE));
+        assertFalse(builder.knowsAbout(Integer.MAX_VALUE));
+
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testBuilderThrowsOnBuildOfUnknownPid() {
+        Clock clock = mock(Clock.class);
+        long ticksPerSecond = 0;
+        ProcessStatusInfoBuilder statusBuilder = mock(ProcessStatusInfoBuilder.class);
+        VmCpuStatBuilder builder = new VmCpuStatBuilder(clock, ticksPerSecond, statusBuilder);
+        builder.build(0);
+    }
+
+    @Test
+    public void testSaneBuild() {
+        final int PID = 0;
+        final long USER_INITIAL_TICKS = 1;
+        final long KERNEL_INITIAL_TICKS = 1;
+
+        final long USER_LATER_TICKS = 10;
+        final long KERNEL_LATER_TICKS = 10;
+
+        final long CLOCK1 = 10000;
+        final long CLOCK2 = 20000;
+
+        final long TICKS_PER_SECOND = 100;
+
+        final double CPU_LOAD_PERCENT =
+                100.0
+                * ((USER_LATER_TICKS + KERNEL_LATER_TICKS) - (USER_INITIAL_TICKS + KERNEL_INITIAL_TICKS))
+                / TICKS_PER_SECOND
+                / ((CLOCK2 - CLOCK1) * 1E-3 /* millis to seconds */);
+
+        final ProcessStatusInfo initialInfo = new ProcessStatusInfo(PID, USER_INITIAL_TICKS, KERNEL_INITIAL_TICKS);
+        final ProcessStatusInfo laterInfo = new ProcessStatusInfo(PID, USER_LATER_TICKS, KERNEL_LATER_TICKS);
+
+        Clock clock = mock(Clock.class);
+        when(clock.getRealTimeMillis()).thenReturn(CLOCK2);
+        when(clock.getMonotonicTimeNanos()).thenReturn((long) (CLOCK1 * 1E6)).thenReturn((long) (CLOCK2 * 1E6));
+
+        ProcessStatusInfoBuilder statusBuilder = mock(ProcessStatusInfoBuilder.class);
+        when(statusBuilder.build(any(Integer.class))).thenReturn(initialInfo).thenReturn(laterInfo).thenReturn(null);
+
+        VmCpuStatBuilder builder = new VmCpuStatBuilder(clock, TICKS_PER_SECOND, statusBuilder);
+
+        builder.learnAbout(PID);
+        VmCpuStat stat = builder.build(PID);
+
+        assertNotNull(stat);
+        assertEquals(PID, stat.getVmId());
+        assertEquals(CLOCK2, stat.getTimeStamp());
+        assertEquals(CPU_LOAD_PERCENT, stat.getCpuLoad(), 0.0001);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/Clock.java	Thu Mar 29 11:31:43 2012 -0400
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+public interface Clock {
+
+    /**
+     * Returns the current real time in milliseconds (measured since the UNIX epoch).
+     */
+    long getRealTimeMillis();
+
+    /**
+     * Returns a time value corresponding to a monotonic clock measuring time
+     * in nanoseconds since some unspecified epoch.
+     */
+    long getMonotonicTimeNanos();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/src/main/java/com/redhat/thermostat/common/SystemClock.java	Thu Mar 29 11:31:43 2012 -0400
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+public class SystemClock implements Clock {
+
+    @Override
+    public long getRealTimeMillis() {
+        return System.currentTimeMillis();
+    }
+
+    @Override
+    public long getMonotonicTimeNanos() {
+        return System.nanoTime();
+    }
+
+}
--- a/common/src/main/java/com/redhat/thermostat/common/model/VmCpuStat.java	Thu Mar 29 13:15:28 2012 +0200
+++ b/common/src/main/java/com/redhat/thermostat/common/model/VmCpuStat.java	Thu Mar 29 11:31:43 2012 -0400
@@ -56,6 +56,9 @@
         return vmId;
     }
 
+    /**
+     * The cpu load in percent (as in 100.0 for 100%)
+     */
     public double getCpuLoad() {
         return cpuLoad;
     }