changeset 1562:dee75542747f

Add gui bits for the profiler Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011622.html
author Omair Majid <omajid@redhat.com>
date Mon, 24 Nov 2014 13:12:39 -0500
parents 7b5d6d7edb9b
children 67f51a18294b
files vm-profiler/client-core/pom.xml vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParser.java vm-profiler/client-core/src/test/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParserTest.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/Activator.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResources.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileController.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileService.java vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileView.java vm-profiler/client-swing/src/main/resources/com/redhat/thermostat/vm/profiler/client/swing/internal/strings.properties vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/ActivatorTest.java vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResourcesTest.java vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileControllerTest.java vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileServiceTest.java vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileDAO.java vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImpl.java vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistration.java vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistrationTest.java vm-profiler/distribution/thermostat-plugin.xml vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java
diffstat 20 files changed, 1326 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/vm-profiler/client-core/pom.xml	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/client-core/pom.xml	Mon Nov 24 13:12:39 2014 -0500
@@ -75,7 +75,7 @@
             <Bundle-Activator>com.redhat.thermostat.vm.profiler.client.core.internal.Activator</Bundle-Activator>
             <Export-Package>
               com.redhat.thermostat.vm.profiler.client.core,
-              com.redhat.thermostat.vm.profiler.client.locale
+              com.redhat.thermostat.vm.profiler.client.locale,
             </Export-Package>
             <Private-Package>
               com.redhat.thermostat.vm.profiler.client.core.internal
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-core/src/main/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParser.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.core;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class ProfilingResultParser {
+
+    private static final Logger logger = LoggingUtils.getLogger(ProfilingResultParser.class);
+
+    public class ProfilingResult {
+        /** Method Name -> Time (ns) */
+        public final Map<String, Long> time;
+        public ProfilingResult(Map<String, Long> data) {
+            this.time = Collections.unmodifiableMap(data);
+        }
+    }
+
+    public ProfilingResult parse(InputStream in) {
+        Map<String, Long> result = new HashMap<String, Long>();
+
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                String[] parts = line.split("\\s+");
+                long time = Long.valueOf(parts[0]);
+                String name = parts[1];
+                result.put(name, time);
+            }
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "Unable to parse profiling data: ", e);
+        }
+        return new ProfilingResult(result);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-core/src/test/java/com/redhat/thermostat/vm/profiler/client/core/ProfilingResultParserTest.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.core;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser.ProfilingResult;
+
+public class ProfilingResultParserTest {
+
+    @Test
+    public void parsesCorrectly() throws Exception {
+        String data = "1 foo\n2 bar";
+        ByteArrayInputStream in = new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8));
+        ProfilingResultParser parser = new ProfilingResultParser();
+        ProfilingResult result = parser.parse(in);
+        Map<String, Long> times = result.time;
+        assertEquals(1, (long) times.get("foo"));
+        assertEquals(2, (long) times.get("bar"));
+    }
+}
--- a/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/Activator.java	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/Activator.java	Mon Nov 24 13:12:39 2014 -0500
@@ -36,26 +36,66 @@
 
 package com.redhat.thermostat.vm.profiler.client.swing.internal;
 
+import java.util.Hashtable;
 import java.util.Map;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
 
