changeset 927:8a6697503832

Initial NUMA Swing client. Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-January/005282.html
author Roman Kennke <rkennke@redhat.com>
date Tue, 22 Jan 2013 16:14:48 +0100
parents ce04776b8b81
children 279aeb153688
files distribution/config/commands/gui.properties distribution/pom.xml numa/agent/src/main/java/com/redhat/thermostat/numa/agent/internal/NumaBackend.java numa/agent/src/main/java/com/redhat/thermostat/numa/agent/internal/NumaCollector.java numa/agent/src/test/java/com/redhat/thermostat/numa/agent/internal/NumaBackendTest.java numa/client-core/pom.xml numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/NumaInformationService.java numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/NumaView.java numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/NumaViewProvider.java numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/internal/Activator.java numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/internal/NumaController.java numa/client-core/src/main/java/com/redhat/thermostat/numa/client/locale/LocaleResources.java numa/client-core/src/main/resources/com/redhat/thermostat/numa/client/locale/strings.properties numa/client-core/src/test/java/com/redhat/thermostat/numa/client/core/NumaInformationServiceTest.java numa/client-core/src/test/java/com/redhat/thermostat/numa/client/core/internal/ActivatorTest.java numa/client-core/src/test/java/com/redhat/thermostat/numa/client/core/internal/NumaControllerTest.java numa/client-core/src/test/java/com/redhat/thermostat/numa/client/locale/LocaleResourcesTest.java numa/client-swing/pom.xml numa/client-swing/src/main/java/com/redhat/thermostat/numa/client/swing/internal/Activator.java numa/client-swing/src/main/java/com/redhat/thermostat/numa/client/swing/internal/NumaPanel.java numa/client-swing/src/main/java/com/redhat/thermostat/numa/client/swing/internal/SwingNumaViewProvider.java numa/client-swing/src/test/java/com/redhat/thermostat/numa/client/swing/internal/ActivatorTest.java numa/client-swing/src/test/java/com/redhat/thermostat/numa/client/swing/internal/NumaPanelTest.java numa/common/src/main/java/com/redhat/thermostat/numa/common/NodeStat.java numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaDAO.java numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaHostInfo.java numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaStat.java numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/Activator.java numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImpl.java numa/common/src/test/java/com/redhat/thermostat/numa/common/NumaHostInfoTest.java numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplTest.java numa/pom.xml
diffstat 32 files changed, 2036 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/distribution/config/commands/gui.properties	Fri Jan 18 18:43:12 2013 +0100
+++ b/distribution/config/commands/gui.properties	Tue Jan 22 16:14:48 2013 +0100
@@ -49,6 +49,9 @@
           thermostat-gc-remote-collector-client-common-@project.version@.jar, \
           thermostat-gc-remote-collector-client-swing-@project.version@.jar, \
           thermostat-osgi-process-handler-@project.version@.jar, \
+          thermostat-numa-common-@project.version@.jar, \
+          thermostat-numa-client-core-@project.version@.jar, \
+          thermostat-numa-client-swing-@project.version@.jar, \
           httpcomponents-core.jar, \
           httpcomponents-client.jar, \
           netty.jar
--- a/distribution/pom.xml	Fri Jan 18 18:43:12 2013 +0100
+++ b/distribution/pom.xml	Tue Jan 22 16:14:48 2013 +0100
@@ -596,5 +596,10 @@
       <artifactId>thermostat-numa-agent</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-numa-client-swing</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 </project>
--- a/numa/agent/src/main/java/com/redhat/thermostat/numa/agent/internal/NumaBackend.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/agent/src/main/java/com/redhat/thermostat/numa/agent/internal/NumaBackend.java	Tue Jan 22 16:14:48 2013 +0100
@@ -79,6 +79,9 @@
 
     @Override
     public boolean activate() {
+        int numNodes = numaCollector.getNumberOfNumaNodes();
+        numaDAO.putNumberOfNumaNodes(numNodes);
+
         TimerFactory timerFactory = appService.getTimerFactory();
         timer = timerFactory.createTimer();
         timer.setDelay(NUMA_CHECK_INTERVAL_SECONDS);
--- a/numa/agent/src/main/java/com/redhat/thermostat/numa/agent/internal/NumaCollector.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/agent/src/main/java/com/redhat/thermostat/numa/agent/internal/NumaCollector.java	Tue Jan 22 16:14:48 2013 +0100
@@ -94,4 +94,8 @@
     String getBaseDir() {
         return baseDir.getAbsolutePath();
     }
+
+    public int getNumberOfNumaNodes() {
+        return numberOfNodes;
+    }
 }
--- a/numa/agent/src/test/java/com/redhat/thermostat/numa/agent/internal/NumaBackendTest.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/agent/src/test/java/com/redhat/thermostat/numa/agent/internal/NumaBackendTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -106,6 +106,7 @@
         NumaNodeStat stat2 = mock(NumaNodeStat.class);
         NumaNodeStat[] stats = new NumaNodeStat[] { stat1, stat2 };
         when(collector.collectData()).thenReturn(stats);
+        when(collector.getNumberOfNumaNodes()).thenReturn(42);
         ArgumentCaptor<NumaStat> statCaptor = ArgumentCaptor.forClass(NumaStat.class);
         doNothing().when(numaDAO).putNumaStat(statCaptor.capture());
         
@@ -121,8 +122,10 @@
         verifyNoMoreInteractions(timer);
 
         Runnable action = actionCaptor.getValue();
-        verifyZeroInteractions(numaDAO);
-        verifyZeroInteractions(collector);
+        verify(numaDAO).putNumberOfNumaNodes(42);
+        verifyNoMoreInteractions(numaDAO);
+        verify(collector).getNumberOfNumaNodes();
+        verifyNoMoreInteractions(collector);
 
         action.run();
         verify(collector).collectData();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/pom.xml	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>thermostat-numa</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-numa-client-core</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat NUMA Core Client plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-SymbolicName>com.redhat.thermostat.numa.client.core</Bundle-SymbolicName>
