Mercurial > hg > release > thermostat-1.6
changeset 1714:d9f3dd1eaed2
PR2558: [1.4] Backport - Add tree map view to heap analysis tab
Reviewed-by: jerboaa
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2015-July/014412.html
line wrap: on
line diff
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapDumpDetailsView.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapDumpDetailsView.java Thu Jul 23 14:03:27 2015 +0200 @@ -44,6 +44,7 @@ public abstract void addSubView(LocalizedString title, HeapHistogramView child); public abstract void addSubView(LocalizedString title, ObjectDetailsView child); + public abstract void addSubView(LocalizedString title, HeapTreeMapView child); public abstract void removeSubView(LocalizedString title); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapTreeMapView.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2015 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.client.core; + +import com.redhat.thermostat.client.core.views.BasicView; +import com.redhat.thermostat.client.core.views.UIComponent; +import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; + +public abstract class HeapTreeMapView extends BasicView implements UIComponent { + + public abstract void display(ObjectHistogram histogram); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/HeapTreeMapViewProvider.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2015 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.client.core; + +import com.redhat.thermostat.annotations.ExtensionPoint; +import com.redhat.thermostat.client.core.views.ViewProvider; + +@ExtensionPoint +public interface HeapTreeMapViewProvider extends ViewProvider { + + @Override + public HeapTreeMapView createView(); +} +
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/Activator.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/Activator.java Thu Jul 23 14:03:27 2015 +0200 @@ -57,6 +57,7 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumperService; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; @@ -78,6 +79,7 @@ HeapViewProvider.class, HeapDumpDetailsViewProvider.class, HeapHistogramViewProvider.class, + HeapTreeMapViewProvider.class, ObjectDetailsViewProvider.class, ObjectRootsViewProvider.class, HeapDumpListViewProvider.class, @@ -106,6 +108,8 @@ Objects.requireNonNull(detailsViewProvider); HeapHistogramViewProvider histogramViewProvider = (HeapHistogramViewProvider) services .get(HeapHistogramViewProvider.class.getName()); + HeapTreeMapViewProvider treeMapViewProvider = (HeapTreeMapViewProvider) services + .get(HeapTreeMapViewProvider.class.getName()); Objects.requireNonNull(histogramViewProvider); ObjectDetailsViewProvider objectDetailsViewProvider = (ObjectDetailsViewProvider) services .get(ObjectDetailsViewProvider.class.getName()); @@ -119,7 +123,7 @@ HeapDumperService service = new HeapDumperServiceImpl(appSvc, vmInfoDao, vmMemoryStatDao, heapDao, viewProvider, - detailsViewProvider, histogramViewProvider, + detailsViewProvider, histogramViewProvider, treeMapViewProvider, objectDetailsViewProvider, objectRootsViewProvider, heapDumpListViewProvider, notifier); Dictionary<String, String> properties = new Hashtable<>(); @@ -141,4 +145,3 @@ tracker.close(); } } -
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpController.java Thu Jul 23 14:03:27 2015 +0200 @@ -69,6 +69,7 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.HeapDumperAction; @@ -102,6 +103,7 @@ private ApplicationService appService; private HeapDumpDetailsViewProvider detailsViewProvider; private HeapHistogramViewProvider histogramViewProvider; + private HeapTreeMapViewProvider treeMapViewProvider; private ObjectDetailsViewProvider objectDetailsViewProvider; private ObjectRootsViewProvider objectRootsViewProvider; private HeapDumpListViewProvider heapDumpListViewProvider; @@ -114,13 +116,14 @@ final ApplicationService appService, HeapViewProvider viewProvider, HeapDumpDetailsViewProvider detailsViewProvider, HeapHistogramViewProvider histogramProvider, + HeapTreeMapViewProvider treeMapProvider, ObjectDetailsViewProvider objectDetailsProvider, ObjectRootsViewProvider objectRootsProvider, HeapDumpListViewProvider heapDumpListViewProvider, ProgressNotifier notifier) { this(vmMemoryStatDao, vmInfoDao, heapDao, ref, appService, viewProvider, - detailsViewProvider, histogramProvider, objectDetailsProvider, + detailsViewProvider, histogramProvider, treeMapProvider, objectDetailsProvider, objectRootsProvider, heapDumpListViewProvider, new HeapDumper(ref), notifier); } @@ -132,6 +135,7 @@ HeapViewProvider viewProvider, HeapDumpDetailsViewProvider detailsViewProvider, HeapHistogramViewProvider histogramProvider, + HeapTreeMapViewProvider treeMapProvider, ObjectDetailsViewProvider objectDetailsProvider, ObjectRootsViewProvider objectRootsProvider, HeapDumpListViewProvider heapDumpListViewProvider, @@ -142,6 +146,7 @@ this.objectDetailsViewProvider = objectDetailsProvider; this.objectRootsViewProvider = objectRootsProvider; this.histogramViewProvider = histogramProvider; + this.treeMapViewProvider = treeMapProvider; this.detailsViewProvider = detailsViewProvider; this.appService = appService; this.ref = ref; @@ -355,6 +360,7 @@ HeapDumpDetailsController controller = new HeapDumpDetailsController(appService, detailsViewProvider, histogramViewProvider, + treeMapViewProvider, objectDetailsViewProvider, objectRootsViewProvider); controller.setDump(dump); @@ -429,4 +435,3 @@ } } } -
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsController.java Thu Jul 23 14:03:27 2015 +0200 @@ -49,6 +49,8 @@ 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.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsView; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; @@ -66,12 +68,14 @@ private HeapDumpDetailsView view; private HeapDump heapDump; private HeapHistogramViewProvider histogramViewProvider; + private HeapTreeMapViewProvider treeMapViewProvider; private ObjectDetailsViewProvider objectDetailsViewProvider; private ObjectRootsViewProvider objectRootsViewProvider; - public HeapDumpDetailsController(ApplicationService appService, HeapDumpDetailsViewProvider viewProvider, HeapHistogramViewProvider histogramProvider, ObjectDetailsViewProvider objectDetailsProvider, ObjectRootsViewProvider objectRootsProvider) { + public HeapDumpDetailsController(ApplicationService appService, HeapDumpDetailsViewProvider viewProvider, HeapHistogramViewProvider histogramProvider, HeapTreeMapViewProvider treeMapProvider, ObjectDetailsViewProvider objectDetailsProvider, ObjectRootsViewProvider objectRootsProvider) { this.appService = appService; this.histogramViewProvider = histogramProvider; + this.treeMapViewProvider = treeMapProvider; this.objectDetailsViewProvider = objectDetailsProvider; this.objectRootsViewProvider = objectRootsProvider; view = viewProvider.createView(); @@ -84,6 +88,11 @@ heapHistogramView.display(heapDump.getHistogram()); LocalizedString title = translator.localize(LocaleResources.HEAP_DUMP_SECTION_HISTOGRAM); view.addSubView(title, heapHistogramView); + + HeapTreeMapView heapTreeMapView = treeMapViewProvider.createView(); + heapTreeMapView.display(heapDump.getHistogram()); + LocalizedString titleTreeMap = translator.localize(LocaleResources.HEAP_DUMP_SECTION_TREEMAP); + view.addSubView(titleTreeMap, heapTreeMapView); } catch (IOException e) { log.log(Level.SEVERE, "unexpected error while reading heap dump", e); } @@ -101,4 +110,3 @@ } } -
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumperServiceImpl.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumperServiceImpl.java Thu Jul 23 14:03:27 2015 +0200 @@ -36,17 +36,18 @@ package com.redhat.thermostat.vm.heap.analysis.client.core.internal; -import com.redhat.thermostat.common.Filter; import com.redhat.thermostat.client.core.NameMatchingRefFilter; import com.redhat.thermostat.client.core.controllers.InformationServiceController; import com.redhat.thermostat.client.core.progress.ProgressNotifier; import com.redhat.thermostat.common.ApplicationService; +import com.redhat.thermostat.common.Filter; import com.redhat.thermostat.storage.core.VmRef; import com.redhat.thermostat.storage.dao.VmInfoDAO; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumperService; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; @@ -66,6 +67,7 @@ private HeapViewProvider viewProvider; private HeapDumpDetailsViewProvider detailsViewProvider; private HeapHistogramViewProvider histogramViewProvider; + private HeapTreeMapViewProvider treeMapViewProvider; private ObjectDetailsViewProvider objectDetailsViewProvider; private ObjectRootsViewProvider objectRootsViewProvider; @@ -78,6 +80,7 @@ HeapDAO heapDao, HeapViewProvider viewProvider, HeapDumpDetailsViewProvider detailsViewProvider, HeapHistogramViewProvider histogramViewProvider, + HeapTreeMapViewProvider treeMapViewProvider, ObjectDetailsViewProvider objectDetailsViewProvider, ObjectRootsViewProvider objectRootsViewProvider, HeapDumpListViewProvider heapDumpListViewProvider, @@ -89,6 +92,7 @@ this.viewProvider = viewProvider; this.detailsViewProvider = detailsViewProvider; this.histogramViewProvider = histogramViewProvider; + this.treeMapViewProvider = treeMapViewProvider; this.objectDetailsViewProvider = objectDetailsViewProvider; this.objectRootsViewProvider = objectRootsViewProvider; this.heapDumpListViewProvider = heapDumpListViewProvider; @@ -98,7 +102,7 @@ @Override public InformationServiceController<VmRef> getInformationServiceController(VmRef ref) { return new HeapDumpController(vmMemoryStatDao, vmInfoDao, heapDao, ref, appService, - viewProvider, detailsViewProvider, histogramViewProvider, objectDetailsViewProvider, + viewProvider, detailsViewProvider, histogramViewProvider, treeMapViewProvider, objectDetailsViewProvider, objectRootsViewProvider, heapDumpListViewProvider, notifier); } @@ -112,4 +116,3 @@ return ORDER; } } -
--- a/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/locale/LocaleResources.java Thu Jul 23 14:03:27 2015 +0200 @@ -77,7 +77,8 @@ PROCESS_EXITED, DUMPS_LIST, - LIST_DUMPS_ACTION, + LIST_DUMPS_ACTION, + HEAP_DUMP_SECTION_TREEMAP, ; @@ -87,4 +88,3 @@ return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class); } } -
--- a/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/main/resources/com/redhat/thermostat/vm/heap/analysis/client/locale/strings.properties Thu Jul 23 14:03:27 2015 +0200 @@ -12,6 +12,7 @@ HEAP_DUMP_SECTION_HISTOGRAM = Histogram HEAP_DUMP_SECTION_OBJECT_BROWSER = Object Browser +HEAP_DUMP_SECTION_TREEMAP = Heap Tree Map HEAP_DUMP_CLASS_USAGE = Classes Usage HEAP_DUMP_HISTOGRAM_COLUMN_CLASS = Class @@ -35,4 +36,4 @@ DUMPS_LIST = Available Dumps HEAP_DUMP_IN_PROGRESS = dumping... HEAP_DUMP_LOADING_IN_PROGRESS = loading... -PROCESS_EXITED = Process exited. +PROCESS_EXITED = Process exited. \ No newline at end of file
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ActivatorTest.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/ActivatorTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -51,6 +51,7 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; @@ -86,6 +87,7 @@ HeapViewProvider viewProvider = mock(HeapViewProvider.class); HeapDumpDetailsViewProvider detailsViewProvider = mock(HeapDumpDetailsViewProvider.class); HeapHistogramViewProvider histogramViewProvider = mock(HeapHistogramViewProvider.class); + HeapTreeMapViewProvider treeMapViewProvider = mock(HeapTreeMapViewProvider.class); ObjectDetailsViewProvider objectDetailsViewProvider = mock(ObjectDetailsViewProvider.class); ObjectRootsViewProvider objectRootsViewProvider = mock(ObjectRootsViewProvider.class); HeapDumpListViewProvider heapDumpListViewProvider = mock(HeapDumpListViewProvider.class); @@ -99,6 +101,7 @@ context.registerService(HeapViewProvider.class, viewProvider, null); context.registerService(HeapDumpDetailsViewProvider.class, detailsViewProvider, null); context.registerService(HeapHistogramViewProvider.class, histogramViewProvider, null); + context.registerService(HeapTreeMapViewProvider.class, treeMapViewProvider, null); context.registerService(ObjectDetailsViewProvider.class, objectDetailsViewProvider, null); context.registerService(ObjectRootsViewProvider.class, objectRootsViewProvider, null); context.registerService(HeapDumpListViewProvider.class, heapDumpListViewProvider, null); @@ -113,7 +116,7 @@ activator.stop(context); assertEquals(0, context.getServiceListeners().size()); - assertEquals(11, context.getAllServices().size()); + assertEquals(12, context.getAllServices().size()); } }
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpControllerTest.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpControllerTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -36,8 +36,8 @@ package com.redhat.thermostat.vm.heap.analysis.client.core.internal; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.isA; @@ -49,8 +49,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -85,6 +83,8 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView; 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; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.DumpDisabledReason; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapView.HeapDumperAction; @@ -125,6 +125,7 @@ private HeapViewProvider viewProvider; private HeapDumpDetailsViewProvider detailsViewProvider; private HeapHistogramViewProvider histogramProvider; + private HeapTreeMapViewProvider treeMapProvider; private ObjectDetailsViewProvider objectDetailsProvider; private ObjectRootsViewProvider objectRootsProvider; @@ -173,6 +174,10 @@ histogramProvider = mock(HeapHistogramViewProvider.class); when(histogramProvider.createView()).thenReturn(histogramView); + HeapTreeMapView treeMapView = mock(HeapTreeMapView.class); + treeMapProvider = mock(HeapTreeMapViewProvider.class); + when(treeMapProvider.createView()).thenReturn(treeMapView); + ObjectDetailsView objectView = mock(ObjectDetailsView.class); objectDetailsProvider = mock(ObjectDetailsViewProvider.class); when(objectDetailsProvider.createView()).thenReturn(objectView); @@ -209,7 +214,7 @@ when(ref.getHostRef()).thenReturn(hostRef); controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService, - viewProvider, detailsViewProvider, histogramProvider, + viewProvider, detailsViewProvider, histogramProvider, treeMapProvider, objectDetailsProvider, objectRootsProvider, heapDumpListViewProvider, heapDumper, notifier); } @@ -222,6 +227,7 @@ viewProvider = null; detailsViewProvider = null; histogramProvider = null; + treeMapProvider = null; objectDetailsProvider = null; objectRootsProvider = null; appService = null; @@ -294,7 +300,7 @@ when(appService.getApplicationCache()).thenReturn(cache); VmRef ref = mock(VmRef.class); controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService, - viewProvider, detailsViewProvider, histogramProvider, + viewProvider, detailsViewProvider, histogramProvider, treeMapProvider, objectDetailsProvider, objectRootsProvider, heapDumpListViewProvider, heapDumper, notifier); @@ -320,7 +326,7 @@ when(appService.getApplicationCache()).thenReturn(cache); VmRef ref = mock(VmRef.class); controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService, - viewProvider, detailsViewProvider, histogramProvider, + viewProvider, detailsViewProvider, histogramProvider, treeMapProvider, objectDetailsProvider, objectRootsProvider, heapDumpListViewProvider, heapDumper, notifier); @@ -341,7 +347,7 @@ when(vmInfoDao.getVmInfo(ref)).thenReturn(vmInfo); controller = new HeapDumpController(vmDao, vmInfoDao, heapDao, ref, appService, - viewProvider, detailsViewProvider, histogramProvider, + viewProvider, detailsViewProvider, histogramProvider, treeMapProvider, objectDetailsProvider, objectRootsProvider, heapDumpListViewProvider, heapDumper, notifier); @@ -563,4 +569,3 @@ } } -
--- a/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsControllerTest.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-core/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/core/internal/HeapDumpDetailsControllerTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -54,6 +54,8 @@ 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.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsView; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsView; @@ -66,6 +68,7 @@ private HeapDumpDetailsView view; private HeapDumpDetailsViewProvider viewProvider; private HeapHistogramViewProvider histogramProvider; + private HeapTreeMapViewProvider treeMapProvider; private ObjectDetailsViewProvider objectDetailsProvider; private ObjectRootsViewProvider objectRootsProvider; @@ -79,6 +82,10 @@ histogramProvider = mock(HeapHistogramViewProvider.class); when(histogramProvider.createView()).thenReturn(histogramView); + HeapTreeMapView treeMapView = mock(HeapTreeMapView.class); + treeMapProvider = mock(HeapTreeMapViewProvider.class); + when(treeMapProvider.createView()).thenReturn(treeMapView); + ObjectDetailsView objectView = mock(ObjectDetailsView.class); objectDetailsProvider = mock(ObjectDetailsViewProvider.class); when(objectDetailsProvider.createView()).thenReturn(objectView); @@ -106,7 +113,7 @@ when(dump.getHistogram()).thenReturn(histogram); HeapDumpDetailsController controller = new HeapDumpDetailsController( - appService, viewProvider, histogramProvider, + appService, viewProvider, histogramProvider, treeMapProvider, objectDetailsProvider, objectRootsProvider); controller.setDump(dump); @@ -118,4 +125,3 @@ } } -
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/Activator.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/Activator.java Thu Jul 23 14:03:27 2015 +0200 @@ -42,6 +42,7 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; @@ -57,6 +58,8 @@ context.registerService(HeapDumpDetailsViewProvider.class.getName(), detailsViewProvider, null); HeapHistogramViewProvider histogramViewProvider = new SwingHeapHistogramViewProvider(); context.registerService(HeapHistogramViewProvider.class.getName(), histogramViewProvider, null); + HeapTreeMapViewProvider treeMapViewProvider = new SwingHeapTreeMapViewProvider(); + context.registerService(HeapTreeMapViewProvider.class.getName(), treeMapViewProvider, null); ObjectDetailsViewProvider objectDetailsViewProvider = new SwingObjectDetailsViewProvider(); context.registerService(ObjectDetailsViewProvider.class.getName(), objectDetailsViewProvider, null); ObjectRootsViewProvider objectRootsViewProvider = new SwingObjectRootsViewProvider();
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapDetailsSwing.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HeapDetailsSwing.java Thu Jul 23 14:03:27 2015 +0200 @@ -47,6 +47,7 @@ import com.redhat.thermostat.shared.locale.LocalizedString; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsView; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramView; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsView; public class HeapDetailsSwing extends HeapDumpDetailsView implements SwingComponent { @@ -116,5 +117,15 @@ } } + @Override + public void addSubView(final LocalizedString title, final HeapTreeMapView view) { + verifyIsSwingComponent(view); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + tabPane.insertTab(title.getContents(), null, ((SwingComponent)view).getUiComponent(), null, 0); + } + }); + } + } -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverter.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,172 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import java.util.ArrayList; +import java.util.List; + +import com.redhat.thermostat.common.utils.DescriptorConverter; +import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord; +import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; + +/** + * This class provides statics function to create a {@link TreeMapNode} tree + * from an ObjectHistrogram. + */ +public class HistogramConverter { + + /** + * The splitter regular expression to split records' className + */ + private static final String SPLIT_REG_EXP = "\\."; //escaped dot + + /** + * Key used to put into nodes the <i>number of instances</i> information + * stored in histogram records. + */ + private static final String NUMBER_OF = "Number Of Instances"; + + /** + * Call this method to create the full TreeMapNode object corresponding to + * the {@link ObjectHistogram} histogram given in input. + * @param histrogram the histogram to represent as TreeMapNode + * @return the resulting tree + */ + public static TreeMapNode convertToTreeMap(ObjectHistogram histrogram) { + TreeMapNode root = new TreeMapNode("", 0); + + List<HistogramRecord> records = new ArrayList<>(); + records.addAll(histrogram.getHistogram()); + + // build the tree from the histogram object + processRecords(records, root); + // calculates weights for inner nodes + fillWeights(root); + // collapse nodes with only one child + packTree(root); + return root; + } + + /** + * This method is responsible for building correctly the histogram + * corresponding tree. For each histogram record, a tree branch is created + * but only leaves node have a weight value. + * Furthermore, additional information are added to the nodes' map. + * + * @param records {@list} of HistogramRecord used to build the tree. + * @param root the tree's root. + */ + private static void processRecords(List<HistogramRecord> records, TreeMapNode root) { + + for (int i = 0; i < records.size(); i++) { + + TreeMapNode lastProcessed = root; + String className = records.get(i).getClassname(); + + // if className is a primitive type it is converted with its full name + className = DescriptorConverter.toJavaType(className); + + while (!className.equals("")) { + + String nodeId = className.split(SPLIT_REG_EXP)[0]; + + TreeMapNode child = lastProcessed.searchNodeByLabel(nodeId); + + if (child == null) { + child = new TreeMapNode(nodeId, 0); + lastProcessed.addChild(child); + } + + lastProcessed = child; + + className = className.substring(nodeId.length()); + if (className.startsWith(".")) { + className = className.substring(1); + } + // removes semicolon from leaves + if (className.endsWith(";")) { + className.replace(";", ""); + } + } + + // at this point lastProcessed references to a leaf + lastProcessed.setRealWeight(records.get(i).getTotalSize()); + lastProcessed.addInfo(NUMBER_OF, + Long.toString(records.get(i).getNumberOf())); + } + } + + /** + * This method calcs the real weights using a bottom-up traversal. From leaves, + * weights are passed to parent nodem which will have as weight the sum of + * the children's weights. + * + * @param node the subtree's root from which start to calc weights. + * @return the node's real weight. + */ + private static double fillWeights(TreeMapNode node) { + if (node.getChildren().size() == 0) { + return node.getRealWeight(); + } + + double sum = 0; + for (TreeMapNode child : node.getChildren()) { + sum += fillWeights(child); + } + node.setRealWeight(sum); + return node.getRealWeight(); + } + + /** + * This method allows to collapse nodes which have only one child. + * E.g. nodes labeled <i>com</i> and <i>example</i> are collapsed in the + * parent node, which will have as label <i>com.example</i>. + * @param node the subree's root from which start packing. + */ + private static void packTree(TreeMapNode node) { + if (node.getChildren().size() == 1) { + TreeMapNode child = node.getChildren().get(0); + node.setLabel(node.getLabel() + "." + child.getLabel()); + node.setChildren(child.getChildren()); + packTree(node); + } else { + for (TreeMapNode child : node.getChildren()) { + packTree(child); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/SquarifiedTreeMap.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,463 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + + +/** + * This class implements the Squarified algorithm for TreeMaps. Using it, it is + * possible to associate a rectangle to a {@link TreeMapNode} element and its + * children. + * <p> + * @see TreeMapNode + * @see TreMapBuilder + */ +public class SquarifiedTreeMap { + + /** + * List of node to represent as TreeMap. + */ + private LinkedList<TreeMapNode> elements; + + /** + * Represent the area in which draw nodes. + */ + private Rectangle2D.Double container; + + private enum DIRECTION { + LEFT_RIGHT, + TOP_BOTTOM + } + + /** + * Indicates the drawing direction. + */ + private DIRECTION drawingDir; + + /** + * The rectangles area available for drawing. + */ + private Rectangle2D.Double availableArea; + + /** + * List of the calculated rectangles. + */ + private List<TreeMapNode> squarifiedNodes; + + /** + * List of the current rectangles under processing. + */ + private List<TreeMapNode> currentRow; + + /** + * Coordinates on which to draw. + */ + private double lastX = 0; + private double lastY = 0; + + + /** + * Constructor. + * + * @param d the dimension of the total area in which draw elements. + * @param list the list of elements to draw as TreeMap. + * + * @throws a NullPointerException if one of the arguments is null. + */ + public SquarifiedTreeMap(Rectangle2D.Double bounds, List<TreeMapNode> list) { + this.elements = new LinkedList<>(); + elements.addAll(Objects.requireNonNull(list)); + this.container = Objects.requireNonNull(bounds); + } + + /** + * Invoke this method to calculate the rectangles for the TreeMap. + * + * @return a list of node having a rectangle built in percentage to the + * available area. + */ + public List<TreeMapNode> squarify() { + initializeArea(); + prepareData(elements); + List<TreeMapNode> row = new ArrayList<>(); + double w = getPrincipalSide(); + squarify(elements, row, w); + return getSquarifiedNodes(); + } + + /** + * Calculate recursively the rectangles to draw and their size. + * + * @param nodes the list of elements to draw. + * @param row the list of current rectangles to process. + * @param w the side against which to calculate the rectangles. + */ + private void squarify(LinkedList<TreeMapNode> nodes, List<TreeMapNode> row, double w) { + if (nodes.isEmpty() && row.isEmpty()) { + // work done + return; + } + if (nodes.isEmpty()) { + // no more element to process, just draw current row + finalizeRow(row); + return; + } + if (row.isEmpty()) { + // add the first element to the row and iterate the process over it + row.add(nodes.getFirst()); + nodes.removeFirst(); + squarify(nodes, row, w); + return; + } + + /* Greedy step: calculate the best aspect ratio of actual row and the + * best aspect ratio given by adding another rectangle to the row. + * If the current row can not be improved then finalize it + * else add the next element, to improve the global aspect ratio + */ + List<TreeMapNode> expandedRow = new ArrayList<TreeMapNode>(row); + expandedRow.add(nodes.getFirst()); + double actualAspectRatio = bestAspectRatio(row, w); + double expandedAspectRatio = bestAspectRatio(expandedRow, w); + + if (!willImprove(actualAspectRatio, expandedAspectRatio)) { + finalizeRow(row); + squarify(nodes, new ArrayList<TreeMapNode>(), getPrincipalSide()); + } else { + nodes.removeFirst(); + squarify(nodes, expandedRow, w); + } + } + + /** + * Return the rectangles list. + * @return a list of rectangles. + */ + public List<TreeMapNode> getSquarifiedNodes() { + return squarifiedNodes; + } + + /** + * Initialize the available area used to create the tree map + */ + private void initializeArea() { + availableArea = new Rectangle2D.Double(container.getX(), container.getY(), + container.getWidth(), container.getHeight()); + lastX = 0; + lastY = 0; + squarifiedNodes = new ArrayList<>(); + currentRow = new ArrayList<>(); + updateDirection(); + } + + /** + * Recalculate the drawing direction. + */ + private void updateDirection() { + drawingDir = availableArea.getWidth() > availableArea.getHeight() ? + DIRECTION.TOP_BOTTOM : DIRECTION.LEFT_RIGHT; + } + + + /** + * Invert the drawing direction. + */ + private void invertDirection() { + drawingDir = drawingDir == DIRECTION.LEFT_RIGHT ? + DIRECTION.TOP_BOTTOM : DIRECTION.LEFT_RIGHT; + } + + /** + * Keep the current list of nodes which produced the best aspect ratio + * in the available area, draw their respective rectangles and reinitialize + * the current row to draw. + * <p> + * @param nodes the list of numbers which represent the rectangles' area. + * @return the number of Rectangles created. + */ + private void finalizeRow(List<TreeMapNode> nodes) { + if (nodes == null || nodes.isEmpty()) { + return; + } + // get the total weight of nodes in order to calculate their percentages + double sum = getSum(nodes); + // greedy optimization step: get the best aspect ratio for nodes drawn + // on the longer and on the smaller side, to evaluate the best. + double actualAR = bestAspectRatio(nodes, getPrincipalSide()); + double alternativeAR = bestAspectRatio(nodes, getSecondarySide()); + + if (willImprove(actualAR, alternativeAR)) { + invertDirection(); + } + + for (TreeMapNode node: nodes) { + // assign a rectangle calculated as percentage of the total weight + Rectangle2D.Double r = createRectangle(sum, node.getWeight()); + node.setRectangle(r); + + // recalculate coordinates to draw next rectangle + updateXY(r); + + // add the node to the current list of rectangles in processing + currentRow.add(node); + } + // recalculate the area in which new rectangles will be drawn and + // reinitialize the current list of node to represent. + reduceAvailableArea(); + newRow(); + } + + + /** + * Create a rectangle having area = @param area in percentage of @param sum. + * <p> + * For example: assume @param area = 4 and @param sum = 12 and the + * drawing direction is top to bottom. <br> + * <p> + * __ __ __ __ + * | | | + * |__ __| | + * |__ __ __ __| + * + * <br>the internal rectangle will be calculated as follow:<br> + * {@code height = (4 / 9) * 3} <--note that the principal side for actual + * drawing direction is 3. + * <br>Now it is possible to calculate the width:<br> + * {@code width = 4 / 1.3} <-- note this is the height value + * + * <p> + * @param sum the total size of all rectangles in the actual row. + * @param area this Rectangle's area. + * @return the Rectangle which correctly fill the available area. + */ + private Rectangle2D.Double createRectangle(Double sum, Double area) { + double side = getPrincipalSide(); + double w = 0; + double h = 0; + + //don't want division by 0 + if (validate(area) == 0 || validate(sum) == 0 || validate(side) == 0) { + return new Rectangle2D.Double(lastX, lastY, 0, 0); + } + + // calculate the rectangle's principal side relatively to the container + // rectangle's principal side. + if (drawingDir == DIRECTION.TOP_BOTTOM) { + h = (area / sum) * side; + w = area / h; + } else { + w = (area / sum) * side; + h = area / w; + } + return new Rectangle2D.Double(lastX, lastY, w, h); + } + + /** + * Check if a double value is defined as Not a Number and sets it to 0. + * @param d the value to check. + * @return the checked value: 0 if the given number is NaN, else the number + * itself. + */ + private double validate(double d) { + if (d == Double.NaN) { + d = 0; + } + return d; + } + + /** + * Check in which direction the rectangles have to be drawn. + * @return the side on which rectangles will be created. + */ + private double getPrincipalSide() { + return drawingDir == DIRECTION.LEFT_RIGHT ? + availableArea.getWidth() : availableArea.getHeight(); + } + + /** + * + * @return the secondary available area's side. + */ + private double getSecondarySide() { + return drawingDir == DIRECTION.LEFT_RIGHT ? + availableArea.getHeight() : availableArea.getWidth(); + } + + /** + * Sum the elements in the list. + * @param nodes the list which contains elements to sum. + * @return the sum of the elements. + */ + private double getSum(List<TreeMapNode> nodes) { + int sum = 0; + for (TreeMapNode n : nodes) { + sum += n.getWeight(); + } + return sum; + } + + /** + * Recalculate the origin to draw next rectangles. + * @param r the rectangle from which recalculate the origin. + */ + private void updateXY(Rectangle2D.Double r) { + if (drawingDir == DIRECTION.LEFT_RIGHT) { + //lastY doesn't change + lastX += r.width; + } else { + //lastX doesn't change + lastY += r.height; + } + } + + /** + * Initialize the origin at the rectangle's origin. + * @param r the rectangle used as origin source. + */ + private void initializeXY(Rectangle2D.Double r) { + lastX = r.x; + lastY = r.y; + } + + /** + * Reduce the size of the available rectangle. Use it after the current + * row's closure. + */ + private void reduceAvailableArea() { + if (drawingDir == DIRECTION.LEFT_RIGHT) { + // all rectangles inside the row have the same height + availableArea.height -= currentRow.get(0).getRectangle().height; + availableArea.y = lastY + currentRow.get(0).getRectangle().height; + availableArea.x = currentRow.get(0).getRectangle().x; + } else { + // all rectangles inside the row have the same width + availableArea.width -= currentRow.get(0).getRectangle().width; + availableArea.x = lastX + currentRow.get(0).getRectangle().width; + availableArea.y = currentRow.get(0).getRectangle().y; + } + updateDirection(); + initializeXY(availableArea); + } + + /** + * Close the current row and initialize a new one. + */ + private void newRow() { + squarifiedNodes.addAll(currentRow); + currentRow = new ArrayList<>(); + } + + /** + * Calculate the aspect ratio for all the rectangles in the list and + * return the max of them. + * @param row the list of rectangles. + * @param side the side against which to calculate the the aspect ratio. + * @return the max aspect ratio calculated for the row. + */ + private double bestAspectRatio(List<TreeMapNode> row, double side) { + if (row == null || row.isEmpty()) { + return Double.MAX_VALUE; + } + double sum = getSum(row); + double max = 0; + // calculate the aspect ratio against the main side, and also its inverse. + // this is because aspect ratio of rectangle 6x4 can be calculated as + // 6/4 but also 4/6. Here the aspect ratio has been calculated as + // indicated in the Squarified algorithm. + for (TreeMapNode node : row) { + double m1 = (Math.pow(side, 2) * node.getWeight()) / Math.pow(sum, 2); + double m2 = Math.pow(sum, 2) / (Math.pow(side, 2) * node.getWeight()); + double m = Math.max(m1, m2); + + if (m > max) { + max = m; + } + } + return max; + } + + + /** + * Prepare the elements in the list, sorting them and transforming them + * proportionally the given dimension. + * @param dim the dimension in which rectangles will be drawn. + * @param elements the list of elements to draw. + * @return the list sorted and proportioned to the dimension. + */ + private void prepareData(List<TreeMapNode> elements) { + if (elements == null || elements.isEmpty()) { + return; + } + TreeMapNode.sort(elements); + double totArea = availableArea.width * availableArea.height; + double sum = getSum(elements); + + // recalculate weights in percentage of their sum + for (TreeMapNode node : elements) { + int w = (int) Math.round((node.getWeight()/sum) * totArea); + node.setWeight(w); + } + } + + /** + * This method check which from the values in input, that represent + * rectangles' aspect ratio, produces more approximatively a square. + * It checks if one of the aspect ratio values gives a value nearest to 1 + * against the other, which means that width and height are similar. + * @param actualAR the actual aspect ratio + * @param expandedAR the aspect ratio to evaluate + * @return false if the actual aspect ratio is better than the new one, + * else true. + */ + private boolean willImprove(double actualAR, double expandedAR) { + if (actualAR == 0) { + return true; + } + if (expandedAR == 0) { + return false; + } + // check which value is closer to 1, the square's aspect ratio + double v1 = Math.abs(actualAR - 1); + double v2 = Math.abs(expandedAR - 1); + return v1 > v2; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/SwingHeapTreeMapViewProvider.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; + +public class SwingHeapTreeMapViewProvider implements HeapTreeMapViewProvider { + + @Override + public HeapTreeMapView createView() { + return new TreeMapPanel(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponent.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,676 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.font.FontRenderContext; +import java.awt.geom.Rectangle2D; +import java.util.Objects; +import java.util.Stack; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; +import javax.swing.border.EtchedBorder; +import javax.swing.border.LineBorder; + +import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; + +/** + * This class allows to represent a hierarchical data structure as a TreeMap. + * It extends {@link JComponent} so it can be used like usual Swing objects. + * + */ +public class TreeMapComponent extends JComponent { + + private static final long serialVersionUID = 1L; + + /** + * TreeMap's graphic root. + */ + Comp mainComp; + + /** + * Label Object to clone for faster initialization. + */ + private Label cachedLabel; + + /** + * The tree to render as TreeMap. + */ + TreeMapNode tree; + + /** + * Horizontal and vertical padding for nested component. + */ + private final int X_PADDING = TreeProcessor.X_PADDING; + private final int Y_PADDING = TreeProcessor.Y_PADDING; + + /** + * Min size for rectangles' sides. rectangles having one or both sides less + * than MIN_SIDE pixels will be not drawn. + */ + private final int MIN_SIDE = 1; + + /** + * Default value for a TreeMap component. + */ + private static final String TITLE = ""; + + /** + * TreeMap UI Constraint. + */ + public static final int SIMPLE = 0; + public static final int FLAT = 1; + public static final int ETCHED_LOWERED = 2; + public static final int ETCHED_RAISED = 3; + + /** + * Stores the chosen UI mode. + */ + private int borderStyle = ETCHED_LOWERED; + + /** + * The components' border + */ + private Border defaultBorder; + + /** + * Font and size for this component's label. + */ + private int FONT_SIZE = 8; + private Font FONT = (Font) UIManager.get("thermostat-default-font"); + + + /** + * Variable in which store last resize dimension. + */ + private Dimension lastDim; + + /** + * Variable in which store last resize event call time. + */ + private static long lastCall = 0; + + /** + * Wait time in millisec to resize the TreeMap. + */ + private final int MIN_DRAGGING_TIME = 60; + + + /** + * Stack containing the zoom calls on the TreeMap. + */ + private Stack<TreeMapNode> zoomStack; + + /** + * This object stores the last clicked rectangle in the TreeMap, in order to + * repaint it when another rectangle will be selected. + */ + private static Comp lastClicked; + + /** + * Constructor which creates a TreeMapComponent by an histogram object. + * @param histogram the histogram to represent as tree map. + */ + public TreeMapComponent(ObjectHistogram histogram) { + this(HistogramConverter.convertToTreeMap(histogram), new Dimension()); + } + + /** + * Constructor. It draw a TreeMap of the given tree in according to the + * {@Dimension} object in input. + * + * @param tree the tree to represent as TreeMap. + * @param d the dimension the TreeMap will fulfill. + * + * @throws NullPointerException if one of the parameters is null + */ + public TreeMapComponent(TreeMapNode tree, Dimension d) { + super(); + Objects.requireNonNull(tree); + Objects.requireNonNull(d); + this.tree = tree; + lastDim = getSize(); + this.zoomStack = new Stack<>(); + this.zoomStack.push(this.tree); + + // assign a rectangle to the tree's root in order to process the tree. + Rectangle2D.Double area = new Rectangle2D.Double(0, 0, d.width, d.height); + + // calculate rectangles of tree's subtrees + TreeProcessor.processTreeMap(tree, area); + + drawTreeMap(tree); + + addResizeListener(this); + repaint(); + } + + /** + * This method returns the root of the tree showed ad TreeMap. + * @return the TreeMap's root node. + */ + public TreeMapNode getTreeMapRoot() { + return this.tree; + } + + /** + * This method is responsible for the TreeMap drawing process. + * @param tree the tree to represent as TreeMap. + */ + private void drawTreeMap(TreeMapNode tree) { + // draw root + drawMainComp(tree); + setBorderStyle(borderStyle); + + // draw subtrees nested in children + for (TreeMapNode child : tree.getChildren()) { + drawSubTree(child, mainComp); + } + // setup this component + prepareGUI(); + } + + /** + * This method prepares the layout for this component. + */ + private void prepareGUI() { + setLayout(new BorderLayout()); + setBounds(mainComp.getBounds()); + setBorder(null); + add(mainComp, BorderLayout.CENTER); + revalidate(); + repaint(); + } + + /** + * This method prepares the main component which is the parent object where + * sub components will be placed. + * @param tree the tree's root used to prepare the main component. + */ + private void drawMainComp(TreeMapNode tree) { + mainComp = new Comp(); + mainComp.setLayout(null); + mainComp.setBounds(tree.getRectangle().getBounds()); + mainComp.setNode(tree); + cachedLabel = new Label(TITLE + tree.getLabel()); + addLabelIfPossible(TITLE + tree.getLabel(), mainComp); + } + + /** + * Create a TreeMapComp from the given node. The component is not + * instantiated as a new component but is cloned from an existing one, in + * order to improve performance. + * + * @param node the node to represent as a component. + * @return the component representing the given node. + */ + private Comp renderizeNode(TreeMapNode node) { + // if the rectangle's node is too small to be viewed, don't draw it. + if (node.getRectangle().getWidth() <= MIN_SIDE || + node.getRectangle().getHeight() <= MIN_SIDE) { + return null; + } + + Comp comp = (Comp) mainComp.clone(); + comp.setBounds(node.getRectangle().getBounds()); + + return comp; + } + + /** + * This method checks if the given container has enough space to instantiate + * a Label in it. If yes, a Label is cloned from an existing one, in order + * to improve performance. If not, it exits. + * + * @param s the label text. + * @param cont the parent container which will contain the new label. + * @return the cloned label. + */ + private Label addLabelIfPossible(String s, Container cont) { + if (s == null || s.equals("")) { + return null; + } + int componentW = cont.getSize().width; + int componentH = cont.getSize().height; + // get the rectangle associated to the area needed for the label's text + Rectangle fontArea = FONT.getStringBounds(s, + new FontRenderContext(FONT.getTransform(), + false, false)).getBounds(); + + // if the container is greater than the label, add it to the container + if (componentW > fontArea.width && componentH > fontArea.height) { + Label label = (Label) cachedLabel.clone(); + label.setBounds(5, 1, cont.getWidth(), fontArea.height); + label.setText(s); + cont.add(label); + return label; + } + return null; + } + + /** + * Draw the whole {@param tree}'s subtree inside the given component. + * @param tree the tree to draw + * @param parent the component in which build the tree. + */ + private void drawSubTree(TreeMapNode tree, JComponent parent) { + Comp comp = addCompIfPossible(tree, parent); + + // if space was enough to draw a component, try to draw its children + if (comp != null) { + comp.setNode(tree); + for (TreeMapNode child : tree.getChildren()) { + drawSubTree(child, comp); + } + } + } + + /** + * Create and add to the {@link Container} given in input a + * {@link ComponentResized} listener. + * @param c the container in to assign the listener. + */ + private void addResizeListener(final Container container) { + ComponentAdapter adapter = new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + // if enough time is passed from the last call, redraw the TreeMap + if (canResize(MIN_DRAGGING_TIME)) { + Dimension newDim = container.getSize(); + + if (isChangedSize(newDim)) { + redrawTreeMap(); + } + } + } + }; + container.addComponentListener(adapter); + } + + /** + * This method checks if the given container has enough space to instantiate + * a TreeMapComp object in it. If yes, a Label is cloned from an existing + * one, in order to improve performance. If not, it exits. + * + * @param node the node to draw and add to the given container. + * @param cont the parent container which will contain the new component. + * @return true if the component was created and added, else false. + */ + private Comp addCompIfPossible(TreeMapNode node, Container cont) { + Rectangle2D rect = node.getRectangle(); + // if the ndoe's rectangle is smaller than the container, it is added + if (cont.getWidth() > rect.getWidth() + X_PADDING && + cont.getHeight() > rect.getHeight() + Y_PADDING) { + + Comp toReturn = renderizeNode(node); + if (toReturn == null) { + return null; + } + addLabelIfPossible(TITLE + node.getLabel(), toReturn); + + // leaves some space from the parent's origin location + Point loc = toReturn.getLocation(); + loc.x += X_PADDING; + loc.y += Y_PADDING; + toReturn.setLocation(loc); + + cont.add(toReturn); + return toReturn; + } + return null; + } + + + /** + * This method recalculates and redraws the TreeMap in according to the size + * of this component and the actual {@link TreeMapNode} object. + */ + private void redrawTreeMap() { + Rectangle2D.Double newArea = tree.getRectangle(); + // give to the root node the size of this object so it can be recalculated + newArea.width = getSize().width; + newArea.height = getSize().height; + + // recalculate the tree + TreeProcessor.processTreeMap(tree, newArea); + + removeAll(); + drawTreeMap(tree); + } + + /** + * Zoom the TreeMap on the given node. + * @param node the new TreeMap's root. + */ + public void zoomIn(TreeMapNode node) { + if (node != null && node != this.tree && !zoomStack.contains(node)) { + zoomStack.push(node); + tree = node; + redrawTreeMap(); + } + } + + /** + * Zoom out the view to the last zoom level, until the original root is + * reached. + */ + public void zoomOut() { + if (zoomStack.size() > 1) { + zoomStack.pop(); + tree = zoomStack.peek(); + redrawTreeMap(); + } + } + + /** + * Zoom out the view directly to the original root. + */ + public void zoomFull() { + if (zoomStack.size() > 1) { + clearZoomCallsStack(); + tree = zoomStack.peek(); + redrawTreeMap(); + } + } + + /** + * Returns the list of zoom operation calls. + * @return the stack that holds the zoom calls. + */ + public Stack<TreeMapNode> getZoomCallsStack() { + return zoomStack; + } + + /** + * Clear the zoom calls of this object leaving the stack with just the root. + */ + public void clearZoomCallsStack() { + while (zoomStack.size() > 1) { + zoomStack.pop(); + } + } + + /** + * check if last resize operation was called too closer to this + * one. If so, ignore it: the container is being dragged. + * + * @return true if this method is invoked at distance of + * MIN_DRAGGING_TIME millisec, else false. + */ + private boolean canResize(int millisec) { + long time = System.currentTimeMillis(); + if (time - lastCall >= millisec) { + lastCall = time; + return true; + } + return false; + } + + + /** + * Check if the dimension given in input differs from the last one stored + * by 2. + * @param newDim the new dimension to check. + * @return true if the dimensions are different, else false. + */ + private boolean isChangedSize(Dimension newDim) { + int minResizeDim = 2; + int deltaX = Math.abs(newDim.width - lastDim.width); + int deltaY = Math.abs(newDim.height - lastDim.height); + + if (deltaX > minResizeDim || deltaY > minResizeDim) { + lastDim = newDim; + return true; + } + return false; + } + + /** + * Switch the component's visualization mode to the one given in input. + * Use static constraints to set correctly a visualization mode. + * @param constraint the UI visualization mode to set. + */ + public void setBorderStyle(int UIMode) { + this.borderStyle = UIMode; + switch (borderStyle) { + case 1 : { + defaultBorder = new EmptyBorder(0, 0, 0, 0); + break; + } + case 2 : { + defaultBorder = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED, Color.white, Color.darkGray); + break; + } + case 3 : { + defaultBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.white, Color.darkGray); + break; + } + default : { + defaultBorder = new LineBorder(Color.black, 1); + break; + } + } + applyBorderToSubtree(mainComp); + } + + /** + * Traverse recursively the tree from the given component applying to it + * the default border. + * @param comp the subtree's root from which apply the border style. + */ + private void applyBorderToSubtree(Comp comp) { + comp.setBorder(defaultBorder); + Component[] children = comp.getComponents(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof Comp) { + applyBorderToSubtree((Comp) children[i]); + } + } + } + + /** + * Return the last clicked component inside the TreeMap. + * @return the last clicked {@Comp} object. + */ + public Comp getClickedComponent() { + return lastClicked; + } + + /** + * This class provides an extension of {@link JLabel} which main + * characteristic is to implement the {@link Cloneable} interface in order + * to make his creation faster then JLabel class. + */ + class Label extends JLabel implements Cloneable { + private static final long serialVersionUID = 1L; + + public Label(String s) { + super(s); + setFont(FONT); + setBounds(0, 0, getPreferredSize().width, FONT_SIZE); + } + + @Override + protected JLabel clone() { + Label clone = new Label(""); + clone.setFont(getFont()); + clone.setText(getText()); + clone.setBackground(getBackground()); + clone.setBounds(getBounds()); + clone.setBorder(getBorder()); + return clone; + } + } + + /** + * This class provides an extension of {@link JComponent} which main + * characteristic is to implement {@link Cloneable} interface in order to + * make his creation faster. <br> + * It also provides some action listeners that allow to select it, performing + * zoom operations for the treemap. + */ + class Comp extends JComponent implements Cloneable { + + private static final long serialVersionUID = 1L; + + /** + * The node represented by this component. + */ + private TreeMapNode node; + + /** + * The background color. It depends by the node's depth. + */ + private Color color; + + /** + * Reference to this. + */ + private Comp thisComponent; + + public Comp() { + super(); + thisComponent = this; + addClickListener(this); + } + + @Override + public Comp clone() { + Comp clone = new Comp(); + clone.setBounds(getBounds()); + clone.setBorder(getBorder()); + clone.setLayout(getLayout()); + clone.setOpaque(true); + return clone; + } + + public void setNode(TreeMapNode node) { + this.node = node; + this.color = node.getColor(); + ValueFormatter f = new ValueFormatter(this.node.getRealWeight()); + this.setToolTipText(this.node.getLabel() + " - " + f.format()); + } + + public TreeMapNode getNode() { + return this.node; + } + + public Color getColor() { + return this.color; + } + + public void setColor(Color c) { + this.color = c; + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + if (this.color != null) { + g.setColor(color); + g.fillRect(0, 0, getWidth(), getHeight()); + } + } + + /** + * Add a mouse listener to this component. It allows to select it and + * zoom it. + * @param component the component which will have the mouse listener. + */ + private void addClickListener(final JComponent component) { + MouseListener click = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + // one left click select the rectangle + if (SwingUtilities.isLeftMouseButton(e)) { + selectComp(); + } + // two left click: zoom-in + // two right click: zoom-out + // two middle click: zoom full + if (e.getClickCount() == 2) { + if (SwingUtilities.isLeftMouseButton(e)) { + zoomIn(getNode()); + } else if (SwingUtilities.isRightMouseButton(e)) { + zoomOut(); + } else { + zoomFull(); + } + } + } + }; + component.addMouseListener(click); + } + + /** + * This method gives a darker color to this component and restore the + * original color to the last selected component. + */ + private void selectComp() { + if (lastClicked != null) { + lastClicked.setColor(lastClicked.getColor().brighter()); + lastClicked.repaint(); + } + lastClicked = thisComponent; + setColor(getColor().darker()); + repaint(); + } + } +} + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNode.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,450 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class provide a tree recursive implementation used in + * {@link SquarifiedTreeMap}. It contains a reference to the parent node and to + * a node list, which represent the node's children. It is also + * possible to store generic information inside the node using a {@link Map} + * object. Furthermore, the main property of this class is the chance to have a + * weight for the node and associate to it a {@link Rectangle2D.Double} object. + * + * <p>When an instance of this class is created, it will automatically be + * assigned a unique id. + * + * <p>By default, this class' comparator is based on the nodes' weight. + * + * <p>A static Quick Sort algorithm implementation is also provided by this + * class. + * + * @see Rectangle2D.Double + */ +public class TreeMapNode { + + /** + * Counter for assign unique id to nodes. + */ + private static int idCounter = 0; + + /** + * The rectangle which will graphically represent this node. + */ + private Rectangle2D.Double rectangle; + + /** + * This node's id. + */ + private int id; + + /** + * A Map in which store information for this node. + */ + private Map<String, String> info; + + /** + * Reference to the parent. + */ + private TreeMapNode parent; + + /** + * Reference to children. + */ + private List<TreeMapNode> children; + + /** + * The node's weight. + */ + private double weight; + + /** + * The node's label. It can be the same of another node. + */ + private String label; + + /** + * The node's weight which has been set inside the constructor. Note that + * this value can be assigned just one time, using the constructor. All + * operations which refers to node's weight work on the weight field, that + * is used to make calcs. + */ + private double realWeight; + + /** + * This flag indicates if weight value can be a non positive number. + */ + static boolean allowNonPositiveWeight = false; + + /** + * The color of this node. + */ + private Color color; + + /** + * Colors available on which iterate + */ + static final Color[] colors = { + Color.decode("#FACED2"), // red + Color.decode("#B9D6FF"), // blue + Color.decode("#E5E5E5"), // grey + Color.decode("#FFE7C7"), // orange + Color.decode("#ABEBEE"), // aqua + Color.decode("#E4D1FC"), // purple + Color.decode("#FFFFFF"), // white + Color.decode("#CDF9D4") // green + }; + + public final Color START_COLOR = colors[0]; + + /** + * + * Constructor that allow to set the nodes' real weight. Others fields are + * initialized to their default value. + * It automatically set the node's id. + * + * <p> + * @param realWeight the nodes real weight, which will be not affected + * during node processing. + * + */ + public TreeMapNode(double realWeight) { + this("", realWeight); + } + + /** + * + * Constructor that allow to set the nodes' real weight and the label. + * Others fields are initialized to their default value. + * It automatically set the node's id. + * + * <p> + * @param label the node's label. + * @param realWeight the nodes real weight, which will be not affected + * during node processing. + * + */ + public TreeMapNode(String label, double realWeight) { + this.id = idCounter++; + this.label = label; + this.parent = null; + this.children = new ArrayList<TreeMapNode>(); + this.rectangle = new Rectangle2D.Double(); + this.info = new HashMap<String, String>(); + this.weight = realWeight; + this.realWeight = realWeight; + } + + /** + * Return the id of this object. + * @return the id automatically assigned at this object initialization. + */ + public int getId() { + return this.id; + } + + /** + * Set this node's label. + * @param newLabel the new label to set. + */ + public void setLabel(String newLabel) { + this.label = newLabel; + } + + /** + * Return the label of this object. + * @return the label assigned at instantiation time to this object. + */ + public String getLabel() { + return this.label; + } + + /** + * Return the reference to the node parent of this object. + * @return the parent of this node. It can be null. + */ + public TreeMapNode getParent() { + return this.parent; + } + + /** + * Set as parent of this object the node given in input. + * @param parent the new parent of this object. No checks are made for null + * value. + */ + public void setParent(TreeMapNode parent) { + this.parent = parent; + } + + /** + * Return the list of nodes representing this node's children. + * @return a list of {@link TreeMapNode} objects. + */ + public List<TreeMapNode> getChildren() { + return this.children; + } + + /** + * Set as children list of this object the list given in input. + * @param children the new list of children for this node. + */ + public void setChildren(List<TreeMapNode> children) { + this.children = children; + for (TreeMapNode child : this.children) { + child.setParent(this); + } + } + + /** + * Return the {@link Map} object containing all information of this node. + * @return a {@link Map} object. + */ + public Map<String, String> getInfo() { + return this.info; + } + + /** + * Store the given information into this object. + * @param key the key searching value for the information to store. + * @param value the information to store into this object. + * @return the old value for the given key. + */ + public String addInfo(String key, String value) { + return this.info.put(key, value); + } + + /** + * Return the information stored in this object, corresponding to the key + * given in input. + * @param key the key value for the search information. + * @return the corresponding value for the given key. + */ + public String getInfo(String key) { + return this.info.get(key); + } + + /** + * Add the object given in input to the children list of this object. It + * also add this object as its parent. + * @param child the new child to add at this object. + */ + public void addChild(TreeMapNode child) { + if (child != null) { + this.children.add(child); + child.setParent(this); + } + } + + @Override + /** + * Return a {@link String} representing this object. + */ + public String toString() { + return getClass().getSimpleName() + " [" + "label = " + getLabel() + + "; weight =" + getRealWeight() + + "; rectangle=" + rectangle.getBounds() + "]"; + } + + + /** + * Search into the tree the node with id = key. + * @param key the id of the node to search. + * @return the node of exists, else null. + */ + public TreeMapNode searchNodeByLabel(String key) { + if (this.getLabel().equals(key)) { + return this; + } + + TreeMapNode result = null; + for (TreeMapNode child : getChildren()) { + result = child.searchNodeByLabel(key) ; + if (result != null) { + return result; + } + } + return result; + } + + /** + * Return the weight of this object. In case of allowNonPositiveWeight is + * set to false and the weight is 0, less than 0 or not a number + * ({@link Double.Nan}), this method returns a value that can be transformed + * by external objects, so if you need the real weight you have to + * invoke getrealWeight(). + * + * @return the node's weight. + */ + public double getWeight() { + if ((weight <= 0 || weight == Double.NaN) && !allowNonPositiveWeight) { + return realWeight; + } + return this.weight; + } + + /** + * Use this method to retrieve the real weight assigned to this node. + * @return the weight corresponding to this node. + */ + public double getRealWeight() { + return this.realWeight; + } + + + /** + * Use this method to set the real weight of this node. + */ + public void setRealWeight(double w) { + this.realWeight = w; + } + + /** + * Set the weight of this object. If a negative value is given, it is set + * automatically to 0. + * @param weight the new weight for this object. + */ + public void setWeight(int w) { + this.weight = w < 0 && !allowNonPositiveWeight ? 0 : w; + } + + + /** + * Return the rectangle representing this object. + * @return a {@link Rectangle2D.Double} object. + */ + public Rectangle2D.Double getRectangle() { + if (this.rectangle == null) { + throw new RuntimeException(); + } + return this.rectangle; + } + + /** + * Set a new rectangle for this object. + * @param rectangle the new rectangle that represent this node. + */ + public void setRectangle(Rectangle2D.Double rectangle) { + this.rectangle = rectangle; + } + + /** + * + * @return true if non positive value can be used as weight, else false. + */ + public static boolean isAllowNonPositiveWeight() { + return allowNonPositiveWeight; + } + + /** + * Set this value to false and nodes will be not able to manage non positive + * values for weight field, otherwise set to true. + * @param allowed the flag value for managing non positive values as weight + */ + public static void setAllowNonPositiveWeight(boolean allowed) { + allowNonPositiveWeight = allowed; + } + + /** + * This method assess if the rectangle associated to this node is drawable, + * which means that its sides are greater than 1. + * @return true if the rectangle associated to this node is drawable, + * else false. + */ + public boolean isDrawable() { + if (rectangle.width >= 1 && rectangle.height >= 1) { + return true; + } + return false; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + /** + * Returns this node's next color. + * @return the color which came after this node's color in the color list. + * If this node has no color assigned then the START_COLOR is returned. + */ + public Color getNextColor() { + if (this.color != null) { + for (int i = 0; i < colors.length; i++) { + if (this.color.equals(colors[i])) { + return colors[(i + 1) % colors.length]; + } + } + } + return START_COLOR; + } + + + public int getDepth() { + if (this.parent == null) { + return 0; + } else { + return 1 + parent.getDepth(); + } + } + + /** + * This method sorts the given list in <b>descending<b> way. + * + * @param nodes the list of {@link TreeMapNode} to sort. + */ + public static void sort(List<TreeMapNode> nodes) { + Comparator<TreeMapNode> c = new Comparator<TreeMapNode>() { + @Override + public int compare(TreeMapNode o1, TreeMapNode o2) { + // inverting the result to descending sort the list + return -(Double.compare(o1.getWeight(), o2.getWeight())); + } + }; + Collections.sort(nodes, c); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapPanel.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import java.awt.Component; + +import javax.swing.BoxLayout; +import javax.swing.JPanel; + +import com.redhat.thermostat.client.swing.SwingComponent; +import com.redhat.thermostat.shared.locale.Translate; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapView; +import com.redhat.thermostat.vm.heap.analysis.client.locale.LocaleResources; +import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; + +@SuppressWarnings("serial") +public class TreeMapPanel extends HeapTreeMapView implements SwingComponent { + + private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer(); + + private final JPanel panel; + + private TreeMapComponent treeMap; + + public TreeMapPanel() { + panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS)); + } + + @Override + public void display(ObjectHistogram histogram) { + treeMap = new TreeMapComponent(histogram); + panel.add(treeMap); + } + + @Override + public Component getUiComponent() { + return panel; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeProcessor.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,111 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.util.Objects; + + +public class TreeProcessor { + + /** + * Padding between the main component and its sub component. + */ + public static final int X_PADDING = 15; + public static final int Y_PADDING = 20; + + /** + * This method process recursively the tree nested in the node element + * passed as argument in the constructor calculating the children TreeMap + * for each node also applying coloring. + * @return the updated tree, where nodes have additional information like + * {@link Rectangle2D>Float} instance and a color. + */ + public static TreeMapNode processTreeMap(TreeMapNode tree, Rectangle2D.Double area) { + Objects.requireNonNull(tree); + Objects.requireNonNull(area); + tree.setRectangle(area); + if (tree.getColor() == null) { + tree.setColor(tree.START_COLOR); + } + + process(tree); + return tree; + } + + /** + * This method is used to effectively process the whole tree structure. It + * uses a {@link SquarifiedTreeMap} object to calculate a TreeMap for each + * node who has children. + * @param node the subtree's root to process + */ + private static void process(TreeMapNode node) { + + SquarifiedTreeMap algorithm = new SquarifiedTreeMap(getSubArea(node.getRectangle()), node.getChildren()); + node.setChildren(algorithm.squarify()); + + Color c = node.getNextColor(); + + for (TreeMapNode child : node.getChildren()) { + //children will have all the same color, which is the parent's next one + if (child.getColor() == null) { + child.setColor(c); + } + // if squarified rectangles have drawable sides then continue to + // process, else don't process the subtree having as root a + // non drawable rectangle. + if (child.isDrawable()) { + process(child); + } + } + } + + /** + * Calculate space and coordinates in which children's rectangle will be + * drawn, from the main component. + * @return the rectangle representing the new available area. + */ + private static Rectangle2D.Double getSubArea(Rectangle2D.Double area) { + Rectangle2D.Double subArea = new Rectangle2D.Double(); + subArea.setRect(area); + + subArea.width = Math.max(0, (subArea.width - 2 * X_PADDING)); + subArea.height = Math.max(0, (subArea.height - 1.5 * Y_PADDING)); + return subArea; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ValueFormatter.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +public class ValueFormatter { + + private double value; + + public ValueFormatter(double val) { + this.value = val; + } + + /** + * This method returns the node value calculating it in bytes, KB or MB. + * + * i.e. if node's weight = 200 it returns: "200 bytes" <br> + * if weight = 20152: "20.15 KB" <br> + * if weight = 2015248: "2.01 MB" <br> + * + * Note that float values are approximated to the second decimal digit. + */ + public String format() { + int KB = 1000; + int MB = 1000000; + String unit = "Bytes"; + + if (value >= KB && value < MB) { + value /= KB; + unit = "KBytes"; + } else if (value >= MB) { + value /= MB; + unit = "MBytes"; + } + // show 2 decimal digits + String formattedValue = String.format("%.2f", value); + return formattedValue + " " + unit; + } +}
--- a/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ActivatorTest.java Tue Jul 07 12:23:45 2015 -0400 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ActivatorTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -45,15 +45,10 @@ import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapDumpListViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapHistogramViewProvider; +import com.redhat.thermostat.vm.heap.analysis.client.core.HeapTreeMapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.HeapViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectDetailsViewProvider; import com.redhat.thermostat.vm.heap.analysis.client.core.ObjectRootsViewProvider; -import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.Activator; -import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.SwingHeapDumpDetailsViewProvider; -import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.SwingHeapHistogramViewProvider; -import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.SwingHeapViewProvider; -import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.SwingObjectDetailsViewProvider; -import com.redhat.thermostat.vm.heap.analysis.client.swing.internal.SwingObjectRootsViewProvider; public class ActivatorTest { @@ -65,11 +60,12 @@ assertTrue(ctx.isServiceRegistered(HeapViewProvider.class.getName(), SwingHeapViewProvider.class)); assertTrue(ctx.isServiceRegistered(HeapDumpDetailsViewProvider.class.getName(), SwingHeapDumpDetailsViewProvider.class)); assertTrue(ctx.isServiceRegistered(HeapHistogramViewProvider.class.getName(), SwingHeapHistogramViewProvider.class)); + assertTrue(ctx.isServiceRegistered(HeapTreeMapViewProvider.class.getName(), SwingHeapTreeMapViewProvider.class)); assertTrue(ctx.isServiceRegistered(ObjectDetailsViewProvider.class.getName(), SwingObjectDetailsViewProvider.class)); assertTrue(ctx.isServiceRegistered(ObjectRootsViewProvider.class.getName(), SwingObjectRootsViewProvider.class)); assertTrue(ctx.isServiceRegistered(HeapDumpListViewProvider.class.getName(), SwingHeapDumpListViewProvider.class)); - assertEquals(6, ctx.getAllServices().size()); + assertEquals(7, ctx.getAllServices().size()); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/HistogramConverterTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,155 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.redhat.thermostat.vm.heap.analysis.common.HistogramRecord; +import com.redhat.thermostat.vm.heap.analysis.common.ObjectHistogram; +import com.sun.tools.hat.internal.model.JavaClass; +import com.sun.tools.hat.internal.model.JavaHeapObject; + + +public class HistogramConverterTest { + + List<HistogramRecord> histrogramRecords; + ObjectHistogram histrogram; + + @Before + public void setUp() throws Exception { + + + /* + * This is the classes structure used for the test and built using histogram + * + * ________com_______ java + * / \ | + * __example1__ example2 lang + * / \ | | + * package1 package2 package1 Object + * / \ | | + * Class1 Class2 Class3 Class4 + * + * + * + * + * Expected tree after conversion: + * + * ________________________root_______________ + * / \ + * ________com_______ java.lang.Object + * / \ + * __example1__ example2.package1.Class4 + * / \ + * package1 package2.Class3 + * / \ + * Class1 Class2 + * + */ + + final String[] classes = { + "com.example1.package1.Class1", + "com.example1.package1.Class2", + "com.example1.package2.Class3", + "com.example2.package1.Class4", + "java.lang.Object" + }; + + histrogram = new ObjectHistogram(); + + for (int i = 0; i < classes.length; i++) { + final String className = classes[i]; + + histrogram.addThing(new JavaHeapObject() { + public int getSize() { + return 0; + } + + public long getId() { + return 0; + } + + public JavaClass getClazz() { + return new JavaClass(className, 0, 0, 0, 0, null, null, 0); + } + }); + } + } + + @Test + public final void testconvertToTreeMap() { + TreeMapNode tree = HistogramConverter.convertToTreeMap(histrogram); + + //tree node is the root element, which has an empty label by default + assertEquals(tree.getLabel(), ""); + + assertTrue(tree.getChildren().size() == 2); + + TreeMapNode java = tree.getChildren().get(1); + //java subtree collapses into java node + assertEquals(java.getLabel(), "java.lang.Object"); + + TreeMapNode com = tree.getChildren().get(0); + assertEquals(com.getLabel(), "com"); + + // com node has 2 children + assertTrue(com.getChildren().size() == 2); + + TreeMapNode example2 = com.getChildren().get(1); + //example2 subtree has been collapsed in example2 node + assertEquals(example2.getLabel(), "example2.package1.Class4"); + + TreeMapNode example1 = com.getChildren().get(0); + assertTrue(example1.getChildren().size() == 2); + + TreeMapNode package2 = example1.getChildren().get(0); + //class3 node has been collapsed in package2 node + assertEquals(package2.getLabel(), "package2.Class3"); + + TreeMapNode package1 = example1.getChildren().get(1); + assertTrue(package1.getChildren().size() == 2); + + assertEquals(package1.getChildren().get(0).getLabel(), "Class2"); + assertEquals(package1.getChildren().get(1).getLabel(), "Class1"); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/SquarifiedTreeMapTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,118 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +/** + * Using eclEmma tool has been proved that this test covers 100% + * of {@link SquarifiedTreeMap} code and also 90% of {@link TreeMapBuilder} code. + */ +public class SquarifiedTreeMapTest { + + private SquarifiedTreeMap algorithm; + Rectangle2D.Double bounds; + List<TreeMapNode> list; + + @Before + public void setUp() throws Exception { + bounds = new Rectangle2D.Double(0, 0, 10, 5); + list = new ArrayList<>(); + } + + @Test + public final void testSquarifiedTreeMap() { + //check every parameters combinations + boolean catched = false; + try { + algorithm = new SquarifiedTreeMap(null, null); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + catched = false; + + try { + algorithm = new SquarifiedTreeMap(bounds, null); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + catched = false; + + try { + algorithm = new SquarifiedTreeMap(null, list); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + } + + @Test + public final void testSquarify() { + // test using an empty node list + algorithm = new SquarifiedTreeMap(bounds, new ArrayList<TreeMapNode>()); + assertEquals(0, algorithm.squarify().size()); + + // test using a correct list + int n = 10; + for (int i = 0; i < n; i++) { + list.add(new TreeMapNode(i+1)); + } + // process the list + algorithm = new SquarifiedTreeMap(bounds, list); + list = algorithm.squarify(); + + assertEquals(n, list.size()); + + for (int i = 0; i < n; i++) { + // node has been processed + assertNotNull(list.get(i).getRectangle()); + } + + assertEquals(list, algorithm.getSquarifiedNodes()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapComponentTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,224 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.awt.Dimension; +import java.lang.reflect.InvocationTargetException; + +import javax.swing.SwingUtilities; + +import junit.framework.Assert; + +import org.junit.BeforeClass; +import org.junit.Test; + + +public class TreeMapComponentTest { + + private TreeMapComponent treeMap; + private static TreeMapNode tree; + private static TreeMapNode node1; + private static TreeMapNode node2; + private static Dimension dim; + + @BeforeClass + public static void setUpOnce() { + tree = new TreeMapNode(1); + node1 = new TreeMapNode(1); + node2 = new TreeMapNode(1); + tree.addChild(node1); + node1.addChild(node2); + dim = new Dimension(500, 500); + } + + + @Test + public final void testTreeMapComponent() throws InvocationTargetException, InterruptedException { + + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + + boolean catched = false; + + try { + treeMap = new TreeMapComponent(tree, dim); + // pass + } catch(NullPointerException e) { + Assert.fail("Didn't expect exception."); + } + try { + treeMap = new TreeMapComponent(null, null); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + catched = false; + + try { + treeMap = new TreeMapComponent(tree, null); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + catched = false; + + try { + treeMap = new TreeMapComponent(null, dim); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + } + }); + } + + @Test + public final void testGetRoot() throws InvocationTargetException, InterruptedException { + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + treeMap = new TreeMapComponent(tree, dim); + assertEquals(tree, treeMap.getTreeMapRoot()); + } + }); + } + + @Test + public final void testZoomIn() throws InvocationTargetException, InterruptedException { + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + TreeMapComponent treeMap = new TreeMapComponent(tree, dim); + + treeMap.zoomIn(node1); + assertEquals(node1, treeMap.getTreeMapRoot()); + + treeMap.zoomIn(node2); + assertEquals(node2, treeMap.getTreeMapRoot()); + } + }); + } + + @Test + public final void testZoomOut() throws InvocationTargetException, InterruptedException { + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + treeMap = new TreeMapComponent(tree, dim); + + treeMap.zoomOut(); + assertEquals(tree, treeMap.getTreeMapRoot()); + + treeMap.zoomIn(node1); //if zoom out root is tree + treeMap.zoomIn(node2); //if zoom out root is node1 + + treeMap.zoomOut(); + assertEquals(node1, treeMap.getTreeMapRoot()); + + treeMap.zoomOut(); + assertEquals(tree, treeMap.getTreeMapRoot()); + } + }); + } + + @Test + public final void testZoomFull() throws InvocationTargetException, InterruptedException { + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + treeMap = new TreeMapComponent(tree, dim); + + treeMap.zoomIn(node2); + treeMap.zoomFull(); + assertEquals(tree, treeMap.getTreeMapRoot()); + + } + }); + } + + @Test + public final void testGetZoomCallsStack() throws InvocationTargetException, InterruptedException { + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + treeMap = new TreeMapComponent(tree, dim); + + // the root is always in the stack + assertEquals(1, treeMap.getZoomCallsStack().size()); + + treeMap.zoomIn(tree); + // zooming on the same element nothing happen + assertEquals(1, treeMap.getZoomCallsStack().size()); + + treeMap.zoomIn(node1); + treeMap.zoomIn(node2); + treeMap.zoomFull(); + assertEquals(tree, treeMap.getTreeMapRoot()); + } + }); + } + + + @Test + public final void testClearZoomCallsStack() throws InvocationTargetException, InterruptedException { + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + treeMap = new TreeMapComponent(tree, dim); + + treeMap.clearZoomCallsStack(); + assertEquals(1, treeMap.getZoomCallsStack().size()); + + treeMap.zoomIn(node1); + treeMap.zoomIn(node2); + treeMap.clearZoomCallsStack(); + assertEquals(1, treeMap.getZoomCallsStack().size()); + } + }); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeMapNodeTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,284 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + + +public class TreeMapNodeTest { + + private TreeMapNode node; + + @Before + public void setUp() { + node = new TreeMapNode(null, 1); + } + + @Test + public final void testGetId() { + TreeMapNode node1 = new TreeMapNode(null, 1); + TreeMapNode node2 = new TreeMapNode(null, 1); + assertTrue(node1.getId() != node2.getId()); + assertTrue(node1.getId() + 1 == node2.getId()); + } + + @Test + public final void testGetSetParent() { + TreeMapNode parent = new TreeMapNode(null, 1); + assertTrue(node.getParent() == null); + node.setParent(parent); + assertTrue(node.getParent() == parent); + } + + @Test + public final void testGetSetLabel() { + TreeMapNode node = new TreeMapNode("MyLabel", 1); + assertTrue(node.getLabel().equals("MyLabel")); + node.setLabel("MyNewLabel"); + assertTrue(node.getLabel().equals("MyNewLabel")); + } + + @Test + public final void testGetSetChildren() { + assertTrue(node.getChildren().isEmpty()); + + TreeMapNode node = new TreeMapNode(null, 1); + List<TreeMapNode> children = new ArrayList<>(); + children.add(node); + + node.setChildren(children); + assertTrue(1 == node.getChildren().size()); + } + + + @Test + public final void testGetAddInfo() { + node.addInfo("exampleKey", "exampleValue"); + assertEquals("exampleValue", node.getInfo("exampleKey")); + } + + @Test + public final void testAddChild() { + assertTrue(node.getChildren().size() == 0); + node.addChild(new TreeMapNode(null, 1)); + assertTrue(node.getChildren().size() == 1); + + node.addChild(null); + assertTrue(node.getChildren().size() == 1); // null has not been added + } + + @Test + public final void testGetSetWeight() { + assertTrue(1 == node.getWeight()); + node.setWeight(5); + assertTrue(5 == node.getWeight()); + } + + @Test + public final void testGetSetRectangle() { + Rectangle2D.Double r = new Rectangle2D.Double(5, 5, 5, 5); + node.setRectangle(r); + assertEquals(r, node.getRectangle()); + + node.setRectangle(null); + boolean catched = false; + try { + node.getRectangle(); + } catch(RuntimeException e) { + catched = true; + } + assertTrue(catched); + } + + + @Test + public final void testGetSetRealWeight() { + node = new TreeMapNode(null, 5); + assertTrue(node.getRealWeight() == 5); + node.setRealWeight(8); + assertTrue(node.getRealWeight() == 8); + } + + @Test + public final void testAllowNonPositiveWeight() { + assertFalse(TreeMapNode.isAllowNonPositiveWeight()); + node.setWeight(-5); + assertTrue(node.getWeight() == 1); // the real node weight + assertTrue(node.getRealWeight() == 1); + + + TreeMapNode.setAllowNonPositiveWeight(true); + node.setWeight(-5); + assertTrue(node.getWeight() == -5); + } + + @Test + public final void testIsDrawable() { + Rectangle2D.Double r = new Rectangle2D.Double(5, 5, 5, 5); + node.setRectangle(r); + assertTrue(node.isDrawable()); + + r.setRect(0, 0, 0.5f, 0.5f); + assertFalse(node.isDrawable()); + + r.setRect(0, 0, 5f, 0.5f); + assertFalse(node.isDrawable()); + + r.setRect(0, 0, 0.5f, 5f); + assertFalse(node.isDrawable()); + } + + @Test + public final void testGetSetColor() { + assertNull(node.getColor()); + node.setColor(Color.black); + assertEquals(Color.black, node.getColor()); + } + + @Test + public final void testGetDepth() { + TreeMapNode depth1 = new TreeMapNode(null, 1); + TreeMapNode depth2 = new TreeMapNode(null, 1); + + node.addChild(depth1); + depth1.addChild(depth2); + + assertTrue(node.getDepth() == 0); + assertTrue(depth1.getDepth() == 1); + assertTrue(depth2.getDepth() == 2); + } + + @Test + public final void testSearchByLabel() { + TreeMapNode root = new TreeMapNode("root", 0); + + TreeMapNode a = new TreeMapNode("a", 3); + TreeMapNode b = new TreeMapNode("b", 2); + TreeMapNode c = new TreeMapNode("c", 1); + root.addChild(a); + root.addChild(b); + root.addChild(c); + + TreeMapNode aa = new TreeMapNode("aa", 3); + TreeMapNode ab = new TreeMapNode("ab", 3); + TreeMapNode ac = new TreeMapNode("ac", 3); + a.addChild(aa); + a.addChild(ab); + a.addChild(ac); + + assertEquals(aa, root.searchNodeByLabel("aa")); + assertEquals(b, root.searchNodeByLabel("b")); + assertEquals(ac, root.searchNodeByLabel("ac")); + assertEquals(c, root.searchNodeByLabel("c")); + } + + + + @Test + public final void testSort() { + + TreeMapNode n1 = new TreeMapNode(null, 5); + TreeMapNode n2 = new TreeMapNode(null, 4); + TreeMapNode n4 = new TreeMapNode(null, 2); + TreeMapNode n3 = new TreeMapNode(null, 3); + TreeMapNode n5 = new TreeMapNode(null, 0); + TreeMapNode n6 = new TreeMapNode(null, 7); + TreeMapNode n7 = new TreeMapNode(null, 1); + TreeMapNode n8 = new TreeMapNode(null, 9); + + List<TreeMapNode> toSort = new ArrayList<>(); + toSort.add(n3); + toSort.add(n2); + toSort.add(n4); + toSort.add(n1); + toSort.add(n5); + toSort.add(n6); + toSort.add(n7); + toSort.add(n8); + + TreeMapNode.sort(toSort); + + assertEquals(toSort.get(0), n8); + assertEquals(toSort.get(1), n6); + assertEquals(toSort.get(2), n1); + assertEquals(toSort.get(3), n2); + assertEquals(toSort.get(4), n3); + assertEquals(toSort.get(5), n4); + assertEquals(toSort.get(6), n7); + assertEquals(toSort.get(7), n5); + } + + @Test + public final void testGetInfo() { + Map<String, String> map = node.getInfo(); + assertNotNull(map); + assertEquals(0, map.keySet().size()); + } + + @Test + public final void testToString() { + assertNotNull(node.toString()); + } + + + @Test + public final void testGetNextColor() { + assertNull(node.getColor()); + assertTrue(node.getNextColor().equals(node.START_COLOR)); + + Color start = node.START_COLOR; + node.setColor(start); + + for (int i = 0; i < TreeMapNode.colors.length; i++) { + assertEquals(TreeMapNode.colors[i], node.getColor()); + node.setColor(node.getNextColor()); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/TreeProcessorTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.awt.geom.Rectangle2D; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Test; + +public class TreeProcessorTest { + + TreeMapNode node; + Rectangle2D.Double area; + + @Before + public void setUp() throws Exception { + node = new TreeMapNode(1); + area = new Rectangle2D.Double(0, 0, 500, 500); + } + + @Test + public final void testTreeProcessor() { + boolean catched = false; + // this test check all wrong combinations for constructor parameters + try { + TreeProcessor.processTreeMap(null, area); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + catched = false; + + try { + TreeProcessor.processTreeMap(node, null); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + catched = false; + + try { + TreeProcessor.processTreeMap(null, null); + } catch(NullPointerException e) { + catched = true; + } + assertTrue(catched); + } + + + @Test + public final void testProcessTreeMap() { + generateTree(node, 5, 5); + TreeProcessor.processTreeMap(node, area); + + // the test will check if any drawable node in the tree has a rectangle and a + // color, which means the processor function has processed the whole tree + traverse(node); + } + + private void traverse(TreeMapNode tree) { + if (tree.isDrawable() && (tree.getRectangle() == null || tree.getColor() == null)) { + fail("node " + tree.getId() + " not processed"); + } + for (TreeMapNode child : tree.getChildren()) { + traverse(child); + } + } + + private void generateTree(TreeMapNode root, int levels, int childrenNumber) { + if (levels == 0) { + return; + } else { + for (int i = 0; i < childrenNumber; i++) { + root.addChild(new TreeMapNode(100)); + } + for (TreeMapNode child : root.getChildren()) { + generateTree(child, levels-1, childrenNumber); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/vm-heap-analysis/client-swing/src/test/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/ValueFormatterTest.java Thu Jul 23 14:03:27 2015 +0200 @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2015 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.client.swing.internal; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ValueFormatterTest { + + private ValueFormatter formatter; + + @Test + public final void getFormattedWeight() { + formatter = new ValueFormatter(2); + assertEquals("2.00 Bytes", formatter.format()); + + formatter = new ValueFormatter(2222); + assertEquals("2.22 KBytes", formatter.format()); + + formatter = new ValueFormatter(2222222); + assertEquals("2.22 MBytes", formatter.format()); + } +} \ No newline at end of file