changeset 2576:15a168c5a68d

Remove vm-numa dependency on external 'numastats' process Reviewed-by: stooke Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-December/021854.html
author Andrew Azores <aazores@redhat.com>
date Mon, 30 Jan 2017 16:06:59 -0500
parents 902e2e96f4e8
children 7dfe66ada478
files common/portability/src/main/java/com/redhat/thermostat/common/portability/SysConf.java common/portability/src/main/java/com/redhat/thermostat/common/portability/linux/ProcDataSource.java common/portability/src/test/java/com/redhat/thermostat/common/portability/internal/SysConfTest.java common/portability/src/test/java/com/redhat/thermostat/common/portability/linux/ProcDataSourceTest.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/Activator.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/NumaMapsReaderProvider.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/NumaMapsReaderProviderImpl.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/PageSizeProvider.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/PageSizeProviderImpl.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaBackend.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaCollector.java vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaStatParser.java vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/ActivatorTest.java vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/NumaMapsReaderProviderImplTest.java vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaBackendTest.java vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaCollectorTest.java vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaStatParserTest.java
diffstat 17 files changed, 609 insertions(+), 396 deletions(-) [+]
line wrap: on
line diff
--- a/common/portability/src/main/java/com/redhat/thermostat/common/portability/SysConf.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/common/portability/src/main/java/com/redhat/thermostat/common/portability/SysConf.java	Mon Jan 30 16:06:59 2017 -0500
@@ -70,6 +70,14 @@
         }
     }
 
+    public static long getPageSize() {
+        try {
+            return Long.valueOf(sysConf("PAGESIZE"));
+        } catch (NumberFormatException nfe) {
+            return 0;
+        }
+    }
+
     private static String sysConf(String arg) {
         try {
             Process process = Runtime.getRuntime().exec(new String[] { "getconf", arg });
--- a/common/portability/src/main/java/com/redhat/thermostat/common/portability/linux/ProcDataSource.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/common/portability/src/main/java/com/redhat/thermostat/common/portability/linux/ProcDataSource.java	Mon Jan 30 16:06:59 2017 -0500
@@ -60,6 +60,7 @@
     private static final String PID_IO_FILE = "/proc/${pid}/io";
     private static final String PID_STAT_FILE = "/proc/${pid}/stat";
     private static final String PID_STATUS_FILE = "/proc/${pid}/status";
+    private static final String PID_NUMA_MAPS_FILE = "/proc/${pid}/numa_maps";
 
     /**
      * Returns a reader for /proc/cpuinfo
@@ -109,7 +110,7 @@
     public Reader getStatReader(int pid) throws IOException {
         return new FileReader(getPidFile(PID_STAT_FILE, pid));
     }
-    
+
     /**
      * Returns a reader for /proc/$PID/status
      */
@@ -117,6 +118,13 @@
         return new FileReader(getPidFile(PID_STATUS_FILE, pid));
     }
 
+    /**
+     * Returns a reader for /proc/$PID/numa_maps
+     */
+    public Reader getNumaMapsReader(int pid) throws IOException {
+        return new FileReader(getPidFile(PID_NUMA_MAPS_FILE, pid));
+    }
+
     private String getPidFile(String fileName, int pid) {
         return fileName.replace("${pid}", Integer.toString(pid));
     }
--- a/common/portability/src/test/java/com/redhat/thermostat/common/portability/internal/SysConfTest.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/common/portability/src/test/java/com/redhat/thermostat/common/portability/internal/SysConfTest.java	Mon Jan 30 16:06:59 2017 -0500
@@ -44,9 +44,15 @@
 public class SysConfTest {
 
     @Test
-    public void test() {
+    public void testTicksPerSecond() {
         long ticksPerSecond = SysConf.getClockTicksPerSecond();
         assertTrue(ticksPerSecond >= 1);
     }
+
+    @Test
+    public void testPageSize() {
+        long pagesize = SysConf.getPageSize();
+        assertTrue(pagesize >= 1);
+    }
 }
 
--- a/common/portability/src/test/java/com/redhat/thermostat/common/portability/linux/ProcDataSourceTest.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/common/portability/src/test/java/com/redhat/thermostat/common/portability/linux/ProcDataSourceTest.java	Mon Jan 30 16:06:59 2017 -0500
@@ -109,5 +109,13 @@
         Reader r = new ProcDataSource().getStatusReader(pid);
         assertNotNull(r);
     }
+
+    @Test
+    public void testNumaMapsReader() throws Exception {
+        Assume.assumeTrue(OS.IS_LINUX);
+        int pid = TestUtils.getProcessId();
+        Reader r = new ProcDataSource().getNumaMapsReader(pid);
+        assertNotNull(r);
+    }
 }
 
--- a/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/Activator.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/Activator.java	Mon Jan 30 16:06:59 2017 -0500
@@ -36,9 +36,15 @@
 
 package com.redhat.thermostat.vm.numa.agent.internal;
 