+import com.redhat.thermostat.client.command.RequestQueue;
+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.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 
 public class Activator implements BundleActivator {
 
+    private ServiceRegistration<InformationService> registration;
+    private MultipleServiceTracker tracker;
+
     @Override
     public void start(final BundleContext context) throws Exception {
-        // TODO implement me
+
+        Class<?>[] deps = new Class<?>[] {
+                ApplicationService.class,
+                AgentInfoDAO.class,
+                ProfileDAO.class,
+                RequestQueue.class,
+        };
+
+        tracker = new MultipleServiceTracker(context, deps, new MultipleServiceTracker.Action() {
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                ApplicationService service = (ApplicationService) services.get(ApplicationService.class.getName());
+                AgentInfoDAO agentInfoDao = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
+                ProfileDAO profileDao = (ProfileDAO) services.get(ProfileDAO.class.getName());
+                RequestQueue queue = (RequestQueue) services.get(RequestQueue.class.getName());
+
+                InformationService<VmRef> profileService = new VmProfileService(service, agentInfoDao, profileDao, queue);
+
+                Hashtable<String,String> properties = new Hashtable<>();
+                properties.put(Constants.GENERIC_SERVICE_CLASSNAME, VmRef.class.getName());
+                properties.put(InformationService.KEY_SERVICE_ID, profileService.getClass().getName());
+
+                registration = context.registerService(InformationService.class, profileService, properties);
+            }
+
+            @Override
+            public void dependenciesUnavailable() {
+                registration.unregister();
+                registration = null;
+            }
+        });
+        tracker.open();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        // TODO implement me
+        tracker.close();
     }
 
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResources.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+
+    PROFILER_TAB_NAME,
+
+    PROFILER_HEADING,
+    START_PROFILING,
+    STOP_PROFILING,
+
+    PROFILER_LIST_ITEM,
+    PROFILER_RESULTS_METHOD,
+    PROFILER_RESULTS_TIME,
+    ;
+
+    static final String RESOURCE_BUNDLE = "com.redhat.thermostat.vm.profiler.client.swing.internal.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/SwingVmProfileView.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.util.Date;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingWorker;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.DefaultTableModel;
+
+import com.redhat.thermostat.client.swing.IconResource;
+import com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.ActionToggleButton;
+import com.redhat.thermostat.client.swing.components.HeaderPanel;
+import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser.ProfilingResult;
+
+public class SwingVmProfileView extends VmProfileView implements SwingComponent {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private static final double SPLIT_PANE_RATIO = 0.3;
+
+    private final CopyOnWriteArrayList<ActionListener<ProfileAction>> listeners =
+            new CopyOnWriteArrayList<>();
+
+    private HeaderPanel mainContainer;
+
+    private ActionToggleButton startStopProfilingButton;
+
+    private DefaultListModel<Profile> listModel;
+    private JList<Profile> profileList;
+
+    private DefaultTableModel tableModel;
+
+    static class ProfileItemRenderer extends DefaultListCellRenderer {
+        @Override
+        public Component getListCellRendererComponent(JList<?> list,
+                Object value, int index, boolean isSelected,
+                boolean cellHasFocus) {
+            if (value instanceof Profile) {
+                Profile profile = (Profile) value;
+                value = translator
+                        .localize(LocaleResources.PROFILER_LIST_ITEM,
+                                profile.name, new Date(profile.timeStamp).toString())
+                        .getContents();
+            }
+            return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+        }
+    }
+
+    public SwingVmProfileView() {
+        listModel = new DefaultListModel<>();
+
+        startStopProfilingButton = new ActionToggleButton(
+                IconResource.RECORD.getIcon());
+        updateProfilingButtonStatus(false);
+
+        startStopProfilingButton.addActionListener(new java.awt.event.ActionListener() {
+            @Override
+            public void actionPerformed(java.awt.event.ActionEvent e) {
+                if (startStopProfilingButton.isSelected()) {
+                    updateProfilingButtonStatus(true);
+                    fireProfileAction(ProfileAction.START_PROFILING);
+                } else {
+                    updateProfilingButtonStatus(false);
+                    fireProfileAction(ProfileAction.STOP_PROFILING);
+                }
+            }
+        });
+
+        mainContainer = new HeaderPanel(translator.localize(LocaleResources.PROFILER_HEADING));
+        mainContainer.addToolBarButton(startStopProfilingButton);
+        new ComponentVisibilityNotifier().initialize(mainContainer, notifier);
+
+        profileList = new JList<>(listModel);
+        profileList.setCellRenderer(new ProfileItemRenderer());
+        profileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        profileList.addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent e) {
+                if (e.getValueIsAdjusting()) {
+                    return;
+                }
+
+                fireProfileAction(ProfileAction.PROFILE_SELECTED);
+            }
+        });
+        JScrollPane profileListPane = new JScrollPane(profileList);
+
+        Vector<String> columnNames = new Vector<>();
+        columnNames.add(translator.localize(LocaleResources.PROFILER_RESULTS_METHOD).getContents());
+        columnNames.add(translator.localize(LocaleResources.PROFILER_RESULTS_TIME, "ns").getContents());
+        tableModel = new DefaultTableModel(columnNames, 0);
+
+        JTable profileTable = new JTable(tableModel);
+        JScrollPane profileTablePane = new JScrollPane(profileTable);
+
+        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+                profileListPane, profileTablePane);
+        splitPane.setDividerLocation(SPLIT_PANE_RATIO);
+        splitPane.setResizeWeight(0.5);
+
+        mainContainer.add(splitPane, BorderLayout.CENTER);
+    }
+
+    @Override
+    public void addProfileActionListener(ActionListener<ProfileAction> listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeProfileActionlistener(ActionListener<ProfileAction> listener) {
+        listeners.remove(listener);
+    }
+
+    private void fireProfileAction(final ProfileAction action) {
+        new SwingWorker<Void, Void>() {
+            @Override
+            protected Void doInBackground() throws Exception {
+                ActionEvent<ProfileAction> event = new ActionEvent<>(this, action);
+                for (ActionListener<ProfileAction> listener : listeners) {
+                    listener.actionPerformed(event);
+                }
+                return null;
+            }
+        }.execute();
+    }
+
+    @Override
+    public void setCurrentlyProfiling(boolean currentlyProfiling) {
+        updateProfilingButtonStatus(currentlyProfiling);
+    }
+
+    private void updateProfilingButtonStatus(boolean currentlyProfiling) {
+        if (currentlyProfiling) {
+            startStopProfilingButton.setText(translator.localize(LocaleResources.STOP_PROFILING).getContents());
+        } else {
+            startStopProfilingButton.setText(translator.localize(LocaleResources.START_PROFILING).getContents());
+        }
+    }
+
+    @Override
+    public void setAvailableProfilingRuns(List<Profile> data) {
+        listModel.clear();
+        for (Profile item : data) {
+            listModel.addElement(item);
+        }
+    }
+
+    @Override
+    public Profile getSelectedProfile() {
+        if (profileList.isSelectionEmpty()) {
+            throw new AssertionError("Selection is empty");
+            // return null;
+        }
+        return profileList.getSelectedValue();
+    }
+
+    @Override
+    public void setProfilingDetailData(ProfilingResult results) {
+        // delete all existing data
+        tableModel.setRowCount(0);
+
+        for (Entry<String, Long> methodAndTime : results.time.entrySet()) {
+            Object[] data = new Object[] {
+                    methodAndTime.getKey(),
+                    methodAndTime.getValue(),
+            };
+            tableModel.addRow(data);
+        }
+    }
+
+    @Override
+    public Component getUiComponent() {
+        return mainContainer;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileController.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.client.core.controllers.InformationServiceController;
+import com.redhat.thermostat.client.core.views.BasicView;
+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.Clock;
+import com.redhat.thermostat.common.SystemClock;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser;
+import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser.ProfilingResult;
+import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.Profile;
+import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.ProfileAction;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
+import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
+
+public class VmProfileController implements InformationServiceController<VmRef> {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private ApplicationService service;
+    private ProfileDAO profileDao;
+    private AgentInfoDAO agentInfoDao;
+    private RequestQueue queue;
+    private VmRef vm;
+
+    private VmProfileView view;
+
+    private Timer updater;
+
+    private Clock clock;
+
+    public VmProfileController(ApplicationService service,
+            AgentInfoDAO agentInfoDao, ProfileDAO dao,
+            RequestQueue queue,
+            VmRef vm) {
+        this(service, agentInfoDao, dao, queue, new SystemClock(), new SwingVmProfileView(), vm);
+    }
+
+    VmProfileController(ApplicationService service,
+            AgentInfoDAO agentInfoDao, ProfileDAO dao,
+            RequestQueue queue, Clock clock,
+            VmProfileView view, VmRef vm) {
+        this.service = service;
+        this.agentInfoDao = agentInfoDao;
+        this.profileDao = dao;
+        this.queue = queue;
+        this.clock = clock;
+        this.view = view;
+        this.vm = vm;
+
+        // TODO dispose the timer when done
+        updater = service.getTimerFactory().createTimer();
+        updater.setSchedulingType(SchedulingType.FIXED_DELAY);
+        updater.setInitialDelay(0);
+        updater.setDelay(5);
+        updater.setTimeUnit(TimeUnit.SECONDS);
+        updater.setAction(new Runnable() {
+            @Override
+            public void run() {
+                updateViewWithProfiledRuns();
+            }
+        });
+
+        view.addActionListener(new ActionListener<BasicView.Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                    case HIDDEN:
+                        updater.stop();
+                        break;
+                    case VISIBLE:
+                        updater.start();
+                        break;
+                    default:
+                        throw new AssertionError("Unknown action event: " + actionEvent);
+                }
+            }
+        });
+
+        view.addProfileActionListener(new ActionListener<VmProfileView.ProfileAction>() {
+            @Override
+            public void actionPerformed(ActionEvent<ProfileAction> actionEvent) {
+                ProfileAction id = actionEvent.getActionId();
+                switch (id) {
+                case START_PROFILING:
+                    sendProfilingRequest(true);
+                    break;
+                case STOP_PROFILING:
+                    sendProfilingRequest(false);
+                    break;
+                case PROFILE_SELECTED:
+                    updateViewWithProfileRunData();
+                    break;
+                default:
+                    throw new AssertionError("Unknown event: " + id);
+                }
+            }
+        });
+    }
+
+    private void sendProfilingRequest(boolean start) {
+        InetSocketAddress address = agentInfoDao.getAgentInformation(vm.getHostRef()).getRequestQueueAddress();
+        String action = start ? ProfileRequest.START_PROFILING : ProfileRequest.STOP_PROFILING;
+        Request req = ProfileRequest.create(address, vm.getVmId(), action);
+        queue.putRequest(req);
+    }
+
+    private void updateViewWithProfiledRuns() {
+        long end = clock.getRealTimeMillis();
+        long start = end - TimeUnit.DAYS.toMillis(1); // FIXME hardcoded 1 day
+
+        List<ProfileInfo> profileInfos = profileDao.getAllProfileInfo(vm, new Range<>(start, end));
+        List<Profile> profiles = new ArrayList<>();
+        for (ProfileInfo profileInfo : profileInfos) {
+            Profile profile = new Profile(profileInfo.getProfileId(), profileInfo.getTimeStamp());
+            profiles.add(profile);
+        }
+
+        view.setAvailableProfilingRuns(profiles);
+    }
+
+    private void updateViewWithProfileRunData() {
+        Profile selectedProfile = view.getSelectedProfile();
+        String profileId = selectedProfile.name;
+        InputStream in = profileDao.loadProfileDataById(vm, profileId);
+        ProfilingResult result = new ProfilingResultParser().parse(in);
+        view.setProfilingDetailData(result);
+    }
+
+    @Override
+    public UIComponent getView() {
+        return view;
+    }
+
+    @Override
+    public LocalizedString getLocalizedName() {
+        return translator.localize(LocaleResources.PROFILER_TAB_NAME);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileService.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.client.core.InformationService;
+import com.redhat.thermostat.client.core.controllers.InformationServiceController;
+import com.redhat.thermostat.common.AllPassFilter;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Filter;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
+
+public class VmProfileService implements InformationService<VmRef> {
+
+    private ApplicationService service;
+    private AgentInfoDAO agentInfoDao;
+    private ProfileDAO dao;
+    private RequestQueue queue;
+
+    public VmProfileService(ApplicationService service, AgentInfoDAO agentInfoDao, ProfileDAO dao, RequestQueue queue) {
+        this.service = service;
+        this.agentInfoDao = agentInfoDao;
+        this.dao = dao;
+        this.queue = queue;
+    }
+
+    @Override
+    public int getOrderValue() {
+        return ORDER_CPU_GROUP + 20;
+    }
+
+    @Override
+    public Filter<VmRef> getFilter() {
+        // we can't profile dead VMs, but we can still look at old profiling data.
+        return new AllPassFilter<>();
+    }
+
+    @Override
+    public InformationServiceController<VmRef> getInformationServiceController(VmRef ref) {
+        return new VmProfileController(service, agentInfoDao, dao, queue, ref);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/main/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileView.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import java.util.List;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.client.core.views.UIComponent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser.ProfilingResult;
+
+public abstract class VmProfileView extends BasicView implements UIComponent {
+
+    static class Profile {
+        public final String name;
+        public final long timeStamp;
+        public Profile(String name, long timeStamp) {
+            this.name = name;
+            this.timeStamp = timeStamp;
+        }
+    }
+
+    enum ProfileAction {
+        START_PROFILING,
+        STOP_PROFILING,
+
+        PROFILE_SELECTED,
+    }
+
+    public abstract void addProfileActionListener(ActionListener<ProfileAction> listener);
+
+    public abstract void removeProfileActionlistener(ActionListener<ProfileAction> listener);
+
+    public abstract void setCurrentlyProfiling(boolean profiling);
+
+    public abstract void setAvailableProfilingRuns(List<Profile> data);
+
+    public abstract Profile getSelectedProfile();
+
+    public abstract void setProfilingDetailData(ProfilingResult results);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/main/resources/com/redhat/thermostat/vm/profiler/client/swing/internal/strings.properties	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,10 @@
+PROFILER_TAB_NAME = Profiler
+
+PROFILER_HEADING = JVM Profiler
+
+START_PROFILING = Start Profiling
+STOP_PROFILING = Stop Profiling
+
+PROFILER_LIST_ITEM = Session @ {1} ({0})
+PROFILER_RESULTS_METHOD = Method
+PROFILER_RESULTS_TIME = Cumulative Time ({0})
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/ActivatorTest.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.client.core.InformationService;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.testutils.StubBundleContext;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
+
+public class ActivatorTest {
+
+    @Test
+    public void verifyActivatorRegistersGuiService() throws Exception {
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        ApplicationService appService = mock(ApplicationService.class);
+        bundleContext.registerService(ApplicationService.class, appService, null);
+
+        AgentInfoDAO agentInfoDao = mock(AgentInfoDAO.class);
+        bundleContext.registerService(AgentInfoDAO.class, agentInfoDao, null);
+
+        ProfileDAO profielDao = mock(ProfileDAO.class);
+        bundleContext.registerService(ProfileDAO.class, profielDao, null);
+
+        RequestQueue requestQueue = mock(RequestQueue.class);
+        bundleContext.registerService(RequestQueue.class, requestQueue, null);
+
+        Activator activator = new Activator();
+
+        activator.start(bundleContext);
+
+        assertTrue(bundleContext.isServiceRegistered(InformationService.class.getName(), VmProfileService.class));
+
+        activator.stop(bundleContext);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/LocaleResourcesTest.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import com.redhat.thermostat.testutils.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/vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileControllerTest.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.client.core.views.BasicView.Action;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ApplicationService;
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.TimerFactory;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.vm.profiler.client.core.ProfilingResultParser.ProfilingResult;
+import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.Profile;
+import com.redhat.thermostat.vm.profiler.client.swing.internal.VmProfileView.ProfileAction;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
+import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
+
+public class VmProfileControllerTest {
+
+    private static final String AGENT_ID = "some-agent-id";
+    private static final String AGENT_HOST = "foo";
+    private static final int AGENT_PORT = 10;
+    private static final InetSocketAddress AGENT_ADDRESS = new InetSocketAddress(AGENT_HOST, AGENT_PORT);
+    private static final String VM_ID = "some-vm-id";
+    private static final String PROFILE_ID = "some-profile-id";
+
+    private Timer timer;
+    private ApplicationService appService;
+    private AgentInfoDAO agentInfoDao;
+    private ProfileDAO profileDao;
+    private RequestQueue queue;
+    private Clock clock;
+    private VmProfileView view;
+    private VmRef vm;
+
+    private VmProfileController controller;
+    private HostRef agent;
+
+
+    @Before
+    public void setUp() {
+        timer = mock(Timer.class);
+
+        TimerFactory timerFactory = mock(TimerFactory.class);
+        when(timerFactory.createTimer()).thenReturn(timer);
+
+        appService = mock(ApplicationService.class);
+        when(appService.getTimerFactory()).thenReturn(timerFactory);
+
+        agentInfoDao = mock(AgentInfoDAO.class);
+        profileDao = mock(ProfileDAO.class);
+        queue = mock(RequestQueue.class);
+
+        clock = mock(Clock.class);
+        view = mock(VmProfileView.class);
+
+        agent = mock(HostRef.class);
+        when(agent.getAgentId()).thenReturn(AGENT_ID);
+
+        vm = mock(VmRef.class);
+        when(vm.getHostRef()).thenReturn(agent);
+        when(vm.getVmId()).thenReturn(VM_ID);
+
+        AgentInformation agentInfo = new AgentInformation();
+        agentInfo.setConfigListenAddress(AGENT_HOST + ":" + AGENT_PORT);
+        when(agentInfoDao.getAgentInformation(agent)).thenReturn(agentInfo);
+    }
+
+    @Test
+    public void timerRunsWhenVisible() throws Exception {
+        controller = createController();
+
+        verify(timer, never()).start();
+
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        verify(view).addActionListener(listenerCaptor.capture());
+
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, Action.VISIBLE));
+        verify(timer).start();
+    }
+
+
+    @Test
+    public void timerStopsWhenHidden() throws Exception {
+        controller = createController();
+
+        verify(timer, never()).start();
+
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        verify(view).addActionListener(listenerCaptor.capture());
+
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<Enum<?>>(view, Action.HIDDEN));
+        verify(timer).stop();
+    }
+
+    @Test
+    public void timerUpdatesView() throws Exception {
+        final long SOME_TIMESTAMP = 1000000000;
+        final long PROFILE_TIMESTAMP = SOME_TIMESTAMP - 100;
+
+        when(clock.getRealTimeMillis()).thenReturn(SOME_TIMESTAMP);
+        controller = createController();
+
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(timer).setAction(runnableCaptor.capture());
+
+        ProfileInfo profile = new ProfileInfo(AGENT_ID, VM_ID, PROFILE_TIMESTAMP, PROFILE_ID);
+
+        when(profileDao.getAllProfileInfo(vm,
+                new Range<>(SOME_TIMESTAMP - TimeUnit.DAYS.toMillis(1) , SOME_TIMESTAMP)))
+            .thenReturn(Arrays.asList(profile));
+
+        Runnable runnable = runnableCaptor.getValue();
+        runnable.run();
+
+        ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class);
+        verify(view).setAvailableProfilingRuns(listCaptor.capture());
+        List<Profile> resultList = listCaptor.getValue();
+        assertEquals(1, resultList.size());
+        assertEquals(PROFILE_TIMESTAMP, resultList.get(0).timeStamp);
+    }
+
+    @Test
+    public void startProfilingWorks() throws Exception {
+        controller = createController();
+
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        verify(view).addProfileActionListener(listenerCaptor.capture());
+
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, ProfileAction.START_PROFILING));
+
+        ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+        verify(queue).putRequest(requestCaptor.capture());
+        Request expectedRequest = ProfileRequest.create(AGENT_ADDRESS, VM_ID, ProfileRequest.START_PROFILING);
+        Request actualRequest = requestCaptor.getValue();
+        assertRequestEquals(actualRequest, expectedRequest);
+    }
+
+    @Test
+    public void stopProfilingWorks() throws Exception {
+        controller = createController();
+
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        verify(view).addProfileActionListener(listenerCaptor.capture());
+
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, ProfileAction.STOP_PROFILING));
+
+        ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+        verify(queue).putRequest(requestCaptor.capture());
+
+        Request expectedRequest = ProfileRequest.create(AGENT_ADDRESS, VM_ID, ProfileRequest.STOP_PROFILING);
+        Request actualRequest = requestCaptor.getValue();
+
+        assertRequestEquals(actualRequest, expectedRequest);
+    }
+
+    private void assertRequestEquals(Request actual, Request expected) {
+        assertEquals(expected.getParameterNames(), actual.getParameterNames());
+        assertEquals(expected.getReceiver(), actual.getReceiver());
+        assertEquals(expected.getType(), actual.getType());
+    }
+
+    @Test
+    public void selectingAProfileShowsDetails() throws Exception {
+        final String PROFILE_DATA = "1 foo";
+
+        controller = createController();
+
+        ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        verify(view).addProfileActionListener(listenerCaptor.capture());
+
+        Profile PROFILE = new Profile(PROFILE_ID, 10);
+
+        when(view.getSelectedProfile()).thenReturn(PROFILE);
+        when(profileDao.loadProfileDataById(vm, PROFILE_ID)).thenReturn(new ByteArrayInputStream(PROFILE_DATA.getBytes(StandardCharsets.UTF_8)));
+
+        listenerCaptor.getValue().actionPerformed(new ActionEvent<>(view, ProfileAction.PROFILE_SELECTED));
+
+        verify(view).setProfilingDetailData(isA(ProfilingResult.class));
+    }
+
+    private VmProfileController createController() {
+        return new VmProfileController(appService, agentInfoDao, profileDao, queue, clock, view, vm);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-swing/src/test/java/com/redhat/thermostat/vm/profiler/client/swing/internal/VmProfileServiceTest.java	Mon Nov 24 13:12:39 2014 -0500
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012-2014 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.vm.profiler.client.swing.internal;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.Filter;
+import com.redhat.thermostat.storage.core.VmRef;
+
+public class VmProfileServiceTest {
+
+    @Test
+    public void worksWithDeadAndAliveVms() throws Exception {
+        VmRef vm = mock(VmRef.class);
+
+        VmProfileService service = new VmProfileService(null, null, null, null);
+        Filter<VmRef> filter = service.getFilter();
+
+        assertTrue(filter.matches(vm));
+    }
+
+}
--- a/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileDAO.java	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileDAO.java	Mon Nov 24 13:12:39 2014 -0500
@@ -37,8 +37,10 @@
 package com.redhat.thermostat.vm.profiler.common;
 
 import java.io.InputStream;
+import java.util.List;
 
 import com.redhat.thermostat.annotations.Service;
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.storage.core.VmRef;
 
 @Service
@@ -46,7 +48,12 @@
 
     void saveProfileData(ProfileInfo info, InputStream data);
 
+    List<ProfileInfo> getAllProfileInfo(VmRef vm, Range<Long> timeRange);
+
+    InputStream loadProfileDataById(VmRef vm, String profileId);
+
     /** @return {@code null} if no data is available */
     InputStream loadLatestProfileData(VmRef vm);
 
+
 }
