*/
package org.opendaylight.controller.md.sal.trace.closetracker.impl;
+import java.util.Arrays;
+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 java.util.concurrent.ConcurrentSkipListSet;
import javax.annotation.concurrent.ThreadSafe;
private final @SuppressWarnings("unused") String createDescription;
private final Set<CloseTracked<T>> tracked = new ConcurrentSkipListSet<>(
- (o1, o2) -> o1.getObjectCreated().compareTo(o2.getObjectCreated()));
+ (o1, o2) -> Integer.compare(System.identityHashCode(o1), System.identityHashCode(o2)));
private final boolean isDebugContextEnabled;
tracked.remove(closeTracked);
}
- // TODO Later add methods to dump & query what's not closed, by creation time, incl. creation stack trace
-
- // TODO we could even support add/close of Object instead of CloseTracked, by creating a wrapper?
+ /**
+ * Creates and returns a "report" of (currently) tracked but not (yet) closed
+ * instances.
+ *
+ * @return Map where key is the StackTraceElement[] identifying a unique
+ * allocation contexts (or an empty List if debugContextEnabled is false),
+ * and value is the number of open instances created at that point.
+ */
+ public Map<List<StackTraceElement>, Long> getAllUnique() {
+ Map<List<StackTraceElement>,Long> mapToReturn = new HashMap<>();
+ Set<CloseTracked<T>> copyOfTracked = new HashSet<>(tracked);
+ for (CloseTracked<T> closeTracked : copyOfTracked) {
+ final StackTraceElement[] stackTraceArray = closeTracked.getAllocationContextStackTrace();
+ List<StackTraceElement> stackTraceElements =
+ stackTraceArray != null ? Arrays.asList(stackTraceArray) : Collections.emptyList();
+ mapToReturn.merge(stackTraceElements, 1L, (oldValue, value) -> oldValue + 1);
+ }
+ return mapToReturn;
+ }
}
* the objects themselves, as well as the stack trace of the call invoking the registration or write operation.
* It works by operating as a "bump on the stack" between the application and actual DataBroker, intercepting write
* and registration calls and writing to the log.
+ *
+ * <p>In addition, it (optionally) can also keep track of the stack trace of all new transaction allocations
+ * (including TransactionChains, and transactions created in turn from them), in order to detect and report leaks
+ * results from transactions which were not closed.
+ *
* <h1>Wiring:</h1>
* TracingBroker is designed to be easy to use. In fact, for bundles using Blueprint to inject their DataBroker
* TracingBroker can be used without modifying your code at all in two simple steps:
* <ol>
* <li>
* Simply add the dependency "mdsaltrace-features" to
- * your karaf pom:
+ * your Karaf pom:
* <pre>
* {@code
* <dependency>
--- /dev/null
+/*
+ * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.trace.closetracker.impl.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.AbstractCloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+
+public class CloseTrackedRegistryTest {
+
+ private static class SomethingClosable extends AbstractCloseTracked<SomethingClosable> implements AutoCloseable {
+ SomethingClosable(CloseTrackedRegistry<SomethingClosable> transactionChainRegistry) {
+ super(transactionChainRegistry);
+ }
+
+ @Override
+ public void close() {
+ removeFromTrackedRegistry();
+ }
+ }
+
+ @Test
+ public void testDuplicateAllocationContexts() {
+ final CloseTrackedRegistry<SomethingClosable> registry =
+ new CloseTrackedRegistry<>(this, "testDuplicateAllocationContexts", true);
+
+ for (int i = 0; i < 100; i++) {
+ SomethingClosable isClosedManyTimes = new SomethingClosable(registry);
+ isClosedManyTimes.close();
+ someOtherMethodWhichDoesNotClose(registry);
+ }
+ @SuppressWarnings({ "resource", "unused" })
+ SomethingClosable forgotToCloseOnce = new SomethingClosable(registry);
+
+ Map<List<StackTraceElement>, Long> uniqueNonClosed = registry.getAllUnique();
+ assertThat(uniqueNonClosed.values()).containsExactly(100L, 1L);
+ uniqueNonClosed.forEach((stackTraceElements, occurrences) -> {
+ if (occurrences == 100) {
+ assertThatIterableContains(stackTraceElements,
+ element -> element.getMethodName().equals("someOtherMethodWhichDoesNotClose"));
+ } else { // occurrences == 1
+ assertThatIterableContains(stackTraceElements,
+ element -> element.getMethodName().equals("testDuplicateAllocationContexts"));
+ }
+ });
+ // one of the two has a StackTraceElement containing
+ }
+
+ // Something like this really should be in Google Truth...
+ private <T> void assertThatIterableContains(Iterable<T> iterable, Predicate<T> predicate) {
+ for (T element : iterable) {
+ if (predicate.test(element)) {
+ return;
+ }
+ }
+ fail("Iterable did not contain any element matching predicate");
+ }
+
+ @SuppressWarnings({ "resource", "unused" })
+ private void someOtherMethodWhichDoesNotClose(CloseTrackedRegistry<SomethingClosable> registry) {
+ new SomethingClosable(registry);
+ }
+
+ @Test
+ @SuppressWarnings({ "unused", "resource" })
+ public void testDebugContextDisabled() {
+ final CloseTrackedRegistry<SomethingClosable> debugContextDisabledRegistry =
+ new CloseTrackedRegistry<>(this, "testDebugContextDisabled", false);
+
+ SomethingClosable forgotToCloseOnce = new SomethingClosable(debugContextDisabledRegistry);
+
+ assertThat(debugContextDisabledRegistry.getAllUnique()).hasSize(1);
+ assertThat(debugContextDisabledRegistry.getAllUnique().values().iterator().next()).isEqualTo(1);
+ assertThat(debugContextDisabledRegistry.getAllUnique().keySet().iterator().next()).isEmpty();
+ }
+}