Mercurial > hg > release > thermostat-1.6
changeset 1939:a09beb448a72
Add search functionality to HistogramPanel
PR3036
Reviewed-by: jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019693.html
line wrap: on
line diff
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramView.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapHistogramView.java Thu Jun 23 11:17:14 2016 -0400 @@ -38,11 +38,18 @@ import com.redhat.thermostat.client.core.views.BasicView; import com.redhat.thermostat.client.core.views.UIComponent; +import com.redhat.thermostat.common.ActionListener; import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; public abstract class HeapHistogramView extends BasicView implements UIComponent { - public abstract void display(ObjectHistogram histogram); + public enum HistogramAction { + SEARCH, + } + + public abstract void setHistogram(ObjectHistogram histogram); + + public abstract void addHistogramActionListener(ActionListener<HistogramAction> listener); }
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java Thu Jun 23 11:17:14 2016 -0400 @@ -37,17 +37,22 @@ package com.redhat.thermostat.vm.heap.analysis.client.core.internal; import java.io.IOException; +import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import com.redhat.thermostat.client.core.views.BasicView; +import com.redhat.thermostat.common.ActionEvent; +import com.redhat.thermostat.common.ActionListener; import com.redhat.thermostat.common.ApplicationService; +import com.redhat.thermostat.common.NotImplementedException; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.shared.locale.LocalizedString; import com.redhat.thermostat.shared.locale.Translate; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsView; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView.HistogramAction; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; @@ -56,6 +61,8 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources; import com.redhat.thermostat.vm.heap.analysis.common.HeapDump; +import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; +import com.sun.tools.hat.internal.model.JavaHeapObject; public class HeapDumpDetailsController { @@ -71,6 +78,7 @@ private HeapTreeMapViewProvider treeMapViewProvider; private ObjectDetailsViewProvider objectDetailsViewProvider; private ObjectRootsViewProvider objectRootsViewProvider; + private HeapHistogramView heapHistogramView; public HeapDumpDetailsController(ApplicationService appService, HeapDumpDetailsViewProvider viewProvider, HeapHistogramViewProvider histogramProvider, HeapTreeMapViewProvider treeMapProvider, ObjectDetailsViewProvider objectDetailsProvider, ObjectRootsViewProvider objectRootsProvider) { this.appService = appService; @@ -84,8 +92,22 @@ public void setDump(HeapDump dump) { this.heapDump = dump; try { - HeapHistogramView heapHistogramView = histogramViewProvider.createView(); - heapHistogramView.display(heapDump.getHistogram()); + ObjectHistogram histogram = heapDump.getHistogram(); + heapHistogramView = histogramViewProvider.createView(); + heapHistogramView.setHistogram(histogram); + heapHistogramView.addHistogramActionListener(new ActionListener<HistogramAction>() { + @Override + public void actionPerformed(ActionEvent<HistogramAction> actionEvent) { + switch (actionEvent.getActionId()) { + case SEARCH: + searchForObject((String) actionEvent.getPayload()); + break; + default: + throw new NotImplementedException("unknown action fired by " + actionEvent.getSource()); + } + } + }); + LocalizedString title = translator.localize(LocaleResources.HEAP_DUMP_SECTION_HISTOGRAM); view.addSubView(title, heapHistogramView); @@ -105,6 +127,23 @@ heapDump.searchObjects("A_RANDOM_PATTERN", 1); } + private void searchForObject(final String searchText) { + + appService.getApplicationExecutor().execute(new Runnable() { + @Override + public void run() { + Collection<String> objectIds = heapDump.wildcardSearch(searchText); + + ObjectHistogram toDisplay = new ObjectHistogram(); + for (String id : objectIds) { + JavaHeapObject heapObject = heapDump.findObject(id); + toDisplay.addThing(heapObject); + } + heapHistogramView.setHistogram(toDisplay); + } + }); + } + public BasicView getView() { return view; }
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsController.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsController.java Thu Jun 23 11:17:14 2016 -0400 @@ -151,25 +151,17 @@ } private void searchForObject() { - String searchText = view.getSearchText(); + final String searchText = view.getSearchText(); if (searchText == null || searchText.trim().isEmpty()) { view.setMatchingObjects(Collections.<HeapObjectUI>emptySet()); return; } - final int maxResults = computeResultLimit(searchText); - - if (!searchText.contains("*") && !searchText.contains("?")) { - searchText = "*" + searchText + "*"; - } - - final String wildcardQuery = searchText; - appService.getApplicationExecutor().execute(new Runnable() { @Override public void run() { - Collection<String> objectIds = heapDump.searchObjects(wildcardQuery, maxResults); + Collection<String> objectIds = heapDump.wildcardSearch(searchText); List<HeapObjectUI> toDisplay = new ArrayList<>(); for (String id: objectIds) { @@ -182,10 +174,6 @@ } - private int computeResultLimit(String searchText) { - return Math.min(1000, searchText.length() * 100); - } - private void showObjectInfo() { HeapObjectUI matchingObject = view.getSelectedMatchingObject(); if (matchingObject == null) {
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java Thu Jun 23 11:17:14 2016 -0400 @@ -58,7 +58,8 @@ HEAP_DUMP_HISTOGRAM_COLUMN_CLASS, HEAP_DUMP_HISTOGRAM_COLUMN_INSTANCES, HEAP_DUMP_HISTOGRAM_COLUMN_SIZE, - + HEAP_DUMP_HISTOGRAM_BROWSE_SEARCH_HINT, + HEAP_DUMP_OBJECT_BROWSE_SEARCH_HINT, HEAP_DUMP_OBJECT_BROWSE_SEARCH_PATTERN_HELP, HEAP_DUMP_OBJECT_BROWSE_SEARCH_LABEL,
--- a/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties Thu Jun 23 11:17:14 2016 -0400 @@ -18,6 +18,7 @@ HEAP_DUMP_HISTOGRAM_COLUMN_CLASS = Class HEAP_DUMP_HISTOGRAM_COLUMN_INSTANCES = Instances HEAP_DUMP_HISTOGRAM_COLUMN_SIZE = Size (in bytes) +HEAP_DUMP_HISTOGRAM_BROWSE_SEARCH_HINT = Search by class name (either a partial class name or a wildcard pattern) HEAP_DUMP_OBJECT_BROWSE_SEARCH_HINT = Search for objects by class name (either a partial class name or a wildcard pattern) HEAP_DUMP_OBJECT_BROWSE_SEARCH_PATTERN_HELP = Either a partial class name ("Button" will find "javax.swing.JButton") or a wildcard pattern ("*JButton*" will find "javax.swing.JButtonBeanInfo")
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsControllerTest.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ObjectDetailsControllerTest.java Thu Jun 23 11:17:14 2016 -0400 @@ -42,7 +42,6 @@ import static org.mockito.Matchers.contains; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -57,8 +56,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; -import org.mockito.stubbing.OngoingStubbing; import com.redhat.thermostat.common.ActionEvent; import com.redhat.thermostat.common.ActionListener; @@ -122,7 +119,7 @@ when(heapObject.getClazz()).thenReturn(heapObjectClass); HeapDump dump = mock(HeapDump.class); - when(dump.searchObjects(contains(SEARCH_TEXT), anyInt())).thenReturn(Arrays.asList(OBJECT_ID)); + when(dump.wildcardSearch(contains(SEARCH_TEXT))).thenReturn(Arrays.asList(OBJECT_ID)); when(dump.findObject(eq(OBJECT_ID))).thenReturn(heapObject); ArgumentCaptor<ActionListener> viewArgumentCaptor1 = ArgumentCaptor.forClass(ActionListener.class); @@ -147,92 +144,6 @@ @SuppressWarnings({ "rawtypes", "unchecked" }) @Test - public void verifyInputConvertedIntoWildcardsIfNeeded() { - HeapDump heap = mock(HeapDump.class); - when(view.getSearchText()).thenReturn("a"); - - ArgumentCaptor<ActionListener> objectActionListenerCaptor = ArgumentCaptor.forClass(ActionListener.class); - doNothing().when(view).addObjectActionListener(objectActionListenerCaptor.capture()); - - @SuppressWarnings("unused") - ObjectDetailsController controller = new ObjectDetailsController(appService, heap, this.objectDetailsProvider, this.objectRootsProvider); - - ActionListener<ObjectAction> actionListener = objectActionListenerCaptor.getValue(); - assertNotNull(actionListener); - actionListener.actionPerformed(new ActionEvent<ObjectAction>(view, ObjectAction.SEARCH)); - - runnableCaptor.getValue().run(); - - verify(heap).searchObjects("*a*", 100); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Test - public void verifyWildcardInputNotConvertedIntoWildcards() { - HeapDump heap = mock(HeapDump.class); - when(view.getSearchText()).thenReturn("*a?"); - - ArgumentCaptor<ActionListener> objectActionListenerCaptor = ArgumentCaptor.forClass(ActionListener.class); - doNothing().when(view).addObjectActionListener(objectActionListenerCaptor.capture()); - - @SuppressWarnings("unused") - ObjectDetailsController controller = new ObjectDetailsController(appService, heap, this.objectDetailsProvider, this.objectRootsProvider); - - ActionListener<ObjectAction> actionListener = objectActionListenerCaptor.getValue(); - assertNotNull(actionListener); - actionListener.actionPerformed(new ActionEvent<ObjectAction>(view, ObjectAction.SEARCH)); - - runnableCaptor.getValue().run(); - - verify(heap).searchObjects("*a?", 300); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Test - public void verifySearchLimits() { - - Object[][] limits = new Object[][] { - { "a", 100 }, - { "ab", 200 }, - { "abc", 300 }, - { "abcd", 400 }, - { "abcde", 500 }, - { "abcdef", 600}, - { "abcdefg", 700}, - { "java.lang.Class", 1000 }, - }; - - HeapDump heap = mock(HeapDump.class); - - OngoingStubbing<String> ongoing = when(view.getSearchText()); - for (int i = 0; i < limits.length; i++) { - ongoing = ongoing.thenReturn((String)limits[i][0]); - } - - ArgumentCaptor<ActionListener> objectActionListenerCaptor = ArgumentCaptor.forClass(ActionListener.class); - doNothing().when(view).addObjectActionListener(objectActionListenerCaptor.capture()); - - @SuppressWarnings("unused") - ObjectDetailsController controller = new ObjectDetailsController(appService, heap, this.objectDetailsProvider, this.objectRootsProvider); - - ActionListener<ObjectAction> actionListener = objectActionListenerCaptor.getValue(); - assertNotNull(actionListener); - - for (int i = 0; i < limits.length; i++) { - actionListener.actionPerformed(new ActionEvent<ObjectAction>(view, ObjectAction.SEARCH)); - runnableCaptor.getValue().run(); - } - - InOrder inOrder = inOrder(heap); - for (int i = 0; i < limits.length; i++) { - String text = (String) limits[i][0]; - int times = (Integer) limits[i][1]; - inOrder.verify(heap).searchObjects("*" + text + "*", times); - } - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Test public void verifyGettingDetailsWorks() { final String OBJECT_ID = "0xcafebabe"; final String OBJECT_ID_VISIBLE = "FOO BAR";
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramPanel.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramPanel.java Thu Jun 23 11:17:14 2016 -0400 @@ -42,14 +42,24 @@ import java.util.List; import javax.swing.BoxLayout; +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; +import javax.swing.LayoutStyle.ComponentPlacement; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; import com.redhat.thermostat.client.swing.NonEditableTableModel; import com.redhat.thermostat.client.swing.SwingComponent; import com.redhat.thermostat.client.swing.components.HeaderPanel; +import com.redhat.thermostat.client.swing.components.SearchField; import com.redhat.thermostat.client.swing.components.ThermostatTable; import com.redhat.thermostat.client.swing.components.ThermostatTableRenderer; +import com.redhat.thermostat.client.ui.SearchProvider.SearchAction; +import com.redhat.thermostat.common.ActionEvent; +import com.redhat.thermostat.common.ActionListener; +import com.redhat.thermostat.common.ActionNotifier; import com.redhat.thermostat.common.utils.DescriptorConverter; import com.redhat.thermostat.shared.locale.Translate; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView; @@ -57,6 +67,7 @@ import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord; import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; + @SuppressWarnings("serial") public class HistogramPanel extends HeapHistogramView implements SwingComponent { @@ -64,21 +75,80 @@ private final JPanel panel; - private HeaderPanel headerPanel; + private ThermostatTable table; + + private final ActionNotifier<HistogramAction> notifier = new ActionNotifier<>(this); + public HistogramPanel() { panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); - headerPanel = new HeaderPanel(translator.localize(LocaleResources.HEAP_DUMP_CLASS_USAGE)); + HeaderPanel headerPanel = createHeader(); panel.add(headerPanel); } + public HeaderPanel createHeader() { + table = new ThermostatTable(); + table.setDefaultRenderer(Long.class, new NiceNumberFormatter()); + JScrollPane scrollPane = table.wrap(); + + final SearchField searchField = new SearchField(); + searchField.setTooltip(translator.localize(LocaleResources.HEAP_DUMP_OBJECT_BROWSE_SEARCH_PATTERN_HELP)); + searchField.setLabel(translator.localize(LocaleResources.HEAP_DUMP_HISTOGRAM_BROWSE_SEARCH_HINT)); + searchField.addSearchListener(new ActionListener<SearchAction>() { + @Override + public void actionPerformed(ActionEvent<SearchAction> actionEvent) { + switch (actionEvent.getActionId()) { + case PERFORM_SEARCH: + notifier.fireAction(HistogramAction.SEARCH, searchField.getSearchText()); + break; + default: + break; + } + } + }); + + JPanel displayContents = new JPanel(); + + GroupLayout layout = new GroupLayout(displayContents); + displayContents.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(Alignment.LEADING) + .addComponent(searchField) + .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(searchField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) + .addPreferredGap(ComponentPlacement.RELATED) + .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 287, Short.MAX_VALUE)) + ); + + HeaderPanel headerPanel = new HeaderPanel(translator.localize(LocaleResources.HEAP_DUMP_CLASS_USAGE)); + headerPanel.setContent(displayContents); + return headerPanel; + } + @Override - public void display(ObjectHistogram histogram) { - ThermostatTable table = new ThermostatTable(new HistogramTableModel(histogram)); - table.setDefaultRenderer(Long.class, new NiceNumberFormatter()); - headerPanel.setContent(table.wrap()); + public void addHistogramActionListener(ActionListener<HistogramAction> listener) { + notifier.addActionListener(listener); + } + + @Override + public void setHistogram(final ObjectHistogram histogram) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + table.setModel(new HistogramTableModel(histogram)); + } + }); } private final class NiceNumberFormatter extends ThermostatTableRenderer {
--- a/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDump.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/common/src/main/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDump.java Thu Jun 23 11:17:14 2016 -0400 @@ -85,6 +85,8 @@ private static final Logger log = LoggingUtils.getLogger(HeapDump.class); + private static final int MAX_SEARCH_RESULTS = 1000; + private final HeapInfo heapInfo; private final HeapDAO heapDAO; @@ -225,6 +227,16 @@ return results; } + public Collection<String> wildcardSearch(String searchText) { + int limit = Math.min(MAX_SEARCH_RESULTS, searchText.length() * 100); + + String wildCardClassNamePattern = searchText; + if (!searchText.contains("*") && !searchText.contains("?")) { + wildCardClassNamePattern = "*" + searchText + "*"; + } + return searchObjects(wildCardClassNamePattern, limit); + } + public JavaHeapObject findObject(String id) { loadHeapDumpIfNecessary(); return snapshot.findThing(id);
--- a/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDumpTest.java Thu Jun 23 11:15:46 2016 -0400 +++ b/vm-heap-analysis/common/src/test/java/com/redhat/thermostat/vm/heap/analysis/common/HeapDumpTest.java Thu Jun 23 11:17:14 2016 -0400 @@ -41,6 +41,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; @@ -85,15 +86,17 @@ private static final String HEAP_ID = "TEST_HEAP_ID"; private HeapDump heapDump; + private HeapInfo heapInfo; + private HeapDAO heapDAO; @Before public void setUp() throws IOException { InputStream in = getClass().getResourceAsStream("/heapdump.hprof.gz"); GZIPInputStream gzipIn = new GZIPInputStream(in); - HeapInfo heapInfo = mock(HeapInfo.class); + heapInfo = mock(HeapInfo.class); when(heapInfo.getHeapId()).thenReturn(HEAP_ID); - HeapDAO heapDAO = mock(HeapDAO.class); + heapDAO = mock(HeapDAO.class); when(heapDAO.getHeapDumpData(heapInfo)).thenReturn(gzipIn); heapDump = new HeapDump(heapInfo, heapDAO); } @@ -172,5 +175,59 @@ assertEquals("0x7d704eb20", obj.getIdString()); assertEquals("java.util.ArrayDeque", obj.getClazz().getName()); } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void verifyWildcardSearchInputConvertedIntoWildcardsIfNeeded() { + String input = "a"; + String wildCardInput = "*" + input + "*"; + verifyWildcardSearchObjectInput(input, wildCardInput, 100); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void verifyWildcardSearchInputNotConvertedIntoWildcards() { + String input1 = "a?"; + String input2 = "a*"; + String input3 = "*a?"; + + verifyWildcardSearchObjectInput(input1, input1, 200); + verifyWildcardSearchObjectInput(input2, input2, 200); + verifyWildcardSearchObjectInput(input3, input3, 300); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void verifyWildcardSearchLimits() { + Object[][] limits = new Object[][] { + { "a", 100 }, + { "ab", 200 }, + { "abc", 300 }, + { "abcd", 400 }, + { "abcde", 500 }, + { "abcdef", 600 }, + { "abcdefg", 700 }, + { "java.lang.Class", 1000 }, + }; + + for (Object[] limit : limits) { + String text = (String) limit[0]; + int times = (Integer) limit[1]; + verifyWildcardSearchObjectInput(text, "*" + text + "*", times); + } + } + + public void verifyWildcardSearchObjectInput(String inputPattern, final String expectedPattern, final int expectedLimit) { + new HeapDump(heapInfo, heapDAO) { + @Override + public Collection<String> searchObjects(String pattern, int limit) { + assertEquals(pattern, expectedPattern); + assertEquals(limit, expectedLimit); + + // doesn't matter + return null; + } + }.wildcardSearch(inputPattern); + } }