Mercurial > hg > release > thermostat-0.4
changeset 575:0375b44c73d4
Implement remote heapdumper.
Reviewed-by: vanaltj, jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-August/002967.html
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/pom.xml Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Copyright 2012 Red Hat, Inc. + + This file is part of Thermostat. + + Thermostat is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + Thermostat is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Thermostat; see the file COPYING. If not see + <http://www.gnu.org/licenses />. + + Linking this code with other modules is making a combined work + based on this code. Thus, the terms and conditions of the GNU + General Public License cover the whole combination. + + As a special exception, the copyright holders of this code give + you permission to link this code with independent modules to + produce an executable, regardless of the license terms of these + independent modules, and to copy and distribute the resulting + executable under terms of your choice, provided that you also + meet, for each linked independent module, the terms and conditions + of the license of that module. An independent module is a module + which is not derived from or based on this code. If you modify + this code, you may extend this exception to your version of the + library, but you are not obligated to do so. If you do not wish + to do so, delete this exception statement from your version. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-agent</artifactId> + <version>0.4.0-SNAPSHOT</version> + </parent> + + <artifactId>thermostat-agent-heapdumper</artifactId> + <packaging>bundle</packaging> + + <name>Thermostat Heapdumper Agent Module</name> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + + <dependency> + <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-agent-command</artifactId> + <version>${project.version}</version> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor> + <Bundle-Activator>com.redhat.thermostat.agent.heapdumper.internal.Activator</Bundle-Activator> + <Bundle-SymbolicName>com.redhat.thermostat.agent.heapdumper</Bundle-SymbolicName> + <Export-Package> + com.redhat.thermostat.agent.heapdumper + </Export-Package> + <Private-Package> + com.redhat.thermostat.agent.heapdumper.internal + </Private-Package> + <!-- Do not autogenerate uses clauses in Manifests --> + <_nouses>true</_nouses> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + +</project> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/Activator.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,60 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import com.redhat.thermostat.agent.command.ReceiverRegistry; + +public class Activator implements BundleActivator { + + private ReceiverRegistry receivers; + + @Override + public void start(BundleContext context) throws Exception { + receivers = new ReceiverRegistry(context); + receivers.registerReceiver(new HeapDumpReceiver()); + } + + @Override + public void stop(BundleContext context) throws Exception { + receivers.unregisterReceivers(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpException.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +@SuppressWarnings("serial") +public class HeapDumpException extends Exception { + + public HeapDumpException() { + super(); + } + + public HeapDumpException(String message) { + super(message); + } + + public HeapDumpException(Throwable cause) { + super(cause); + } + + public HeapDumpException(String message, Throwable cause) { + super(message, cause); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiver.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,125 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.redhat.thermostat.agent.command.RequestReceiver; +import com.redhat.thermostat.common.appctx.ApplicationContext; +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.common.dao.HeapDAO; +import com.redhat.thermostat.common.heap.HistogramLoader; +import com.redhat.thermostat.common.heap.ObjectHistogram; +import com.redhat.thermostat.common.model.HeapInfo; + +public class HeapDumpReceiver implements RequestReceiver { + + private static final Logger log = Logger.getLogger(HeapDumpReceiver.class.getName()); + + private JMXHeapDumper jmxHeapDumper; + private JMapHeapDumper jmapHeapDumper; + + private HistogramLoader histogramLoader; + public HeapDumpReceiver() { + this(new JMXHeapDumper(), new JMapHeapDumper(), new HistogramLoader()); + } + + HeapDumpReceiver(JMXHeapDumper jmxHeapDumper, JMapHeapDumper jmapHeapDumper, HistogramLoader histogramLoader) { + this.jmxHeapDumper = jmxHeapDumper; + this.jmapHeapDumper = jmapHeapDumper; + this.histogramLoader = histogramLoader; + } + + @Override + public Response receive(Request request) { + String vmId = request.getParameter("vmId"); + try { + File heapDumpFile = dumpHeap(vmId); + ObjectHistogram histogram = loadHistogram(heapDumpFile.getAbsolutePath()); + saveHeapDumpInfo(vmId, heapDumpFile, histogram); + + } catch (IOException e) { + log.log(Level.SEVERE, "Unexpected IO problem while writing heap dump", e); + return new Response(ResponseType.EXCEPTION); + } catch (HeapDumpException e) { + log.log(Level.SEVERE, "Unexpected problem while writing heap dump", e); + return new Response(ResponseType.EXCEPTION); + } + return new Response(ResponseType.OK); + } + + private File dumpHeap(String vmId) throws IOException, HeapDumpException { + File tempFile = Files.createTempFile("thermostat-", "-heapdump").toFile(); + String tempFileName = tempFile.getAbsolutePath(); + tempFile.delete(); // Need to delete before dumping heap, jmap does not override existing file and stop with an error. + dumpHeapUsingJMX(vmId, tempFileName); + return tempFile; + } + + private void dumpHeapUsingJMX(String vmId, String filename) throws HeapDumpException { + + try { + jmxHeapDumper.dumpHeap(vmId, filename); + } catch (HeapDumpException e) { + log.log(Level.WARNING, "Heap dump using JMX failed, trying jmap", e); + dumpHeapUsingJMap(vmId, filename); + } + } + + private void dumpHeapUsingJMap(String vmId, String filename) throws HeapDumpException { + jmapHeapDumper.dumpHeap(vmId, filename); + } + + private ObjectHistogram loadHistogram(String heapDumpFilename) throws IOException { + return histogramLoader.load(heapDumpFilename); + } + + private void saveHeapDumpInfo(String vmId, File tempFile, ObjectHistogram histogram) throws IOException { + + HeapDAO heapDAO = ApplicationContext.getInstance().getDAOFactory().getHeapDAO(); + HeapInfo heapInfo = new HeapInfo(Integer.parseInt(vmId), System.currentTimeMillis()); + heapDAO.putHeapInfo(heapInfo, tempFile, histogram); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/JMXHeapDumper.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,92 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +import java.io.File; +import java.util.Properties; + +import javax.management.MBeanServerConnection; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +import com.sun.tools.attach.VirtualMachine; + +class JMXHeapDumper { + + private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress"; + + void dumpHeap(String vmId, String filename) throws HeapDumpException { + try { + doHeapDump(vmId, filename); + } catch (Exception ex) { + throw new HeapDumpException(ex); + } + } + + private void doHeapDump(String vmId, String filename) throws Exception { + + VirtualMachine vm = VirtualMachine.attach(vmId); + String connectorAddress = getConnectorAddress(vm); + JMXServiceURL url = new JMXServiceURL(connectorAddress); + + try (JMXConnector conn = JMXConnectorFactory.connect(url)) { + MBeanServerConnection mbsc = conn.getMBeanServerConnection(); + mbsc.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"), + "dumpHeap", + new Object[] { filename, Boolean.TRUE }, + new String[] { String.class.getName(), boolean.class.getName() }); + } + } + + private String getConnectorAddress(VirtualMachine vm) throws Exception { + + Properties props = vm.getAgentProperties(); + String connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY); + if (connectorAddress == null) { + props = vm.getSystemProperties(); + String home = props.getProperty("java.home"); + String agent = home + File.separator + "lib" + File.separator + "management-agent.jar"; + vm.loadAgent(agent); + props = vm.getAgentProperties(); + connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY); + } + return connectorAddress; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/main/java/com/redhat/thermostat/agent/heapdumper/internal/JMapHeapDumper.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +import java.io.IOException; +import java.util.logging.Logger; + +class JMapHeapDumper { + + private static final Logger log = Logger.getLogger(JMapHeapDumper.class.getName()); + + void dumpHeap(String vmId, String filename) throws HeapDumpException { + try { + Process proc = Runtime.getRuntime().exec( + new String[] { "jmap", "-dump:format=b,file=" + filename, vmId }); + proc.waitFor(); + log.info("Heap dump written to: " + filename); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (IOException ex) { + throw new HeapDumpException(ex); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/ActivatorTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Dictionary; + +import org.junit.Test; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import com.redhat.thermostat.agent.command.RequestReceiver; + +public class ActivatorTest { + + @Test + public void testStartStop() throws Exception { + BundleContext ctx = mock(BundleContext.class); + ServiceRegistration serviceReg = mock(ServiceRegistration.class); + when(ctx.registerService(anyString(), any(), any(Dictionary.class))).thenReturn(serviceReg); + Activator activator = new Activator(); + activator.start(ctx); + verify(ctx).registerService(eq(RequestReceiver.class.getName()), isA(HeapDumpReceiver.class), any(Dictionary.class)); + activator.stop(ctx); + verify(serviceReg).unregister(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/heapdumper/src/test/java/com/redhat/thermostat/agent/heapdumper/internal/HeapDumpReceiverTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,153 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + + +package com.redhat.thermostat.agent.heapdumper.internal; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.redhat.thermostat.common.appctx.ApplicationContext; +import com.redhat.thermostat.common.appctx.ApplicationContextUtil; +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.common.dao.DAOFactory; +import com.redhat.thermostat.common.dao.HeapDAO; +import com.redhat.thermostat.common.heap.HistogramLoader; +import com.redhat.thermostat.common.heap.ObjectHistogram; +import com.redhat.thermostat.common.model.HeapInfo; + +public class HeapDumpReceiverTest { + + private HeapDAO heapDAO; + private Request request; + private JMXHeapDumper jmxDumper; + private HistogramLoader histogramLoader; + + private HeapDumpReceiver receiver; + private JMapHeapDumper jmapDumper; + + @Before + public void setUp() { + ApplicationContextUtil.resetApplicationContext(); + + heapDAO = mock(HeapDAO.class); + DAOFactory daoFactory = mock(DAOFactory.class); + when(daoFactory.getHeapDAO()).thenReturn(heapDAO); + ApplicationContext.getInstance().setDAOFactory(daoFactory); + + request = mock(Request.class); + when(request.getParameter("vmId")).thenReturn("123"); + jmxDumper = mock(JMXHeapDumper.class); + jmapDumper = mock(JMapHeapDumper.class); + histogramLoader = mock(HistogramLoader.class); + receiver = new HeapDumpReceiver(jmxDumper, jmapDumper, histogramLoader); + } + + @After + public void tearDown() { + histogramLoader = null; + jmapDumper = null; + jmxDumper = null; + request = null; + heapDAO = null; + ApplicationContextUtil.resetApplicationContext(); + } + + @Test + public void testJMXHeapDump() throws Exception { + + ObjectHistogram expectedHistogramData = mock(ObjectHistogram.class); + when(histogramLoader.load(anyString())).thenReturn(expectedHistogramData); + + Response response = receiver.receive(request); + + assertEquals(ResponseType.OK, response.getType()); + ArgumentCaptor<String> filename = ArgumentCaptor.forClass(String.class); + verify(jmxDumper).dumpHeap(eq("123"), filename.capture()); + verify(histogramLoader).load(filename.getValue()); + ArgumentCaptor<HeapInfo> heapInfo = ArgumentCaptor.forClass(HeapInfo.class); + verify(heapDAO).putHeapInfo(heapInfo.capture(), eq(new File(filename.getValue())), same(expectedHistogramData)); + assertEquals(123, heapInfo.getValue().getVmId()); + } + + @Test + public void verifyFallbackWhenJMXFails() throws HeapDumpException { + + doThrow(new HeapDumpException()).when(jmxDumper).dumpHeap(anyString(), anyString()); + + Response response = receiver.receive(request); + + assertEquals(ResponseType.OK, response.getType()); + verify(jmxDumper).dumpHeap(eq("123"), anyString()); + + } + + @Test + public void verifyResponseTypeWhenAllFails() throws HeapDumpException { + doThrow(new HeapDumpException()).when(jmxDumper).dumpHeap(anyString(), anyString()); + doThrow(new HeapDumpException()).when(jmapDumper).dumpHeap(anyString(), anyString()); + Response response = receiver.receive(request); + + assertEquals(ResponseType.EXCEPTION, response.getType()); + + } + + @Test + public void verifyResponseTypeWhenIOFails() throws HeapDumpException, IOException { + doThrow(new IOException()).when(histogramLoader).load(anyString()); + + Response response = receiver.receive(request); + + assertEquals(ResponseType.EXCEPTION, response.getType()); + + } +}
--- a/agent/pom.xml Wed Aug 22 14:52:00 2012 +0200 +++ b/agent/pom.xml Fri Aug 31 11:22:52 2012 +0200 @@ -62,6 +62,7 @@ <module>cli</module> <module>core</module> <module>command</module> + <module>heapdumper</module> </modules> </project>
--- a/client/heapdumper/pom.xml Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/pom.xml Fri Aug 31 11:22:52 2012 +0200 @@ -126,6 +126,19 @@ </dependency> <dependency> + <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-client-command</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-agent-heapdumper</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <dependency> <groupId>org.jfree</groupId> <artifactId>jfreechart</artifactId> </dependency>
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapDumpController.java Fri Aug 31 11:22:52 2012 +0200 @@ -38,16 +38,17 @@ import java.text.DecimalFormat; import java.text.NumberFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; -import com.redhat.thermostat.client.heap.HeapView.HeadDumperAction; +import com.redhat.thermostat.client.heap.HeapView.HeapDumperAction; import com.redhat.thermostat.client.heap.chart.OverviewChart; import com.redhat.thermostat.client.heap.cli.HeapDumperCommand; import com.redhat.thermostat.client.osgi.service.ApplicationService; +import com.redhat.thermostat.client.osgi.service.BasicView.Action; import com.redhat.thermostat.client.osgi.service.VmInformationServiceController; -import com.redhat.thermostat.client.osgi.service.BasicView.Action; import com.redhat.thermostat.client.ui.UIComponent; import com.redhat.thermostat.common.ActionEvent; import com.redhat.thermostat.common.ActionListener; @@ -79,6 +80,10 @@ private ApplicationService appService; public HeapDumpController(final VmRef ref, final ApplicationService appService) { + this(ref, appService, new HeapDumperCommand()); + } + + HeapDumpController(final VmRef ref, final ApplicationService appService, final HeapDumperCommand command) { this.appService = appService; this.ref = ref; @@ -134,22 +139,18 @@ } } }); - - final HeapDumperCommand command = new HeapDumperCommand(); - view.addDumperListener(new ActionListener<HeapView.HeadDumperAction>() { + + view.addDumperListener(new ActionListener<HeapView.HeapDumperAction>() { @Override - public void actionPerformed(ActionEvent<HeadDumperAction> actionEvent) { + public void actionPerformed(ActionEvent<HeapDumperAction> actionEvent) { HeapDump dump = null; switch (actionEvent.getActionId()) { case DUMP_REQUESTED: - dump = command.execute(ref); - view.addHeapDump(dump); - - // also, only if it's the fist dump, we jump there - // it doesn't disrupt the workflow if it's the first dump - if (appService.getApplicationCache().getAttribute(ref) == null) { - analyseDump(dump); - } + command.execute(ref, new Runnable() { + public void run() { + view.notifyHeapDumpComplete(); + } + }); break; @@ -192,9 +193,18 @@ } class HeapOverviewDataCollector implements Runnable { + private void checkForHeapDumps() { + Collection<HeapInfo> heapInfos = heapDAO.getAllHeapInfo(ref); + List<HeapDump> heapDumps = new ArrayList<HeapDump>(heapInfos.size()); + for (HeapInfo heapInfo : heapInfos) { + heapDumps.add(new HeapDump(heapInfo, heapDAO)); + } + view.updateHeapDumpList(heapDumps); + } + @Override public void run() { - + checkForHeapDumps(); List<VmMemoryStat> vmInfo = vmDao.getLatestVmMemoryStats(ref, System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)); for (VmMemoryStat memoryStats: vmInfo) { long used = 0l;
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapView.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/HeapView.java Fri Aug 31 11:22:52 2012 +0200 @@ -36,6 +36,8 @@ package com.redhat.thermostat.client.heap; +import java.util.List; + import com.redhat.thermostat.client.heap.chart.OverviewChart; import com.redhat.thermostat.client.osgi.service.BasicView; import com.redhat.thermostat.common.ActionListener; @@ -44,22 +46,22 @@ public abstract class HeapView extends BasicView { - public enum HeadDumperAction { + public enum HeapDumperAction { DUMP_REQUESTED, ANALYSE, REQUEST_ABORTED } - protected final ActionNotifier<HeadDumperAction> heapDumperNotifier; + protected final ActionNotifier<HeapDumperAction> heapDumperNotifier; protected HeapView() { - heapDumperNotifier = new ActionNotifier<HeadDumperAction>(this); + heapDumperNotifier = new ActionNotifier<HeapDumperAction>(this); } - public void addDumperListener(ActionListener<HeadDumperAction> listener) { + public void addDumperListener(ActionListener<HeapDumperAction> listener) { heapDumperNotifier.addActionListener(listener); } - public void removeDumperListener(ActionListener<HeadDumperAction> listener) { + public void removeDumperListener(ActionListener<HeapDumperAction> listener) { heapDumperNotifier.removeActionListener(listener); } @@ -69,4 +71,7 @@ abstract public void openDumpView(); abstract public void setChildView(BasicView childView); + public abstract void notifyHeapDumpComplete(); + + public abstract void updateHeapDumpList(List<HeapDump> heapDumps); }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommand.java Fri Aug 31 11:22:52 2012 +0200 @@ -37,6 +37,7 @@ package com.redhat.thermostat.client.heap.cli; import java.util.Collection; +import java.util.concurrent.Semaphore; import com.redhat.thermostat.client.heap.LocaleResources; import com.redhat.thermostat.client.heap.Translate; @@ -87,9 +88,22 @@ public void run(CommandContext ctx) throws CommandException { HostVMArguments args = new HostVMArguments(ctx.getArguments()); - HeapDump hd = implementation.execute(args.getVM()); - ctx.getConsole().getOutput().print(Translate.localize(LocaleResources.COMMAND_HEAP_DUMP_DONE)); - ctx.getConsole().getOutput().print("\n"); + final Semaphore s = new Semaphore(0); + Runnable r = new Runnable() { + + @Override + public void run() { + s.release(); + } + }; + implementation.execute(args.getVM(), r); + try { + s.acquire(); + ctx.getConsole().getOutput().print(Translate.localize(LocaleResources.COMMAND_HEAP_DUMP_DONE)); + ctx.getConsole().getOutput().print("\n"); + } catch (InterruptedException ex) { + // Nothing to do here, just return ASAP. + } } }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommand.java Fri Aug 31 11:22:52 2012 +0200 @@ -36,123 +36,55 @@ package com.redhat.thermostat.client.heap.cli; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.net.InetSocketAddress; -import javax.management.InstanceNotFoundException; -import javax.management.MBeanException; -import javax.management.MBeanServerConnection; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; -import javax.management.ReflectionException; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; - +import com.redhat.thermostat.client.command.RequestQueue; import com.redhat.thermostat.common.appctx.ApplicationContext; -import com.redhat.thermostat.common.dao.HeapDAO; +import com.redhat.thermostat.common.command.Request; +import com.redhat.thermostat.common.command.Request.RequestType; +import com.redhat.thermostat.common.command.RequestResponseListener; +import com.redhat.thermostat.common.command.Response; +import com.redhat.thermostat.common.dao.DAOFactory; +import com.redhat.thermostat.common.dao.HostRef; import com.redhat.thermostat.common.dao.VmRef; -import com.redhat.thermostat.common.heap.HeapDump; -import com.redhat.thermostat.common.heap.HistogramLoader; -import com.redhat.thermostat.common.heap.ObjectHistogram; -import com.redhat.thermostat.common.model.HeapInfo; -import com.sun.tools.attach.AgentInitializationException; -import com.sun.tools.attach.AgentLoadException; -import com.sun.tools.attach.AttachNotSupportedException; -import com.sun.tools.attach.VirtualMachine; +import com.redhat.thermostat.common.utils.OSGIUtils; public class HeapDumperCommand { - private static final Logger log = Logger.getLogger(HeapDumperCommand.class.getName()); - private static final String CONNECTOR_ADDRESS_PROPERTY = "com.sun.management.jmxremote.localConnectorAddress"; + private static final String RECEIVER_CLASS_NAME = "com.redhat.thermostat.agent.heapdumper.internal.HeapDumpReceiver"; + private static final String VM_ID_PARAM = "vmId"; - public HeapDump execute(VmRef reference) { + private class HeapDumpListener implements RequestResponseListener { + + private Runnable heapDumpCompleteAction; - try { - File heapDumpFile = dumpHeap(reference); - ObjectHistogram histogram = loadHistogram(heapDumpFile.getAbsolutePath()); - HeapInfo info = saveHeapDumpInfo(reference, heapDumpFile, histogram); - - HeapDump dump = new HeapDump(info, ApplicationContext.getInstance().getDAOFactory().getHeapDAO()); - - return dump; + private HeapDumpListener(Runnable heapDumpCompleteAction) { + this.heapDumpCompleteAction = heapDumpCompleteAction; + } + + @Override + public void fireComplete(Request request, Response response) { + heapDumpCompleteAction.run(); + } - } catch (IOException e) { - - log.log(Level.SEVERE, "Unexpected IO problem while writing heap dump", e); - return null; - } } - private File dumpHeap(VmRef reference) throws IOException { - File tempFile = Files.createTempFile("thermostat-", "-heapdump").toFile(); - String tempFileName = tempFile.getAbsolutePath(); - tempFile.delete(); // Need to delete before dumping heap, jmap does not override existing file and stop with an error. - dumpHeapUsingJMX(reference, tempFileName); - return tempFile; - } - - private void dumpHeapUsingJMX(VmRef reference, String filename) { + public void execute(VmRef reference, Runnable heapDumpCompleteAction) { - try { - VirtualMachine vm = VirtualMachine.attach(reference.getStringID()); - Properties props = vm.getAgentProperties(); - String connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY); - if (connectorAddress == null) { - props = vm.getSystemProperties(); - String home = props.getProperty("java.home"); - String agent = home + File.separator + "lib" + File.separator + "management-agent.jar"; - vm.loadAgent(agent); - props = vm.getAgentProperties(); - connectorAddress = props.getProperty(CONNECTOR_ADDRESS_PROPERTY); - } - JMXServiceURL url = new JMXServiceURL(connectorAddress); - - try (JMXConnector conn = JMXConnectorFactory.connect(url)) { - MBeanServerConnection mbsc = conn.getMBeanServerConnection(); - mbsc.invoke(new ObjectName("com.sun.management:type=HotSpotDiagnostic"), "dumpHeap", - new Object[] {filename, Boolean.TRUE}, - new String[] {String.class.getName(), boolean.class.getName()}); - } - } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException | - InstanceNotFoundException | MalformedObjectNameException | MBeanException | ReflectionException e) { - log.log(Level.WARNING, "Heap dump using JMX failed, trying jmap", e); - dumpHeapUsingJMap(reference, filename); - } + DAOFactory df = ApplicationContext.getInstance().getDAOFactory(); + HostRef targetHostRef = reference.getAgent(); + String address = df.getStorage().getConfigListenAddress(targetHostRef); + + String [] host = address.split(":"); + InetSocketAddress target = new InetSocketAddress(host[0], Integer.parseInt(host[1])); + Request req = new Request(RequestType.RESPONSE_EXPECTED, target); + req.setReceiver(RECEIVER_CLASS_NAME); + req.setParameter(VM_ID_PARAM, reference.getIdString()); + req.addListener(new HeapDumpListener(heapDumpCompleteAction)); + + RequestQueue queue = OSGIUtils.getInstance().getService(RequestQueue.class); + queue.putRequest(req); + } - private void dumpHeapUsingJMap(VmRef reference, String filename) { - try { - Process proc = Runtime.getRuntime().exec( - new String[] { "jmap", "-dump:format=b,file=" + filename, reference.getIdString() }); - proc.waitFor(); - log.info("Heap dump written to: " + filename); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - private ObjectHistogram loadHistogram(String heapDumpFilename) throws IOException { - HistogramLoader histoLoader = new HistogramLoader(heapDumpFilename); - return histoLoader.load(); - } - - private HeapInfo saveHeapDumpInfo(VmRef reference, File tempFile, ObjectHistogram histogram) throws FileNotFoundException { - - HeapDAO heapDAO = ApplicationContext.getInstance().getDAOFactory().getHeapDAO(); - HeapInfo heapInfo = new HeapInfo(reference, System.currentTimeMillis()); - heapInfo.setHeapDumpId(reference.getStringID() + "-" + reference.getAgent().getAgentId() + "-" + heapInfo.getTimestamp()); - - heapDAO.putHeapInfo(heapInfo, new FileInputStream(tempFile), histogram); - - return heapInfo; - } }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/HeapSwingView.java Fri Aug 31 11:22:52 2012 +0200 @@ -37,8 +37,10 @@ package com.redhat.thermostat.client.heap.swing; import java.awt.Component; +import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.List; import javax.swing.BoxLayout; import javax.swing.JPanel; @@ -75,19 +77,7 @@ stats.disableHeapDumperControl(); - SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { - @Override - protected Void doInBackground() throws Exception { - heapDumperNotifier.fireAction(HeadDumperAction.DUMP_REQUESTED); - return null; - } - - @Override - protected void done() { - stats.enableHeapDumperControl(); - } - }; - worker.execute(); + heapDumperNotifier.fireAction(HeapDumperAction.DUMP_REQUESTED); } }); @@ -95,7 +85,7 @@ @Override public void valueChanged(ListSelectionEvent arg0) { HeapDump dump = stats.getSelectedHeapDump(); - heapDumperNotifier.fireAction(HeadDumperAction.ANALYSE, dump); + heapDumperNotifier.fireAction(HeapDumperAction.ANALYSE, dump); } }); @@ -194,4 +184,24 @@ public Component getUiComponent() { return visiblePane; } + + @Override + public void notifyHeapDumpComplete() { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + stats.enableHeapDumperControl(); + } + }); + } + + @Override + public void updateHeapDumpList(final List<HeapDump> heapDumps) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + stats.updateHeapDumpList(heapDumps); + } + }); + } }
--- a/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/main/java/com/redhat/thermostat/client/heap/swing/StatsPanel.java Fri Aug 31 11:22:52 2012 +0200 @@ -37,6 +37,7 @@ package com.redhat.thermostat.client.heap.swing; import java.awt.event.ActionListener; +import java.util.List; import javax.swing.BoxLayout; import javax.swing.DefaultListModel; @@ -99,9 +100,11 @@ max.setHorizontalAlignment(SwingConstants.RIGHT); heapDumpButton = new JButton("Heap Dump"); - + heapDumpButton.setName("heapDumpButton"); + JScrollPane dumpListScrollPane = new JScrollPane(); dumpList = new JList<>(); + dumpList.setName("heapDumpList"); listModel = new DefaultListModel<>(); dumpList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); dumpList.setModel(listModel); @@ -145,9 +148,6 @@ setLayout(groupLayout); dumpListScrollPane.setViewportView(dumpList); - - // initially invisible - dumpList.setVisible(false); } void setChartPanel(JPanel panel) { @@ -186,19 +186,25 @@ public void addDump(HeapDump dump) { listModel.addElement(dump); - if (!dumpList.isVisible()) { - dumpList.setVisible(true); - } } public void clearDumpList() { listModel.clear(); - if (dumpList.isVisible()) { - dumpList.setVisible(false); - } } public HeapDump getSelectedHeapDump() { return dumpList.getSelectedValue(); } + + public void updateHeapDumpList(List<HeapDump> heapDumps) { + int numItemsBefore = listModel.getSize(); + for (HeapDump heapDump : heapDumps) { + if (! listModel.contains(heapDump)) { + listModel.addElement(heapDump); + } + } + if (numItemsBefore == 0 && listModel.getSize() > 0) { + dumpList.repaint(); + } + } }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/HeapDumpControllerTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -36,6 +36,7 @@ package com.redhat.thermostat.client.heap; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; @@ -46,14 +47,15 @@ import java.util.ArrayList; import java.util.Collection; - -import javax.swing.JComponent; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import com.redhat.thermostat.client.heap.HeapView.HeapDumperAction; +import com.redhat.thermostat.client.heap.cli.HeapDumperCommand; import com.redhat.thermostat.client.osgi.service.ApplicationCache; import com.redhat.thermostat.client.osgi.service.ApplicationService; import com.redhat.thermostat.common.ActionEvent; @@ -67,6 +69,7 @@ import com.redhat.thermostat.common.dao.HeapDAO; import com.redhat.thermostat.common.dao.MongoDAOFactory; import com.redhat.thermostat.common.dao.VmClassStatDAO; +import com.redhat.thermostat.common.dao.VmMemoryStatDAO; import com.redhat.thermostat.common.dao.VmRef; import com.redhat.thermostat.common.heap.HeapDump; import com.redhat.thermostat.common.model.HeapInfo; @@ -74,17 +77,20 @@ public class HeapDumpControllerTest { private ActionListener<HeapView.Action> actionListener; - private ActionListener<HeapView.HeadDumperAction> heapDumperListener; + private ActionListener<HeapView.HeapDumperAction> heapDumperListener; private Timer timer; - private HeapDAO heapDao; + private HeapDAO heapDAO; + private VmMemoryStatDAO vmDAO; private HeapView view; private HeapDumpDetailsView detailsView; private HeapDumpController controller; + private HeapDumperCommand heapDumperCommand; private ApplicationService appService; - + private ArgumentCaptor<Runnable> timerActionCaptor; + @Before public void setUp() { ApplicationContextUtil.resetApplicationContext(); @@ -94,9 +100,11 @@ DAOFactory daoFactory = mock(MongoDAOFactory.class); when(daoFactory.getVmClassStatsDAO()).thenReturn(vmClassStatDAO); - heapDao = mock(HeapDAO.class); - when(daoFactory.getHeapDAO()).thenReturn(heapDao); - + heapDAO = mock(HeapDAO.class); + when(daoFactory.getHeapDAO()).thenReturn(heapDAO); + vmDAO = mock(VmMemoryStatDAO.class); + when(daoFactory.getVmMemoryStatDAO()).thenReturn(vmDAO); + ApplicationContext.getInstance().setDAOFactory(daoFactory); setUpTimers(); @@ -105,7 +113,7 @@ private void setUpTimers() { timer = mock(Timer.class); - ArgumentCaptor<Runnable> timerActionCaptor = ArgumentCaptor.forClass(Runnable.class); + timerActionCaptor = ArgumentCaptor.forClass(Runnable.class); doNothing().when(timer).setAction(timerActionCaptor.capture()); TimerFactory timerFactory = mock(TimerFactory.class); @@ -148,10 +156,19 @@ ApplicationCache cache = mock(ApplicationCache.class); appService = mock(ApplicationService.class); when(appService.getApplicationCache()).thenReturn(cache); - VmRef ref = mock(VmRef.class); - controller = new HeapDumpController(ref, appService); + VmRef ref = mock(VmRef.class); + heapDumperCommand = mock(HeapDumperCommand.class); + controller = new HeapDumpController(ref, appService, heapDumperCommand); } + @After + public void tearDown() { + controller = null; + vmDAO = null; + heapDAO = null; + ApplicationContextUtil.resetApplicationContext(); + } + @Test public void testTimerStartOnViewVisible() { @@ -173,7 +190,7 @@ @Test public void testNotAddHeapDumpsAtStartupWhenNoDumps() { - when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(new ArrayList<HeapInfo>()); + when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(new ArrayList<HeapInfo>()); createController(); @@ -188,8 +205,8 @@ infos.add(info1); infos.add(info2); - when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); - + when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); + createController(); verify(view, times(2)).addHeapDump(any(HeapDump.class)); @@ -208,7 +225,7 @@ infos.add(info1); infos.add(info2); - when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); + when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); ApplicationCache cache = mock(ApplicationCache.class); when(cache.getAttribute(any(VmRef.class))).thenReturn(dump); @@ -224,14 +241,14 @@ @Test public void testNotOpenDumpCalledWhenNoPreviousDump() { - + HeapInfo info1 = mock(HeapInfo.class); HeapInfo info2 = mock(HeapInfo.class); Collection<HeapInfo> infos = new ArrayList<HeapInfo>(); infos.add(info1); infos.add(info2); - when(heapDao.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); + when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); ApplicationCache cache = mock(ApplicationCache.class); when(cache.getAttribute(any(VmRef.class))).thenReturn(null); @@ -243,9 +260,41 @@ verify(view, times(0)).openDumpView(); } + + @Test + public void testRequestHeapDump() { + + setUpListeners(); + + heapDumperListener.actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED)); + + ArgumentCaptor<Runnable> heapDumpCompleteAction = ArgumentCaptor.forClass(Runnable.class); + verify(heapDumperCommand).execute(any(VmRef.class), heapDumpCompleteAction.capture()); + heapDumpCompleteAction.getValue().run(); + verify(view).notifyHeapDumpComplete(); + + } + + @SuppressWarnings("unchecked") + @Test + public void testTimerChecksForNewHeapDumps() { + + HeapInfo info1 = mock(HeapInfo.class); + HeapInfo info2 = mock(HeapInfo.class); + Collection<HeapInfo> infos = new ArrayList<HeapInfo>(); + infos.add(info1); + infos.add(info2); - @After - public void tearDown() { - ApplicationContextUtil.resetApplicationContext(); + when(heapDAO.getAllHeapInfo(any(VmRef.class))).thenReturn(infos); + + createController(); + + timerActionCaptor.getValue().run(); + + @SuppressWarnings("rawtypes") + ArgumentCaptor<List> heapDumps = ArgumentCaptor.forClass(List.class); + verify(view).updateHeapDumpList(heapDumps.capture()); + assertTrue(heapDumps.getValue().contains(new HeapDump(info1, heapDAO))); + assertTrue(heapDumps.getValue().contains(new HeapDump(info2, heapDAO))); } }
--- a/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java Wed Aug 22 14:52:00 2012 +0200 +++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/DumpHeapCommandTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -39,14 +39,16 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.isA; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.isA; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; -import com.redhat.thermostat.client.heap.cli.DumpHeapCommand; -import com.redhat.thermostat.client.heap.cli.HeapDumperCommand; import com.redhat.thermostat.common.cli.Command; import com.redhat.thermostat.common.cli.CommandException; import com.redhat.thermostat.common.cli.SimpleArguments; @@ -66,6 +68,16 @@ @Test public void verifyAcuallyCallsWorker() throws CommandException { HeapDumperCommand impl = mock(HeapDumperCommand.class); + final ArgumentCaptor<Runnable> arg = ArgumentCaptor.forClass(Runnable.class); + doAnswer(new Answer<Void>() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + arg.getValue().run(); + return null; + } + }).when(impl).execute(any(VmRef.class), arg.capture()); + DumpHeapCommand command = new DumpHeapCommand(impl); TestCommandContextFactory factory = new TestCommandContextFactory(); @@ -76,7 +88,7 @@ command.run(factory.createContext(args)); - verify(impl).execute(isA(VmRef.class)); + verify(impl).execute(isA(VmRef.class), any(Runnable.class)); assertEquals("Done\n", factory.getOutput()); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/cli/HeapDumperCommandTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,137 @@ +/* + * Copyright 2012 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * <http://www.gnu.org/licenses/>. + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.heap.cli; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.InetSocketAddress; +import java.util.Collection; + +import junit.framework.AssertionFailedError; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.redhat.thermostat.client.command.RequestQueue; +import com.redhat.thermostat.common.appctx.ApplicationContext; +import com.redhat.thermostat.common.appctx.ApplicationContextUtil; +import com.redhat.thermostat.common.command.Request; +import com.redhat.thermostat.common.command.Request.RequestType; +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.dao.DAOFactory; +import com.redhat.thermostat.common.dao.HostRef; +import com.redhat.thermostat.common.dao.VmRef; +import com.redhat.thermostat.common.storage.Storage; +import com.redhat.thermostat.common.utils.OSGIUtils; + +public class HeapDumperCommandTest { + + private HeapDumperCommand cmd; + private VmRef vmRef; + private RequestQueue reqQueue; + private Runnable heapDumpCompleteAction; + + @Before + public void setUp() { + ApplicationContextUtil.resetApplicationContext(); + + reqQueue = mock(RequestQueue.class); + OSGIUtils osgiUtils = mock(OSGIUtils.class); + when(osgiUtils.getService(RequestQueue.class)).thenReturn(reqQueue); + OSGIUtils.setInstance(osgiUtils); + + HostRef host = mock(HostRef.class); + Storage storage = mock(Storage.class); + when(storage.getConfigListenAddress(host)).thenReturn("test:123"); + DAOFactory daoFactory = mock(DAOFactory.class); + when(daoFactory.getStorage()).thenReturn(storage); + ApplicationContext.getInstance().setDAOFactory(daoFactory); + + cmd = new HeapDumperCommand(); + vmRef = mock(VmRef.class); + when(vmRef.getIdString()).thenReturn("123"); + when(vmRef.getAgent()).thenReturn(host); + heapDumpCompleteAction = mock(Runnable.class); + + } + + @After + public void tearDown() { + heapDumpCompleteAction = null; + vmRef = null; + cmd = null; + reqQueue = null; + ApplicationContextUtil.resetApplicationContext(); + } + + @Test + public void testExecute() { + + cmd.execute(vmRef, heapDumpCompleteAction); + + ArgumentCaptor<Request> reqArg = ArgumentCaptor.forClass(Request.class); + verify(reqQueue).putRequest(reqArg.capture()); + Request req = reqArg.getValue(); + assertEquals("com.redhat.thermostat.agent.heapdumper.internal.HeapDumpReceiver", req.getReceiver()); + verifyClassExists(req.getReceiver()); + assertEquals(RequestType.RESPONSE_EXPECTED, req.getType()); + assertEquals("123", req.getParameter("vmId")); + assertEquals(new InetSocketAddress("test", 123), req.getTarget()); + + Collection<RequestResponseListener> ls = req.getListeners(); + for (RequestResponseListener l : ls) { + l.fireComplete(req, new Response(ResponseType.OK)); + } + verify(heapDumpCompleteAction).run(); + } + + private void verifyClassExists(String receiver) { + try { + Class.forName(receiver); + } catch (ClassNotFoundException e) { + throw new AssertionFailedError(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/heapdumper/src/test/java/com/redhat/thermostat/client/heap/swing/HeapSwingViewTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -0,0 +1,115 @@ +package com.redhat.thermostat.client.heap.swing; + +import static org.junit.Assert.fail; + +import java.awt.Container; +import java.awt.EventQueue; +import java.util.Arrays; +import java.util.List; + +import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner; + +import org.fest.swing.annotation.GUITest; +import org.fest.swing.annotation.RunsInEDT; +import org.fest.swing.edt.FailOnThreadViolationRepaintManager; +import org.fest.swing.edt.GuiActionRunner; +import org.fest.swing.edt.GuiTask; +import org.fest.swing.fixture.Containers; +import org.fest.swing.fixture.DialogFixture; +import org.fest.swing.fixture.FrameFixture; +import org.fest.swing.fixture.JButtonFixture; +import org.fest.swing.fixture.JListFixture; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.redhat.thermostat.client.heap.HeapView.HeapDumperAction; +import com.redhat.thermostat.common.ActionEvent; +import com.redhat.thermostat.common.ActionListener; +import com.redhat.thermostat.common.heap.HeapDump; + +import static org.mockito.Mockito.* +; + +@RunWith(CacioFESTRunner.class) +public class HeapSwingViewTest { + + private HeapSwingView view; + + private FrameFixture frame; + + @BeforeClass + public static void setUpOnce() { + FailOnThreadViolationRepaintManager.install(); + } + + + @Before + public void setUp() throws Exception { + GuiActionRunner.execute(new GuiTask() { + + @Override + protected void executeInEDT() throws Throwable { + view = new HeapSwingView(); + } + }); + frame = Containers.showInFrame((Container) view.getUiComponent()); + } + + @After + public void tearDown() { + frame.cleanUp(); + frame = null; + view = null; + } + + @Test + @GUITest + public void testActivateHeapDump() { + @SuppressWarnings("unchecked") + ActionListener<HeapDumperAction> l = mock(ActionListener.class); + view.addDumperListener(l); + JButtonFixture heapDumpButton = frame.button("heapDumpButton"); + heapDumpButton.click(); + verify(l).actionPerformed(new ActionEvent<HeapDumperAction>(view, HeapDumperAction.DUMP_REQUESTED)); + } + + @Test + @GUITest + public void testNotifyHeapDumpComplete() { + final JButtonFixture heapDumpButton = frame.button("heapDumpButton"); + GuiActionRunner.execute(new GuiTask() { + + @Override + protected void executeInEDT() throws Throwable { + heapDumpButton.component().setEnabled(false); + } + }); + + view.notifyHeapDumpComplete(); + frame.robot.waitForIdle(); + + heapDumpButton.requireEnabled(); + } + + @Test + @GUITest + public void testUpdateHeapDumpList() { + JListFixture heapDumpList = frame.list("heapDumpList"); + heapDumpList.requireItemCount(0); + + HeapDump heapDump = mock(HeapDump.class); + List<HeapDump> heapDumps = Arrays.asList(heapDump); + + view.updateHeapDumpList(heapDumps); + frame.robot.waitForIdle(); + heapDumpList.requireItemCount(1); + + view.updateHeapDumpList(heapDumps); + frame.robot.waitForIdle(); + heapDumpList.requireItemCount(1); + + } +}
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAO.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAO.java Fri Aug 31 11:22:52 2012 +0200 @@ -36,6 +36,8 @@ package com.redhat.thermostat.common.dao; +import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.util.Collection; @@ -52,7 +54,7 @@ public static final Category heapInfoCategory = new Category("vm-heap-info", Key.ID, Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP, heapDumpIdKey, histogramIdKey); - void putHeapInfo(HeapInfo heapInfo, InputStream heapDump, ObjectHistogram histogramData); + void putHeapInfo(HeapInfo heapInfo, File heapDumpFile, ObjectHistogram histogramData) throws IOException; Collection<HeapInfo> getAllHeapInfo(VmRef vm);
--- a/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/dao/HeapDAOImpl.java Fri Aug 31 11:22:52 2012 +0200 @@ -38,6 +38,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; @@ -47,8 +49,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import org.bson.types.ObjectId; - import com.redhat.thermostat.common.heap.HeapDump; import com.redhat.thermostat.common.heap.ObjectHistogram; import com.redhat.thermostat.common.model.HeapInfo; @@ -69,14 +69,13 @@ } @Override - public void putHeapInfo(HeapInfo heapInfo, InputStream heapDumpData, ObjectHistogram histogramData) { - VmRef vm = heapInfo.getVm(); + public void putHeapInfo(HeapInfo heapInfo, File heapDumpData, ObjectHistogram histogramData) throws IOException { + int vmId = heapInfo.getVmId(); Chunk chunk = new Chunk(heapInfoCategory, false); - chunk.put(Key.AGENT_ID, vm.getAgent().getStringID()); - chunk.put(Key.VM_ID, vm.getId()); + chunk.put(Key.VM_ID, vmId); chunk.put(Key.TIMESTAMP, heapInfo.getTimestamp()); - String id = vm.getAgent().getStringID() + "-" + vm.getId() + "-" + heapInfo.getTimestamp(); + String id = heapInfo.getHeapId(); String heapDumpId = "heapdump-" + id; String histogramId = "histogram-" + id; if (heapDumpData != null) { @@ -89,7 +88,7 @@ } storage.putChunk(chunk); if (heapDumpData != null) { - storage.saveFile(heapDumpId, heapDumpData); + storage.saveFile(heapDumpId, new FileInputStream(heapDumpData)); } if (histogramData != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -123,7 +122,8 @@ } private HeapInfo convertChunkToHeapInfo(VmRef vm, Chunk chunk) { - HeapInfo info = new HeapInfo(vm, chunk.get(Key.TIMESTAMP)); + int vmId = chunk.get(Key.VM_ID); + HeapInfo info = new HeapInfo(vmId, chunk.get(Key.TIMESTAMP)); info.setHeapId(chunk.get(Key.ID)); info.setHeapDumpId(chunk.get(HeapDAO.heapDumpIdKey)); info.setHistogramId(chunk.get(HeapDAO.histogramIdKey));
--- a/common/core/src/main/java/com/redhat/thermostat/common/heap/HeapDump.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/heap/HeapDump.java Fri Aug 31 11:22:52 2012 +0200 @@ -96,10 +96,6 @@ this.heapDAO = heapDAO; } - public String getName() { - return heapInfo.getVm().getName(); - } - public long getTimestamp() { return heapInfo.getTimestamp(); } @@ -223,4 +219,11 @@ return snapshot.findThing(id); } + public boolean equals(Object o) { + return o instanceof HeapDump && ((HeapDump) o).heapInfo.equals(heapInfo); + } + + public int hashCode() { + return heapInfo.hashCode(); + } }
--- a/common/core/src/main/java/com/redhat/thermostat/common/heap/HistogramLoader.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/heap/HistogramLoader.java Fri Aug 31 11:22:52 2012 +0200 @@ -46,18 +46,12 @@ public class HistogramLoader { - private String filename; - - public HistogramLoader(String filename) { - this.filename = filename; - } - - public ObjectHistogram load() throws IOException { - Snapshot snapshot = loadHeapdump(); + public ObjectHistogram load(String filename) throws IOException { + Snapshot snapshot = loadHeapdump(filename); return computeHistogram(snapshot); } - private Snapshot loadHeapdump() throws IOException { + private Snapshot loadHeapdump(String filename) throws IOException { File heapdump = new File(filename); Snapshot snapshot = Reader.readFile(heapdump.getAbsolutePath(), true, 0); snapshot.resolve(true);
--- a/common/core/src/main/java/com/redhat/thermostat/common/model/HeapInfo.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/model/HeapInfo.java Fri Aug 31 11:22:52 2012 +0200 @@ -38,23 +38,21 @@ import java.util.Objects; -import com.redhat.thermostat.common.dao.VmRef; - public class HeapInfo { - private VmRef vm; + private int vmId; private long timestamp; private String heapId; private String heapDumpId; private String histogramId; - public HeapInfo(VmRef vm, long timestamp) { - this.vm = vm; + public HeapInfo(int vmId, long timestamp) { + this.vmId = vmId; this.timestamp = timestamp; } - public VmRef getVm() { - return vm; + public int getVmId() { + return vmId; } public long getTimestamp() { @@ -83,13 +81,13 @@ return false; } HeapInfo other = (HeapInfo) o; - return Objects.equals(vm, other.vm) && Objects.equals(heapDumpId, other.heapDumpId) + return vmId == other.vmId && Objects.equals(heapDumpId, other.heapDumpId) && Objects.equals(histogramId, other.histogramId) && timestamp == other.timestamp; } @Override public int hashCode() { - return Objects.hash(vm, heapDumpId, histogramId, timestamp); + return Objects.hash(vmId, heapDumpId, histogramId, timestamp); } public String getHistogramId() {
--- a/common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/main/java/com/redhat/thermostat/common/utils/OSGIUtils.java Fri Aug 31 11:22:52 2012 +0200 @@ -44,11 +44,17 @@ public class OSGIUtils { - private final static OSGIUtils instance = new OSGIUtils(); + // TODO: Maybe we should stick this singleton into an ApplicationContext? + private static OSGIUtils instance = new OSGIUtils(); public static OSGIUtils getInstance() { return instance; } - + + // This is only here to be used by test code. + public static void setInstance(OSGIUtils utils) { + instance = utils; + } + @SuppressWarnings({ "rawtypes", "unchecked" }) public <E extends Object> E getService(Class<E> clazz) { BundleContext ctx = FrameworkUtil.getBundle(getClass()).getBundleContext();
--- a/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/test/java/com/redhat/thermostat/common/dao/HeapDAOTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -43,7 +43,6 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isA; -import static org.mockito.Matchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -51,6 +50,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; @@ -78,7 +79,7 @@ private HeapDAO dao; private Storage storage; private HeapInfo heapInfo; - private InputStream heapDumpData; + private File heapDumpData; private ObjectHistogram histogram; private InputStream histogramData; @@ -87,11 +88,13 @@ storage = mock(Storage.class); dao = new HeapDAOImpl(storage); - HostRef host = new HostRef("987", "test-host"); - VmRef vm = new VmRef(host, 123, "test-vm"); - heapInfo = new HeapInfo(vm, 12345); + heapInfo = new HeapInfo(123, 12345); + heapInfo.setHeapId("987-123-12345"); byte[] data = new byte[] { 1, 2, 3 }; - heapDumpData = new ByteArrayInputStream(data); + heapDumpData = File.createTempFile("test", "test"); + FileOutputStream out = new FileOutputStream(heapDumpData); + out.write(data); + out.close(); histogramData = createHistogramData(); // Setup for reading data from DB. @@ -118,12 +121,11 @@ when(storage.findAll(findAllQuery)).thenReturn(cursor); // Setup for reading heapdump data. - when(storage.loadFile("test-heap")).thenReturn(heapDumpData); + when(storage.loadFile("test-heap")).thenReturn(new ByteArrayInputStream(data)); when(storage.loadFile("test-histo")).thenReturn(histogramData); // Prepare queries for read-back of _id in putHeapInfo() tests. Chunk heap1query = new Chunk(HeapDAO.heapInfoCategory, false); - heap1query.put(Key.AGENT_ID, "987"); heap1query.put(Key.VM_ID, 123); heap1query.put(Key.TIMESTAMP, 12345l); heap1query.put(HeapDAO.heapDumpIdKey, "heapdump-987-123-12345"); @@ -133,7 +135,6 @@ when(storage.find(heap1query)).thenReturn(heap1); Chunk heap2query = new Chunk(HeapDAO.heapInfoCategory, false); - heap2query.put(Key.AGENT_ID, "987"); heap2query.put(Key.VM_ID, 123); heap2query.put(Key.TIMESTAMP, 12345l); Chunk heap2 = new Chunk(HeapDAO.heapInfoCategory, false); @@ -173,6 +174,7 @@ public void tearDown() { histogramData = null; histogram = null; + heapDumpData.delete(); heapDumpData = null; heapInfo = null; dao = null; @@ -199,13 +201,18 @@ dao.putHeapInfo(heapInfo, heapDumpData, histogram); Chunk expectedChunk = new Chunk(HeapDAO.heapInfoCategory, false); - expectedChunk.put(Key.AGENT_ID, "987"); expectedChunk.put(Key.VM_ID, 123); expectedChunk.put(Key.TIMESTAMP, 12345L); expectedChunk.put(HeapDAO.heapDumpIdKey, "heapdump-987-123-12345"); expectedChunk.put(HeapDAO.histogramIdKey, "histogram-987-123-12345"); verify(storage).putChunk(expectedChunk); - verify(storage).saveFile(eq("heapdump-987-123-12345"), same(heapDumpData)); + ArgumentCaptor<InputStream> data = ArgumentCaptor.forClass(InputStream.class); + verify(storage).saveFile(eq("heapdump-987-123-12345"), data.capture()); + InputStream in = data.getValue(); + assertEquals(1, in.read()); + assertEquals(2, in.read()); + assertEquals(3, in.read()); + assertEquals(-1, in.read()); assertEquals("id1", heapInfo.getHeapId()); ArgumentCaptor<InputStream> histoStream = ArgumentCaptor.forClass(InputStream.class); verify(storage).saveFile(eq("histogram-987-123-12345"), histoStream.capture()); @@ -220,11 +227,10 @@ } @Test - public void testPutHeapInfoWithoutDump() { + public void testPutHeapInfoWithoutDump() throws IOException { dao.putHeapInfo(heapInfo, null, null); Chunk expectedChunk = new Chunk(HeapDAO.heapInfoCategory, false); - expectedChunk.put(Key.AGENT_ID, "987"); expectedChunk.put(Key.VM_ID, 123); expectedChunk.put(Key.TIMESTAMP, 12345L); @@ -244,11 +250,11 @@ VmRef vm = new VmRef(host, 234, "test-vm"); Collection<HeapInfo> heapInfos = dao.getAllHeapInfo(vm); - HeapInfo info1 = new HeapInfo(vm, 12345); + HeapInfo info1 = new HeapInfo(234, 12345); info1.setHeapDumpId("test1"); info1.setHistogramId("histotest1"); - HeapInfo info2 = new HeapInfo(vm, 23456); + HeapInfo info2 = new HeapInfo(234, 23456); info2.setHeapDumpId("test2"); info2.setHistogramId("histotest2");
--- a/common/core/src/test/java/com/redhat/thermostat/common/model/HeapInfoTest.java Wed Aug 22 14:52:00 2012 +0200 +++ b/common/core/src/test/java/com/redhat/thermostat/common/model/HeapInfoTest.java Fri Aug 31 11:22:52 2012 +0200 @@ -49,18 +49,15 @@ public class HeapInfoTest { private HeapInfo heapInfo; - private VmRef vm; @Before public void setUp() { - HostRef hostRef = new HostRef("321", "test-host"); - vm = new VmRef(hostRef, 123, "test-vm"); - heapInfo = new HeapInfo(vm, 12345); + heapInfo = new HeapInfo(321, 12345); } @Test public void testProperties() { - assertSame(vm, heapInfo.getVm()); + assertEquals(321, heapInfo.getVmId()); assertEquals(12345, heapInfo.getTimestamp()); }
--- a/distribution/config/bundles.properties Wed Aug 22 14:52:00 2012 +0200 +++ b/distribution/config/bundles.properties Fri Aug 31 11:22:52 2012 +0200 @@ -34,7 +34,8 @@ thermostat-common-core-@project.version@.jar, \ thermostat-agent-cli-@project.version@.jar, \ thermostat-common-command-@project.version@.jar, \ - thermostat-agent-command-@project.version@.jar + thermostat-agent-command-@project.version@.jar, \ + thermostat-agent-heapdumper-@project.version@.jar storage = thermostat-agent-core-@project.version@.jar, \ thermostat-osgi-process-handler-@project.version@.jar, \
--- a/distribution/pom.xml Wed Aug 22 14:52:00 2012 +0200 +++ b/distribution/pom.xml Fri Aug 31 11:22:52 2012 +0200 @@ -287,6 +287,11 @@ </dependency> <dependency> <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-agent-heapdumper</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.redhat.thermostat</groupId> <artifactId>thermostat-common-core</artifactId> <version>${project.version}</version> </dependency>