+            <Bundle-Activator>com.redhat.thermostat.numa.client.core.internal.Activator</Bundle-Activator>
+            <Export-Package>
+              com.redhat.thermostat.numa.client.core,
+              com.redhat.thermostat.numa.client.locale
+            </Export-Package>
+            <Private-Package>
+              com.redhat.thermostat.numa.client.core.internal
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-numa-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/NumaInformationService.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 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.numa.client.core;
+
+import com.redhat.thermostat.client.core.Filter;
+import com.redhat.thermostat.client.core.InformationService;
+import com.redhat.thermostat.client.core.NameMatchingRefFilter;
+import com.redhat.thermostat.client.core.controllers.InformationServiceController;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.numa.client.core.internal.NumaController;
+import com.redhat.thermostat.numa.common.NumaDAO;
+
+public class NumaInformationService implements InformationService<HostRef> {
+    
+    private static final int ORDER = ORDER_MEMORY_GROUP;
+    private static final Filter<HostRef> FILTER = new NameMatchingRefFilter<>();
+
+    private ApplicationService appSvc;
+    private NumaDAO numaDAO;
+    private NumaViewProvider numaViewProvider;
+
+    public NumaInformationService(ApplicationService appSvc, NumaDAO numaDAO, NumaViewProvider numaViewProvider) {
+        this.appSvc = appSvc;
+        this.numaDAO = numaDAO;
+        this.numaViewProvider = numaViewProvider;
+    }
+
+    @Override
+    public Filter<HostRef> getFilter() {
+        return FILTER;
+    }
+
+    @Override
+    public InformationServiceController<HostRef> getInformationServiceController(HostRef ref) {
+        return new NumaController(appSvc, numaDAO, ref, numaViewProvider);
+    }
+
+    @Override
+    public int getOrderValue() {
+        return ORDER;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/NumaView.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 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.numa.client.core;
+
+import java.util.List;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.storage.model.DiscreteTimeData;
+
+public abstract class NumaView extends BasicView implements UIComponent {
+
+    public interface GraphVisibilityChangeListener {
+        public void show(String tag);
+
+        public void hide(String tag);
+    }
+
+    public abstract void addNumaChart(String tag, String humanReadableName);
+
+    public abstract void removeNumaChart(String tag);
+
+    public abstract void showNumaChart(String tag);
+
+    public abstract void hideNumaChart(String tag);
+
+    public abstract void addNumaData(String tag, List<DiscreteTimeData<? extends Number>> data);
+
+    public abstract void clearNumaData(String tag);
+
+    public abstract void addGraphVisibilityListener(GraphVisibilityChangeListener listener);
+
+    public abstract void removeGraphVisibilityListener(GraphVisibilityChangeListener listener);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/NumaViewProvider.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,46 @@
+/*
+// * Copyright 2013 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.numa.client.core;
+
+import com.redhat.thermostat.client.core.views.ViewProvider;
+
+public interface NumaViewProvider extends ViewProvider {
+
+    @Override
+    public NumaView createView();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 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.numa.client.core.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Objects;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.client.core.InformationService;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.numa.client.core.NumaInformationService;
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+import com.redhat.thermostat.numa.common.NumaDAO;
+
+public class Activator implements BundleActivator {
+    
+    private MultipleServiceTracker tracker;
+    private ServiceRegistration reg;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        Class<?>[] deps = new Class<?>[] {
+            NumaDAO.class,
+            ApplicationService.class,
+            NumaViewProvider.class
+        };
+
+        tracker = new MultipleServiceTracker(context, deps, new Action() {
+
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                NumaDAO numaDAO = (NumaDAO) services.get(NumaDAO.class.getName());
+                Objects.requireNonNull(numaDAO);
+                ApplicationService appSvc = (ApplicationService) services.get(ApplicationService.class.getName());
+                Objects.requireNonNull(appSvc);
+                NumaViewProvider numaViewProvider = (NumaViewProvider) services.get(NumaViewProvider.class.getName());
+                Objects.requireNonNull(numaViewProvider);
+                NumaInformationService service = new NumaInformationService(appSvc, numaDAO, numaViewProvider);
+                Dictionary<String, String> properties = new Hashtable<>();
+                properties.put(Constants.GENERIC_SERVICE_CLASSNAME, HostRef.class.getName());
+                reg = context.registerService(InformationService.class.getName(), service, properties);
+            }
+
+            @Override
+            public void dependenciesUnavailable() {
+                reg.unregister();
+            }
+
+        });
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        tracker.close();
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/java/com/redhat/thermostat/numa/client/core/internal/NumaController.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2013 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.numa.client.core.internal;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.core.controllers.InformationServiceController;
+import com.redhat.thermostat.client.core.views.BasicView.Action;
+import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.numa.client.core.NumaView;
+import com.redhat.thermostat.numa.client.core.NumaView.GraphVisibilityChangeListener;
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+import com.redhat.thermostat.numa.client.locale.LocaleResources;
+import com.redhat.thermostat.numa.common.NumaDAO;
+import com.redhat.thermostat.numa.common.NumaStat;
+import com.redhat.thermostat.storage.model.DiscreteTimeData;
+
+public class NumaController implements InformationServiceController<HostRef> {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private final NumaView view;
+
+    private final NumaDAO numaDAO;
+    private final HostRef ref;
+
+    private final Timer backgroundUpdateTimer;
+    private final GraphVisibilityChangeListener listener = new ShowHideGraph();
+
+    private long lastSeenTimeStamp = Long.MIN_VALUE;
+
+    private int numberOfNumaNodes;
+
+    public NumaController(ApplicationService appSvc, NumaDAO numaDAO, final HostRef ref, NumaViewProvider provider) {
+        this.ref = ref;
+        this.numaDAO = numaDAO;
+
+        numberOfNumaNodes = numaDAO.getNumberOfNumaNodes(ref);
+
+        view = provider.createView();
+
+        for (int i = 0; i < numberOfNumaNodes; i++) {
+            view.addNumaChart("node" + i, translator.localize(LocaleResources.NUMA_NODE, String.valueOf(i)));
+        }
+        view.addGraphVisibilityListener(listener);
+        view.addActionListener(new ActionListener<NumaView.Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case HIDDEN:
+                        stopBackgroundUpdates();
+                        break;
+                    case VISIBLE:
+                        startBackgroundUpdates();
+                        break;
+                    default:
+                        assert false; // Cannot happen: null is caught in ActionEvent constructor, everything else by javac.
+                }
+            }
+        });
+
+        backgroundUpdateTimer = appSvc.getTimerFactory().createTimer();
+        backgroundUpdateTimer.setAction(new Runnable() {
+            @Override
+            public void run() {
+                doNumaChartUpdate();
+            }
+        });
+        backgroundUpdateTimer.setSchedulingType(SchedulingType.FIXED_RATE);
+        backgroundUpdateTimer.setTimeUnit(TimeUnit.SECONDS);
+        backgroundUpdateTimer.setInitialDelay(0);
+        backgroundUpdateTimer.setDelay(5);
+    }
+
+    private void startBackgroundUpdates() {
+        for (int i = 0; i < numberOfNumaNodes; i++) {
+            view.showNumaChart("node" + i);
+        }
+
+        backgroundUpdateTimer.start();
+    }
+
+    private void stopBackgroundUpdates() {
+        backgroundUpdateTimer.stop();
+        for (int i = 0; i < numberOfNumaNodes; i++) {
+            view.hideNumaChart("node" + i);
+        }
+    }
+
+    public UIComponent getView() {
+        return view;
+    }
+
+    private void doNumaChartUpdate() {
+        List<NumaStat> stats = numaDAO.getLatestNumaStats(ref, lastSeenTimeStamp);
+        for (int i = 0; i < numberOfNumaNodes; i++) {
+            List<DiscreteTimeData<? extends Number>> numaHitRatio = new LinkedList<>();
+
+            for (NumaStat stat : stats) {
+                long timeStamp = stat.getTimeStamp();
+                long numaHitVal = stat.getNodeStats()[i].getNumaHit();
+                long numaMissVal = stat.getNodeStats()[i].getNumaMiss();
+                double hitRatio = 100 * numaHitVal / (numaHitVal + numaMissVal);
+                numaHitRatio.add(new DiscreteTimeData<Double>(timeStamp, hitRatio));
+                lastSeenTimeStamp = Math.max(lastSeenTimeStamp, stat.getTimeStamp());
+            }
+
+            view.addNumaData("node" + i, numaHitRatio);
+        }
+    }
+
+    private class ShowHideGraph implements GraphVisibilityChangeListener {
+        @Override
+        public void show(String tag) {
+            view.showNumaChart(tag);
+        }
+        @Override
+        public void hide(String tag) {
+            view.hideNumaChart(tag);
+        }
+    }
+
+    @Override
+    public String getLocalizedName() {
+        return translator.localize(LocaleResources.NUMA_TAB);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/java/com/redhat/thermostat/numa/client/locale/LocaleResources.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 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.numa.client.locale;
+
+import com.redhat.thermostat.common.locale.Translate;
+
+public enum LocaleResources {
+    NUMA_TAB,
+    NUMA_NODE, NUMA_SECTION_OVERVIEW, NUMA_CHART_TITLE, NUMA_CHART_TIME_LABEL, NUMA_CHART_NUM_HITS_LABEL,
+    ;
+
+    static final String RESOURCE_BUNDLE =
+            "com.redhat.thermostat.numa.client.locale.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/main/resources/com/redhat/thermostat/numa/client/locale/strings.properties	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,6 @@
+NUMA_TAB = NUMA
+NUMA_NODE = Node {0}
+NUMA_SECTION_OVERVIEW = NUMA
+NUMA_CHART_TITLE = NUMA
+NUMA_CHART_TIME_LABEL = Time
+NUMA_CHART_NUM_HITS_LABEL = Number of hits
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/test/java/com/redhat/thermostat/numa/client/core/NumaInformationServiceTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 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.numa.client.core;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Ordered;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.numa.client.core.internal.NumaController;
+import com.redhat.thermostat.numa.common.NumaDAO;
+
+import static org.mockito.Mockito.*;
+
+public class NumaInformationServiceTest {
+
+    @Test
+    public void test() {
+        ApplicationService appSvc = mock(ApplicationService.class);
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        Timer timer = mock(Timer.class);
+        when(timerFactory.createTimer()).thenReturn(timer);
+        when(appSvc.getTimerFactory()).thenReturn(timerFactory);
+        NumaDAO numaDAO = mock(NumaDAO.class);
+        NumaViewProvider numaViewProvider = mock(NumaViewProvider.class);
+        NumaView view = mock(NumaView.class);
+        when(numaViewProvider.createView()).thenReturn(view);
+
+        NumaInformationService numaInfoService = new NumaInformationService(appSvc, numaDAO, numaViewProvider);
+
+        int order = numaInfoService.getOrderValue();
+        assertEquals(Ordered.ORDER_MEMORY_GROUP, order);
+        HostRef ref = mock(HostRef.class);
+        assertTrue(numaInfoService.getInformationServiceController(ref) instanceof NumaController);
+        assertNotNull(numaInfoService.getFilter());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/test/java/com/redhat/thermostat/numa/client/core/internal/ActivatorTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 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.numa.client.core.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.core.InformationService;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.numa.client.core.NumaInformationService;
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+import com.redhat.thermostat.numa.common.NumaDAO;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class ActivatorTest {
+    
+    @Test
+    public void verifyActivatorDoesNotRegisterServiceOnMissingDeps() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertEquals(0, context.getAllServices().size());
+        assertNotSame(1, context.getServiceListeners().size());
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+    }
+
+    @Test
+    public void verifyActivatorRegistersServices() throws Exception {
+        StubBundleContext context = new StubBundleContext();
+        NumaViewProvider numaViewProvider = mock(NumaViewProvider.class);
+        NumaDAO memoryStatDAO = mock(NumaDAO.class);
+        ApplicationService appSvc = mock(ApplicationService.class);
+
+        context.registerService(NumaViewProvider.class, numaViewProvider, null);
+        context.registerService(NumaDAO.class, memoryStatDAO, null);
+        context.registerService(ApplicationService.class, appSvc, null);
+
+        Activator activator = new Activator();
+
+        activator.start(context);
+
+        assertTrue(context.isServiceRegistered(InformationService.class.getName(), NumaInformationService.class));
+
+        activator.stop(context);
+
+        assertEquals(0, context.getServiceListeners().size());
+        assertEquals(3, context.getAllServices().size());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/test/java/com/redhat/thermostat/numa/client/core/internal/NumaControllerTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2013 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.numa.client.core.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.dao.HostRef;
+import com.redhat.thermostat.numa.client.core.NumaView;
+import com.redhat.thermostat.numa.client.core.NumaView.GraphVisibilityChangeListener;
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+import com.redhat.thermostat.numa.common.NumaDAO;
+import com.redhat.thermostat.numa.common.NumaNodeStat;
+import com.redhat.thermostat.numa.common.NumaStat;
+import com.redhat.thermostat.storage.model.DiscreteTimeData;
+
+public class NumaControllerTest {
+
+    private NumaController numaController;
+    private Timer timer;
+    private NumaView view;
+    private ArgumentCaptor<GraphVisibilityChangeListener> graphVisibilityListener;
+    @SuppressWarnings("rawtypes")
+    private ArgumentCaptor<ActionListener> actionListener;
+    private ArgumentCaptor<Runnable> timerAction;
+
+    @SuppressWarnings("unchecked")
+    @Before
+    public void setUp() {
+        ApplicationService appSvc = mock(ApplicationService.class);
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        timer = mock(Timer.class);
+        timerAction = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(timerAction.capture());
+        when(timerFactory.createTimer()).thenReturn(timer);
+        when(appSvc.getTimerFactory()).thenReturn(timerFactory);
+        HostRef hostRef = new HostRef("fluff", "boo");
+        NumaDAO numaDAO = mock(NumaDAO.class);
+
+        List<NumaStat> stats = createTestData();
+        when(numaDAO.getLatestNumaStats(eq(hostRef), anyLong())).thenReturn(stats);
+        when(numaDAO.getNumberOfNumaNodes(hostRef)).thenReturn(3);
+        NumaViewProvider numaViewProvider = mock(NumaViewProvider.class);
+        view = mock(NumaView.class);
+        graphVisibilityListener = ArgumentCaptor.forClass(GraphVisibilityChangeListener.class);
+        actionListener = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addGraphVisibilityListener(graphVisibilityListener.capture());
+        doNothing().when(view).addActionListener(actionListener.capture());
+        when(numaViewProvider.createView()).thenReturn(view);
+        numaController = new NumaController(appSvc, numaDAO, hostRef, numaViewProvider);
+    }
+
+    private List<NumaStat> createTestData() {
+        NumaNodeStat nodeStat11 = new NumaNodeStat();
+        nodeStat11.setNumaHit(100);
+        nodeStat11.setNumaMiss(0);
+        NumaNodeStat nodeStat12 = new NumaNodeStat();
+        nodeStat12.setNumaHit(50);
+        nodeStat12.setNumaMiss(50);
+        NumaNodeStat nodeStat13 = new NumaNodeStat();
+        nodeStat13.setNumaHit(70);
+        nodeStat13.setNumaMiss(30);
+        NumaStat stat1 = new NumaStat();
+        stat1.setAgentId("fluff");
+        stat1.setTimeStamp(123);
+        stat1.setNodeStats(new NumaNodeStat[] {nodeStat11, nodeStat12, nodeStat13 });
+        NumaNodeStat nodeStat21 = new NumaNodeStat();
+        nodeStat21.setNumaHit(90);
+        nodeStat21.setNumaMiss(10);
+        NumaNodeStat nodeStat22 = new NumaNodeStat();
+        nodeStat22.setNumaHit(60);
+        nodeStat22.setNumaMiss(40);
+        NumaNodeStat nodeStat23 = new NumaNodeStat();
+        nodeStat23.setNumaHit(80);
+        nodeStat23.setNumaMiss(20);
+        NumaStat stat2 = new NumaStat();
+        stat2.setAgentId("fluff");
+        stat2.setTimeStamp(234);
+        stat2.setNodeStats(new NumaNodeStat[] {nodeStat21, nodeStat22, nodeStat23 });
+        List<NumaStat> stats = Arrays.asList(stat1, stat2);
+        return stats;
+    }
+
+    @After
+    public void tearDown() {
+        timer = null;
+        graphVisibilityListener = null;
+        view = null;
+        numaController = null;
+    }
+
+    @Test
+    public void verifyTimerSettings() {
+        verify(timer).setAction(any(Runnable.class));
+        verify(timer).setSchedulingType(SchedulingType.FIXED_RATE);
+        verify(timer).setTimeUnit(TimeUnit.SECONDS);
+        verify(timer).setInitialDelay(0);
+        verify(timer).setDelay(5);
+        verifyNoMoreInteractions(timer);
+    }
+
+    @Test
+    public void verifyNumCharts() {
+        verify(view).addNumaChart(eq("node0"), anyString());
+        verify(view).addNumaChart(eq("node1"), anyString());
+        verify(view).addNumaChart(eq("node2"), anyString());
+    }
+
+    @Test
+    public void verifyGraphVisibility() {
+        graphVisibilityListener.getValue().show("node0");
+        verify(view).showNumaChart("node0");
+        graphVisibilityListener.getValue().hide("node1");
+        verify(view).hideNumaChart("node1");
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Test
+    public void verifyViewActions() {
+        actionListener.getValue().actionPerformed(new ActionEvent(view, NumaView.Action.VISIBLE));
+        verify(view).showNumaChart("node0");
+        verify(view).showNumaChart("node1");
+        verify(view).showNumaChart("node2");
+        verify(timer).start();
+
+        actionListener.getValue().actionPerformed(new ActionEvent(view, NumaView.Action.HIDDEN));
+        verify(view).hideNumaChart("node0");
+        verify(view).hideNumaChart("node1");
+        verify(view).hideNumaChart("node2");
+        verify(timer).stop();
+
+        // Can't test the default branch.
+    }
+
+    @Test
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public void verifyTimerAction() {
+        timerAction.getValue().run();
+        ArgumentCaptor<List> dataCaptor = ArgumentCaptor.forClass(List.class);
+        verify(view).addNumaData(eq("node0"), dataCaptor.capture());
+        verify(view).addNumaData(eq("node1"), dataCaptor.capture());
+        verify(view).addNumaData(eq("node2"), dataCaptor.capture());
+
+        List list1 = dataCaptor.getAllValues().get(0);
+        DiscreteTimeData<Double> data11 = (DiscreteTimeData<Double>) list1.get(0);
+        assertEquals(100.0, data11.getData(), 0.0);
+        DiscreteTimeData<Double> data12 = (DiscreteTimeData<Double>) list1.get(1);
+        assertEquals(90., data12.getData(), 0.0);
+
+        List list2 = dataCaptor.getAllValues().get(1);
+        DiscreteTimeData<Double> data21 = (DiscreteTimeData<Double>) list2.get(0);
+        assertEquals(50.0, data21.getData(), 0.0);
+        DiscreteTimeData<Double> data22 = (DiscreteTimeData<Double>) list2.get(1);
+        assertEquals(60.0, data22.getData(), 0.0);
+
+        List list3 = dataCaptor.getAllValues().get(2);
+        DiscreteTimeData<Double> data31 = (DiscreteTimeData<Double>) list3.get(0);
+        assertEquals(70.0, data31.getData(), 0.0);
+        DiscreteTimeData<Double> data32 = (DiscreteTimeData<Double>) list3.get(1);
+        assertEquals(80.0, data32.getData(), 0.0);
+    }
+
+    @Test
+    public void testView() {
+        assertSame(view, numaController.getView());
+    }
+
+    @Test
+    public void testLocalizedName() {
+        Locale defaultLocale = Locale.getDefault();
+        try {
+            Locale.setDefault(Locale.US);
+            assertEquals("NUMA", numaController.getLocalizedName());
+        } finally {
+            Locale.setDefault(defaultLocale);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-core/src/test/java/com/redhat/thermostat/numa/client/locale/LocaleResourcesTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 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.numa.client.locale;
+
+import com.redhat.thermostat.numa.client.locale.LocaleResources;
+import com.redhat.thermostat.test.locale.AbstractLocaleResourcesTest;
+
+public class LocaleResourcesTest extends AbstractLocaleResourcesTest<LocaleResources> {
+
+    @Override
+    protected Class<LocaleResources> getEnumClass() {
+        return LocaleResources.class;
+    }
+
+    @Override
+    protected String getResourceBundle() {
+        return LocaleResources.RESOURCE_BUNDLE;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-swing/pom.xml	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <artifactId>thermostat-numa</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>0.5.0-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-numa-client-swing</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat NUMA Swing Client plugin</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Private-Package>com.redhat.thermostat.numa.client.swing.internal</Private-Package>
+            <Bundle-Activator>com.redhat.thermostat.numa.client.swing.internal.Activator</Bundle-Activator>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-SymbolicName>com.redhat.thermostat.numa.client.swing</Bundle-SymbolicName>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.easytesting</groupId>
+      <artifactId>fest-swing</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>net.java.openjdk.cacio</groupId>
+      <artifactId>cacio-tta</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jfree</groupId>
+      <artifactId>jfreechart</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-swing</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-swing-components</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-numa-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-swing/src/main/java/com/redhat/thermostat/numa/client/swing/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 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.numa.client.swing.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+
+public class Activator implements BundleActivator {
+    
+    private ServiceRegistration serviceReg;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        NumaViewProvider viewProvider = new SwingNumaViewProvider();
+        // Unregistered on Activator.stop
+        serviceReg = context.registerService(NumaViewProvider.class.getName(), viewProvider, null);
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        serviceReg.unregister();
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-swing/src/main/java/com/redhat/thermostat/numa/client/swing/internal/NumaPanel.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,332 @@
+/*
+ * Copyright 2013 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.numa.client.swing.internal;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.LayoutStyle.ComponentPlacement;
+import javax.swing.SwingUtilities;
+
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.NumberTickUnit;
+import org.jfree.chart.renderer.xy.XYItemRenderer;
+import org.jfree.data.RangeType;
+import org.jfree.data.time.FixedMillisecond;
+import org.jfree.data.time.RegularTimePeriod;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+
+import com.redhat.thermostat.client.swing.ComponentVisibleListener;
+import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.RecentTimeSeriesChartPanel;
+import com.redhat.thermostat.client.swing.components.SectionHeader;
+import com.redhat.thermostat.client.ui.ChartColors;
+import com.redhat.thermostat.client.ui.RecentTimeSeriesChartController;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.numa.client.core.NumaView;
+import com.redhat.thermostat.numa.client.locale.LocaleResources;
+import com.redhat.thermostat.storage.model.DiscreteTimeData;
+import com.redhat.thermostat.swing.components.experimental.WrapLayout;
+
+public class NumaPanel extends NumaView implements SwingComponent {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private JPanel visiblePanel;
+
+    private final NumaCheckboxListener numaCheckboxListener = new NumaCheckboxListener();
+
+    private final JPanel numaCheckBoxPanel = new JPanel(new WrapLayout(FlowLayout.LEADING));
+    private final CopyOnWriteArrayList<GraphVisibilityChangeListener> listeners = new CopyOnWriteArrayList<>();
+    private final TimeSeriesCollection numaCollection = new TimeSeriesCollection();
+    private final Map<String, TimeSeries> dataset = new HashMap<>();
+    private final Map<String, JCheckBox> checkBoxes = new HashMap<>();
+    private final Map<String, Color> colors = new HashMap<>();
+
+    private JFreeChart chart;
+
+    public NumaPanel() {
+        super();
+        initializePanel();
+
+        visiblePanel.addHierarchyListener(new ComponentVisibleListener() {
+            @Override
+            public void componentShown(Component component) {
+                notifier.fireAction(Action.VISIBLE);
+            }
+            @Override
+            public void componentHidden(Component component) {
+                notifier.fireAction(Action.HIDDEN);
+            }
+        });
+    }
+
+    @Override
+    public Component getUiComponent() {
+        return visiblePanel;
+    }
+
+    @Override
+    public void addNumaChart(final String tag, final String humanReadableName) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                int colorIndex = colors.size();
+                colors.put(tag, ChartColors.getColor(colorIndex));
+                TimeSeries series = new TimeSeries(tag);
+                dataset.put(tag, series);
+                JCheckBox newCheckBox = new JCheckBox(createLabelWithLegend(humanReadableName, colors.get(tag)));
+                newCheckBox.setActionCommand(tag);
+                newCheckBox.setSelected(true);
+                newCheckBox.addActionListener(numaCheckboxListener);
+                newCheckBox.setOpaque(false);
+                checkBoxes.put(tag, newCheckBox);
+                numaCheckBoxPanel.add(newCheckBox);
+
+                updateColors();
+            }
+        });
+
+    }
+
+    private String createLabelWithLegend(String text, Color color) {
+        String hexColor = "#" + Integer.toHexString(color.getRGB() & 0x00ffffff);
+        return "<html> <font color='" + hexColor + "'>\u2588</font> " + text + "</html>";
+    }
+
+    @Override
+    public void removeNumaChart(final String tag) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                TimeSeries series = dataset.remove(tag);
+                numaCollection.removeSeries(series);
+                JCheckBox box = checkBoxes.remove(tag);
+                numaCheckBoxPanel.remove(box);
+
+                updateColors();
+            }
+        });
+    }
+
+    @Override
+    public void showNumaChart(final String tag) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                TimeSeries series = dataset.get(tag);
+                numaCollection.addSeries(series);
+
+                updateColors();
+            }
+        });
+    }
+
+    @Override
+    public void hideNumaChart(final String tag) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                TimeSeries series = dataset.get(tag);
+                numaCollection.removeSeries(series);
+
+                updateColors();
+            }
+        });
+    }
+
+    @Override
+    public void addNumaData(final String tag, List<DiscreteTimeData<? extends Number>> data) {
+        final List<DiscreteTimeData<? extends Number>> copy = new ArrayList<>(data);
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                final TimeSeries series = dataset.get(tag);
+                for (DiscreteTimeData<? extends Number> timeData: copy) {
+                    RegularTimePeriod period = new FixedMillisecond(timeData.getTimeInMillis());
+                    if (series.getDataItem(period) == null) {
+                        Double data = (Double) timeData.getData();
+                        series.add(new FixedMillisecond(timeData.getTimeInMillis()), data, false);
+                    }
+                }
+                series.fireSeriesChanged();
+            }
+        });
+    }
+
+    @Override
+    public void clearNumaData(final String tag) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                TimeSeries series = dataset.get(tag);
+                series.clear();
+            }
+        });
+    }
+
+    @Override
+    public void addGraphVisibilityListener(GraphVisibilityChangeListener listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeGraphVisibilityListener(GraphVisibilityChangeListener listener) {
+        listeners.remove(listener);
+    }
+
+    @Override
+    public void addActionListener(ActionListener<Action> listener) {
+        notifier.addActionListener(listener);
+    }
+
+    @Override
+    public void removeActionListener(ActionListener<Action> listener) {
+        notifier.removeActionListener(listener);
+    }
+
+    private void initializePanel() {
+        visiblePanel = new JPanel();
+        visiblePanel.setOpaque(false);
+
+        chart = createNumaChart();
+
+        JPanel chartPanel = new RecentTimeSeriesChartPanel(new RecentTimeSeriesChartController(chart));
+        chartPanel.setOpaque(false);
+
+        JLabel lblNuma = new SectionHeader(translator.localize(LocaleResources.NUMA_SECTION_OVERVIEW));
+
+        numaCheckBoxPanel.setOpaque(false);
+
+        GroupLayout groupLayout = new GroupLayout(visiblePanel);
+        groupLayout.setHorizontalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addGroup(groupLayout.createParallelGroup(Alignment.LEADING)
+                        .addComponent(chartPanel, GroupLayout.DEFAULT_SIZE, 883, Short.MAX_VALUE)
+                        .addComponent(lblNuma)
+                        .addComponent(numaCheckBoxPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE))
+                    .addContainerGap())
+        );
+        groupLayout.setVerticalGroup(
+            groupLayout.createParallelGroup(Alignment.LEADING)
+                .addGroup(groupLayout.createSequentialGroup()
+                    .addContainerGap()
+                    .addComponent(lblNuma)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(chartPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, Short.MAX_VALUE)
+                    .addPreferredGap(ComponentPlacement.RELATED)
+                    .addComponent(numaCheckBoxPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE, GroupLayout.PREFERRED_SIZE)
+                    .addContainerGap())
+        );
+        visiblePanel.setLayout(groupLayout);
+    }
+
+    private JFreeChart createNumaChart() {
+        JFreeChart chart = ChartFactory.createTimeSeriesChart(
+                translator.localize(LocaleResources.NUMA_CHART_TITLE), // Title
+                translator.localize(LocaleResources.NUMA_CHART_TIME_LABEL), // x-axis Label
+                translator.localize(LocaleResources.NUMA_CHART_NUM_HITS_LABEL), // y-axis Label
+                numaCollection, // Dataset
+                false, // Show Legend
+                false, // Use tooltips
+                false // Configure chart to generate URLs?
+                );
+
+        chart.getPlot().setBackgroundPaint( new Color(255,255,255,0) );
+        chart.getPlot().setBackgroundImageAlpha(0.0f);
+        chart.getPlot().setOutlinePaint(new Color(0,0,0,0));
+
+        NumberAxis rangeAxis = (NumberAxis) chart.getXYPlot().getRangeAxis();
+        rangeAxis.setAutoRangeMinimumSize(100);
+        rangeAxis.setAutoRangeIncludesZero(true);
+        rangeAxis.setRangeType(RangeType.POSITIVE);
+        rangeAxis.setTickUnit(new NumberTickUnit(10.0));
+
+        return chart;
+    }
+
+    private void fireShowHideHandlers(boolean show, String tag) {
+        for (GraphVisibilityChangeListener listener: listeners) {
+            if (show) {
+                listener.show(tag);
+            } else {
+                listener.hide(tag);
+            }
+        }
+    }
+
+    /**
+     * Adding or removing series to the series collection may change the order
+     * of existing items. Plus the paint for the index is now out-of-date. So
+     * let's walk through all the series and set the right paint for those.
+     */
+    private void updateColors() {
+        XYItemRenderer itemRenderer = chart.getXYPlot().getRenderer();
+        for (int i = 0; i < numaCollection.getSeriesCount(); i++) {
+            String tag = (String) numaCollection.getSeriesKey(i);
+            Color color = colors.get(tag);
+            itemRenderer.setSeriesPaint(i, color);
+        }
+    }
+
+    private class NumaCheckboxListener implements java.awt.event.ActionListener {
+        @Override
+        public void actionPerformed(java.awt.event.ActionEvent e) {
+            JCheckBox source = (JCheckBox) e.getSource();
+            fireShowHideHandlers(source.isSelected(), source.getActionCommand());
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-swing/src/main/java/com/redhat/thermostat/numa/client/swing/internal/SwingNumaViewProvider.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 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.numa.client.swing.internal;
+
+import com.redhat.thermostat.numa.client.core.NumaView;
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+
+public class SwingNumaViewProvider implements NumaViewProvider {
+
+    @Override
+    public NumaView createView() {
+        return new NumaPanel();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-swing/src/test/java/com/redhat/thermostat/numa/client/swing/internal/ActivatorTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 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.numa.client.swing.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.numa.client.core.NumaViewProvider;
+import com.redhat.thermostat.numa.client.swing.internal.Activator;
+import com.redhat.thermostat.numa.client.swing.internal.SwingNumaViewProvider;
+import com.redhat.thermostat.test.StubBundleContext;
+
+public class ActivatorTest {
+    
+    @Test
+    public void verifyStartRegistersViewProvider() throws Exception {
+        StubBundleContext ctx = new StubBundleContext();
+        Activator activator = new Activator();
+        activator.start(ctx);
+        assertTrue(ctx.isServiceRegistered(NumaViewProvider.class.getName(), SwingNumaViewProvider.class));
+        assertEquals(1, ctx.getAllServices().size());
+        activator.stop(ctx);
+        assertEquals(0, ctx.getAllServices().size());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/client-swing/src/test/java/com/redhat/thermostat/numa/client/swing/internal/NumaPanelTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 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.numa.client.swing.internal;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.awt.Container;
+
+import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner;
+
+import org.fest.swing.fixture.Containers;
+import org.fest.swing.fixture.FrameFixture;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+
+@RunWith(CacioFESTRunner.class)
+public class NumaPanelTest {
+
+    @Test
+    public void testActionListener() {
+        NumaPanel numaPanel = new NumaPanel();
+        ActionListener listener = mock(ActionListener.class);
+        numaPanel.addActionListener(listener);
+        FrameFixture frameFixture = Containers.frameFixtureFor((Container) numaPanel.getUiComponent());
+        frameFixture.show();
+        verify(listener).actionPerformed(new ActionEvent(numaPanel, BasicView.Action.VISIBLE));
+        frameFixture.close();
+        verify(listener).actionPerformed(new ActionEvent(numaPanel, BasicView.Action.VISIBLE));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/NodeStat.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 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.numa.common;
+
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class NodeStat implements Pojo {
+
+    private long numaHit = -1;
+    private long numaMiss = -1;
+    private long numaForeign = -1;
+    private long interleaveHit = -1;
+    private long localNode = -1;
+    private long otherNode = -1;
+    private int node = -1;
+
+    @Persist
+    public long getNumaHit() {
+        return numaHit;
+    }
+
+    @Persist
+    public void setNumaHit(long numaHit) {
+        this.numaHit = numaHit;
+    }
+
+    @Persist
+    public long getNumaMiss() {
+        return numaMiss;
+    }
+
+    @Persist
+    public void setNumaMiss(long numaMiss) {
+        this.numaMiss = numaMiss;
+    }
+
+    @Persist
+    public long getNumaForeign() {
+        return numaForeign;
+    }
+
+    @Persist
+    public void setNumaForeign(long numaForeign) {
+        this.numaForeign = numaForeign;
+    }
+
+    @Persist
+    public long getInterleaveHit() {
+        return interleaveHit;
+    }
+
+    @Persist
+    public void setInterleaveHit(long interleaveHit) {
+        this.interleaveHit = interleaveHit;
+    }
+
+    @Persist
+    public long getLocalNode() {
+        return localNode;
+    }
+
+    @Persist
+    public void setLocalNode(long localNode) {
+        this.localNode = localNode;
+    }
+
+    @Persist
+    public long getOtherNode() {
+        return otherNode;
+    }
+
+    @Persist
+    public void setOtherNode(long otherNode) {
+        this.otherNode = otherNode;
+    }
+
+    @Persist
+    public int getNode() {
+        return node;
+    }
+
+    @Persist
+    public void setNode(int node) {
+        this.node = node;
+    }
+
+    public String toString() {
+        return "NumaStat: node: " + node + ", numaHit: " + numaHit + ", numaMiss: " + numaMiss + ", numaForeign: " + numaForeign + ", interleaveHit: " + interleaveHit + ", localNode: " + localNode + ", otherNode: " + otherNode;
+    }
+}
--- a/numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaDAO.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaDAO.java	Tue Jan 22 16:14:48 2013 +0100
@@ -36,6 +36,9 @@
 
 package com.redhat.thermostat.numa.common;
 
+import java.util.List;
+
+import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Key;
 
@@ -45,5 +48,14 @@
     
     static final Category<NumaStat> numaStatCategory = new Category<>("numa-stat", NumaStat.class, Key.AGENT_ID, Key.TIMESTAMP, nodeStats);
 
+    static final Key<Integer> hostNumNumaNodes = new Key<>("hostNumNumaNodes", false);
+
+    static final Category<NumaHostInfo> numaHostCategory = new Category<>("numa-host-info", NumaHostInfo.class, Key.AGENT_ID, hostNumNumaNodes);
+
+    void putNumberOfNumaNodes(int numNodes);
+    int getNumberOfNumaNodes(HostRef ref);
+
     void putNumaStat(NumaStat stat);
+
+    List<NumaStat> getLatestNumaStats(HostRef ref, long lastSeenTimeStamp);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaHostInfo.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 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.numa.common;
+
+import com.redhat.thermostat.storage.model.BasePojo;
+
+public class NumaHostInfo extends BasePojo {
+
+    private int numNumaNodes;
+
+    public void setNumNumaNodes(int numNumaNodes) {
+        this.numNumaNodes = numNumaNodes;
+    }
+
+    public int getNumNumaNodes() {
+        return numNumaNodes;
+    }
+
+}
--- a/numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaStat.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/NumaStat.java	Tue Jan 22 16:14:48 2013 +0100
@@ -40,13 +40,15 @@
 import com.redhat.thermostat.storage.core.Entity;
 import com.redhat.thermostat.storage.core.Persist;
 import com.redhat.thermostat.storage.model.BasePojo;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
 
 @Entity
-public class NumaStat extends BasePojo {
+public class NumaStat extends BasePojo implements TimeStampedPojo{
 
     private long timeStamp = -1;
     private NumaNodeStat[] nodeStats = new NumaNodeStat[0];
 
+    @Override
     @Persist
     public long getTimeStamp() {
         return timeStamp;
--- a/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/Activator.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/Activator.java	Tue Jan 22 16:14:48 2013 +0100
@@ -56,14 +56,13 @@
             @Override
             public Object addingService(ServiceReference reference) {
                 Storage storage = (Storage) context.getService(reference);
-                NumaDAO memoryStatDao = new NumaDAOImpl(storage);
-                reg = context.registerService(NumaDAO.class.getName(), memoryStatDao, null);
+                NumaDAO numaDao = new NumaDAOImpl(storage);
+                reg = context.registerService(NumaDAO.class.getName(), numaDao, null);
                 return super.addingService(reference);
             }
             
             @Override
-            public void removedService(ServiceReference reference,
-                    Object service) {
+            public void removedService(ServiceReference reference, Object service) {
                 reg.unregister();
                 super.removedService(reference, service);
             }
--- a/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImpl.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/common/src/main/java/com/redhat/thermostat/numa/common/internal/NumaDAOImpl.java	Tue Jan 22 16:14:48 2013 +0100
@@ -36,18 +36,32 @@
 
 package com.redhat.thermostat.numa.common.internal;
 
+import java.util.List;
+
+import com.redhat.thermostat.common.dao.HostLatestPojoListGetter;
+import com.redhat.thermostat.common.dao.HostRef;
 import com.redhat.thermostat.numa.common.NumaDAO;
+import com.redhat.thermostat.numa.common.NumaHostInfo;
 import com.redhat.thermostat.numa.common.NumaStat;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Put;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.Criteria;
+import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.Storage;
 
 public class NumaDAOImpl implements NumaDAO {
 
     private final Storage storage;
+    private final HostLatestPojoListGetter<NumaStat> getter;
+
 
     NumaDAOImpl(Storage storage) {
         this.storage = storage;
         storage.registerCategory(numaStatCategory);
+        storage.registerCategory(numaHostCategory);
+        this.getter = new HostLatestPojoListGetter<>(storage, numaStatCategory);
     }
 
     @Override
@@ -56,4 +70,31 @@
         add.setPojo(stat);
         add.apply();
     }
+
+    @Override
+    public List<NumaStat> getLatestNumaStats(HostRef ref, long lastTimeStamp) {
+        return getter.getLatest(ref, lastTimeStamp);
+    }
+
+    @Override
+    public void putNumberOfNumaNodes(int numNodes) {
+        Replace replace = storage.createReplace(numaHostCategory);
+        NumaHostInfo numaHostInfo = new NumaHostInfo();
+        numaHostInfo.setNumNumaNodes(numNodes);
+        replace.setPojo(numaHostInfo);
+        replace.apply();
+    }
+
+    @Override
+    public int getNumberOfNumaNodes(HostRef ref) {
+        Query<NumaHostInfo> query = storage.createQuery(numaHostCategory);
+        query.where(Key.AGENT_ID, Criteria.EQUALS, ref.getAgentId());
+        query.limit(1);
+        Cursor<NumaHostInfo> numaHostInfo = query.execute();
+        if (numaHostInfo.hasNext()) {
+            return numaHostInfo.next().getNumNumaNodes();
+        } else {
+            return 0;
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/numa/common/src/test/java/com/redhat/thermostat/numa/common/NumaHostInfoTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 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.numa.common;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class NumaHostInfoTest {
+
+    @Test
+    public void testGetterSetter() {
+        NumaHostInfo numaHostInfo = new NumaHostInfo();
+        numaHostInfo.setNumNumaNodes(42);
+        assertEquals(42, numaHostInfo.getNumNumaNodes());
+    }
+}
--- a/numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplTest.java	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/common/src/test/java/com/redhat/thermostat/numa/common/internal/NumaDAOImplTest.java	Tue Jan 22 16:14:48 2013 +0100
@@ -95,6 +95,7 @@
         numaDAO.putNumaStat(numaStat);
 
         verify(storage).registerCategory(NumaDAO.numaStatCategory);
+        verify(storage).registerCategory(NumaDAO.numaHostCategory);
         verify(storage).createAdd(NumaDAO.numaStatCategory);
         verifyNoMoreInteractions(storage);
         verify(add).setPojo(numaStat);
--- a/numa/pom.xml	Fri Jan 18 18:43:12 2013 +0100
+++ b/numa/pom.xml	Tue Jan 22 16:14:48 2013 +0100
@@ -53,6 +53,8 @@
   <modules>
     <module>agent</module>
     <module>common</module>
+    <module>client-core</module>
+    <module>client-swing</module>
   </modules>
 
 </project>