changeset 543:7c48230daefb

Client controllers review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-August/002720.html reviewed-by: omajid PR 1107
author Mario Torre <neugens.limasoftware@gmail.com>
date Fri, 17 Aug 2012 12:52:08 +0200
parents f04463bcfa11
children ed971fd48ad4
files thread/client-controllers/pom.xml thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/CommonController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationService.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/osgi/Activator.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesControllerTest.java
diffstat 10 files changed, 1173 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/pom.xml	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses />.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<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>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-thread</artifactId>
+    <version>0.4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-thread-client-controllers</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Thermostat Thread Info Client Controllers</name>
+
+  <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>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </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-client-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-thread-collector</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-thread-client-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+  </dependencies>
+
+  <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-Activator>com.redhat.thermostat.thread.client.controller.osgi.Activator</Bundle-Activator>
+            <Private-Package>
+              com.redhat.thermostat.thread.client.controller.osgi,
+              com.redhat.thermostat.thread.client.controller.impl,
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/CommonController.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+public interface CommonController {
+    void initialise();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.client.osgi.service.ApplicationCache;
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.client.osgi.service.VmInformationServiceController;
+import com.redhat.thermostat.client.osgi.service.BasicView.Action;
+import com.redhat.thermostat.client.ui.UIComponent;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.NotImplementedException;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.thread.client.common.ThreadView;
+import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
+import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
+import com.redhat.thermostat.thread.client.common.ThreadView.ThreadAction;
+import com.redhat.thermostat.thread.client.common.chart.LivingDaemonThreadDifferenceChart;
+
+import com.redhat.thermostat.thread.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.collector.ThreadSummary;
+import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+
+public class ThreadInformationController implements VmInformationServiceController {
+
+    private static final Logger logger = Logger.getLogger(ThreadInformationController.class.getSimpleName());
+    
+    private ThreadView view;
+    private ThreadCollector collector;
+    private ApplicationService appService;
+    
+    private Timer timer;
+    private ApplicationCache cache;
+    
+    String CACHE_RECORDING_KEY = "thread-live-recording-";
+
+    private LivingDaemonThreadDifferenceChart model;
+    
+    public ThreadInformationController(VmRef ref, ApplicationService appService,
+                                       ThreadCollectorFactory collectorFactory, 
+                                       ThreadViewProvider viewFactory)
+    {
+        CACHE_RECORDING_KEY = CACHE_RECORDING_KEY + ref.getStringID();
+        
+        this.appService = appService;
+        cache = appService.getApplicationCache();
+
+        view = viewFactory.createView();
+        collector = collectorFactory.getCollector(ref);
+        
+        initControllers();
+        
+        timer = ApplicationContext.getInstance().getTimerFactory().createTimer();
+        
+        timer.setInitialDelay(0);
+        timer.setDelay(1000);
+        timer.setTimeUnit(TimeUnit.MILLISECONDS);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+        
+        timer.setAction(new ThreadInformationDataCollector());
+        
+        model = new LivingDaemonThreadDifferenceChart("Living Threads vs. Daemon Threads",
+                                                      "time", "threads", "Living Threads",
+                                                      "Daemon Threads");
+        model.setMaximumItemCount(3600);
+        
+        view.addActionListener(new ActionListener<Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case HIDDEN:
+                    timer.stop();
+                    break;
+                
+                case VISIBLE:                    
+                    timer.start();
+                    break;
+
+                default:
+                    throw new NotImplementedException("unknown event: " + actionEvent.getActionId());
+                }
+            }
+        });
+        
+        view.setRecording(isRecording());
+        view.addThreadActionListener(new ThreadActionListener());
+    }
+    
+    private boolean isRecording() {
+        Boolean isRecording = (Boolean) cache.getAttribute(CACHE_RECORDING_KEY);
+        return (isRecording != null && isRecording); 
+    }
+    
+    private void setRecording(boolean recording) {
+        cache.addAttribute(CACHE_RECORDING_KEY, recording);
+    }
+    
+    @Override
+    public String getLocalizedName() {
+        return "Threads";
+    }
+
+    @Override
+    public UIComponent getView() {
+        return view;
+    }
+    
+    private class ThreadInformationDataCollector implements Runnable {
+        @Override
+        public void run() {
+
+            // load the very latest thread summary
+            ThreadSummary latestSummary = collector.getLatestThreadSummary();
+            if (latestSummary.getTimeStamp() != 0) {
+                view.setLiveThreads(Long.toString(latestSummary.currentLiveThreads()));
+                view.setDaemonThreads(Long.toString(latestSummary.currentDaemonThreads()));
+            }
+            
+            long lastHour = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1);
+            List<ThreadSummary> summaries = collector.getThreadSummary(lastHour);
+            if (summaries.size() != 0) {
+                for (ThreadSummary summary : summaries) {
+                    model.addData(summary.getTimeStamp(), summary.currentLiveThreads(), summary.currentDaemonThreads());
+                }
+                view.updateLivingDaemonTimeline(model);
+            }
+        }
+    }
+    
+    private class ThreadActionListener implements ActionListener<ThreadAction> {
+
+        @Override
+        public void actionPerformed(ActionEvent<ThreadAction> actionEvent) {
+            switch (actionEvent.getActionId()) {
+            case START_LIVE_RECORDING:
+                collector.startHarvester();
+                setRecording(true);
+                break;
+            
+            case STOP_LIVE_RECORDING:
+                collector.stopHarvester();
+                setRecording(false);
+                break;
+                
+            default:
+                logger.log(Level.WARNING, "unkown action: " + actionEvent.getActionId());
+                break;
+            }
+        }
+    }
+    
+    private void initControllers() {
+        CommonController capsController =
+                new VMThreadCapabilitiesController(view.createVMThreadCapabilitiesView(), collector);
+        capsController.initialise();
+        
+        CommonController threadTableController =
+                new ThreadTableController(view.createThreadTableView(), collector,
+                                          ApplicationContext.getInstance().getTimerFactory().createTimer());
+        threadTableController.initialise();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationService.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import com.redhat.thermostat.client.osgi.service.AlwaysMatchFilter;
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.client.osgi.service.VmFilter;
+import com.redhat.thermostat.client.osgi.service.VmInformationService;
+import com.redhat.thermostat.client.osgi.service.VmInformationServiceController;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
+import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
+
+public class ThreadInformationService implements VmInformationService {
+
+    private VmFilter filter = new AlwaysMatchFilter();
+    private ApplicationService service;
+    private ThreadCollectorFactory collectorFactory;
+    private ThreadViewProvider viewFactory;
+    
+    public ThreadInformationService(ApplicationService appService, ThreadCollectorFactory collectorFactory,
+                                    ThreadViewProvider viewFactory)
+    {
+        this.service = appService;
+        this.collectorFactory = collectorFactory;
+        this.viewFactory = viewFactory;
+    }
+    
+    @Override
+    public VmFilter getFilter() {
+        return filter;
+    }
+
+    @Override
+    public VmInformationServiceController getInformationServiceController(VmRef ref) {
+        return new ThreadInformationController(ref, service, collectorFactory, viewFactory);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import java.lang.Thread.State;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.osgi.service.BasicView.Action;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.thread.client.common.ThreadTableBean;
+import com.redhat.thermostat.thread.client.common.ThreadTableView;
+import com.redhat.thermostat.thread.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.ThreadInfo;
+
+public class ThreadTableController implements CommonController {
+    
+    private ThreadTableView threadTableView;
+    private ThreadCollector collector;
+    private Timer timer;
+    
+    public ThreadTableController(ThreadTableView threadTableView,
+                                 ThreadCollector collector,
+                                 Timer timer)
+    {
+        this.collector = collector;
+        this.threadTableView = threadTableView;
+        this.timer = timer;
+    }
+
+    @Override
+    public void initialise() {
+        
+        timer.setInitialDelay(0);
+        timer.setDelay(1000);
+        timer.setTimeUnit(TimeUnit.MILLISECONDS);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+        timer.setAction(new ThreadTableControllerAction());
+        
+        threadTableView.addActionListener(new ActionListener<Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case VISIBLE:
+                    timer.start();
+                    break;
+
+                case HIDDEN:
+                    timer.stop();
+                    break;
+
+                default:
+                    break;
+                }
+            }
+        });
+    }
+    
+    private class ThreadTableControllerAction implements Runnable {
+        @Override
+        public void run() {
+            List<ThreadInfo> infos = collector.getThreadInfo();
+            if(infos.size() > 0) {
+                
+                long lastPollTimestamp = infos.get(0).getTimeStamp();
+                
+                // build the statistics for each thread in the
+                // collected thread list
+                
+                // first, get a map of all threads with the respective info
+                // the list will contain an ordered-by-timestamp list
+                // with the known history for each thread
+                Map<ThreadInfo, List<ThreadInfo>> stats = new HashMap<>();
+                for (ThreadInfo info : infos) {
+                    List<ThreadInfo> beanList = stats.get(info);
+                    if (beanList == null) {
+                        beanList = new ArrayList<ThreadInfo>();
+                        stats.put(info, beanList);
+                    }                    
+                    beanList.add(info);
+                }
+                
+                List<ThreadTableBean> tableBeans = new ArrayList<>();
+                
+                // now we have the list, we can do all the analysis we need
+                for (ThreadInfo key : stats.keySet()) {
+                    ThreadTableBean bean = new ThreadTableBean();
+                    
+                    bean.setName(key.getName());
+                    bean.setId(key.getThreadID());
+                    
+                    bean.setWaitedCount(key.getWaitedCount());
+                    bean.setBlockedCount(key.getBlockedCount());
+                    
+                    // get start time and stop time, if any
+                    List<ThreadInfo> beanList = stats.get(key);
+                    long last = beanList.get(0).getTimeStamp();
+                    long first = beanList.get(beanList.size() - 1).getTimeStamp();
+                    
+                    bean.setStartTimeStamp(first);
+                    if (last < lastPollTimestamp) {
+                        // this thread died somewhere after this polling time... rip
+                        bean.setStopTimeStamp(last);
+                    }
+                    
+                    // time for some stats
+                    double running = 0;
+                    double waiting = 0;
+                    for (ThreadInfo info : beanList) {
+                        State state = info.getState();
+                        switch (state) {
+                        case RUNNABLE:
+                            running++;
+                            break;
+                        case NEW:
+                        case TERMINATED:
+                            System.err.println("yeah!");
+                            break;
+                        case BLOCKED:
+                        case TIMED_WAITING:
+                        case WAITING:
+                            waiting++;
+                        default:
+                            break;
+                        }
+                    }
+                    int polls = beanList.size();
+                    double runningPercent = (running/polls) * 100;
+                    double waitingPercent = (waiting/polls) * 100;
+                    
+                    bean.setRunningPercent(runningPercent);
+                    bean.setWaitingPercent(waitingPercent);
+                    
+                    tableBeans.add(bean);
+                }
+                
+                threadTableView.display(tableBeans);
+            }
+            
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import com.redhat.thermostat.client.osgi.service.BasicView.Action;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
+import com.redhat.thermostat.thread.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+
+public class VMThreadCapabilitiesController implements CommonController {
+
+    private ThreadCollector collector;
+    private VMThreadCapabilitiesView view;
+    
+    public VMThreadCapabilitiesController(VMThreadCapabilitiesView view, ThreadCollector collector) {
+        this.view = view;
+        this.collector = collector;
+    }
+    
+    @Override
+    public void initialise() {
+        view.addActionListener(new ActionListener<Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case VISIBLE:
+                    VMThreadCapabilities caps = collector.getVMThreadCapabilities();
+                    view.setVMThreadCapabilities(caps);
+                    break;
+
+                default:
+                    break;
+                }
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/osgi/Activator.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.osgi;
+
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.client.osgi.service.VmInformationService;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
+import com.redhat.thermostat.thread.client.controller.impl.ThreadInformationService;
+import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
+
+public class Activator implements BundleActivator {
+
+    @SuppressWarnings({ "rawtypes" })
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        
+        Class[] classes = new Class[] {
+                ThreadCollectorFactory.class,
+                ApplicationService.class,
+                ThreadViewProvider.class
+        };
+        
+        Action action = new Action() {
+            @Override
+            public void doIt(Map<String, Object> services) {
+                ThreadCollectorFactory collectorFactory = (ThreadCollectorFactory) services.get(ThreadCollectorFactory.class.getName());
+                ApplicationService applicationService = (ApplicationService) services.get(ApplicationService.class.getName());
+                ThreadViewProvider viewFactory = (ThreadViewProvider) services.get(ThreadViewProvider.class.getName());
+                
+                VmInformationService vmInfoService = new ThreadInformationService(applicationService, collectorFactory, viewFactory);
+                context.registerService(VmInformationService.class.getName(), vmInfoService, null);
+            }
+        };
+        
+        MultipleServiceTracker tracker = new MultipleServiceTracker(context, classes, action);
+        tracker.open();
+    }
+    
+    @Override
+    public void stop(BundleContext context) throws Exception {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.anyString;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.osgi.service.ApplicationCache;
+import com.redhat.thermostat.client.osgi.service.ApplicationService;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.appctx.ApplicationContext;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.common.dao.VmRef;
+import com.redhat.thermostat.thread.client.common.ThreadTableView;
+import com.redhat.thermostat.thread.client.common.ThreadView;
+import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
+import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
+import com.redhat.thermostat.thread.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.ThreadCollectorFactory;
+
+public class ThreadInformationControllerTest {
+
+    private Timer timer;
+    private ThreadView view;
+    private ActionListener<ThreadView.Action> actionListener;
+        
+    private ThreadViewProvider viewFactory;
+    private ThreadInformationController controller;
+    
+    private ApplicationService appService;
+    
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+        setUpTimers();
+        setUpView();
+    }
+    
+    @After
+    public void tearDown() {
+        ApplicationContextUtil.resetApplicationContext();
+    }
+
+    private void setUpView() {
+        VMThreadCapabilitiesView commonController1 = mock(VMThreadCapabilitiesView.class);
+        ThreadTableView commonController2 = mock(ThreadTableView.class);
+
+        view = mock(ThreadView.class);
+        viewFactory = mock(ThreadViewProvider.class);
+        when(viewFactory.createView()).thenReturn(view);
+        
+        when(view.createVMThreadCapabilitiesView()).thenReturn(commonController1);
+        when(view.createThreadTableView()).thenReturn(commonController2);
+    }
+    
+    private void setUpTimers() {
+        timer = mock(Timer.class);
+        ArgumentCaptor<Runnable> timerActionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(timerActionCaptor.capture());
+
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        when(timerFactory.createTimer()).thenReturn(timer);
+        ApplicationContext.getInstance().setTimerFactory(timerFactory);
+    }
+    
+    private void setUpListeners() {        
+        ArgumentCaptor<ActionListener> viewArgumentCaptor1 = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addActionListener(viewArgumentCaptor1.capture());
+        
+        createController();
+        
+        actionListener = viewArgumentCaptor1.getValue();
+    }
+    
+    private void createController() {
+        ApplicationCache cache = mock(ApplicationCache.class);
+        appService = mock(ApplicationService.class);
+        when(appService.getApplicationCache()).thenReturn(cache);
+        
+        VmRef ref = mock(VmRef.class);
+        
+        ThreadCollectorFactory collectorFactory = mock(ThreadCollectorFactory.class); 
+        
+        controller = new ThreadInformationController(ref, appService, collectorFactory, viewFactory);
+    }
+    
+    @Test
+    public void liveRecodingKeySet() {
+        
+        ActionListener<ThreadView.ThreadAction> threadActionListener;
+        ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addThreadActionListener(viewArgumentCaptor.capture());
+        
+        VmRef ref = mock(VmRef.class);
+        when(ref.getStringID()).thenReturn("42");
+        ThreadCollectorFactory collectorFactory = mock(ThreadCollectorFactory.class);
+        
+        ThreadCollector collector = mock(ThreadCollector.class);
+        when(collectorFactory.getCollector(ref)).thenReturn(collector);
+        
+        ApplicationCache cache = mock(ApplicationCache.class);
+        appService = mock(ApplicationService.class);
+        when(appService.getApplicationCache()).thenReturn(cache);
+        when(cache.getAttribute(anyString())).thenReturn(false).thenReturn(true);
+        
+        controller = new ThreadInformationController(ref, appService, collectorFactory, viewFactory);
+        assertEquals("thread-live-recording-42", controller.CACHE_RECORDING_KEY);
+        
+        verify(cache).getAttribute("thread-live-recording-42");
+        verify(view).setRecording(false);
+        
+        threadActionListener = viewArgumentCaptor.getValue();
+        threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.START_LIVE_RECORDING));
+        
+        verify(cache).addAttribute("thread-live-recording-42", true);
+        verify(collector).startHarvester();
+        
+        threadActionListener.actionPerformed(new ActionEvent<>(view, ThreadView.ThreadAction.STOP_LIVE_RECORDING));
+        verify(cache).addAttribute("thread-live-recording-42", false);
+        verify(collector).stopHarvester();        
+        
+        // check that the value indeed persist across sessions
+        controller = new ThreadInformationController(ref, appService, collectorFactory, viewFactory);
+        verify(view).setRecording(true);
+    }
+    
+    @Test
+    public void testTimerStartOnViewVisible() {
+        setUpListeners();
+
+        actionListener.actionPerformed(new ActionEvent<>(view, ThreadView.Action.VISIBLE));
+        verify(timer).start();
+    }
+    
+    @Test
+    public void testTimerStopsOnViewHidden() {
+        setUpListeners();
+        
+        actionListener.actionPerformed(new ActionEvent<>(view, ThreadView.Action.HIDDEN));
+        verify(timer).stop();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.Timer;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.thread.client.common.ThreadTableView;
+import com.redhat.thermostat.thread.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.ThreadInfo;
+
+public class ThreadTableControllerTest {
+
+    private ThreadTableView view;
+    private ThreadCollector collector;
+    
+    private Timer timer;
+    
+    private ActionListener<ThreadTableView.Action> actionListener;
+    ArgumentCaptor<Runnable> timerActionCaptor;
+    
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+        collector = mock(ThreadCollector.class);
+        
+        timer = mock(Timer.class);
+                
+        view = mock(ThreadTableView.class);
+        
+        setUpTimers();
+    }
+    
+    @After
+    public void tearDown() {
+        ApplicationContextUtil.resetApplicationContext();
+    }
+    
+    private void setUpTimers() {
+        timer = mock(Timer.class);
+        timerActionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(timerActionCaptor.capture());
+    }
+    
+    @Test
+    public void testStartThreadTableController() {
+        
+        ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addActionListener(viewArgumentCaptor.capture());
+        
+        ThreadTableController controller = new ThreadTableController(view, collector, timer);
+        controller.initialise();
+
+        actionListener = viewArgumentCaptor.getValue();
+        actionListener.actionPerformed(new ActionEvent<>(view, ThreadTableView.Action.VISIBLE));
+        
+        verify(timer).start();
+        
+        actionListener.actionPerformed(new ActionEvent<>(view, ThreadTableView.Action.HIDDEN));
+
+        verify(timer).stop();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesControllerTest.java	Fri Aug 17 12:52:08 2012 +0200
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+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.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
+import com.redhat.thermostat.thread.collector.ThreadCollector;
+import com.redhat.thermostat.thread.collector.VMThreadCapabilities;
+
+public class VMThreadCapabilitiesControllerTest {
+
+    private VMThreadCapabilitiesView vmThreadCapsView;
+    private ThreadCollector collector;
+    private VMThreadCapabilities caps;
+    
+    private ActionListener<VMThreadCapabilitiesView.Action> actionListener;
+    
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+        collector = mock(ThreadCollector.class);
+        caps = mock(VMThreadCapabilities.class);
+        
+        when(collector.getVMThreadCapabilities()).thenReturn(caps);
+        
+        vmThreadCapsView = mock(VMThreadCapabilitiesView.class);
+    }
+    
+    @After
+    public void tearDown() {
+        ApplicationContextUtil.resetApplicationContext();
+    }
+    
+    @Test
+    public void testStartVMThreadCapabilities() {
+        
+        ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(vmThreadCapsView).addActionListener(viewArgumentCaptor.capture());
+        
+        VMThreadCapabilitiesController controller = new VMThreadCapabilitiesController(vmThreadCapsView, collector);
+        controller.initialise();
+
+        actionListener = viewArgumentCaptor.getValue();
+        actionListener.actionPerformed(new ActionEvent<>(vmThreadCapsView, VMThreadCapabilitiesView.Action.VISIBLE));
+        
+        verify(collector).getVMThreadCapabilities();
+        verify(vmThreadCapsView).setVMThreadCapabilities(caps);
+    }
+
+}