+import java.io.IOException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.common.SystemClock;
+import com.redhat.thermostat.common.utils.LoggingUtils;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
@@ -54,6 +60,8 @@
 
 public class Activator implements BundleActivator {
 
+    private static final Logger logger = LoggingUtils.getLogger(Activator.class);
+
     private ScheduledExecutorService executor;
     private MultipleServiceTracker tracker;
     private VmNumaBackend backend;
@@ -75,15 +83,20 @@
                 VmNumaDAO vmNumaDAO = services.get(VmNumaDAO.class);
                 Version version = new Version(context.getBundle());
                 WriterID writerID = services.get(WriterID.class);
-                backend = constructBackend(executor, vmNumaDAO, version, registrar, writerID);
-                if (backend.canRegister()) {
+                Clock clock = new SystemClock();
+                try {
+                    PageSizeProvider pageSizeProvider = new PageSizeProviderImpl();
+                    NumaMapsReaderProvider readerProvider = new NumaMapsReaderProviderImpl();
+                    backend = constructBackend(executor, clock, readerProvider, pageSizeProvider, vmNumaDAO, version, registrar, writerID);
                     reg = context.registerService(Backend.class, backend, null);
+                } catch (IOException e) {
+                    logger.log(Level.WARNING, "Unexpected exception retrieving Linux page sizes. NUMA counts will be disabled", e);
                 }
             }
 
             @Override
             public void dependenciesUnavailable() {
-                if (backend.isActive()) {
+                if (backend != null && backend.isActive()) {
                     backend.deactivate();
                 }
                 if (reg != null) {
@@ -93,7 +106,6 @@
         });
 
         tracker.open();
-
     }
 
     @Override
@@ -107,7 +119,8 @@
     }
 
     //Package private for testing