--- a/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImpl.java	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImpl.java	Mon Nov 24 13:12:39 2014 -0500
@@ -37,9 +37,11 @@
 package com.redhat.thermostat.vm.profiler.common.internal;
 
 import java.io.InputStream;
+import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
@@ -50,6 +52,7 @@
 import com.redhat.thermostat.storage.core.StatementExecutionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.core.VmTimeIntervalPojoListGetter;
 import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
 
@@ -77,11 +80,24 @@
             + Key.VM_ID.getName() + "' = ?s SORT '"
             + Key.TIMESTAMP.getName() + "' DSC LIMIT 1";
 
+    static final String DESC_QUERY_BY_ID = "QUERY "
+            + CATEGORY.getName() + " WHERE '"
+            + Key.AGENT_ID.getName() + "' = ?s AND '"
+            + Key.VM_ID.getName() + "' = ?s AND '"
+            + Key.TIMESTAMP.getName() + "' = ?s LIMIT 1";
+
+    // internal information of VmTimeIntervalPojoListGetter being leaked :(
+    static final String DESC_INTERVAL_QUERY = String.format(
+            VmTimeIntervalPojoListGetter.VM_INTERVAL_QUERY_FORMAT, ProfileDAOImpl.CATEGORY.getName());
+
     private final Storage storage;
