Mercurial > hg > release > thermostat-1.6
changeset 1942:9db29a97e702
Add caching to heap analysis shell commands
Backport of 6aea706e89e9
Reviewed-by: jerboaa, neugens, jkang
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-December/017262.html
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019793.html
PR3043
line wrap: on
line diff
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindObjectsCommand.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindObjectsCommand.java Fri Dec 18 10:04:12 2015 -0500 @@ -39,6 +39,7 @@ import java.util.Collection; import java.util.List; +import com.redhat.thermostat.vm.heap.analysis.common.HeapDump; import org.osgi.framework.BundleContext; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.ServiceReference; @@ -58,12 +59,12 @@ private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer(); - private static final String HEAP_ID_ARG = "heapId"; - private static final String LIMIT_ARG = "limit"; private static final String HEADER_OBJECT_ID = translator.localize(LocaleResources.HEADER_OBJECT_ID).getContents(); private static final String HEADER_TYPE = translator.localize(LocaleResources.HEADER_OBJECT_TYPE).getContents(); private static final int DEFAULT_LIMIT = 10; + private static final String LIMIT_ARG = "limit"; + private BundleContext context; public FindObjectsCommand() { @@ -88,30 +89,14 @@ } private void run(CommandContext ctx, HeapDAO heapDAO) throws CommandException { - String heapId = ctx.getArguments().getArgument(HEAP_ID_ARG); - HeapInfo heapInfo = heapDAO.getHeapInfo(heapId); - if (heapInfo == null) { - throw new HeapNotFoundException(heapId); - } - HeapDump heapDump = heapDAO.getHeapDump(heapInfo); - if (heapDump == null) { - throw new HeapNotFoundException(heapId); - } - - List<String> terms = ctx.getArguments().getNonOptionArguments(); - if (terms.size() == 0) { - ctx.getConsole().getOutput().println(translator.localize(LocaleResources.SEARCH_TERM_REQUIRED).getContents()); - return; - } - String searchTerm = terms.get(0); - if (searchTerm.trim().length() == 0) { - ctx.getConsole().getOutput().println(translator.localize(LocaleResources.SEARCH_TERM_REQUIRED).getContents()); - return; - } + String searchTerm = getSearchTerm(ctx); + HeapCommandHelper helper = HeapCommandHelper.getHelper(ctx, heapDAO); + HeapDump heapDump = helper.getHeapDump(); String limitArg = ctx.getArguments().getArgument(LIMIT_ARG); int limit = parseLimit(limitArg); Collection<String> results = heapDump.searchObjects(searchTerm, limit); + TableRenderer table = new TableRenderer(2); table.printLine(HEADER_OBJECT_ID, HEADER_TYPE); for (String objectId : results) { @@ -123,6 +108,23 @@ table.render(ctx.getConsole().getOutput()); } + private static String getSearchTerm(CommandContext ctx) throws SearchTermRequiredException { + assertSearchTermProvided(ctx); + List<String> terms = ctx.getArguments().getNonOptionArguments(); + return terms.get(0); + } + + private static void assertSearchTermProvided(CommandContext ctx) throws SearchTermRequiredException { + List<String> terms = ctx.getArguments().getNonOptionArguments(); + if (terms.size() == 0) { + throw new SearchTermRequiredException(); + } + String searchTerm = terms.get(0); + if (searchTerm.trim().length() == 0) { + throw new SearchTermRequiredException(); + } + } + private int parseLimit(String limitArg) throws CommandException { int limit = DEFAULT_LIMIT; if (limitArg != null) {
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindRootCommand.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindRootCommand.java Fri Dec 18 10:04:12 2015 -0500 @@ -87,10 +87,10 @@ } private void run(CommandContext ctx, HeapDAO heapDao) throws CommandException { - ObjectCommandHelper objCmdHelper = new ObjectCommandHelper(ctx, heapDao); + HeapCommandHelper objCmdHelper = HeapCommandHelper.getHelper(ctx, heapDao); HeapDump heapDump = objCmdHelper.getHeapDump(); Snapshot snapshot = heapDump.getSnapshot(); - JavaHeapObject obj = objCmdHelper.getJavaHeapObject(); + JavaHeapObject obj = objCmdHelper.getJavaHeapObject(ctx); boolean findAll = ctx.getArguments().hasArgument(ALL_ARG); FindRoot findRoot = new FindRoot(); Collection<HeapPath<JavaHeapObject>> pathsToRoot = findRoot.findShortestPathsToRoot(obj, findAll);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/HeapCommandHelper.java Fri Dec 18 10:04:12 2015 -0500 @@ -0,0 +1,162 @@ +/* + * Copyright 2012-2016 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.heap.analysis.command.internal; + +import com.redhat.thermostat.common.cli.Arguments; +import com.redhat.thermostat.common.cli.CommandContext; +import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO; +import com.redhat.thermostat.vm.heap.analysis.common.HeapDump; +import com.redhat.thermostat.vm.heap.analysis.common.model.HeapInfo; +import com.sun.tools.hat.internal.model.JavaHeapObject; + +import java.lang.ref.SoftReference; +import java.util.Map; +import java.util.Objects; + +public class HeapCommandHelper { + + public static final int MAXIMUM_CACHED_HELPERS = 5; + + static final String HEAP_ID_ARG = "heapId"; + static final String OBJECT_ID_ARG = "objectId"; + + protected static final Object getLock = new Object(); + protected static final Map<HeapDumpIdentifier, SoftReference<HeapCommandHelper>> helperCache = new LRUMap<>(MAXIMUM_CACHED_HELPERS); + + protected HeapDAO dao; + protected String heapId; + protected HeapDump heapDump; + + // package private for testing only + HeapCommandHelper(HeapDumpIdentifier identifier) { + this.heapId = identifier.heapId; + this.dao = identifier.dao; + } + + public static HeapCommandHelper getHelper(CommandContext ctx, HeapDAO heapDAO) { + synchronized (getLock) { + HeapDumpIdentifier heapDumpIdentifier = HeapDumpIdentifier.makeIdentifier(ctx, heapDAO); + HeapCommandHelper helper; + if (!helperCache.containsKey(heapDumpIdentifier)) { + helper = new HeapCommandHelper(heapDumpIdentifier); + helperCache.put(heapDumpIdentifier, new SoftReference<>(helper)); + } else { + HeapCommandHelper _h = helperCache.get(heapDumpIdentifier).get(); + if (_h == null) { + helper = new HeapCommandHelper(heapDumpIdentifier); + helperCache.put(heapDumpIdentifier, new SoftReference<>(helper)); + } else { + helper = _h; + } + } + return helper; + } + } + + protected void loadHeapDump() throws HeapNotFoundException { + HeapInfo heapInfo = dao.getHeapInfo(heapId); + if (heapInfo == null) { + throw new HeapNotFoundException(heapId); + } + HeapDump heapDump = dao.getHeapDump(heapInfo); + if (heapDump == null) { + throw new HeapNotFoundException(heapId); + } + this.heapDump = heapDump; + } + + public HeapDump getHeapDump() throws HeapNotFoundException { + if (heapDump == null) { + loadHeapDump(); + } + return heapDump; + } + + public JavaHeapObject getJavaHeapObject(CommandContext ctx) throws CommandException { + Arguments arguments = ctx.getArguments(); + if (!arguments.hasArgument(OBJECT_ID_ARG)) { + throw new ObjectIdRequiredException(); + } + HeapDump heapDump = getHeapDump(); + String objectId = arguments.getArgument(OBJECT_ID_ARG); + JavaHeapObject obj = heapDump.findObject(objectId); + if (obj == null) { + throw new ObjectNotFoundException(objectId); + } + return obj; + } + + protected static class HeapDumpIdentifier { + + protected final String heapId; + protected final HeapDAO dao; + + // package private for testing only + HeapDumpIdentifier(String heapId, HeapDAO dao) { + this.heapId = Objects.requireNonNull(heapId); + this.dao = Objects.requireNonNull(dao); + } + + public static HeapDumpIdentifier makeIdentifier(CommandContext ctx, HeapDAO dao) { + String heapId = ctx.getArguments().getArgument(HEAP_ID_ARG); + return new HeapDumpIdentifier(heapId, dao); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + HeapDumpIdentifier heapDumpIdentifier = (HeapDumpIdentifier) o; + + return heapId.equals(heapDumpIdentifier.heapId) && dao.equals(heapDumpIdentifier.dao); + } + + @Override + public int hashCode() { + int result = heapId.hashCode(); + result = 31 * result + dao.hashCode(); + return result; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/LRUMap.java Fri Dec 18 10:04:12 2015 -0500 @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2016 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.heap.analysis.command.internal; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class LRUMap<K, V> extends LinkedHashMap<K, V> { + + private final int maximumSize; + + public LRUMap(int maximumSize) { + super(maximumSize + 1, 0.75f, true); + this.maximumSize = maximumSize; + } + + @Override + public boolean removeEldestEntry(Map.Entry eldest) { + return size() > maximumSize; + } + +}
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectCommandHelper.java Fri Jun 24 11:14:59 2016 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/* - * Copyright 2012-2016 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.heap.analysis.command.internal; - -import com.redhat.thermostat.common.cli.Arguments; -import com.redhat.thermostat.common.cli.CommandContext; -import com.redhat.thermostat.common.cli.CommandException; -import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO; -import com.redhat.thermostat.vm.heap.analysis.common.HeapDump; -import com.redhat.thermostat.vm.heap.analysis.common.model.HeapInfo; -import com.sun.tools.hat.internal.model.JavaHeapObject; - -public class ObjectCommandHelper { - - private static final String OBJECT_ID_ARG = "objectId"; - private static final String HEAP_ID_ARG = "heapId"; - - private CommandContext ctx; - private HeapDAO dao; - private HeapDump heapDump; - - public ObjectCommandHelper(CommandContext ctx, HeapDAO dao) { - this.ctx = ctx; - this.dao = dao; - } - - public HeapDump getHeapDump() throws CommandException { - if (heapDump == null) { - loadHeapDump(); - } - return heapDump; - } - - private void loadHeapDump() throws CommandException { - Arguments args = ctx.getArguments(); - String heapId = args.getArgument(HEAP_ID_ARG); - HeapInfo heapInfo = dao.getHeapInfo(heapId); - if (heapInfo == null) { - throw new HeapNotFoundException(heapId); - } - heapDump = dao.getHeapDump(heapInfo); - } - - public JavaHeapObject getJavaHeapObject() throws CommandException { - HeapDump heapDump = getHeapDump(); - Arguments args = ctx.getArguments(); - String objectId = args.getArgument(OBJECT_ID_ARG); - JavaHeapObject obj = heapDump.findObject(objectId); - if (obj == null) { - throw new ObjectNotFoundException(objectId); - } - return obj; - } -} -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectIdRequiredException.java Fri Dec 18 10:04:12 2015 -0500 @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2016 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.heap.analysis.command.internal; + +import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.shared.locale.Translate; +import com.redhat.thermostat.vm.heap.analysis.command.locale.LocaleResources; + +public class ObjectIdRequiredException extends CommandException { + + private static final Translate<LocaleResources> tr = LocaleResources.createLocalizer(); + + ObjectIdRequiredException() { + super(tr.localize(LocaleResources.OBJECT_ID_REQUIRED)); + } + +}
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectInfoCommand.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectInfoCommand.java Fri Dec 18 10:04:12 2015 -0500 @@ -62,7 +62,6 @@ private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer(); private final BundleContext context; - private Snapshot snapshot; public ObjectInfoCommand() { this(FrameworkUtil.getBundle(ObjectInfoCommand.class).getBundleContext()); @@ -86,31 +85,30 @@ } private void run(CommandContext ctx, HeapDAO heapDao) throws CommandException { - ObjectCommandHelper objCmdHelper = new ObjectCommandHelper(ctx, heapDao); + HeapCommandHelper objCmdHelper = HeapCommandHelper.getHelper(ctx, heapDao); HeapDump heapDump = objCmdHelper.getHeapDump(); - snapshot = heapDump.getSnapshot(); - JavaHeapObject obj = objCmdHelper.getJavaHeapObject(); + Snapshot snapshot = heapDump.getSnapshot(); + JavaHeapObject obj = objCmdHelper.getJavaHeapObject(ctx); TableRenderer table = new TableRenderer(2); table.printLine(translator.localize(LocaleResources.COMMAND_OBJECT_INFO_OBJECT_ID).getContents(), obj.getIdString()); table.printLine(translator.localize(LocaleResources.COMMAND_OBJECT_INFO_TYPE).getContents(), obj.getClazz().getName()); table.printLine(translator.localize(LocaleResources.COMMAND_OBJECT_INFO_SIZE).getContents(), String.valueOf(obj.getSize()) + " bytes"); table.printLine(translator.localize(LocaleResources.COMMAND_OBJECT_INFO_HEAP_ALLOCATED).getContents(), String.valueOf(obj.isHeapAllocated())); table.printLine(translator.localize(LocaleResources.COMMAND_OBJECT_INFO_REFERENCES).getContents(), ""); - printReferences(table, obj); + printReferences(table, obj, snapshot); table.printLine(translator.localize(LocaleResources.COMMAND_OBJECT_INFO_REFERRERS).getContents(), ""); - printReferrers(table, obj); + printReferrers(table, obj, snapshot); PrintStream out = ctx.getConsole().getOutput(); table.render(out); - } - private void printReferences(final TableRenderer table, final JavaHeapObject obj) { + private void printReferences(final TableRenderer table, final JavaHeapObject obj, final Snapshot snapshot) { JavaHeapObjectVisitor v = new JavaHeapObjectVisitor() { @Override public void visit(JavaHeapObject ref) { - table.printLine("", describeReference(obj, ref) + " -> " + PrintObjectUtils.objectToString(ref)); + table.printLine("", describeReference(obj, ref, snapshot) + " -> " + PrintObjectUtils.objectToString(ref)); } @Override @@ -126,15 +124,15 @@ obj.visitReferencedObjects(v); } - private void printReferrers(TableRenderer table, JavaHeapObject obj) { + private void printReferrers(TableRenderer table, JavaHeapObject obj, Snapshot snapshot) { Enumeration<?> referrers = obj.getReferers(); while (referrers.hasMoreElements()) { JavaHeapObject ref = (JavaHeapObject) referrers.nextElement(); - table.printLine("", PrintObjectUtils.objectToString(ref) + " -> " + describeReference(ref, obj)); + table.printLine("", PrintObjectUtils.objectToString(ref) + " -> " + describeReference(ref, obj, snapshot)); } } - private String describeReference(JavaHeapObject from, JavaHeapObject to) { + private String describeReference(JavaHeapObject from, JavaHeapObject to, Snapshot snapshot) { return "[" + from.describeReferenceTo(to, snapshot) + "]"; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/internal/SearchTermRequiredException.java Fri Dec 18 10:04:12 2015 -0500 @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2016 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.heap.analysis.command.internal; + +import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.shared.locale.Translate; +import com.redhat.thermostat.vm.heap.analysis.command.locale.LocaleResources; + +public class SearchTermRequiredException extends CommandException { + + private static final Translate<LocaleResources> tr = LocaleResources.createLocalizer(); + + SearchTermRequiredException() { + super(tr.localize(LocaleResources.SEARCH_TERM_REQUIRED)); + } + +}
--- a/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/locale/LocaleResources.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/main/java/com/redhat/thermostat/vm/heap/analysis/command/locale/LocaleResources.java Fri Dec 18 10:04:12 2015 -0500 @@ -57,6 +57,7 @@ HEAP_ID_NOT_FOUND, HEAP_ID_REQUIRED, SEARCH_TERM_REQUIRED, + OBJECT_ID_REQUIRED, HEAP_DUMP_ERROR, COMMAND_HEAP_DUMP_DONE,
--- a/vm-heap-analysis/command/src/main/resources/com/redhat/thermostat/vm/heap/analysis/command/locale/strings.properties Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/main/resources/com/redhat/thermostat/vm/heap/analysis/command/locale/strings.properties Fri Dec 18 10:04:12 2015 -0500 @@ -16,6 +16,7 @@ HEAP_ID_NOT_FOUND = Heap ID not found: {0} HEAP_ID_REQUIRED = Heap ID required SEARCH_TERM_REQUIRED = A search term is required +OBJECT_ID_REQUIRED = Object ID required HEAP_DUMP_ERROR = Error dumping heap (agent: {0}, vm: {1}) COMMAND_HEAP_DUMP_DONE = Done @@ -29,7 +30,7 @@ COMMAND_OBJECT_INFO_REFERENCES = References: COMMAND_OBJECT_INFO_REFERRERS = Referrers: -OBJECT_NOT_FOUND_MESSAGE = Object not found: +OBJECT_NOT_FOUND_MESSAGE = Object not found: {0} ERROR_READING_HISTOGRAM_MESSAGE = Unexpected error while reading histogram for heap ID: {0} COMMAND_SAVE_HEAP_DUMP_SAVED_TO_FILE = Saved heap dump to {0}
--- a/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindObjectsCommandTest.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindObjectsCommandTest.java Fri Dec 18 10:04:12 2015 -0500 @@ -45,6 +45,7 @@ import java.util.Arrays; +import com.redhat.thermostat.common.cli.Arguments; import org.junit.Before; import org.junit.Test; @@ -182,6 +183,7 @@ TestCommandContextFactory factory = new TestCommandContextFactory(); SimpleArguments args = new SimpleArguments(); args.addArgument("heapId", INVALID_HEAP_ID); + args.addNonOptionArgument("fluff"); try { cmd.run(factory.createContext(args)); @@ -190,5 +192,14 @@ assertEquals("Heap ID not found: " + INVALID_HEAP_ID, e.getMessage()); } } + + @Test(expected = SearchTermRequiredException.class) + public void testSearchWithNoSearchTerm() throws CommandException { + TestCommandContextFactory factory = new TestCommandContextFactory(); + SimpleArguments args = new SimpleArguments(); + args.addArgument("heapId", HEAP_ID); + + cmd.run(factory.createContext(args)); + } }
--- a/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindRootCommandTest.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/FindRootCommandTest.java Fri Dec 18 10:04:12 2015 -0500 @@ -265,6 +265,7 @@ } + @Test public void testHeapNotFound() throws CommandException { TestCommandContextFactory factory = new TestCommandContextFactory(); SimpleArguments args = new SimpleArguments(); @@ -279,6 +280,7 @@ } } + @Test public void testObjectNotFound() throws CommandException { TestCommandContextFactory factory = new TestCommandContextFactory(); SimpleArguments args = new SimpleArguments();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/HeapCommandHelperTest.java Fri Dec 18 10:04:12 2015 -0500 @@ -0,0 +1,207 @@ +/* + * Copyright 2012-2016 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.heap.analysis.command.internal; + +import com.redhat.thermostat.common.cli.Arguments; +import com.redhat.thermostat.common.cli.CommandContext; +import com.redhat.thermostat.common.cli.CommandException; +import com.redhat.thermostat.common.cli.Console; +import com.redhat.thermostat.vm.heap.analysis.common.HeapDAO; +import com.redhat.thermostat.vm.heap.analysis.common.HeapDump; +import com.redhat.thermostat.vm.heap.analysis.common.model.HeapInfo; +import com.sun.tools.hat.internal.model.JavaHeapObject; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class HeapCommandHelperTest { + + CommandContext ctx; + Arguments args; + HeapDAO dao; + HeapInfo heapInfo; + HeapDump heapDump; + JavaHeapObject heapObject; + Console console; + HeapCommandHelper helper; + public static final String HEAP_ID = "fooId"; + public static final String OBJECT_ID = "objectId"; + + @Before + public void setup() { + ctx = mock(CommandContext.class); + args = mock(Arguments.class); + dao = mock(HeapDAO.class); + heapInfo = mock(HeapInfo.class); + heapDump = mock(HeapDump.class); + heapObject = mock(JavaHeapObject.class); + console = mock(Console.class); + + when(ctx.getArguments()).thenReturn(args); + when(ctx.getConsole()).thenReturn(console); + + when(args.hasArgument(HeapCommandHelper.HEAP_ID_ARG)).thenReturn(true); + when(args.getArgument(HeapCommandHelper.HEAP_ID_ARG)).thenReturn(HEAP_ID); + when(args.hasArgument(HeapCommandHelper.OBJECT_ID_ARG)).thenReturn(true); + when(args.getArgument(HeapCommandHelper.OBJECT_ID_ARG)).thenReturn(OBJECT_ID); + + when(dao.getHeapInfo(HEAP_ID)).thenReturn(heapInfo); + when(dao.getHeapDump(heapInfo)).thenReturn(heapDump); + + when(heapDump.findObject(OBJECT_ID)).thenReturn(heapObject); + + helper = new HeapCommandHelper(new HeapCommandHelper.HeapDumpIdentifier(HEAP_ID, dao)); + } + + @Test + public void testGetHeapDumpReturnsExpectedDump() throws HeapNotFoundException { + HeapDump dump = helper.getHeapDump(); + assertThat(dump, is(heapDump)); + } + + @Test + public void verifyHeapDumpsAreCached() throws HeapNotFoundException { + HeapDump dump = helper.getHeapDump(); + verify(dao).getHeapInfo(HEAP_ID); + verify(dao).getHeapDump(heapInfo); + assertThat(dump, is(heapDump)); + HeapDump dump2 = helper.getHeapDump(); + assertThat(dump2, is(dump)); + // verify no additional DAO accesses due to second getHeapDump call + verify(dao).getHeapInfo(HEAP_ID); + verify(dao).getHeapDump(heapInfo); + } + + @Test + public void testGetJavaHeapObject() throws CommandException { + JavaHeapObject obj = helper.getJavaHeapObject(ctx); + assertThat(obj, is(heapObject)); + verify(heapDump).findObject(OBJECT_ID); + } + + @Test + public void verifyGetJavaHeapObjectIsCached() throws CommandException { + JavaHeapObject obj = helper.getJavaHeapObject(ctx); + verify(dao).getHeapInfo(HEAP_ID); + verify(dao).getHeapDump(heapInfo); + assertThat(obj, is(heapObject)); + JavaHeapObject obj2 = helper.getJavaHeapObject(ctx); + assertThat(obj2, is(obj)); + // verify no additional DAO accesses due to second getJavaHeapObject call + verify(dao).getHeapInfo(HEAP_ID); + verify(dao).getHeapDump(heapInfo); + } + + @Test(expected = ObjectNotFoundException.class) + public void assertExceptionWhenGetJavaHeapObjectSuppliedInvalidId() throws CommandException { + when(args.getArgument(OBJECT_ID)).thenReturn("fluff"); + helper.getJavaHeapObject(ctx); + } + + @Test + public void testHelperCaching() throws HeapNotFoundException { + when(dao.getHeapInfo(any(String.class))).thenReturn(heapInfo); + when(dao.getHeapDump(any(HeapInfo.class))).thenReturn(heapDump); + + List<HeapCommandHelper> helpers = new ArrayList<>(HeapCommandHelper.MAXIMUM_CACHED_HELPERS); + for (int i = 0; i < HeapCommandHelper.MAXIMUM_CACHED_HELPERS; i++) { + String heapId = "heapId-" + i; + when(args.getArgument(HeapCommandHelper.HEAP_ID_ARG)).thenReturn(heapId); + HeapCommandHelper helper = HeapCommandHelper.getHelper(ctx, dao); + helpers.add(helper); + } + + // verify no duplicate helpers were returned to us + Set<HeapCommandHelper> helperSet = new HashSet<>(helpers); + assertThat(helpers.size(), is(equalTo(helperSet.size()))); + + // check each helper so far generated no dao accesses (we haven't accessed any dumps or heap objects yet) + verifyZeroInteractions(dao); + + // use the helpers in order to ensure that they have cached their results and that the 1st is LRU + int accessCount = 0; + for (int i = 0; i < HeapCommandHelper.MAXIMUM_CACHED_HELPERS; i++) { + String heapId = "heapId-" + i; + when(args.getArgument(HeapCommandHelper.HEAP_ID_ARG)).thenReturn(heapId); + HeapCommandHelper helper = HeapCommandHelper.getHelper(ctx, dao); + helper.getHeapDump(); + accessCount++; + } + + verify(dao, times(accessCount)).getHeapInfo(anyString()); + verify(dao, times(accessCount)).getHeapDump(any(HeapInfo.class)); + + // this should evict the old 1st helper which was LRU + String newHeapId = "heapId-" + HeapCommandHelper.MAXIMUM_CACHED_HELPERS; + when(args.getArgument(HeapCommandHelper.HEAP_ID_ARG)).thenReturn(newHeapId); + HeapCommandHelper newHelper = HeapCommandHelper.getHelper(ctx, dao); + + newHelper.getHeapDump(); + accessCount++; + verify(dao, times(accessCount)).getHeapInfo(anyString()); + verify(dao, times(accessCount)).getHeapDump(any(HeapInfo.class)); + + // ask for the evicted helper again and ensure that this produces dao accesses the first time again (only), + // in other words ensure that it really was evicted but is subsequently re-cached + String evictedId = "heapId-0"; + when(args.getArgument(HeapCommandHelper.HEAP_ID_ARG)).thenReturn(evictedId); + HeapCommandHelper evictedHelper = HeapCommandHelper.getHelper(ctx, dao); + + evictedHelper.getHeapDump(); + accessCount++; + verify(dao, times(accessCount)).getHeapInfo(anyString()); + verify(dao, times(accessCount)).getHeapDump(any(HeapInfo.class)); + + assertThat(HeapCommandHelper.getHelper(ctx, dao), is(evictedHelper)); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/LRUMapTest.java Fri Dec 18 10:04:12 2015 -0500 @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2016 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.heap.analysis.command.internal; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class LRUMapTest { + + LRUMap<String, String> map; + + @Before + public void setup() { + map = new LRUMap<>(3); + } + + @Test + public void testDoesNotExceedMaximumSize() { + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + map.put("d", "4"); + assertThat(map.size(), is(3)); + } + + @Test + public void testIsInitiallyEmpty() { + assertThat(map.isEmpty(), is(true)); + } + + @Test + public void testEvictsLeastRecentlyUsed() { + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + + map.get("b"); + map.get("c"); + // "a" is now LRU entry + + map.put("d", "4"); + + Map<String, String> expected = new HashMap<>(); + expected.put("b", "2"); + expected.put("c", "3"); + expected.put("d", "4"); + + assertThat(map, is(equalTo(expected))); + } + +}
--- a/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectInfoCommandTest.java Fri Jun 24 11:14:59 2016 -0400 +++ b/vm-heap-analysis/command/src/test/java/com/redhat/thermostat/vm/heap/analysis/command/internal/ObjectInfoCommandTest.java Fri Dec 18 10:04:12 2015 -0500 @@ -177,6 +177,7 @@ } + @Test public void testHeapNotFound() throws CommandException { StubBundleContext context = new StubBundleContext(); context.registerService(HeapDAO.class, dao, null); @@ -195,6 +196,7 @@ } } + @Test public void testObjectNotFound() throws CommandException { StubBundleContext context = new StubBundleContext(); context.registerService(HeapDAO.class, dao, null);