*/
package org.opendaylight.controller.md.sal.trace.api;
+import java.io.PrintStream;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
/**
*/
public interface TracingDOMDataBroker extends DOMDataBroker {
+ /**
+ * Prints a human-readable "report" of all opened but not closed transactions,
+ * including transactions chains and transactions opened by them, onto the printStream.
+ * @return true if there were any open transactions, false if none
+ */
+ boolean printOpenTransactions(PrintStream printStream);
+
}
private final CloseTrackedTrait<T> closeTracker;
protected AbstractCloseTracked(CloseTrackedRegistry<T> transactionChainRegistry) {
- this.closeTracker = new CloseTrackedTrait<>(transactionChainRegistry);
+ this.closeTracker = new CloseTrackedTrait<>(transactionChainRegistry, this);
}
protected void removeFromTrackedRegistry() {
public @Nullable StackTraceElement[] getAllocationContextStackTrace() {
return closeTracker.getAllocationContextStackTrace();
}
+
+ @Override
+ public final CloseTracked<T> getRealCloseTracked() {
+ return this;
+ }
}
import javax.annotation.Nullable;
/**
- * Object which can track where it has been created, and if it has been correctly "closed".
+ * Object which can track where something has been created, and if it has been correctly "closed".
*
- * <p>Includes preserving the context of the call stack which created this object, and the instant it was created.
+ * <p>Includes preserving the context of the call stack which created an object, and the instant it was created.
*
* @author Michael Vorburger.ch
*/
@Nullable StackTraceElement[] getAllocationContextStackTrace();
+ CloseTracked<T> getRealCloseTracked();
}
*/
package org.opendaylight.controller.md.sal.trace.closetracker.impl;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ThreadSafe
public class CloseTrackedRegistry<T extends CloseTracked<T>> {
- // unused OK for now, at least we'll be able to see this in HPROF heap dumps and know what is which
- private final @SuppressWarnings("unused") Object anchor;
- private final @SuppressWarnings("unused") String createDescription;
+ private final Object anchor;
+ private final String createDescription;
private final Set<CloseTracked<T>> tracked = new ConcurrentSkipListSet<>(
(o1, o2) -> Integer.compare(System.identityHashCode(o1), System.identityHashCode(o2)));
return isDebugContextEnabled;
}
+ public Object getAnchor() {
+ return anchor;
+ }
+
+ public String getCreateDescription() {
+ return createDescription;
+ }
+
// package protected, not public; only CloseTrackedTrait invokes this
void add(CloseTracked<T> closeTracked) {
tracked.add(closeTracked);
* 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.
+ * @return Set of CloseTrackedRegistryReportEntry, of which each the stack trace
+ * element identifies a unique allocation context (or an empty List if
+ * debugContextEnabled is false), and value is the number of open
+ * instances created at that place in the code.
*/
- public Map<List<StackTraceElement>, Long> getAllUnique() {
- Map<List<StackTraceElement>,Long> mapToReturn = new HashMap<>();
+ public Set<CloseTrackedRegistryReportEntry<T>> getAllUnique() {
+ Map<List<StackTraceElement>, Long> map = 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);
+ map.merge(stackTraceElements, 1L, (oldValue, value) -> oldValue + 1);
}
- return mapToReturn;
+
+ Set<CloseTrackedRegistryReportEntry<T>> report = new HashSet<>();
+ map.forEach((stackTraceElements, number) -> {
+ copyOfTracked.stream().filter(closeTracked -> {
+ StackTraceElement[] closeTrackedStackTraceArray = closeTracked.getAllocationContextStackTrace();
+ List<StackTraceElement> closeTrackedStackTraceElements =
+ closeTrackedStackTraceArray != null ? asList(closeTrackedStackTraceArray) : emptyList();
+ return closeTrackedStackTraceElements.equals(stackTraceElements);
+ }).findAny().ifPresent(exampleCloseTracked -> {
+ report.add(new CloseTrackedRegistryReportEntry<>(exampleCloseTracked, number, stackTraceElements));
+ });
+ });
+ return report;
}
}
--- /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;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+/**
+ * Element of a "report" created by a {@link CloseTrackedRegistry}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class CloseTrackedRegistryReportEntry<T extends CloseTracked<T>> {
+
+ private final CloseTracked<T> exampleCloseTracked;
+ private final long numberAddedNotRemoved;
+ private final List<StackTraceElement> stackTraceElements;
+
+ public CloseTrackedRegistryReportEntry(CloseTracked<T> exampleCloseTracked, long numberAddedNotRemoved,
+ List<StackTraceElement> stackTraceElements) {
+ super();
+ this.exampleCloseTracked = requireNonNull(exampleCloseTracked, "closeTracked");
+ this.numberAddedNotRemoved = requireNonNull(numberAddedNotRemoved, "numberAddedNotRemoved");
+ this.stackTraceElements = requireNonNull(stackTraceElements, "stackTraceElements");
+ }
+
+ public long getNumberAddedNotRemoved() {
+ return numberAddedNotRemoved;
+ }
+
+ public CloseTracked<T> getExampleCloseTracked() {
+ return exampleCloseTracked;
+ }
+
+ public List<StackTraceElement> getStackTraceElements() {
+ return stackTraceElements;
+ }
+
+ @Override
+ public String toString() {
+ return "CloseTrackedRegistryReportEntry [numberAddedNotRemoved=" + numberAddedNotRemoved + ", closeTracked="
+ + exampleCloseTracked + ", stackTraceElements.size=" + stackTraceElements.size() + "]";
+ }
+
+
+}
private final @Nullable Throwable allocationContext;
private final CloseTrackedRegistry<T> closeTrackedRegistry;
+ private final CloseTracked<T> realCloseTracked;
- public CloseTrackedTrait(CloseTrackedRegistry<T> transactionChainRegistry) {
+ public CloseTrackedTrait(CloseTrackedRegistry<T> transactionChainRegistry, CloseTracked<T> realCloseTracked) {
if (transactionChainRegistry.isDebugContextEnabled()) {
this.allocationContext = new Throwable("allocated at");
} else {
this.allocationContext = null;
}
+ this.realCloseTracked = Objects.requireNonNull(realCloseTracked, "realCloseTracked");
this.closeTrackedRegistry = Objects.requireNonNull(transactionChainRegistry, "transactionChainRegistry");
this.closeTrackedRegistry.add(this);
}
closeTrackedRegistry.remove(this);
}
+ @Override
+ public CloseTracked<T> getRealCloseTracked() {
+ return realCloseTracked;
+ }
+
}
*/
package org.opendaylight.controller.md.sal.trace.dom.impl;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
-
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
return res;
}
+
+ public boolean printOpenTransactions(PrintStream ps) {
+ if (transactionChainsRegistry.getAllUnique().isEmpty()
+ && readOnlyTransactionsRegistry.getAllUnique().isEmpty()
+ && writeTransactionsRegistry.getAllUnique().isEmpty()
+ && readWriteTransactionsRegistry.getAllUnique().isEmpty()) {
+
+ return false;
+ }
+
+ ps.println(getClass().getSimpleName() + " found not yet (or never..) closed transaction[chain]s");
+ ps.println();
+ printRegistryOpenTransactions(readOnlyTransactionsRegistry, ps, "");
+ printRegistryOpenTransactions(writeTransactionsRegistry, ps, "");
+ printRegistryOpenTransactions(readWriteTransactionsRegistry, ps, "");
+
+ // Now print details for each non-closed TransactionChain
+ // incl. in turn each ones own read/Write[Only]TransactionsRegistry
+ ps.println(transactionChainsRegistry.getCreateDescription());
+ transactionChainsRegistry.getAllUnique().forEach(entry -> {
+ ps.println(" " + entry.getNumberAddedNotRemoved() + "x TransactionChains opened, which are not closed:");
+ entry.getStackTraceElements().forEach(line -> ps.println(" " + line));
+ @SuppressWarnings("resource")
+ TracingTransactionChain txChain = (TracingTransactionChain) entry
+ .getExampleCloseTracked().getRealCloseTracked();
+ printRegistryOpenTransactions(txChain.getReadOnlyTransactionsRegistry(), ps, " ");
+ printRegistryOpenTransactions(txChain.getWriteTransactionsRegistry(), ps, " ");
+ printRegistryOpenTransactions(txChain.getReadWriteTransactionsRegistry(), ps, " ");
+ });
+ ps.println();
+
+ return true;
+ }
+
+ private void printRegistryOpenTransactions(CloseTrackedRegistry<?> registry, PrintStream ps, String indent) {
+ ps.println(indent + registry.getCreateDescription());
+ registry.getAllUnique().forEach(entry -> {
+ ps.println(indent + " " + entry.getNumberAddedNotRemoved()
+ + "x transactions opened here, which are not closed:");
+ entry.getStackTraceElements().forEach(line -> ps.print(" " + line));
+ });
+ ps.println();
+ }
}
TracingReadWriteTransaction(DOMDataReadWriteTransaction delegate, TracingBroker tracingBroker,
CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry) {
super(delegate, tracingBroker);
- this.closeTracker = new CloseTrackedTrait<>(readWriteTransactionsRegistry);
+ this.closeTracker = new CloseTrackedTrait<>(readWriteTransactionsRegistry, this);
this.delegate = Objects.requireNonNull(delegate);
}
public StackTraceElement[] getAllocationContextStackTrace() {
return closeTracker.getAllocationContextStackTrace();
}
+
+ @Override
+ public CloseTracked<TracingReadWriteTransaction> getRealCloseTracked() {
+ return this;
+ }
}
this.delegate = Objects.requireNonNull(delegate);
this.tracingBroker = Objects.requireNonNull(tracingBroker);
- final boolean isDebugging = transactionChainsRegistry.isDebugContextEnabled();
- this.readOnlyTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadOnlyTransaction", isDebugging);
- this.writeTransactionsRegistry = new CloseTrackedRegistry<>(this, "newWriteOnlyTransaction", isDebugging);
- this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadWriteTransaction", isDebugging);
+ final boolean isDebug = transactionChainsRegistry.isDebugContextEnabled();
+ String pf = "TransactionChain_" + toString();
+ this.readOnlyTransactionsRegistry = new CloseTrackedRegistry<>(this, pf + "newReadOnlyTransaction", isDebug);
+ this.writeTransactionsRegistry = new CloseTrackedRegistry<>(this, pf + "newWriteOnlyTransaction", isDebug);
+ this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(this, pf + "newReadWriteTransaction", isDebug);
}
@Override
super.removeFromTrackedRegistry();
}
+ public CloseTrackedRegistry<TracingReadOnlyTransaction> getReadOnlyTransactionsRegistry() {
+ return readOnlyTransactionsRegistry;
+ }
+
+ public CloseTrackedRegistry<TracingReadWriteTransaction> getReadWriteTransactionsRegistry() {
+ return readWriteTransactionsRegistry;
+ }
+
+ public CloseTrackedRegistry<TracingWriteTransaction> getWriteTransactionsRegistry() {
+ return writeTransactionsRegistry;
+ }
}
TracingWriteTransaction(DOMDataWriteTransaction delegate, TracingBroker tracingBroker,
CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry) {
super(delegate, tracingBroker);
- this.closeTracker = new CloseTrackedTrait<>(writeTransactionsRegistry);
+ this.closeTracker = new CloseTrackedTrait<>(writeTransactionsRegistry, this);
}
@Override
return closeTracker.getAllocationContextStackTrace();
}
+ @Override
+ public CloseTracked<TracingWriteTransaction> getRealCloseTracked() {
+ return this;
+ }
+
}
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.Set;
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;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistryReportEntry;
public class CloseTrackedRegistryTest {
@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,
+ Set<CloseTrackedRegistryReportEntry<SomethingClosable>> uniqueNonClosed = registry.getAllUnique();
+ assertThat(uniqueNonClosed).hasSize(2);
+ assertThatIterableContains(uniqueNonClosed, entry ->
+ entry.getNumberAddedNotRemoved() == 100 || entry.getNumberAddedNotRemoved() == 1);
+ uniqueNonClosed.forEach(entry -> {
+ if (entry.getNumberAddedNotRemoved() == 100) {
+ assertThatIterableContains(entry.getStackTraceElements(),
element -> element.getMethodName().equals("someOtherMethodWhichDoesNotClose"));
- } else { // occurrences == 1
- assertThatIterableContains(stackTraceElements,
+ } else if (entry.getNumberAddedNotRemoved() == 1) {
+ assertThatIterableContains(entry.getStackTraceElements(),
element -> element.getMethodName().equals("testDuplicateAllocationContexts"));
+ } else {
+ fail("Unexpected number of added, not removed: " + entry.getNumberAddedNotRemoved());
}
});
- // one of the two has a StackTraceElement containing
}
// Something like this really should be in Google Truth...
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();
+ Set<CloseTrackedRegistryReportEntry<SomethingClosable>>
+ closeRegistryReport = debugContextDisabledRegistry.getAllUnique();
+ assertThat(closeRegistryReport).hasSize(1);
+
+ CloseTrackedRegistryReportEntry<SomethingClosable>
+ closeRegistryReportEntry1 = closeRegistryReport.iterator().next();
+ assertThat(closeRegistryReportEntry1.getNumberAddedNotRemoved()).isEqualTo(1);
+ assertThat(closeRegistryReportEntry1.getStackTraceElements()).isEmpty();
}
}
--- /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 java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.Config;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.ConfigBuilder;
+
+/**
+ * Test of {@link TracingBroker}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class TracingBrokerTest {
+
+ @Test
+ @SuppressWarnings({ "resource", "unused" }) // Finding resource leaks is the point of this test
+ public void testPrintOpenTransactions() {
+ DOMDataBroker domDataBroker = mock(DOMDataBroker.class, RETURNS_DEEP_STUBS);
+ Config config = new ConfigBuilder().setTransactionDebugContextEnabled(true).build();
+ BindingNormalizedNodeSerializer codec = mock(BindingNormalizedNodeSerializer.class);
+ TracingBroker tracingBroker = new TracingBroker(domDataBroker, config, codec);
+
+ DOMTransactionChain txChain = tracingBroker.createTransactionChain(null);
+ DOMDataReadWriteTransaction tx = txChain.newReadWriteTransaction();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos);
+ boolean printReturnValue = tracingBroker.printOpenTransactions(ps);
+ String output = new String(baos.toByteArray(), UTF_8);
+
+ assertThat(printReturnValue).isTrue();
+ assertThat(output).contains("testPrintOpenTransactions(TracingBrokerTest.java:41)"); // in a stack trace
+
+ // We don't do any verify/times on the mocks,
+ // because the main point of the test is just to verify that
+ // printOpenTransactions runs through without any exceptions
+ // (e.g. it used to have a ClassCastException).
+ }
+
+}