+    private final VmTimeIntervalPojoListGetter<ProfileInfo> getter;
 
     public ProfileDAOImpl(Storage storage) {
         this.storage = storage;
         this.storage.registerCategory(CATEGORY);
+
+        this.getter = new VmTimeIntervalPojoListGetter<>(storage, CATEGORY);
     }
 
     @Override
@@ -108,6 +124,18 @@
     }
 
     @Override
+    public List<ProfileInfo> getAllProfileInfo(VmRef vm, Range<Long> timeRange) {
+        System.out.println("ProfileDAOImpl: getAllProfileInfo()");
+        return getter.getLatest(vm, timeRange.getMin(), timeRange.getMax());
+    }
+
+    @Override
+    public InputStream loadProfileDataById(VmRef vm, String profileId) {
+        // TODO should we check whether this profileId is valid by querying the DB first?
+        return getProfileData(profileId);
+    }
+
+    @Override
     public InputStream loadLatestProfileData(VmRef vm) {
         StatementDescriptor<ProfileInfo> desc = new StatementDescriptor<>(CATEGORY, DESC_QUERY_LATEST);
         PreparedStatement<ProfileInfo> prepared;
@@ -120,8 +148,7 @@
                 return null;
             }
             ProfileInfo info = cursor.next();
-            String profileId = info.getProfileId();
-            return storage.loadFile(profileId);
+            return getProfileData(info.getProfileId());
         } catch (DescriptorParsingException e) {
             logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
         } catch (StatementExecutionException e) {
@@ -130,4 +157,7 @@
         return null;
     }
 