-    VmNumaBackend constructBackend(ScheduledExecutorService executor, VmNumaDAO vmNumaDAO, Version version, VmStatusListenerRegistrar registrar, WriterID writerID) {
-        return new VmNumaBackend(executor, vmNumaDAO, version, registrar, writerID);
+    VmNumaBackend constructBackend(ScheduledExecutorService executor, Clock clock, NumaMapsReaderProvider readerProvider, PageSizeProvider pageSizeProvider,
+                                   VmNumaDAO vmNumaDAO, Version version, VmStatusListenerRegistrar registrar, WriterID writerID) {
+        return new VmNumaBackend(executor, clock, readerProvider, pageSizeProvider, vmNumaDAO, version, registrar, writerID);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/NumaMapsReaderProvider.java	Mon Jan 30 16:06:59 2017 -0500
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012-2017 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.vm.numa.agent.internal;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+interface NumaMapsReaderProvider {
+    BufferedReader createReader(int forPid) throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/NumaMapsReaderProviderImpl.java	Mon Jan 30 16:06:59 2017 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2017 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.vm.numa.agent.internal;
+
+import com.redhat.thermostat.common.portability.linux.ProcDataSource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+class NumaMapsReaderProviderImpl implements NumaMapsReaderProvider {
+
+    private final ProcDataSource procDataSource;
+
+    NumaMapsReaderProviderImpl() {
+        this(new ProcDataSource());
+    }
+
+    // testing hook only
+    NumaMapsReaderProviderImpl(ProcDataSource procDataSource) {
+        this.procDataSource = procDataSource;
+    }
+
+    @Override
+    public BufferedReader createReader(int forPid) throws IOException {
+        return new BufferedReader(procDataSource.getNumaMapsReader(forPid));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/PageSizeProvider.java	Mon Jan 30 16:06:59 2017 -0500
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012-2017 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.vm.numa.agent.internal;
+
+interface PageSizeProvider {
+    long getPageSize();
+    long getHugePageSize();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/PageSizeProviderImpl.java	Mon Jan 30 16:06:59 2017 -0500
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012-2017 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.vm.numa.agent.internal;
+
+import com.redhat.thermostat.common.portability.SysConf;
+import com.redhat.thermostat.common.portability.linux.ProcDataSource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+class PageSizeProviderImpl implements PageSizeProvider {
+
+    private static final Pattern HUGE_PAGESIZE_PATTERN = Pattern.compile("Hugepagesize:[\\s]+([\\d]+) kB");
+    private long pagesize = 4L * 1024L;
+    private long hugePagesize = 2048L * 1024L;
+
+    PageSizeProviderImpl() throws IOException {
+        pagesize = SysConf.getPageSize();
+
+        try (BufferedReader br = new BufferedReader(new ProcDataSource().getMemInfoReader())) {
+            for (String line = br.readLine(); line != null; line = br.readLine()) {
+                Matcher matcher = HUGE_PAGESIZE_PATTERN.matcher(line);
+                if (matcher.matches()) {
+                    hugePagesize = Long.parseLong(matcher.group(1)) * 1024;
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public long getPageSize() {
+        return pagesize;
+    }
+
+    @Override
+    public long getHugePageSize() {
+        return hugePagesize;
+    }
+}
--- a/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaBackend.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaBackend.java	Mon Jan 30 16:06:59 2017 -0500
@@ -37,7 +37,6 @@
 package com.redhat.thermostat.vm.numa.agent.internal;
 
 import java.io.IOException;
-import java.text.ParseException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
@@ -47,6 +46,7 @@
 import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
 import com.redhat.thermostat.backend.VmPollingAction;
 import com.redhat.thermostat.backend.VmPollingBackend;
+import com.redhat.thermostat.common.Clock;
 import com.redhat.thermostat.common.Version;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.WriterID;
@@ -55,16 +55,16 @@
 
 public class VmNumaBackend extends VmPollingBackend {
 
+    private static final Logger logger = LoggingUtils.getLogger(VmNumaBackend.class);
     private final VmNumaBackendAction action;
-    private static final Logger logger = LoggingUtils.getLogger(VmNumaBackend.class);
 
-    public VmNumaBackend(ScheduledExecutorService executor, VmNumaDAO vmNumaDAO, Version version,
-                         VmStatusListenerRegistrar registrar, WriterID id) {
+    public VmNumaBackend(ScheduledExecutorService executor, Clock clock, NumaMapsReaderProvider readerProvider, PageSizeProvider pageSizeProvider,
+                         VmNumaDAO vmNumaDAO, Version version, VmStatusListenerRegistrar registrar, WriterID id) {
         super("VM NUMA Backend",
                 "Gathers NUMA statistics about a vm",
                 "Red Hat, Inc.",
                 version, executor, registrar);
-        this.action = new VmNumaBackendAction(id, vmNumaDAO);
+        this.action = new VmNumaBackendAction(id, clock, readerProvider, pageSizeProvider, vmNumaDAO);
         registerAction(action);
     }
 
@@ -74,12 +74,19 @@
     }
 
     private static class VmNumaBackendAction implements VmPollingAction {
+        private WriterID writerID;
+        private Clock clock;
+        private NumaMapsReaderProvider readerProvider;
+        private PageSizeProvider pageSizeProvider;
         private VmNumaDAO dao;
-        private WriterID writerID;
         private Map<Integer, VmNumaCollector> collectors;
 
-        private VmNumaBackendAction(final WriterID writerID, VmNumaDAO dao) {
+        private VmNumaBackendAction(final WriterID writerID, Clock clock, NumaMapsReaderProvider readerProvider,
+                                    PageSizeProvider pageSizeProvider, VmNumaDAO dao) {
             this.writerID = writerID;
+            this.clock = clock;
+            this.readerProvider = readerProvider;
+            this.pageSizeProvider = pageSizeProvider;
             this.dao = dao;
             this.collectors = new HashMap<>();
         }
@@ -87,7 +94,8 @@
         @Override
         public void run(String vmId, int pid) {
             if (!collectors.containsKey(pid)) {
-                collectors.put(pid, new VmNumaCollector(pid));
+                VmNumaCollector collector = new VmNumaCollector(pid, clock, readerProvider, pageSizeProvider);
+                collectors.put(pid, collector);
             }
 
             try {
@@ -95,26 +103,12 @@
                 data.setAgentId(writerID.getWriterID());
                 data.setVmId(vmId);
                 dao.putVmNumaStat(data);
-            } catch (ParseException e) {
-                logger.log(Level.FINE, "Unable to read numa info for: " + pid);
+            } catch (IOException e) {
+                logger.log(Level.FINE, "Unable to read numa info for: " + pid, e);
             }
         }
     }
 
-    /**
-     * VmNumaBackend requires numastat process to function
-     * @return true if numastat process exists, false otherwise
-     */
-    public boolean canRegister() {
-        try {
-            Runtime.getRuntime().exec("numastat");
-            return true;
-        } catch (IOException e) {
-            //numastat does not exist, do nothing
-        }
-        return false;
-    }
-
     // For testing purposes only
     void setVmNumaBackendCollector(int pid, VmNumaCollector collector) {
         action.collectors.put(pid, collector);
--- a/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaCollector.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaCollector.java	Mon Jan 30 16:06:59 2017 -0500
@@ -36,50 +36,177 @@
 
 package com.redhat.thermostat.vm.numa.agent.internal;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.ParseException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.common.SystemClock;
+import com.redhat.thermostat.common.cli.BorderedTableRenderer;
 import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.common.utils.StreamUtils;
+import com.redhat.thermostat.shared.config.OS;
+import com.redhat.thermostat.vm.numa.common.VmNumaNodeStat;
 import com.redhat.thermostat.vm.numa.common.VmNumaStat;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+// the "parsing" performed by this class is very rudimentary, but follows the same simple checks performed by the
+// "numastat" command
 public class VmNumaCollector {
 
-    private static final String PROCESS = "numastat";
     private static final Logger logger = LoggingUtils.getLogger(VmNumaCollector.class);
 
-    private final ProcessBuilder processBuilder;
-    private final VmNumaStatParser parser;
+    private static final int KILOBYTE = 1024;
+    private static final int MEGABYTE = 1024 * KILOBYTE;
+
+    private static final Pattern NODE_PATTERN = Pattern.compile("N([0-9]+)=([0-9]+)");
+    private static final Pattern WHITESPACE_PATTERN = Pattern.compile("[\\s]+");
+
+    private final int pid;
+    private final Clock clock;
+    private final NumaMapsReaderProvider readerProvider;
+    private final PageSizeProvider pageSizeProvider;
+
+    public VmNumaCollector(int pid, Clock clock, NumaMapsReaderProvider readerProvider, PageSizeProvider pageSizeProvider) {
+        this.pid = pid;
+        this.clock = clock;
+        this.readerProvider = readerProvider;
+        this.pageSizeProvider = pageSizeProvider;
+    }
+
+    public VmNumaStat collect() throws IOException {
+        Map<Integer, VmNumaNodeStat> statsMap = new TreeMap<>(); // need sorted keys for converting values to array in order later
+        try (BufferedReader br = readerProvider.createReader(pid)) {
+            for (String line = br.readLine(); line != null; line = br.readLine()) {
+                processLine(statsMap, WHITESPACE_PATTERN.split(line));
+            }
+        }
+        return createVmNumaStat(statsMap);
+    }
 
-    public VmNumaCollector(int pid) {
-        this.processBuilder = new ProcessBuilder(PROCESS, String.valueOf(pid));
-        this.parser = new VmNumaStatParser();
+    private void processLine(Map<Integer, VmNumaNodeStat> map, String[] tokens) {
+        for (String tok : tokens) {
+            Matcher matcher = NODE_PATTERN.matcher(tok);
+            if (matcher.matches()) {
+                int nodeNumber = Integer.parseInt(matcher.group(1));
+                if (!map.containsKey(nodeNumber)) {
+                    VmNumaNodeStat stat = new VmNumaNodeStat();
+                    stat.setNode(nodeNumber);
+                    map.put(nodeNumber, stat);
+                }
+
+                Category category = selectCategory(tokens);
+                double value = Double.parseDouble(matcher.group(2));
+                value *= getMultiplier(category);
+                value /= (double) MEGABYTE;
+
+                VmNumaNodeStat stat = map.get(nodeNumber);
+                updateStat(stat, category, value);
+            }
+        }
+    }
+
+    private Category selectCategory(String[] tokens) {
+        for (String tok : tokens) {
+            for (Category c : Category.values()) {
+                if (tok.startsWith(c.getToken())) {
+                    return c;
+                }
+            }
+        }
+        return Category.PRIVATE;
     }
 
-    public VmNumaStat collect() throws ParseException {
-        String output = getOutput();
-        return parser.parse(output);
+    private long getMultiplier(Category category) {
+        switch (category) {
+            case HUGE:
+                return pageSizeProvider.getHugePageSize();
+            default:
+                return pageSizeProvider.getPageSize();
+        }
+    }
+
+    private void updateStat(VmNumaNodeStat stat, Category category, double value) {
+        switch (category) {
+            case PRIVATE:
+                stat.setPrivateMemory(stat.getPrivateMemory() + value);
+                break;
+            case HEAP:
+                stat.setHeapMemory(stat.getHeapMemory() + value);
+                break;
+            case STACK:
+                stat.setStackMemory(stat.getStackMemory() + value);
+                break;
+            case HUGE:
+                stat.setHugeMemory(stat.getHugeMemory() + value);
+                break;
+            default:
+                throw new IllegalStateException("Invalid category: " + category);
+        }
+    }
+
+    private VmNumaStat createVmNumaStat(Map<Integer, VmNumaNodeStat> map) {
+        VmNumaStat numaStat = new VmNumaStat();
+        numaStat.setTimeStamp(clock.getRealTimeMillis());
+        VmNumaNodeStat[] vmNodeStats = map.values().toArray(new VmNumaNodeStat[map.size()]);
+        numaStat.setVmNodeStats(vmNodeStats);
+        verifyVmNodeStatsArray(vmNodeStats);
+        return numaStat;
     }
 
-    private String getOutput() {
-        try {
-            Process p = startProcess();
-
-            InputStream is = p.getInputStream();
-            return new String(StreamUtils.readAll(is));
-        } catch (InterruptedException | IOException e) {
-            logger.log(Level.WARNING, "Unable to run process numastat");
+    private void verifyVmNodeStatsArray(VmNumaNodeStat[] vmNumaNodeStats) {
+        boolean valid = true;
+        for (int i = 0; i < vmNumaNodeStats.length; i++) {
+            valid = valid && (vmNumaNodeStats[i].getNode() == i);
         }
-        return "";
+        if (!valid) {
+            logger.log(Level.WARNING, "VmNumaNodeStat array contained invalid array indices");
+        }
     }
 
-    //For testing
-    Process startProcess() throws InterruptedException, IOException {
-        Process p = processBuilder.start();
-        p.waitFor();
-        return p;
+    public static void main(String[] args) throws IOException {
+        if (args.length != 1) {
+            throw new RuntimeException("PID argument required");
+        }
+        if (!OS.IS_LINUX) {
+            throw new RuntimeException("Only Linux supported in this manual test case");
+        }
+        int pid = Integer.parseInt(args[0]);
+        Clock clock = new SystemClock();
+        NumaMapsReaderProvider readerProvider = new NumaMapsReaderProviderImpl();
+        PageSizeProvider pageSizeProvider = new PageSizeProviderImpl();
+        VmNumaCollector collector = new VmNumaCollector(pid, clock, readerProvider, pageSizeProvider);
+        VmNumaStat stat = collector.collect();
+
+        for (VmNumaNodeStat nodeStat : stat.getVmNodeStats()) {
+            BorderedTableRenderer tableRenderer = new BorderedTableRenderer(2);
+            tableRenderer.printHeader("Node", String.valueOf(nodeStat.getNode()));
+            tableRenderer.printLine("Huge", String.valueOf(nodeStat.getHugeMemory()));
+            tableRenderer.printLine("Heap", String.valueOf(nodeStat.getHeapMemory()));
+            tableRenderer.printLine("Stack", String.valueOf(nodeStat.getStackMemory()));
+            tableRenderer.printLine("Private", String.valueOf(nodeStat.getPrivateMemory()));
+            tableRenderer.render(System.out);
+        }
     }
+
+    enum Category {
+        HUGE("huge"),
+        HEAP("heap"),
+        STACK("stack"),
+        PRIVATE("N");
+
+        private final String token;
+
+        Category(String token) {
+            this.token = token;
+        }
+
+        public String getToken() {
+            return token;
+        }
+    }
+
 }
--- a/vm-numa/agent/src/main/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaStatParser.java	Mon Jan 30 13:06:07 2017 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/*
- * Copyright 2012-2017 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.vm.numa.agent.internal;
-
-import java.text.ParseException;
-
-import com.redhat.thermostat.vm.numa.common.VmNumaNodeStat;
-import com.redhat.thermostat.vm.numa.common.VmNumaStat;
-
-public class VmNumaStatParser {
-    public VmNumaStat parse(String input) throws ParseException {
-        VmNumaStat stat = new VmNumaStat();
-        stat.setTimeStamp(System.currentTimeMillis());
-        String[] lines = input.split(System.lineSeparator());
-        try {
-            VmNumaNodeStat[] stats = parseStats(lines[4], lines[5], lines[6], lines[7]);
-            stat.setVmNodeStats(stats);
-            return stat;
-        } catch (NumberFormatException | ArrayIndexOutOfBoundsException | NegativeArraySizeException e) {
-            throw new ParseException("Unexpected input to VmNumaStatParser", 0);
-        }
-    }
-
-    private VmNumaNodeStat[] parseStats(String HUGE, String HEAP, String STACK, String PRIVATE) {
-        VmNumaNodeStat[] stats;
-
-        String[] hugeStats = splitWhitespaces(HUGE);
-        String[] heapStats = splitWhitespaces(HEAP);
-        String[] stackStats = splitWhitespaces(STACK);
-        String[] privateStats = splitWhitespaces(PRIVATE);
-
-        //Take the maximum in-case of erroneous input
-        int numberOfNodes = -2 + Math.max(hugeStats.length,
-                                    Math.max(heapStats.length,
-                                    Math.max(stackStats.length, privateStats.length)));
-
-        stats = new VmNumaNodeStat[numberOfNodes];
-        for (int i = 0; i < numberOfNodes; i++) {
-            stats[i] = new VmNumaNodeStat();
-            stats[i].setNode(i);
-            stats[i].setHugeMemory(Double.parseDouble(hugeStats[i + 1]));
-            stats[i].setHeapMemory(Double.parseDouble(heapStats[i + 1]));
-            stats[i].setStackMemory(Double.parseDouble(stackStats[i + 1]));
-            stats[i].setPrivateMemory(Double.parseDouble(privateStats[i + 1]));
-        }
-        return stats;
-    }
-
-    private String[] splitWhitespaces(String s) {
-        return s.split("\\s+");
-    }
-}
--- a/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/ActivatorTest.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/ActivatorTest.java	Mon Jan 30 16:06:59 2017 -0500
@@ -45,7 +45,7 @@
 
 import java.util.concurrent.ScheduledExecutorService;
 
-import org.junit.Ignore;
+import com.redhat.thermostat.common.Clock;
 import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Version;
@@ -94,14 +94,9 @@
 
         Activator activator = new Activator() {
             @Override
-            VmNumaBackend constructBackend(ScheduledExecutorService executor, VmNumaDAO vmNumaDAO, com.redhat.thermostat.common.Version version, VmStatusListenerRegistrar registrar, WriterID writerID) {
-                mock[0] = new VmNumaBackend(executor, vmNumaDAO, version, registrar, writerID)
-                {
-                    @Override
-                    public boolean canRegister() {
-                        return true;
-                    }
-                };
+            VmNumaBackend constructBackend(ScheduledExecutorService executor, Clock clock, NumaMapsReaderProvider readerProvider, PageSizeProvider pageSizeProvider,
+                                           VmNumaDAO vmNumaDAO, com.redhat.thermostat.common.Version version, VmStatusListenerRegistrar registrar, WriterID writerID) {
+                mock[0] = new VmNumaBackend(executor, clock, readerProvider, pageSizeProvider, vmNumaDAO, version, registrar, writerID);
                 return mock[0];
             }
         };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/NumaMapsReaderProviderImplTest.java	Mon Jan 30 16:06:59 2017 -0500
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012-2017 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.vm.numa.agent.internal;
+
+import com.redhat.thermostat.common.portability.linux.ProcDataSource;
+import com.redhat.thermostat.common.utils.StringUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class NumaMapsReaderProviderImplTest {
+
+    private ProcDataSource procDataSource;
+    private NumaMapsReaderProviderImpl readerProvider;
+
+    @Before
+    public void setup() throws IOException {
+        procDataSource = mock(ProcDataSource.class);
+        when(procDataSource.getNumaMapsReader(anyInt())).thenReturn(new InputStreamReader(StringUtils.toInputStream("")));
+
+        readerProvider = new NumaMapsReaderProviderImpl(procDataSource);
+    }
+
+    @Test
+    public void testProvidesReader() throws IOException {
+        BufferedReader reader = readerProvider.createReader(100);
+        verify(procDataSource).getNumaMapsReader(100);
+        assertThat(reader, is(not(equalTo(null))));
+    }
+
+}
--- a/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaBackendTest.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaBackendTest.java	Mon Jan 30 16:06:59 2017 -0500
@@ -39,6 +39,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
@@ -47,10 +48,12 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-import java.text.ParseException;
+import java.io.BufferedReader;
+import java.io.IOException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 
+import com.redhat.thermostat.common.Clock;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -67,12 +70,24 @@
     private VmNumaBackend backend;
     private VmNumaDAO vmNumaDAO;
     private ScheduledExecutorService executor;
+    private Clock clock;
+    private NumaMapsReaderProvider readerProvider;
+    private PageSizeProvider pageSizeProvider;
     private VmStatusListenerRegistrar registrar;
 
     @Before
-    public void setup() {
+    public void setup() throws IOException {
         executor = mock(ScheduledExecutorService.class);
         vmNumaDAO = mock(VmNumaDAO.class);
+        clock = mock(Clock.class);
+        when(clock.getRealTimeMillis()).thenReturn(100L);
+
+        readerProvider = mock(NumaMapsReaderProvider.class);
+        when(readerProvider.createReader(anyInt())).thenReturn(mock(BufferedReader.class));
+
+        pageSizeProvider = mock(PageSizeProvider.class);
+        when(pageSizeProvider.getPageSize()).thenReturn(4L * 1024L);
+        when(pageSizeProvider.getHugePageSize()).thenReturn(2048L * 1024L);
 
         Version version = mock(Version.class);
         when(version.getVersionNumber()).thenReturn("0.0.0");
@@ -81,7 +96,7 @@
 
         WriterID id = mock(WriterID.class);
         when(id.getWriterID()).thenReturn("id");
-        backend = new VmNumaBackend(executor, vmNumaDAO, version, registrar, id);
+        backend = new VmNumaBackend(executor, clock, readerProvider, pageSizeProvider, vmNumaDAO, version, registrar, id);
     }
 
     @Test
@@ -126,7 +141,7 @@
     }
 
     @Test
-    public void testStart() throws ParseException {
+    public void testStart() throws IOException {
         mockCollector(backend, 0);
         mockCollector(backend, 1);
 
@@ -152,7 +167,7 @@
         verifyNoMoreInteractions(vmNumaDAO);
     }
 
-    private void mockCollector(VmNumaBackend backend, int pid) throws ParseException {
+    private void mockCollector(VmNumaBackend backend, int pid) throws IOException {
         VmNumaCollector collector = mock(VmNumaCollector.class);
         when(collector.collect()).thenReturn(mock(VmNumaStat.class));
         backend.setVmNumaBackendCollector(pid, collector);
--- a/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaCollectorTest.java	Mon Jan 30 13:06:07 2017 -0500
+++ b/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaCollectorTest.java	Mon Jan 30 16:06:59 2017 -0500
@@ -36,15 +36,18 @@
 
 package com.redhat.thermostat.vm.numa.agent.internal;
 
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
-import java.io.ByteArrayInputStream;
+import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.ParseException;
+import java.io.StringReader;
 
-import org.junit.Ignore;
+import com.redhat.thermostat.common.Clock;
+import org.junit.Before;
 import org.junit.Test;
 
 import com.redhat.thermostat.vm.numa.common.VmNumaNodeStat;
@@ -52,105 +55,73 @@
 
 public class VmNumaCollectorTest {
 
+    private static final int PID = 100;
+
     private VmNumaCollector collector;
 
+    private Clock clock;
+    private NumaMapsReaderProvider readerProvider;
+    private PageSizeProvider pageSizeProvider;
+
+    @Before
+    public void setup() {
+        clock = mock(Clock.class);
+        when(clock.getRealTimeMillis()).thenReturn(100L);
+
+        pageSizeProvider = mock(PageSizeProvider.class);
+        when(pageSizeProvider.getHugePageSize()).thenReturn(2048L * 1024L);
+        when(pageSizeProvider.getPageSize()).thenReturn(4L * 1024L);
+    }
+
     @Test
-    public void testCollectSingleNodeStat() throws ParseException {
-        final String input = "\n" +
-                "Per-node process memory usage (in MBs) for PID 16816 (java)\n" +
-                "                           Node 0           Total\n" +
-                "                  --------------- ---------------\n" +
-                "Huge                         0.00            0.00\n" +
-                "Heap                         0.05            0.05\n" +
-                "Stack                        6.27            6.27\n" +
-                "Private                    385.07          385.07\n" +
-                "----------------  --------------- ---------------\n" +
-                "Total                      391.39          391.39";
-        setupCollector(input);
+    public void testCollectSingleNodeStat() throws IOException {
+        readerProvider = mock(NumaMapsReaderProvider.class);
+        when(readerProvider.createReader(anyInt())).thenReturn(new BufferedReader(new StringReader(
+                "017ec000 default heap anon=1861 dirty=1796 swapcache=65 active=1667 N0=1861 kernelpagesize_kB=4\n" +
+                "e09ec000 default stack anon=1776 dirty=1776 swapcache=65 active=1667 N0=1776 kernelpagesize_kB=4\n" +
+                "d1200000 default anon=45680 dirty=45680 active=43669 N0=45680 kernelpagesize_kB=4\n" +
+                "d0800000 default huge anon=456 dirty=456 active=43669 N0=456 kernelpagesize_kB=4\n"
+        )));
+        collector = new VmNumaCollector(PID, clock, readerProvider, pageSizeProvider);
 
         VmNumaStat stat = collector.collect();
         VmNumaNodeStat[] stats = stat.getVmNodeStats();
-        assertTrue(stats.length == 1);
+        assertThat(stats.length, is(1));
         VmNumaNodeStat nodeStat = stats[0];
-        assertTrue(nodeStat.getNode() == 0);
-        assertTrue(nodeStat.getHugeMemory() == 0);
-        assertTrue(nodeStat.getHeapMemory() == 0.05d);
-        assertTrue(nodeStat.getStackMemory() == 6.27d);
-        assertTrue(nodeStat.getPrivateMemory() == 385.07d);
+        assertThat(nodeStat.getNode(), is(0));
+        assertThat(nodeStat.getHugeMemory(), is(912.0d));
+        assertThat(nodeStat.getHeapMemory(), is(7.26953125d));
+        assertThat(nodeStat.getStackMemory(), is(6.9375d));
+        assertThat(nodeStat.getPrivateMemory(), is(178.4375d));
     }
 
     @Test
-    public void testCollectMultipleNodeStat() throws ParseException {
-        final String input = "\n" +
-                "Per-node process memory usage (in MBs) for PID 3 (ksoftirqd/0)\n" +
-                "                           Node 0          Node 1           Total\n" +
-                "                  --------------- --------------- ---------------\n" +
-                "Huge                         0.00            0.00            0.00\n" +
-                "Heap                         0.00            0.00            0.00\n" +
-                "Stack                        4.00            1.00            5.00\n" +
-                "Private                      5.00            2.00            7.00\n" +
-                "----------------  --------------- --------------- ---------------\n" +
-                "Total                        9.00            3.00           12.00";
-
-        setupCollector(input);
+    public void testCollectMultipleNodeStat() throws IOException {
+        readerProvider = mock(NumaMapsReaderProvider.class);
+        when(readerProvider.createReader(anyInt())).thenReturn(new BufferedReader(new StringReader(
+                "017ec000 default heap anon=1861 dirty=1796 swapcache=65 active=1667 N0=1861 kernelpagesize_kB=4\n" +
+                "d1200000 default anon=45680 dirty=45680 active=43669 N1=45680 kernelpagesize_kB=4\n"
+        )));
+        collector = new VmNumaCollector(PID, clock, readerProvider, pageSizeProvider);
 
         VmNumaStat stat = collector.collect();
         VmNumaNodeStat[] stats = stat.getVmNodeStats();
 
-        assertTrue(stats.length == 2);
+        assertThat(stats.length, is(2));
 
         VmNumaNodeStat nodeStat1 = stats[0];
-        assertTrue(nodeStat1.getNode() == 0);
-        assertTrue(nodeStat1.getHugeMemory() == 0d);
-        assertTrue(nodeStat1.getHeapMemory() == 0d);
-        assertTrue(nodeStat1.getStackMemory() == 4d);
-        assertTrue(nodeStat1.getPrivateMemory() == 5d);
+        assertThat(nodeStat1.getNode(), is(0));
+        assertThat(nodeStat1.getHugeMemory(), is (0.0d));
+        assertThat(nodeStat1.getHeapMemory(), is(7.26953125d));
+        assertThat(nodeStat1.getStackMemory(), is(0.0d));
+        assertThat(nodeStat1.getPrivateMemory(), is(0.0d));
 
         VmNumaNodeStat nodeStat2 = stats[1];
-        assertTrue(nodeStat2.getNode() == 1);
-        assertTrue(nodeStat2.getHugeMemory() == 0d);
-        assertTrue(nodeStat2.getHeapMemory() == 0d);
-        assertTrue(nodeStat2.getStackMemory() == 1d);
-        assertTrue(nodeStat2.getPrivateMemory() == 2d);
-    }
-
-    private void setupCollector(final String input) {
-        collector = new VmNumaCollector(0) {
-            @Override
-            protected Process startProcess() {
-                return new Process() {
-                    @Override
-                    public OutputStream getOutputStream() {
-                        return null;
-                    }
-
-                    @Override
-                    public InputStream getInputStream() {
-                        return new ByteArrayInputStream(input.getBytes());
-                    }
-
-                    @Override
-                    public InputStream getErrorStream() {
-                        return null;
-                    }
-
-                    @Override
-                    public int waitFor() throws InterruptedException {
-                        return 0;
-                    }
-
-                    @Override
-                    public int exitValue() {
-                        return 0;
-                    }
-
-                    @Override
-                    public void destroy() {
-                        //Do nothing
-                    }
-                };
-            }
-        };
+        assertThat(nodeStat2.getNode(), is(1));
+        assertThat(nodeStat2.getHugeMemory(), is(0.0d));
+        assertThat(nodeStat2.getHeapMemory(), is(0.0d));
+        assertThat(nodeStat2.getStackMemory(), is(0.0d));
+        assertThat(nodeStat2.getPrivateMemory(), is(178.4375d));
     }
 
 }
--- a/vm-numa/agent/src/test/java/com/redhat/thermostat/vm/numa/agent/internal/VmNumaStatParserTest.java	Mon Jan 30 13:06:07 2017 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-/*
- * Copyright 2012-2017 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.vm.numa.agent.internal;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.text.ParseException;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.vm.numa.common.VmNumaNodeStat;
-import com.redhat.thermostat.vm.numa.common.VmNumaStat;
-
-public class VmNumaStatParserTest {
-
-    private final VmNumaStatParser parser = new VmNumaStatParser();
-
-    @Test
-    public void testParseSingleNodeStat() throws ParseException {
-        final String input = "\n" +
-                "Per-node process memory usage (in MBs) for PID 16816 (java)\n" +
-                "                           Node 0           Total\n" +
-                "                  --------------- ---------------\n" +
-                "Huge                         0.00            0.00\n" +
-                "Heap                         0.05            0.05\n" +
-                "Stack                        6.27            6.27\n" +
-                "Private                    385.07          385.07\n" +
-                "----------------  --------------- ---------------\n" +
-                "Total                      391.39          391.39";
-
-        VmNumaStat stat = parser.parse(input);
-        VmNumaNodeStat[] stats = stat.getVmNodeStats();
-        assertTrue(stats.length == 1);
-        VmNumaNodeStat nodeStat = stats[0];
-        assertTrue(nodeStat.getNode() == 0);
-        assertTrue(nodeStat.getHugeMemory() == 0);
-        assertTrue(nodeStat.getHeapMemory() == 0.05d);
-        assertTrue(nodeStat.getStackMemory() == 6.27d);
-        assertTrue(nodeStat.getPrivateMemory() == 385.07d);
-    }
-
-    @Test
-    public void testParseMultipleNodeStat() throws ParseException {
-        final String input = "\n" +
-                "Per-node process memory usage (in MBs) for PID 3 (ksoftirqd/0)\n" +
-                "                           Node 0          Node 1           Total\n" +
-                "                  --------------- --------------- ---------------\n" +
-                "Huge                         0.00            0.00            0.00\n" +
-                "Heap                         0.00            0.00            0.00\n" +
-                "Stack                        4.00            1.00            5.00\n" +
-                "Private                      5.00            2.00            7.00\n" +
-                "----------------  --------------- --------------- ---------------\n" +
-                "Total                        9.00            3.00           12.00";
-
-        VmNumaStat stat = parser.parse(input);
-        VmNumaNodeStat[] stats = stat.getVmNodeStats();
-
-        assertTrue(stats.length == 2);
-
-        VmNumaNodeStat nodeStat1 = stats[0];
-        assertTrue(nodeStat1.getNode() == 0);
-        assertTrue(nodeStat1.getHugeMemory() == 0d);
-        assertTrue(nodeStat1.getHeapMemory() == 0d);
-        assertTrue(nodeStat1.getStackMemory() == 4d);
-        assertTrue(nodeStat1.getPrivateMemory() == 5d);
-
-        VmNumaNodeStat nodeStat2 = stats[1];
-        assertTrue(nodeStat2.getNode() == 1);
-        assertTrue(nodeStat2.getHugeMemory() == 0d);
-        assertTrue(nodeStat2.getHeapMemory() == 0d);
-        assertTrue(nodeStat2.getStackMemory() == 1d);
-        assertTrue(nodeStat2.getPrivateMemory() == 2d);
-    }
-
-    @Test (expected = ParseException.class)
-    public void testParseEmptyString() throws ParseException {
-        String input = "";
-        VmNumaStat stat = parser.parse(input);
-    }
-
-    @Test (expected = ParseException.class)
-    public void testParseIncorrectMemoryData() throws ParseException {
-        final String input = "\n" +
-                "Per-node process memory usage (in MBs) for PID 3 (ksoftirqd/0)\n" +
-                "                           Node 0          Node 1           Total\n" +
-                "                  --------------- --------------- ---------------\n" +
-                "Huge                         ABCD            0.00            0.00\n" +
-                "Heap                         0.00            0.00            0.00\n" +
-                "Stack                        4.00            1.00            5.00\n" +
-                "Private                      5.00            2.00            7.00\n" +
-                "----------------  --------------- --------------- ---------------\n" +
-                "Total                        9.00            3.00           12.00";
-
-        parser.parse(input);
-    }
-
-    @Test (expected = ParseException.class)
-    public void testParseIncorrectMemory() throws ParseException {
-        final String input = "\n" +
-                "Per-node process memory usage (in MBs) for PID 3 (ksoftirqd/0)\n" +
-                "                           Node 0          Node 1           Total\n" +
-                "                  --------------- --------------- ---------------\n" +
-                "Huge\n" +
-                "Heap                         0.00            0.00            0.00\n" +
-                "Stack                        4.00            1.00            5.00\n" +
-                "Private                      5.00            2.00            7.00\n" +
-                "----------------  --------------- --------------- ---------------\n" +
-                "Total                        9.00            3.00           12.00";
-
-        parser.parse(input);
-    }
-}