*/
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 java.util.Set;
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;
+import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTracked;
import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistryReportEntry;
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.yangtools.concepts.ListenerRegistration;
* 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
+ * 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>
* <groupId>org.opendaylight.controller</groupId>
- * <artifactId>mdsal-trace-features</artifactId>
+ * <artifactId>features-mdsal-trace</artifactId>
+ * <version>1.7.0-SNAPSHOT</version>
* <classifier>features</classifier>
* <type>xml</type>
* <scope>runtime</scope>
- * <version>0.1.6-SNAPSHOT</version>
* </dependency>
* }
* </pre>
* </li>
* <li>
- * Then just load the odl-mdsal-trace feature before your feature and you're done.
+ * Then just "feature:install odl-mdsal-trace" before you install your "real" feature(s) and you're done.
+ * Beware that with Karaf 4 due to <a href="https://bugs.opendaylight.org/show_bug.cgi?id=9068">Bug 9068</a>
+ * you'll probably have to use feature:install's --no-auto-refresh flag when installing your "real" feature.
* </li>
* </ol>
* This works because the mdsaltrace-impl bundle registers its service implementing DOMDataBroker with a higher
return child.startsWith(parent.substring(parentOffset), childOffset);
}
+ @SuppressWarnings("checkstyle:hiddenField")
public boolean subtreesOverlap(YangInstanceIdentifier iid, LogicalDatastoreType store,
AsyncDataBroker.DataChangeScope scope) {
if (this.store != null && !this.store.equals(store)) {
}
}
+ @SuppressWarnings("checkstyle:hiddenField")
public boolean eventIsOfInterest(YangInstanceIdentifier iid, LogicalDatastoreType store) {
if (this.store != null && !this.store.equals(store)) {
return false;
} else {
this.isDebugging = false;
}
- this.transactionChainsRegistry = new CloseTrackedRegistry<>(this, "createTransactionChain", isDebugging);
- this.readOnlyTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadOnlyTransaction", isDebugging);
- this.writeTransactionsRegistry = new CloseTrackedRegistry<>(this, "newWriteOnlyTransaction", isDebugging);
- this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadWriteTransaction", isDebugging);
+ final String db = "DataBroker";
+ this.transactionChainsRegistry = new CloseTrackedRegistry<>(db, "createTransactionChain()", isDebugging);
+ this.readOnlyTransactionsRegistry = new CloseTrackedRegistry<>(db, "newReadOnlyTransaction()", isDebugging);
+ this.writeTransactionsRegistry = new CloseTrackedRegistry<>(db, "newWriteOnlyTransaction()", isDebugging);
+ this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(db, "newReadWriteTransaction()", isDebugging);
}
private void configure(Config config) {
@Nonnull DOMDataTreeIdentifier domDataTreeIdentifier, @Nonnull L listener) {
if (isRegistrationWatched(domDataTreeIdentifier.getRootIdentifier(),
domDataTreeIdentifier.getDatastoreType(), DataChangeScope.SUBTREE)) {
- LOG.warn("Registration (registerDataTreeChangeListener) for {} from {}",
+ LOG.warn("{} registration (registerDataTreeChangeListener) for {} from {}.",
+ listener instanceof ClusteredDOMDataTreeChangeListener ? "Clustered" : "Non-clustered",
toPathString(domDataTreeIdentifier.getRootIdentifier()), getStackSummary());
}
return treeChangeSvc.registerDataTreeChangeListener(domDataTreeIdentifier, listener);
return res;
}
+
+ @Override
+ 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 some not yet (or never..) closed transaction[chain]s!");
+ ps.println("[NB: If no stack traces are shown below, then "
+ + "enable transaction-debug-context-enabled in mdsaltrace_config.xml]");
+ 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
+ Set<CloseTrackedRegistryReportEntry<TracingTransactionChain>>
+ entries = transactionChainsRegistry.getAllUnique();
+ if (!entries.isEmpty()) {
+ ps.println(" " + transactionChainsRegistry.getAnchor() + " : "
+ + transactionChainsRegistry.getCreateDescription());
+ }
+ entries.forEach(entry -> {
+ ps.println(" " + entry.getNumberAddedNotRemoved() + "x TransactionChains opened but not closed here:");
+ printStackTraceElements(ps, " ", entry.getStackTraceElements());
+ @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 <T extends CloseTracked<T>> void printRegistryOpenTransactions(
+ CloseTrackedRegistry<T> registry, PrintStream ps, String indent) {
+ Set<CloseTrackedRegistryReportEntry<T>> entries = registry.getAllUnique();
+ if (!entries.isEmpty()) {
+ ps.println(indent + registry.getAnchor() + " : " + registry.getCreateDescription());
+ }
+ entries.forEach(entry -> {
+ ps.println(indent + " " + entry.getNumberAddedNotRemoved()
+ + "x transactions opened here, which are not closed:");
+ printStackTraceElements(ps, indent + " ", entry.getStackTraceElements());
+ });
+ if (!entries.isEmpty()) {
+ ps.println();
+ }
+ }
+
+ private void printStackTraceElements(PrintStream ps, String indent, List<StackTraceElement> stackTraceElements) {
+ boolean ellipsis = false;
+ for (final StackTraceElement stackTraceElement : stackTraceElements) {
+ if (isStackTraceElementInteresting(stackTraceElement)) {
+ ps.println(indent + stackTraceElement);
+ ellipsis = false;
+ } else if (!ellipsis) {
+ ps.println(indent + "(...)");
+ ellipsis = true;
+ }
+ }
+ }
+
+ private boolean isStackTraceElementInteresting(StackTraceElement element) {
+ final String className = element.getClassName();
+ return !className.startsWith(getClass().getPackage().getName())
+ && !className.startsWith(CloseTracked.class.getPackage().getName())
+ && !className.startsWith("Proxy")
+ && !className.startsWith("akka")
+ && !className.startsWith("scala")
+ && !className.startsWith("sun.reflect")
+ && !className.startsWith("java.lang.reflect")
+ && !className.startsWith("org.apache.aries.blueprint")
+ && !className.startsWith("org.osgi.util.tracker");
+ }
}