+    private InputStream getProfileData(String profileId) {
+        return storage.loadFile(profileId);
+    }
 }
--- a/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistration.java	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistration.java	Mon Nov 24 13:12:39 2014 -0500
@@ -48,7 +48,9 @@
     @Override
     public DescriptorMetadata getDescriptorMetadata(String descriptor, PreparedParameter[] params) {
         if (descriptor.equals(ProfileDAOImpl.DESC_ADD_PROFILE_INFO)
-                || descriptor.equals(ProfileDAOImpl.DESC_QUERY_LATEST)) {
+                || descriptor.equals(ProfileDAOImpl.DESC_QUERY_LATEST)
+                || descriptor.equals(ProfileDAOImpl.DESC_QUERY_BY_ID)
+                || descriptor.equals(ProfileDAOImpl.DESC_INTERVAL_QUERY)) {
             String agentId = (String)params[0].getValue();
             String vmId = (String)params[1].getValue();
             DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
@@ -62,7 +64,9 @@
     public Set<String> getStatementDescriptors() {
         Set<String> results = new HashSet<>();
         results.add(ProfileDAOImpl.DESC_ADD_PROFILE_INFO);
+        results.add(ProfileDAOImpl.DESC_QUERY_BY_ID);
         results.add(ProfileDAOImpl.DESC_QUERY_LATEST);
+        results.add(ProfileDAOImpl.DESC_INTERVAL_QUERY);
         return results;
     }
 }
--- a/vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistrationTest.java	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistrationTest.java	Mon Nov 24 13:12:39 2014 -0500
@@ -50,5 +50,7 @@
         Set<String> names = registration.getStatementDescriptors();
         assertTrue(names.contains(ProfileDAOImpl.DESC_ADD_PROFILE_INFO));
         assertTrue(names.contains(ProfileDAOImpl.DESC_QUERY_LATEST));
+        assertTrue(names.contains(ProfileDAOImpl.DESC_QUERY_BY_ID));
+        assertTrue(names.contains(ProfileDAOImpl.DESC_INTERVAL_QUERY));
     }
 }
