changeset 1556:247db2fa05c5

Allow cli client to get profiling results Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011523.html
author Omair Majid <omajid@redhat.com>
date Tue, 18 Nov 2014 14:07:31 -0500
parents 7b8199115995
children a95d1e2e3e69
files vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileUploader.java vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileUploaderTest.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiverTest.java vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/AbstractCommand.java vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/Activator.java vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileVmCommand.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/ProfileInfo.java vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileRequest.java vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/Activator.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/ProfileDAOImplCategoryRegistration.java vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistration.java vm-profiler/common/src/main/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.CategoryRegistration vm-profiler/common/src/main/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplCategoryRegistrationTest.java vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistrationTest.java vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplTest.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControlMXBean.java vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerAgent.java web/war/pom.xml
diffstat 27 files changed, 1039 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/Activator.java	Tue Nov 18 14:07:31 2014 -0500
@@ -46,6 +46,8 @@
 import com.redhat.thermostat.agent.command.ReceiverRegistry;
 import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
 import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.storage.core.WriterID;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 
 public class Activator implements BundleActivator {
 
@@ -56,7 +58,11 @@
         final Properties configuration = new Properties();
         configuration.load(this.getClass().getResourceAsStream("settings.properties"));
 
-        Class<?>[] deps = new Class<?>[] { MXBeanConnectionPool.class };
+        Class<?>[] deps = new Class<?>[] {
+                MXBeanConnectionPool.class,
+                ProfileDAO.class,
+                WriterID.class,
+        };
         tracker = new MultipleServiceTracker(context, deps, new MultipleServiceTracker.Action() {
             private ReceiverRegistry requestHandlerRegisteration;
             private VmStatusListenerRegistrar vmStatusRegistrar;
@@ -74,9 +80,13 @@
 
             @Override
             public void dependenciesAvailable(Map<String, Object> services) {
-                MXBeanConnectionPool pool = (MXBeanConnectionPool) services.get(MXBeanConnectionPool.class.getName());
+                MXBeanConnectionPool pool = get(MXBeanConnectionPool.class, services);
+                WriterID writerIdProvider = get(WriterID.class, services);
+                ProfileDAO dao = get(ProfileDAO.class, services);
+                String writerId = writerIdProvider.getWriterID();
+
                 VmProfiler profiler = new VmProfiler(configuration, pool);
-                profileRequestHandler = new ProfileVmRequestReceiver(profiler);
+                profileRequestHandler = new ProfileVmRequestReceiver(writerId, profiler, dao);
 
                 requestHandlerRegisteration = new ReceiverRegistry(context);
                 requestHandlerRegisteration.registerReceiver(profileRequestHandler);
@@ -84,6 +94,9 @@
                 vmStatusRegistrar = new VmStatusListenerRegistrar(context);
                 vmStatusRegistrar.register(profileRequestHandler);
             }
+            private <T> T get(Class<T> klass, Map<String, Object> services) {
+                return (T) services.get(klass.getName());
+            }
         });
 
         tracker.open();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileUploader.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,70 @@
+/*
+ * 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.agent.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
+
+public class ProfileUploader {
+
+    private final ProfileDAO dao;
+    private final String agentId;
+    private final String vmId;
+
+    public ProfileUploader(ProfileDAO dao, String agentId, String vmId, int pid) {
+        this.dao = dao;
+        this.agentId = agentId;
+        this.vmId = vmId;
+    }
+
+    public void upload(long timeStamp, File data) throws IOException {
+        // FIXME resource leak: file is never closed
+        upload(timeStamp, new FileInputStream(data));
+    }
+
+    public void upload(long timeStamp, InputStream data) throws IOException {
+        String id = UUID.randomUUID().toString();
+        ProfileInfo info = new ProfileInfo(agentId, vmId, timeStamp, id);
+        dao.saveProfileData(info, data);
+    }
+}
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiver.java	Tue Nov 18 14:07:31 2014 -0500
@@ -46,6 +46,7 @@
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
 import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
 
 public class ProfileVmRequestReceiver implements RequestReceiver, VmStatusListener {
@@ -57,10 +58,16 @@
     /** A pid that corresponds to an unknown */
     private static final int UNKNOWN_VMID = -1;
 
+    private final String agentId;
+
     private final VmProfiler profiler;
+    private final ProfileDAO dao;
 
-    public ProfileVmRequestReceiver(VmProfiler profiler) {
+    public ProfileVmRequestReceiver(String agentId, VmProfiler profiler, ProfileDAO dao) {
         this.profiler = profiler;
+        this.dao = dao;
+
+        this.agentId = agentId;
     }
 
     @Override
@@ -102,7 +109,8 @@
         case ProfileRequest.STOP_PROFILING:
             logger.info("Stopping profiling " + pid);
             try {
-                profiler.stopProfiling(pid);
+                ProfileUploader uploader = new ProfileUploader(dao, agentId, vmId, pid);
+                profiler.stopProfiling(pid, uploader);
                 return OK;
             } catch (Exception e) {
                 logger.log(Level.INFO, "stop profiling failed", e);
--- a/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfiler.java	Tue Nov 18 14:07:31 2014 -0500
@@ -36,6 +36,7 @@
 
 package com.redhat.thermostat.vm.profiler.agent.internal;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.Objects;
 import java.util.Properties;
@@ -46,6 +47,8 @@
 
 import com.redhat.thermostat.agent.utils.management.MXBeanConnection;
 import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
+import com.redhat.thermostat.common.Clock;
+import com.redhat.thermostat.common.SystemClock;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.sun.tools.attach.AgentInitializationException;
 import com.sun.tools.attach.AgentLoadException;
@@ -58,17 +61,19 @@
 
     private final MXBeanConnectionPool connectionPool;
     private final Attacher attacher;
+    private final Clock clock;
 
     private String agentJarPath;
     private String asmJarPath;
 
     public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool) {
-        this(configuration, connectionPool, new Attacher());
+        this(configuration, connectionPool, new Attacher(), new SystemClock());
     }
 
-    public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool, Attacher attacher) {
+    public VmProfiler(Properties configuration, MXBeanConnectionPool connectionPool, Attacher attacher, Clock clock) {
         this.connectionPool = connectionPool;
         this.attacher = attacher;
+        this.clock = clock;
 
         // requireNonNull protects against bad config with missing values
         agentJarPath = Objects.requireNonNull(configuration.getProperty("AGENT_JAR"));
@@ -98,22 +103,44 @@
         }
     }
 
-    public void stopProfiling(int pid) throws ProfilerException {
+    public void stopProfiling(int pid, ProfileUploader uploader) throws ProfilerException {
         invokeMethodOnInstrumentation(pid, "stopProfiling");
+
+        String profilingDataFile = (String) getInstrumentationAttribute(pid, "ProfilingDataFile");
+        try {
+            uploader.upload(clock.getRealTimeMillis(), new File(profilingDataFile));
+        } catch (IOException e) {
+            throw new ProfilerException("Unable to save profiling data into storage", e);
+        }
     }
 
-    private void invokeMethodOnInstrumentation(int pid, String name) throws ProfilerException {
+    private Object invokeMethodOnInstrumentation(int pid, String name) throws ProfilerException {
         try {
             MXBeanConnection connection = connectionPool.acquire(pid);
             try {
                 ObjectName instrumentation = new ObjectName("com.redhat.thermostat:type=InstrumentationControl");
                 MBeanServerConnection server = connection.get();
-                server.invoke(instrumentation, name, new Object[0], new String[0]);
+                return server.invoke(instrumentation, name, new Object[0], new String[0]);
             } finally {
                 connectionPool.release(pid, connection);
             }
         } catch (Exception e) {
-            throw new ProfilerException("Unable to start remote profiling", e);
+            throw new ProfilerException("Unable to communicate with remote profiler", e);
+        }
+    }
+
+    private Object getInstrumentationAttribute(int pid, String name) throws ProfilerException {
+        try {
+            MXBeanConnection connection = connectionPool.acquire(pid);
+            try {
+                ObjectName instrumentation = new ObjectName("com.redhat.thermostat:type=InstrumentationControl");
+                MBeanServerConnection server = connection.get();
+                return server.getAttribute(instrumentation, name);
+            } finally {
+                connectionPool.release(pid, connection);
+            }
+        } catch (Exception e) {
+            throw new ProfilerException("Unable to communicate with remote profiler", e);
         }
     }
 
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ActivatorTest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -44,15 +44,22 @@
 
 import com.redhat.thermostat.agent.command.RequestReceiver;
 import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
+import com.redhat.thermostat.storage.core.WriterID;
 import com.redhat.thermostat.testutils.StubBundleContext;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 
 public class ActivatorTest {
 
     @Test
     public void requestHandlerIsRegistered() throws Exception {
         MXBeanConnectionPool pool = mock(MXBeanConnectionPool.class);
+        WriterID writerService = mock(WriterID.class);
+        ProfileDAO dao = mock(ProfileDAO.class);
+
         StubBundleContext context = new StubBundleContext();
         context.registerService(MXBeanConnectionPool.class, pool, null);
+        context.registerService(ProfileDAO.class, dao, null);
+        context.registerService(WriterID.class, writerService, null);
 
         Activator activator = new Activator();
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileUploaderTest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,89 @@
+/*
+ * 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.agent.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
+
+public class ProfileUploaderTest {
+
+    private ProfileDAO dao;
+
+    private final String AGENT_ID = "agent-id";
+    private final String VM_ID = "vm-id";
+    private final int PID = -1;
+    private final long TIME = 1_000_000_000;
+
+    private ProfileUploader uploader;
+
+    @Before
+    public void setUp() {
+        dao = mock(ProfileDAO.class);
+
+        uploader = new ProfileUploader(dao, AGENT_ID, VM_ID, PID);
+    }
+
+    @Test
+    public void uploadFile() throws Exception {
+        byte[] data = "Test Profile Data".getBytes(StandardCharsets.UTF_8);
+        ByteArrayInputStream input = new ByteArrayInputStream(data);
+
+        ArgumentCaptor<ProfileInfo> profileInfoCaptor = ArgumentCaptor.forClass(ProfileInfo.class);
+
+        uploader.upload(TIME, input);
+
+        verify(dao).saveProfileData(profileInfoCaptor.capture(), eq(input));
+        ProfileInfo profileInfo = profileInfoCaptor.getValue();
+        assertEquals(AGENT_ID, profileInfo.getAgentId());
+        assertEquals(VM_ID, profileInfo.getVmId());
+        assertEquals(TIME, profileInfo.getTimeStamp());
+        assertNotNull(profileInfo.getProfileId());
+    }
+}
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiverTest.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/ProfileVmRequestReceiverTest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -37,6 +37,8 @@
 package com.redhat.thermostat.vm.profiler.agent.internal;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
@@ -47,22 +49,28 @@
 import com.redhat.thermostat.common.command.Request;
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
 
 public class ProfileVmRequestReceiverTest {
 
+    private static final String AGENT_ID = "agent-id";
+
     private static final String VM_ID = "foo";
     private static final int VM_PID = 1;
 
     private ProfileVmRequestReceiver requestReceiver;
 
     private VmProfiler profiler;
+    private ProfileDAO dao;
+    private ProfileUploader uploader;
 
     @Before
     public void setUp() {
         profiler = mock(VmProfiler.class);
+        dao = mock(ProfileDAO.class);
 
-        requestReceiver = new ProfileVmRequestReceiver(profiler);
+        requestReceiver = new ProfileVmRequestReceiver(AGENT_ID, profiler, dao);
     }
 
     @Test
@@ -98,7 +106,7 @@
         result = requestReceiver.receive(request);
 
         assertEquals(ResponseType.OK, result.getType());
-        verify(profiler).stopProfiling(VM_PID);
+        verify(profiler).stopProfiling(eq(VM_PID), isA(ProfileUploader.class));
     }
 
     @Test
--- a/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/agent/src/test/java/com/redhat/thermostat/vm/profiler/agent/internal/VmProfilerTest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -38,10 +38,12 @@
 
 import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import java.io.File;
 import java.util.Properties;
 
 import javax.management.MBeanServerConnection;
@@ -52,6 +54,7 @@
 
 import com.redhat.thermostat.agent.utils.management.MXBeanConnection;
 import com.redhat.thermostat.agent.utils.management.MXBeanConnectionPool;
+import com.redhat.thermostat.common.Clock;
 import com.redhat.thermostat.vm.profiler.agent.internal.VmProfiler.Attacher;
 import com.sun.tools.attach.VirtualMachine;
 
@@ -64,11 +67,13 @@
     private MXBeanConnection connection;
     private MXBeanConnectionPool connectionPool;
     private Attacher attacher;
+    private Clock clock;
 
     private final int PID = 0;
 
     private final String AGENT_JAR = "foo";
     private final String ASM_JAR = "bar";
+    private final long TIME = 1_000_000_000;
 
     private ObjectName instrumentationName;
 
@@ -80,6 +85,9 @@
         props.setProperty("AGENT_JAR", AGENT_JAR);
         props.setProperty("ASM_JAR", ASM_JAR);
 
+        clock = mock(Clock.class);
+        when(clock.getRealTimeMillis()).thenReturn(TIME);
+
         attacher = mock(Attacher.class);
         vm = mock(VirtualMachine.class);
         when(attacher.attach(isA(String.class))).thenReturn(vm);
@@ -92,7 +100,7 @@
         connectionPool = mock(MXBeanConnectionPool.class);
         when(connectionPool.acquire(PID)).thenReturn(connection);
 
-        profiler = new VmProfiler(props, connectionPool, attacher);
+        profiler = new VmProfiler(props, connectionPool, attacher, clock);
     }
 
     @Test
@@ -110,11 +118,19 @@
 
     @Test
     public void stoppingProfilingLoadsJvmAgentAndMakesAnRmiCall() throws Exception {
-        profiler.stopProfiling(PID);
+        final String FILE = "foobar";
+        when(server.getAttribute(instrumentationName, "ProfilingDataFile")).thenReturn(FILE);
+
+        ProfileUploader uploader = mock(ProfileUploader.class);
+
+        profiler.stopProfiling(PID, uploader);
 
         verifyNoMoreInteractions(vm);
 
         verify(server).invoke(instrumentationName, "stopProfiling", new Object[0], new String[0]);
-        verify(connectionPool).release(PID, connection);
+        verify(uploader).upload(TIME, new File(FILE));
+        verify(connectionPool, times(2)).release(PID, connection);
+
+
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/AbstractCommand.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,88 @@
+/*
+ * 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.cli.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public abstract class AbstractCommand extends com.redhat.thermostat.common.cli.AbstractCommand {
+
+    // TODO these changes should probably be promoted to the AbstractCommand public API
+
+    protected Map<Class<?>, BlockingQueue<?>> serviceHolder = new HashMap<>();
+
+    private <T> BlockingQueue<T> getHolder(Class<T> serviceClass) {
+        if (!serviceHolder.containsKey(serviceClass)) {
+            serviceHolder.put(serviceClass, new LinkedBlockingQueue<T>(1));
+        }
+        return (BlockingQueue<T>) serviceHolder.get(serviceClass);
+    }
+
+    /** Insert {@code item} if it's non-{@code null} otherwise remove it */
+    protected <T> void addOrRemoveService(Class<T> serviceClass, T item) {
+        BlockingQueue<T> holder = getHolder(serviceClass);
+        if (item == null) {
+            if (holder.peek() != null) {
+                holder.remove();
+            }
+        } else {
+            try {
+                holder.put(item);
+            } catch (InterruptedException e) {
+                throw new AssertionError("Should not happen");
+            }
+        }
+    }
+
+    protected <T> T getService(Class<T> serviceClass) {
+        BlockingQueue<T> holder = getHolder(serviceClass);
+        try {
+            // a crappy version of peek()-with-timeout
+            T retValue = holder.poll(500, TimeUnit.MILLISECONDS);
+            if (retValue != null) {
+                holder.add(retValue);
+            }
+            return retValue;
+        } catch (InterruptedException e) {
+            return null;
+        }
+    }
+
+}
--- a/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/Activator.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/Activator.java	Tue Nov 18 14:07:31 2014 -0500
@@ -49,26 +49,32 @@
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 
 public class Activator implements BundleActivator {
 
-    private MultipleServiceTracker profileCommandDepsTracker;
-    private ServiceRegistration<Command> registration;
+    private MultipleServiceTracker profileVmCommandDepsTracker;
+    private ServiceRegistration<Command> profileVmCommandRegistration;
 
     @Override
     public void start(final BundleContext context) throws Exception {
+        registerProfileVmCommand(context);
+    }
+
+    private void registerProfileVmCommand(final BundleContext context) {
         final ProfileVmCommand command = new ProfileVmCommand();
         Hashtable<String,? super Object> properties = new Hashtable<>();
         properties.put(Command.NAME, "profile-vm");
-        registration = context.registerService(Command.class, command, properties);
+        profileVmCommandRegistration = context.registerService(Command.class, command, properties);
 
         Class<?>[] classes = new Class<?>[] {
                 AgentInfoDAO.class,
                 VmInfoDAO.class,
                 RequestQueue.class,
+                ProfileDAO.class,
         };
 
-        profileCommandDepsTracker = new MultipleServiceTracker(context, classes, new Action() {
+        profileVmCommandDepsTracker = new MultipleServiceTracker(context, classes, new Action() {
             @Override
             public void dependenciesAvailable(Map<String, Object> services) {
                 AgentInfoDAO agentInfoDao = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
@@ -77,6 +83,8 @@
                 command.setVmInfoDAO(vmInfoDao);
                 RequestQueue requestQueue = (RequestQueue) services.get(RequestQueue.class.getName());
                 command.setRequestQueue(requestQueue);
+                ProfileDAO profileDao = (ProfileDAO) services.get(ProfileDAO.class.getName());
+                command.setProfileDAO(profileDao);
             }
 
             @Override
@@ -87,15 +95,16 @@
             }
         });
 
-        profileCommandDepsTracker.open();
+        profileVmCommandDepsTracker.open();
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
-        profileCommandDepsTracker.close();
-        profileCommandDepsTracker = null;
+        profileVmCommandDepsTracker.close();
+        profileVmCommandDepsTracker = null;
 
-        registration.unregister();
+        profileVmCommandRegistration.unregister();
+        profileVmCommandRegistration = null;
     }
 
 }
--- a/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileVmCommand.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/client-cli/src/main/java/com/redhat/thermostat/vm/profiler/client/cli/internal/ProfileVmCommand.java	Tue Nov 18 14:07:31 2014 -0500
@@ -36,16 +36,16 @@
 
 package com.redhat.thermostat.vm.profiler.client.cli.internal;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.InetSocketAddress;
 import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
 
 import com.redhat.thermostat.client.cli.HostVMArguments;
 import com.redhat.thermostat.client.command.RequestQueue;
-import com.redhat.thermostat.common.cli.AbstractCommand;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.cli.Console;
@@ -53,10 +53,13 @@
 import com.redhat.thermostat.common.command.RequestResponseListener;
 import com.redhat.thermostat.common.command.Response;
 import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.utils.StreamUtils;
 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.storage.dao.VmInfoDAO;
 import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 import com.redhat.thermostat.vm.profiler.common.ProfileRequest;
 
 public class ProfileVmCommand extends AbstractCommand {
@@ -65,23 +68,20 @@
 
     private static final String START_ARGUMENT = "start";
     private static final String STOP_ARGUMENT = "stop";
-
-    private final BlockingQueue<VmInfoDAO> vmInfoDAOHolder = new LinkedBlockingQueue<>(1);
-    private final BlockingQueue<AgentInfoDAO> agentInfoDAOHolder = new LinkedBlockingQueue<>(1);
-    private final BlockingQueue<RequestQueue> requestQueueHolder = new LinkedBlockingQueue<>(1);
+    private static final String SHOW_ARGUMENT = "show";
 
     @Override
     public void run(CommandContext ctx) throws CommandException {
 
         HostVMArguments args = new HostVMArguments(ctx.getArguments(), true, true);
 
-        AgentInfoDAO agentInfoDAO = getFromHolder(agentInfoDAOHolder);
-        VmInfoDAO vmInfoDAO = getFromHolder(vmInfoDAOHolder);
+        AgentInfoDAO agentInfoDAO = getService(AgentInfoDAO.class);
+        VmInfoDAO vmInfoDAO = getService(VmInfoDAO.class);
 
         requireNonNull(agentInfoDAO, translator.localize(LocaleResources.AGENT_SERVICE_UNAVAILABLE));
         requireNonNull(vmInfoDAO, translator.localize(LocaleResources.VM_SERVICE_UNAVAILABLE));
 
-        RequestQueue requestQueue = getFromHolder(requestQueueHolder);
+        RequestQueue requestQueue = getService(RequestQueue.class);
         requireNonNull(requestQueue, translator.localize(LocaleResources.QUEUE_SERVICE_UNAVAILABLE));
 
         AgentInformation agentInfo = agentInfoDAO.getAgentInformation(args.getHost());
@@ -106,6 +106,9 @@
         case STOP_ARGUMENT:
             sendStopProfilingRequest(ctx.getConsole(), requestQueue, target, args.getVM().getVmId());
             break;
+        case SHOW_ARGUMENT:
+            showProfilingResults(ctx.getConsole(), args.getVM());
+            break;
         default:
             throw new CommandException(translator.localize(LocaleResources.UNKNOWN_COMMAND, command));
         }
@@ -166,41 +169,34 @@
 
     }
 
-    /** Insert {@code item} if it's non-{@code null} otherwise remove it */
-    private static <T> void addOrRemoveFromHoler(BlockingQueue<T> holder, T item) {
-        if (item == null) {
-            if (holder.peek() != null) {
-                holder.remove();
-            }
-        } else {
-            try {
-                holder.put(item);
-            } catch (InterruptedException e) {
-                throw new AssertionError("Should not happen");
-            }
-        }
+    private void showProfilingResults(Console console, VmRef vm) {
+        ProfileDAO dao = getService(ProfileDAO.class);
+        InputStream data = dao.loadLatestProfileData(vm);
+        displayProfilingData(console, data);
     }
 
-    private static <T> T getFromHolder(BlockingQueue<T> holder) {
+    private void displayProfilingData(Console console, InputStream data) {
         try {
-            // a crappy version of peek()-with-timeout
-            T retValue = holder.poll(500, TimeUnit.MILLISECONDS);
-            holder.add(retValue);
-            return retValue;
-        } catch (InterruptedException e) {
-            return null;
+            StreamUtils.copyStream(new BufferedInputStream(data), new BufferedOutputStream(console.getOutput()));
+        } catch (IOException e) {
+            console.getError().println("Error displaying data");
+            e.printStackTrace();
         }
     }
 
     void setAgentInfoDAO(AgentInfoDAO dao) {
-        addOrRemoveFromHoler(agentInfoDAOHolder, dao);
+        addOrRemoveService(AgentInfoDAO.class, dao);
     }
 
     void setVmInfoDAO(VmInfoDAO dao) {
-        addOrRemoveFromHoler(vmInfoDAOHolder, dao);
+        addOrRemoveService(VmInfoDAO.class, dao);
     }
 
     void setRequestQueue(RequestQueue queue) {
-        addOrRemoveFromHoler(requestQueueHolder, queue);
+        addOrRemoveService(RequestQueue.class, queue);
+    }
+
+    void setProfileDAO(ProfileDAO dao) {
+        addOrRemoveService(ProfileDAO.class, dao);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileDAO.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,52 @@
+/*
+ * 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.common;
+
+import java.io.InputStream;
+
+import com.redhat.thermostat.annotations.Service;
+import com.redhat.thermostat.storage.core.VmRef;
+
+@Service
+public interface ProfileDAO {
+
+    void saveProfileData(ProfileInfo info, InputStream data);
+
+    /** @return {@code null} if no data is available */
+    InputStream loadLatestProfileData(VmRef vm);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileInfo.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,97 @@
+/*
+ * 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.common;
+
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.BasePojo;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+
+@Entity
+public class ProfileInfo extends BasePojo implements TimeStampedPojo {
+
+    private String vmId;
+
+    // FIXME should be two fields for start time and stop time
+    private long timeStamp;
+
+    private String profileId;
+
+    public ProfileInfo(String agentId, String vmId, long timeStamp, String profileId) {
+        super(agentId);
+        this.vmId = vmId;
+        this.timeStamp = timeStamp;
+        this.profileId = profileId;
+    }
+
+    /* for deserialization */
+    public ProfileInfo() {
+        super(null); // fixed up by the deserializer
+    }
+
+    @Persist
+    public void setVmId(String vmId) {
+        this.vmId = vmId;
+    }
+
+    @Persist
+    public String getVmId() {
+        return this.vmId;
+    }
+
+    @Persist
+    public void setTimeStamp(long timeStamp) {
+        this.timeStamp = timeStamp;
+    }
+
+    @Persist
+    @Override
+    public long getTimeStamp() {
+        return this.timeStamp;
+    }
+
+    @Persist
+    public String getProfileId() {
+        return profileId;
+    }
+
+    @Persist
+    public void setProfileId(String fileName) {
+        this.profileId = fileName;
+    }
+
+}
--- a/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileRequest.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/ProfileRequest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -37,7 +37,6 @@
 package com.redhat.thermostat.vm.profiler.common;
 
 import java.net.InetSocketAddress;
-import java.util.Objects;
 
 import com.redhat.thermostat.common.command.Request;
 import com.redhat.thermostat.common.command.Request.RequestType;
--- a/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/Activator.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/Activator.java	Tue Nov 18 14:07:31 2014 -0500
@@ -36,19 +36,48 @@
 
 package com.redhat.thermostat.vm.profiler.common.internal;
 
+import java.util.Map;
+
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.util.tracker.ServiceTracker;
+
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.vm.profiler.common.ProfileDAO;
 
 public class Activator implements BundleActivator {
 
+    private ServiceRegistration<ProfileDAO> daoRegistration;
+    private MultipleServiceTracker tracker;
+
     @Override
-    public void start(BundleContext context) throws Exception {
-        // TODO implement me
+    public void start(final BundleContext context) throws Exception {
+        Class<?>[] deps = new Class<?>[] {
+                Storage.class,
+        };
+        tracker = new MultipleServiceTracker(context, deps, new MultipleServiceTracker.Action() {
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                Storage storage = (Storage) services.get(Storage.class.getName());
+                ProfileDAOImpl impl = new ProfileDAOImpl(storage);
+
+                daoRegistration = context.registerService(ProfileDAO.class, impl, null);
+            }
+            @Override
+            public void dependenciesUnavailable() {
+                daoRegistration.unregister();
+                daoRegistration = 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/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImpl.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,133 @@
+/*
+ * 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.common.internal;
+
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+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.vm.profiler.common.ProfileDAO;
+import com.redhat.thermostat.vm.profiler.common.ProfileInfo;
+
+public class ProfileDAOImpl implements ProfileDAO {
+
+    private static final Logger logger = LoggingUtils.getLogger(ProfileDAOImpl.class);
+
+    private static final Key<String> KEY_PROFILE_ID = new Key<>("profileId");
+
+    static final Category<ProfileInfo> CATEGORY = new Category<>(
+            "profile-info",
+            ProfileInfo.class,
+            Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, KEY_PROFILE_ID);
+
+    static final String DESC_ADD_PROFILE_INFO = ""
+            + "ADD " + CATEGORY.getName() + " SET "
+            + " '" + Key.AGENT_ID.getName() + "' = ?s ,"
+            + " '" + Key.VM_ID.getName() + "' = ?s ,"
+            + " '" + Key.TIMESTAMP.getName() + "' = ?l ,"
+            + " '" + KEY_PROFILE_ID.getName() + "' = ?s";
+
+    static final String DESC_QUERY_LATEST = "QUERY "
+            + CATEGORY.getName() + " WHERE '"
+            + Key.AGENT_ID.getName() + "' = ?s AND '"
+            + Key.VM_ID.getName() + "' = ?s SORT '"
+            + Key.TIMESTAMP.getName() + "' DSC LIMIT 1";
+
+    private final Storage storage;
+
+    public ProfileDAOImpl(Storage storage) {
+        this.storage = storage;
+        this.storage.registerCategory(CATEGORY);
+    }
+
+    @Override
+    public void saveProfileData(ProfileInfo info, InputStream data) {
+        storage.saveFile(info.getProfileId(), data);
+        addProfileInfoToStorage(info);
+    }
+
+    private void addProfileInfoToStorage(ProfileInfo info) {
+        StatementDescriptor<ProfileInfo> desc = new StatementDescriptor<>(CATEGORY, DESC_ADD_PROFILE_INFO);
+        PreparedStatement<ProfileInfo> prepared;
+        try {
+            prepared = storage.prepareStatement(desc);
+            prepared.setString(0, info.getAgentId());
+            prepared.setString(1, info.getVmId());
+            prepared.setLong(2, info.getTimeStamp());
+            prepared.setString(3, info.getProfileId());
+            prepared.execute();
+        } catch (DescriptorParsingException e) {
+            logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e);
+        }
+    }
+
+    @Override
+    public InputStream loadLatestProfileData(VmRef vm) {
+        StatementDescriptor<ProfileInfo> desc = new StatementDescriptor<>(CATEGORY, DESC_QUERY_LATEST);
+        PreparedStatement<ProfileInfo> prepared;
+        try {
+            prepared = storage.prepareStatement(desc);
+            prepared.setString(0, vm.getHostRef().getAgentId());
+            prepared.setString(1, vm.getVmId());
+            Cursor<ProfileInfo> cursor = prepared.executeQuery();
+            if (!cursor.hasNext()) {
+                return null;
+            }
+            ProfileInfo info = cursor.next();
+            String profileId = info.getProfileId();
+            return storage.loadFile(profileId);
+        } catch (DescriptorParsingException e) {
+            logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e);
+        }
+        return null;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplCategoryRegistration.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,52 @@
+/*
+ * 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.common.internal;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.auth.CategoryRegistration;
+
+public class ProfileDAOImplCategoryRegistration implements CategoryRegistration {
+
+    @Override
+    public Set<String> getCategoryNames() {
+        Set<String> names = new HashSet<>();
+        names.add(ProfileDAOImpl.CATEGORY.getName());
+        return names;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/main/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistration.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,68 @@
+/*
+ * 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.common.internal;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
+
+public class ProfileDAOImplStatementDescriptorRegistration implements StatementDescriptorRegistration {
+
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor, PreparedParameter[] params) {
+        if (descriptor.equals(ProfileDAOImpl.DESC_ADD_PROFILE_INFO)
+                || descriptor.equals(ProfileDAOImpl.DESC_QUERY_LATEST)) {
+            String agentId = (String)params[0].getValue();
+            String vmId = (String)params[1].getValue();
+            DescriptorMetadata metadata = new DescriptorMetadata(agentId, vmId);
+            return metadata;
+        } else {
+            throw new IllegalArgumentException("Unknown descriptor: ->" + descriptor + "<-");
+        }
+    }
+
+    @Override
+    public Set<String> getStatementDescriptors() {
+        Set<String> results = new HashSet<>();
+        results.add(ProfileDAOImpl.DESC_ADD_PROFILE_INFO);
+        results.add(ProfileDAOImpl.DESC_QUERY_LATEST);
+        return results;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/main/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.CategoryRegistration	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,1 @@
+com.redhat.thermostat.vm.profiler.common.internal.ProfileDAOImplCategoryRegistration
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/main/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,1 @@
+com.redhat.thermostat.vm.profiler.common.internal.ProfileDAOImplStatementDescriptorRegistration
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplCategoryRegistrationTest.java	Tue Nov 18 14:07:31 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.common.internal;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.junit.Test;
+
+public class ProfileDAOImplCategoryRegistrationTest {
+
+    @Test
+    public void includesProfileInfoCategory() {
+        ProfileDAOImplCategoryRegistration registration = new ProfileDAOImplCategoryRegistration();
+        Set<String> names = registration.getCategoryNames();
+        assertTrue(names.contains(ProfileDAOImpl.CATEGORY.getName()));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplStatementDescriptorRegistrationTest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,54 @@
+/*
+ * 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.common.internal;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.junit.Test;
+
+public class ProfileDAOImplStatementDescriptorRegistrationTest {
+
+    @Test
+    public void includesProfileInfoCategory() {
+        ProfileDAOImplStatementDescriptorRegistration registration = new ProfileDAOImplStatementDescriptorRegistration();
+        Set<String> names = registration.getStatementDescriptors();
+        assertTrue(names.contains(ProfileDAOImpl.DESC_ADD_PROFILE_INFO));
+        assertTrue(names.contains(ProfileDAOImpl.DESC_QUERY_LATEST));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-profiler/common/src/test/java/com/redhat/thermostat/vm/profiler/common/internal/ProfileDAOImplTest.java	Tue Nov 18 14:07:31 2014 -0500
@@ -0,0 +1,41 @@
+/*
+ * 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.common.internal;
+
+public class ProfileDAOImplTest {
+
+}
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControl.java	Tue Nov 18 14:07:31 2014 -0500
@@ -36,11 +36,22 @@
 
 package com.redhat.thermostat.vm.profiler.agent.jvm;
 
+import java.io.BufferedWriter;
+import java.io.IOException;
 import java.lang.instrument.Instrumentation;
 import java.lang.instrument.UnmodifiableClassException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 
 public class InstrumentationControl implements InstrumentationControlMXBean {
@@ -50,6 +61,8 @@
 
     private boolean profiling = false;
 
+    private String lastResults = null;
+
     public InstrumentationControl(Instrumentation instrumentation) {
         this.instrumentation = instrumentation;
         this.classInstrumentor = new AsmBasedInstrumentor();
@@ -60,6 +73,7 @@
     }
 
     private void addShutdownHookToPrintStatsOnEnd() {
+        System.out.println("AGENT: adding shutdown hooks");
         Runtime.getRuntime().addShutdownHook(new Thread() {
             @Override
             public void run() {
@@ -89,7 +103,7 @@
 
     @Override
     public void stopProfiling() {
-        System.out.println("AGENT: stopProfiling()");
+        System.out.println("AGENT: stopProfiling() called");
         if (!profiling) {
             throw new IllegalStateException("Not profiling");
         }
@@ -97,13 +111,9 @@
 
         instrumentation.removeTransformer(classInstrumentor);
         retransformAlreadyLoadedClasses(instrumentation, classInstrumentor);
+
+        lastResults = writeProfilingResultsToDisk();
     }
-
-    @Override
-    public boolean isProfiling() {
-        return profiling;
-    }
-
     private void retransformAlreadyLoadedClasses(Instrumentation instrumentation, ProfilerInstrumentor profiler) {
         long start = System.nanoTime();
 
@@ -140,4 +150,42 @@
         System.out.println("AGENT: Retansforming took: " + (end - start) + "ns");
     }
 
+    private String writeProfilingResultsToDisk() {
+        System.out.println("AGENT: Writing results to disk");
+        try {
+            Path output = createOutput();
+            OpenOption[] options =
+                    new OpenOption[] { StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING };
+
+            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());
+                }
+                System.out.println("AGENT: profiling data written to " + output.toString());
+                return output.toString();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private Path createOutput() throws IOException {
+        Set<PosixFilePermission> perm = PosixFilePermissions.fromString("rw-------");
+        FileAttribute<Set<PosixFilePermission>> attributes = PosixFilePermissions.asFileAttribute(perm);
+        return Files.createTempFile("thermostat", ".perfdata", attributes);
+    }
+
+    @Override
+    public boolean isProfiling() {
+        return profiling;
+    }
+
+    @Override
+    public String getProfilingDataFile() {
+        System.out.println("getProfilingDataFile() called. Returning : " + lastResults);
+        return lastResults;
+    }
+
 }
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControlMXBean.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/InstrumentationControlMXBean.java	Tue Nov 18 14:07:31 2014 -0500
@@ -45,7 +45,9 @@
     /** Disable profiling */
     void stopProfiling();
 
-    /** @returns whether profiling is currently active or not */
+    /** @return whether profiling is currently active or not */
     boolean isProfiling();
 
+    /** @return the path to the profiling data file */
+    String getProfilingDataFile();
 }
--- a/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerAgent.java	Tue Nov 18 14:06:45 2014 -0500
+++ b/vm-profiler/jvm-agent/src/main/java/com/redhat/thermostat/vm/profiler/agent/jvm/ProfilerAgent.java	Tue Nov 18 14:07:31 2014 -0500
@@ -57,6 +57,9 @@
     }
 
     private static void initializeAgent(String args, Instrumentation instrumentation) {
+
+        System.out.println("AGENT: loaded");
+
         // This is the saner approach, but for now we are brute-forcing a
         // hardcoded path using a manifest entry
         // String jars = args;
--- a/web/war/pom.xml	Tue Nov 18 14:06:45 2014 -0500
+++ b/web/war/pom.xml	Tue Nov 18 14:07:31 2014 -0500
@@ -171,6 +171,11 @@
       <artifactId>thermostat-vm-memory-common</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-profiler-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 
   <build>