--- old/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/MemoryLeakPage.java 2018-11-27 11:32:05.000000000 +0530 +++ new/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/MemoryLeakPage.java 2018-11-27 11:32:05.000000000 +0530 @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.ToDoubleFunction; @@ -138,6 +139,24 @@ IRange timeRange = null; + private final Predicate withinTimeRangePredicateFromRootObject = rto -> { + if (timeRange != null) { + if (rto.getTimestamp().compareTo(timeRange.getStart()) >= 0 + && rto.getTimestamp().compareTo(timeRange.getEnd()) <= 0) { + return true; + } else if (rto.getRootObject().getLeafNodes() != null && rto.getRootObject().getLeafNodes().size() > 1) { + for (Map.Entry rt : rto.getRootObject().getLeafNodes().entrySet()) { + if (rt.getKey().compareTo(timeRange.getStart()) >= 0 + && rt.getKey().compareTo(timeRange.getEnd()) <= 0) { + return true; + } + } + } + return false; + } + return true; + }; + private final Predicate withinTimeRangePredicate = rto -> { if (timeRange != null) { return rto.getTimestamp().compareTo(timeRange.getStart()) >= 0 @@ -160,7 +179,7 @@ ReferenceTreeObject object = (ReferenceTreeObject) element; List children = object.getChildren(); if (timeRange != null) { - return children.stream().anyMatch(withinTimeRangePredicate); + return children.stream().anyMatch(withinTimeRangePredicateFromRootObject); } return !children.isEmpty(); } @@ -181,7 +200,7 @@ if (inputElement instanceof Collection) { Collection collection = (Collection) inputElement; if (timeRange != null) { - return collection.stream().filter(withinTimeRangePredicate).toArray(); + return collection.stream().filter(withinTimeRangePredicateFromRootObject).toArray(); } return collection.toArray(); } @@ -194,7 +213,12 @@ ReferenceTreeObject object = (ReferenceTreeObject) element; List children = object.getChildren(); if (timeRange != null) { - return children.stream().filter(withinTimeRangePredicate).toArray(); + // Node has more than 1 children, select only those nodes which are leaky (leaf node) objects + if (children.size() > 1) { + return children.stream().filter(withinTimeRangePredicate).toArray(); + } else { + return children.stream().filter(withinTimeRangePredicateFromRootObject).toArray(); + } } return children.toArray(); } @@ -248,7 +272,12 @@ new TypedLabelProvider(ReferenceTreeObject.class) { @Override protected String getTextTyped(ReferenceTreeObject object) { - return object == null ? "" : Integer.toString(object.getItems().size()); //$NON-NLS-1$ + IRange selectionRange = chart.getSelectionRange(); + if (selectionRange == null) { + return object == null ? "" : Integer.toString(object.getObjectsKeptAliveCount()); //$NON-NLS-1$ + } else { + return (object == null || selectionRange == null) ? "" : Integer.toString(model.getLeakCountInRange(selectionRange, object)); //$NON-NLS-1$ + } }; }).style(SWT.RIGHT).comparator((o1, o2) -> { if (o1 instanceof ReferenceTreeObject && o2 instanceof ReferenceTreeObject) { @@ -310,7 +339,7 @@ state.getChild(REFERENCE_TREE), null); model = ReferenceTreeModel.buildReferenceTree(getDataSource().getItems().apply(TABLE_ITEMS)); model.getLeakCandidates(0.5d); // this doesn't really matter, since we're not saving the return value - aggregatedReferenceTree.setInput(model.getRootObjects()); + aggregatedReferenceTree.setInput(model.getLeakObjects()); chartCanvas.replaceRenderer(createChart()); PersistableSashForm.loadState(mainSash, state.getChild(MAIN_SASH)); @@ -379,7 +408,7 @@ if (selectionRange != null) { ((ReferenceTreeContentProvider) aggregatedReferenceTree .getContentProvider()).timeRange = selectionRange; - aggregatedReferenceTree.setInput(model.getRootObjects(selectionRange)); + aggregatedReferenceTree.setInput(model.getLeakObjects(selectionRange)); } else { ((ReferenceTreeContentProvider) aggregatedReferenceTree.getContentProvider()).timeRange = null; aggregatedReferenceTree.setInput(model.getRootObjects()); --- old/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/memleak/ReferenceTreeModel.java 2018-11-27 11:32:06.000000000 +0530 +++ new/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/memleak/ReferenceTreeModel.java 2018-11-27 11:32:06.000000000 +0530 @@ -230,6 +230,53 @@ } /** + * @param timerange + * a range of time that specifies which root objects to retrieve + * @return a list of all Roots which has a leaked object during the specified time range + */ + public Collection getLeakObjects(IRange timerange) { + List objects = new ArrayList<>(); + for (ReferenceTreeObject referenceTreeObject : leakObjects) { + IQuantity itemTime = referenceTreeObject.getTimestamp(); + if (timerange.getStart().compareTo(itemTime) <= 0 && timerange.getEnd().compareTo(itemTime) >= 0) { + ReferenceTreeObject parent = referenceTreeObject.getParent(); + if (parent == null) { + objects.add(referenceTreeObject); + continue; + } + while (parent.getParent() != null) { + parent = parent.getParent(); + } + + if (objects.contains(parent) == false) { + objects.add(parent); + } + } + } + return objects; + } + + /** + * A helper method to calculate number of leaked object with in specified time + * @param timerange + * a range of time that specifies which root objects to retrieve + * @param referenceTreeObject + * leak candidate + * @return number of leaked object during the specified timerange for a given + * leak candidate + */ + public int getLeakCountInRange(IRange timerange, ReferenceTreeObject referenceTreeObject) { + int leakCount = 0; + for (ReferenceTreeObject rtObject : leakObjects) { + if (rtObject.getRootObject().equals(referenceTreeObject.getRootObject()) && + timerange.getStart().compareTo(rtObject.getTimestamp()) <= 0 && timerange.getEnd().compareTo(rtObject.getTimestamp()) >= 0) { + ++leakCount; + } + } + return leakCount; + } + + /** * @return a list of the actual objects sampled by the Old Object Sample event */ public List getLeakObjects() { @@ -304,6 +351,18 @@ } else { if (last != null) { node.addChild(last); + + // Goto Root object and add this leak node's entry + ReferenceTreeObject parent = node.getParent(); + if (parent == null) { + node.updateLeafNode(map.get(objectAccessor.getMember(item).getAddress())); + } + else { + while (parent.getParent() != null) { + parent = parent.getParent(); + } + parent.updateLeafNode(map.get(objectAccessor.getMember(item).getAddress())); + } } root = false; break; @@ -316,6 +375,7 @@ if (last != null) { if (root) { rootObjects.add(last); + last.updateLeafNode(map.get(objectAccessor.getMember(item).getAddress())); rootObjectsByLeakItems.put(item, last); } } --- old/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/memleak/ReferenceTreeObject.java 2018-11-27 11:32:07.000000000 +0530 +++ new/core/org.openjdk.jmc.flightrecorder/src/main/java/org/openjdk/jmc/flightrecorder/memleak/ReferenceTreeObject.java 2018-11-27 11:32:06.000000000 +0530 @@ -34,8 +34,11 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.openjdk.jmc.common.IMCOldObject; @@ -57,6 +60,7 @@ private final List children = new ArrayList<>(); private final Set items = new HashSet<>(); + private final Map leafNodes = new HashMap<>(); private String rootDescription; private IMCOldObject object; @@ -273,12 +277,42 @@ this.parent = parent; } + /** + * @return the Root object + */ + public ReferenceTreeObject getRootObject() { + if (this.parent == null) { + return this; + } else { + ReferenceTreeObject rto = this.parent; + while (rto.getParent() != null) { + rto = rto.getParent(); + } + return rto; + } + } + @Override public int getReferrerSkip() { return object.getReferrerSkip(); } /** + * @return Map containing allocation time and its leafnode object + */ + public Map getLeafNodes() { + return Collections.unmodifiableMap(leafNodes); + } + + /** + * @param leafNode + * Object which is leaked + */ + public void updateLeafNode(ReferenceTreeObject leafNode) { + leafNodes.put(leafNode.getTimestamp(), leafNode); + } + + /** * Returns a string representation of this object. * * @param displayFormatting