--- a/vm-profiler/distribution/thermostat-plugin.xml	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/distribution/thermostat-plugin.xml	Mon Nov 24 13:12:39 2014 -0500
@@ -89,6 +89,14 @@
     </commands>
     <extensions>
         <extension>
+            <name>gui</name>
+            <bundles>
+                <bundle><symbolic-name>com.redhat.thermostat.vm.profiler.common</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.vm.profiler.client.core</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.vm.profiler.client.swing</symbolic-name><version>${project.version}</version></bundle>
+            </bundles>
+        </extension>
+        <extension>
             <name>agent</name>
             <bundles>
                 <bundle><symbolic-name>com.redhat.thermostat.vm.profiler.common</symbolic-name><version>${project.version}</version></bundle>
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java	Fri Nov 21 11:02:34 2014 -0500
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java	Mon Nov 24 13:12:39 2014 -0500
@@ -160,7 +160,7 @@
             try (BufferedWriter out = Files.newBufferedWriter(output, StandardCharsets.UTF_8, options)) {
                 Map<String, AtomicLong> data = ProfileRecorder.getInstance().getData();
                 for (Map.Entry<String, AtomicLong> entry : data.entrySet()) {
-                    out.write(entry.getValue().get() + "\t" + entry.getKey());
+                    out.write(entry.getValue().get() + "\t" + entry.getKey() + "\n");
                 }
                 System.out.println("AGENT: profiling data written to " + output.toString());
                 return output.toString();