--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-parent</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-api</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-core-api</artifactId>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-artifacts</id>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/classes/initial/mdsaltrace_config.xml</file>
+ <type>xml</type>
+ <classifier>config</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2016 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.api;
+
+import java.io.PrintStream;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+
+/**
+ * Interface so that the tracing broker service can be more explicitly imported.
+ */
+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.
+ * @param minOpenTransactions minimum open number of transactions (leaks with fewer are not printed)
+ * @return true if there were any open transactions, false if none
+ */
+ boolean printOpenTransactions(PrintStream printStream, int minOpenTransactions);
+
+}
--- /dev/null
+<config xmlns="urn:opendaylight:params:xml:ns:yang:mdsaltrace">
+ <!-- Both registration-watches as well as write-watches will
+ log EVERYTHING by default, if we do not constrain any paths;
+ therefore we set a fake one to get nothing out-of-the-box;
+ please remove this first fake one when you configure this
+ to watch what you are really interested in instead: -->
+ <registration-watches>/this/will/never/exist</registration-watches>
+ <!-- <registration-watches>/neutron-router-dpns/router-dpn-list</registration-watches> -->
+ <!-- <registration-watches>/tunnels_state/state-tunnel-list</registration-watches> -->
+
+ <write-watches>/this/will/never/exist</write-watches>
+ <!-- <write-watches> /NetworkTopology/Topology</write-watches> -->
+
+ <!-- Enable or disable transaction context debug. This will preserve the call site trace for
+ transactions, so that the original caller of un-close'd() transaction can be identified.
+ NB: This is a different property from the equally named one in etc/org.opendaylight.controller.cluster.datastore.cfg;
+ that one does something somewhat similar, but serves to include the stack trace on failed transaction submit,
+ whereas this one is specific to odl-mdsal-trace's trace:transaction leak troubleshooting command.
+ [This documentation has been copy/pasted from mdsaltrace.yang, and should be kept in line.] -->
+ <transaction-debug-context-enabled>true</transaction-debug-context-enabled>
+</config>
--- /dev/null
+module mdsaltrace {
+ yang-version 1;
+ namespace "urn:opendaylight:params:xml:ns:yang:mdsaltrace";
+ prefix "mdsaltrace";
+
+ organization
+ "Red Hat, Inc.";
+
+ description
+ "Copyright (c) 2016 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";
+
+ revision "2016-09-08" {
+ description "Initial revision of mdsaltrace model";
+ }
+
+ container config {
+ leaf-list registration-watches {
+ type string;
+ }
+ leaf-list write-watches {
+ type string;
+ }
+ leaf transaction-debug-context-enabled {
+ default false;
+ type boolean;
+ description "Enable or disable transaction context debug. This will preserve the call site trace for
+ transactions, so that the original caller of un-close'd() transaction can be identified.
+ NB: This is a different property from the equally named one in etc/org.opendaylight.controller.cluster.datastore.cfg;
+ that one does something somewhat similar, but serves to include the stack trace on failed transaction submit,
+ whereas this one is specific to odl-mdsal-trace's trace:transaction leak troubleshooting command.";
+ // This ^^^ description is also copy/pasted in mdsaltrace_config.xml, and should be kept in line.]
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-parent</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-binding-impl</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mdsal-trace-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mdsal-trace-dom-impl</artifactId>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ org.opendaylight.controller.md.sal.trace.api,
+ org.opendaylight.controller.md.sal.dom.api,
+ org.opendaylight.controller.md.sal.binding.api,
+ org.opendaylight.controller.md.sal.binding.spi,
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
+ xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+ odl:use-default-for-reference-types="true">
+
+ <reference id="tracingDefaultDOMBroker"
+ interface="org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker"
+ odl:type="default" />
+
+ <reference id="adapterFactory" interface="org.opendaylight.controller.md.sal.binding.spi.AdapterFactory"/>
+
+ <bean id="tracingBindingDataBroker" factory-ref="adapterFactory" factory-method="createDataBroker">
+ <argument ref="tracingDefaultDOMBroker"/>
+ </bean>
+
+ <service id="tracingBindingDataBrokerSvc" ref="tracingBindingDataBroker"
+ interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
+ odl:type="default" ranking="10"/>
+
+ <reference id="tracingPingPongDOMDataBroker"
+ interface="org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker"
+ odl:type="pingpong"/>
+
+ <bean id="bindingTracingPingPongDataBroker" factory-ref="adapterFactory" factory-method="createDataBroker">
+ <argument ref="tracingPingPongDOMDataBroker"/>
+ </bean>
+
+ <service ref="bindingTracingPingPongDataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
+ odl:type="pingpong" ranking="10"/>
+</blueprint>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2017 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-parent</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-cli</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mdsal-trace-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Karaf-Commands>*</Karaf-Commands>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /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.cli;
+
+import java.util.List;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker;
+
+/**
+ * Karaf CLI command to dump all open transactions.
+ *
+ * @author Michael Vorburger.ch
+ */
+@Service
+@Command(scope = "trace", name = "transactions",
+ description = "Show all (still) open transactions; including stack trace of creator, "
+ + "if transaction-debug-context-enabled is true in mdsaltrace_config.xml")
+public class PrintOpenTransactionsCommand implements Action {
+
+ @Argument(index = 0, name = "minOpenTransactions", required = false, multiValued = false,
+ description = "Minimum open number of transactions (leaks with fewer are suppressed)")
+ Integer minOpenTransactions = 1;
+
+ @Reference
+ private List<TracingDOMDataBroker> tracingDOMDataBrokers;
+
+ // NB: Do NOT have a non-default constructor for injection of @Reference
+ // Karaf needs a default constructor to create the command - and it works as is.
+
+ @Override
+ @SuppressWarnings("checkstyle:RegexpSingleLineJava")
+ public Object execute() {
+ boolean hasFound = false;
+ for (TracingDOMDataBroker tracingDOMDataBroker : tracingDOMDataBrokers) {
+ hasFound |= tracingDOMDataBroker.printOpenTransactions(System.out, minOpenTransactions);
+ }
+ if (hasFound) {
+ System.out.println(
+ "Actually did find real leaks with more than " + minOpenTransactions + " open transactions");
+ } else {
+ System.out.println(
+ "Did not find any real leaks with more than " + minOpenTransactions + " open transactions");
+ }
+ return hasFound;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=2 tabstop=2: -->
+<!--
+ Copyright (c) 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>deploy-site</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <properties>
+ <stream>latest</stream>
+ <nexus.site.url>dav:https://nexus.opendaylight.org/content/sites/site/${project.groupId}/${stream}/</nexus.site.url>
+ </properties>
+
+ <build>
+ <extensions>
+ <extension>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-webdav-jackrabbit</artifactId>
+ <version>2.9</version>
+ </extension>
+ </extensions>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>3.4</version>
+ <configuration>
+ <inputDirectory>${project.build.directory}/staged-site</inputDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <distributionManagement>
+ <site>
+ <id>opendaylight-site</id>
+ <url>${nexus.site.url}</url>
+ </site>
+ </distributionManagement>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-parent</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <relativePath>../../parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-dom-impl</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mdsal-trace-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sal-core-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sal-broker-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-binding-dom-codec</artifactId>
+ </dependency>
+
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ org.opendaylight.controller.md.sal.dom.broker.impl,
+ *
+ </Import-Package>
+ <Export-Package>org.opendaylight.controller.md.sal.trace.dom.impl</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /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 javax.annotation.Nullable;
+
+/**
+ * Convenience abstract base class for {@link CloseTracked} implementors.
+ *
+ * @author Michael Vorburger.ch
+ */
+public abstract class AbstractCloseTracked<T extends AbstractCloseTracked<T>> implements CloseTracked<T> {
+
+ private final CloseTrackedTrait<T> closeTracker;
+
+ protected AbstractCloseTracked(CloseTrackedRegistry<T> transactionChainRegistry) {
+ this.closeTracker = new CloseTrackedTrait<>(transactionChainRegistry, this);
+ }
+
+ protected void removeFromTrackedRegistry() {
+ closeTracker.removeFromTrackedRegistry();
+ }
+
+ @Override
+ public @Nullable StackTraceElement[] getAllocationContextStackTrace() {
+ return closeTracker.getAllocationContextStackTrace();
+ }
+
+ @Override
+ public final CloseTracked<T> getRealCloseTracked() {
+ return this;
+ }
+}
--- /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 javax.annotation.Nullable;
+
+/**
+ * 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 an object, and the instant it was created.
+ *
+ * @author Michael Vorburger.ch
+ */
+public interface CloseTracked<T extends CloseTracked<T>> {
+
+ /**
+ * This returns the allocation context as {@link StackTraceElement}s. NB that
+ * this is a relatively <b>EXPENSIVE</b> operation! You should only ever call
+ * this when you really need to, e.g. when you actually produce output for
+ * humans, but not too early.
+ */
+ // TODO When we're on Java 9, then instead return a StackWalker.StackFrame[] here?
+ @Nullable StackTraceElement[] getAllocationContextStackTrace();
+
+ CloseTracked<T> getRealCloseTracked();
+}
--- /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.Arrays.asList;
+import static java.util.Collections.emptyList;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+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;
+
+/**
+ * Registry of {@link CloseTracked} instances.
+ *
+ * @author Michael Vorburger.ch
+ */
+@ThreadSafe
+public class CloseTrackedRegistry<T extends CloseTracked<T>> {
+
+ private final Object anchor;
+ private final String createDescription;
+
+ private final Set<CloseTracked<T>> tracked =
+ new ConcurrentSkipListSet<>(Comparator.comparingInt(System::identityHashCode));
+
+ private final boolean isDebugContextEnabled;
+
+ /**
+ * Constructor.
+ *
+ * @param anchor
+ * object where this registry is stored in, used for human output in
+ * logging and other output
+ * @param createDescription
+ * description of creator of instances of this registry, typically
+ * e.g. name of method in the anchor class
+ * @param isDebugContextEnabled
+ * whether or not the call stack should be preserved; this is (of
+ * course) an expensive operation, and should only be used during
+ * troubleshooting
+ */
+ public CloseTrackedRegistry(Object anchor, String createDescription, boolean isDebugContextEnabled) {
+ this.anchor = anchor;
+ this.createDescription = createDescription;
+ this.isDebugContextEnabled = isDebugContextEnabled;
+ }
+
+ public boolean isDebugContextEnabled() {
+ 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);
+ }
+
+ // package protected, not public; only CloseTrackedTrait invokes this
+ void remove(CloseTracked<T> closeTracked) {
+ tracked.remove(closeTracked);
+ }
+
+ /**
+ * Creates and returns a "report" of (currently) tracked but not (yet) closed
+ * instances.
+ *
+ * @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.
+ */
+ // For some reason, FB sees 'map' as useless but it clearly isn't.
+ @SuppressFBWarnings("UC_USELESS_OBJECT")
+ 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();
+ map.merge(stackTraceElements, 1L, (oldValue, value) -> oldValue + 1);
+ }
+
+ 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) {
+ 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() + "]";
+ }
+
+
+}
--- /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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * Implementation of {@link CloseTracked} which can be used as a field in
+ * another class which implements {@link CloseTracked} and delegates its methods
+ * to this.
+ *
+ * <p>This is useful if that class already has another parent class.
+ * If it does not, then it's typically more convenient to just extend AbstractCloseTracked.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class CloseTrackedTrait<T extends CloseTracked<T>> implements CloseTracked<T> {
+
+ // NB: It's important that we keep a Throwable here, and not directly the StackTraceElement[] !
+ // This is because creating a new Throwable() is a lot less expensive in terms of runtime overhead
+ // than actually calling its getStackTrace(), which we can delay until we really need to.
+ // see also e.g. https://stackoverflow.com/a/26122232/421602
+ private final @Nullable Throwable allocationContext;
+ private final CloseTrackedRegistry<T> closeTrackedRegistry;
+ private final CloseTracked<T> realCloseTracked;
+
+ public CloseTrackedTrait(CloseTrackedRegistry<T> transactionChainRegistry, CloseTracked<T> realCloseTracked) {
+ if (transactionChainRegistry.isDebugContextEnabled()) {
+ // NB: We're NOT doing the (expensive) getStackTrace() here just yet (only below)
+ // TODO When we're on Java 9, then instead use the new java.lang.StackWalker API..
+ this.allocationContext = new Throwable();
+ } else {
+ this.allocationContext = null;
+ }
+ this.realCloseTracked = Objects.requireNonNull(realCloseTracked, "realCloseTracked");
+ this.closeTrackedRegistry = Objects.requireNonNull(transactionChainRegistry, "transactionChainRegistry");
+ this.closeTrackedRegistry.add(this);
+ }
+
+ @Override
+ @Nullable
+ @SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS")
+ public StackTraceElement[] getAllocationContextStackTrace() {
+ return allocationContext != null ? allocationContext.getStackTrace() : null;
+ }
+
+ public void removeFromTrackedRegistry() {
+ closeTrackedRegistry.remove(this);
+ }
+
+ @Override
+ public CloseTracked<T> getRealCloseTracked() {
+ return realCloseTracked;
+ }
+
+}
--- /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
+ */
+
+/**
+ * Utilities to track (non) "closing" of objects.
+ */
+// This generic infra may be moved somewhere else, later
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.opendaylight.controller.md.sal.trace.closetracker.impl;
--- /dev/null
+/*
+ * Copyright (c) 2016 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.dom.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+abstract class AbstractTracingWriteTransaction implements DOMDataWriteTransaction {
+
+ private final DOMDataWriteTransaction delegate;
+ private final TracingBroker tracingBroker;
+ private final List<String> logs = new ArrayList<>();
+
+ AbstractTracingWriteTransaction(DOMDataWriteTransaction delegate, TracingBroker tracingBroker) {
+ this.delegate = Objects.requireNonNull(delegate);
+ this.tracingBroker = Objects.requireNonNull(tracingBroker);
+ recordOp(null, null, "instantiate", null);
+ }
+
+ private void recordOp(LogicalDatastoreType store, YangInstanceIdentifier yiid, String method,
+ NormalizedNode<?, ?> node) {
+ if (!tracingBroker.isWriteWatched(yiid, store)) {
+ return;
+ }
+
+ final Object value = node != null ? node.getValue() : null;
+
+ if (value != null && value instanceof ImmutableSet && ((Set<?>)value).isEmpty()) {
+ if (TracingBroker.LOG.isDebugEnabled()) {
+ TracingBroker.LOG.debug("Empty data set write to {}", tracingBroker.toPathString(yiid));
+ }
+ } else {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Method \"").append(method).append('"');
+ if (store != null) {
+ sb.append(" to ").append(store);
+ }
+ if (yiid != null) {
+ sb.append(" at ").append(tracingBroker.toPathString(yiid));
+ }
+ sb.append('.');
+ if (yiid != null) {
+ // If we don’t have an id, we don’t expect data either
+ sb.append(" Data: ");
+ if (node != null) {
+ sb.append(node.getValue());
+ } else {
+ sb.append("null");
+ }
+ }
+ sb.append(" Stack:").append(tracingBroker.getStackSummary());
+ synchronized (this) {
+ logs.add(sb.toString());
+ }
+ }
+ }
+
+ private synchronized void logOps() {
+ synchronized (this) {
+ if (TracingBroker.LOG.isWarnEnabled()) {
+ TracingBroker.LOG.warn("Transaction {} contains the following operations:", getIdentifier());
+ logs.forEach(TracingBroker.LOG::warn);
+ }
+ logs.clear();
+ }
+ }
+
+ @Override
+ public void put(LogicalDatastoreType store, YangInstanceIdentifier yiid, NormalizedNode<?, ?> node) {
+ recordOp(store, yiid, "put", node);
+ delegate.put(store, yiid, node);
+ }
+
+ @Override
+ public void merge(LogicalDatastoreType store, YangInstanceIdentifier yiid, NormalizedNode<?, ?> node) {
+ recordOp(store, yiid, "merge", node);
+ delegate.merge(store, yiid, node);
+ }
+
+ @Override
+ public boolean cancel() {
+ synchronized (this) {
+ logs.clear();
+ }
+ return delegate.cancel();
+ }
+
+ @Override
+ public void delete(LogicalDatastoreType store, YangInstanceIdentifier yiid) {
+ recordOp(store, yiid, "delete", null);
+ delegate.delete(store, yiid);
+ }
+
+ @Override
+ public FluentFuture<? extends CommitInfo> commit() {
+ recordOp(null, null, "commit", null);
+ logOps();
+ return delegate.commit();
+ }
+
+ @Override
+ @Nonnull
+ public Object getIdentifier() {
+ return delegate.getIdentifier();
+ }
+
+ // https://jira.opendaylight.org/browse/CONTROLLER-1792
+
+ @Override
+ public final boolean equals(Object object) {
+ return object == this || delegate.equals(object);
+ }
+
+ @Override
+ public final int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public final String toString() {
+ return getClass().getName() + "; delegate=" + delegate;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 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.dom.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+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.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
+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;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("checkstyle:JavadocStyle")
+//...because otherwise it whines about the elements in the @code block even though it's completely valid Javadoc
+
+/**
+ * TracingBroker logs "write" operations and listener registrations to the md-sal. It logs the instance identifier path,
+ * 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:
+ * <pre>
+ * {@code
+ * <dependency>
+ * <groupId>org.opendaylight.controller</groupId>
+ * <artifactId>features-mdsal-trace</artifactId>
+ * <version>1.7.0-SNAPSHOT</version>
+ * <classifier>features</classifier>
+ * <type>xml</type>
+ * <scope>runtime</scope>
+ * </dependency>
+ * }
+ * </pre>
+ * </li>
+ * <li>
+ * 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
+ * rank than sal-binding-broker. As such, any OSGi service lookup for DataBroker will receive the TracingBroker.
+ * <p> </p>
+ * <h1>Avoiding log bloat:</h1>
+ * TracingBroker can be configured to only print registrations or write ops pertaining to certain subtrees of the
+ * md-sal. This can be done in the code via the methods of this class or via a config file. TracingBroker uses a more
+ * convenient but non-standard representation of the instance identifiers. Each instance identifier segment's
+ * class.getSimpleName() is used separated by a '/'.
+ * <p> </p>
+ * <h1>Known issues</h1>
+ * <ul>
+ * <li>
+ * Filtering by paths. For some registrations the codec that converts back from the DOM to binding paths is
+ * busted. As such, an aproximated path is used in the output. For now it is recommended not to use
+ * watchRegistrations and allow all registrations to be logged.
+ * </li>
+ * </ul>
+ *
+ */
+public class TracingBroker implements TracingDOMDataBroker {
+ @SuppressFBWarnings("SLF4J_LOGGER_SHOULD_BE_PRIVATE")
+ static final Logger LOG = LoggerFactory.getLogger(TracingBroker.class);
+
+ private static final int STACK_TRACE_FIRST_RELEVANT_FRAME = 2;
+
+ private final String type; // "default" VS "pingpong"
+ private final BindingNormalizedNodeSerializer codec;
+ private final DOMDataBroker delegate;
+ private final List<Watch> registrationWatches = new ArrayList<>();
+ private final List<Watch> writeWatches = new ArrayList<>();
+
+ private final boolean isDebugging;
+ private final CloseTrackedRegistry<TracingTransactionChain> transactionChainsRegistry;
+ private final CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry;
+ private final CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry;
+ private final CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry;
+
+ private class Watch {
+ final String iidString;
+ final LogicalDatastoreType store;
+
+ Watch(String iidString, LogicalDatastoreType storeOrNull) {
+ this.store = storeOrNull;
+ this.iidString = iidString;
+ }
+
+ private String toIidCompString(YangInstanceIdentifier iid) {
+ StringBuilder builder = new StringBuilder();
+ toPathString(iid, builder);
+ builder.append('/');
+ return builder.toString();
+ }
+
+ private boolean isParent(String parent, String child) {
+ int parentOffset = 0;
+ if (parent.length() > 0 && parent.charAt(0) == '<') {
+ parentOffset = parent.indexOf('>') + 1;
+ }
+
+ int childOffset = 0;
+ if (child.length() > 0 && child.charAt(0) == '<') {
+ childOffset = child.indexOf('>') + 1;
+ }
+
+ return child.startsWith(parent.substring(parentOffset), childOffset);
+ }
+
+ @SuppressWarnings({ "checkstyle:hiddenField", "hiding" })
+ public boolean subtreesOverlap(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+ if (this.store != null && !this.store.equals(store)) {
+ return false;
+ }
+
+ String otherIidString = toIidCompString(iid);
+ return isParent(iidString, otherIidString) || isParent(otherIidString, iidString);
+ }
+
+ @SuppressWarnings({ "checkstyle:hiddenField", "hiding" })
+ public boolean eventIsOfInterest(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+ if (this.store != null && !this.store.equals(store)) {
+ return false;
+ }
+
+ return isParent(iidString, toPathString(iid));
+ }
+ }
+
+ public TracingBroker(String type, DOMDataBroker delegate, Config config, BindingNormalizedNodeSerializer codec) {
+ this.type = requireNonNull(type, "type");
+ this.delegate = requireNonNull(delegate, "delegate");
+ this.codec = requireNonNull(codec, "codec");
+ configure(config);
+
+ if (config.isTransactionDebugContextEnabled() != null) {
+ this.isDebugging = config.isTransactionDebugContextEnabled();
+ } else {
+ this.isDebugging = false;
+ }
+ 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) {
+ registrationWatches.clear();
+ List<String> paths = config.getRegistrationWatches();
+ if (paths != null) {
+ for (String path : paths) {
+ watchRegistrations(path, null);
+ }
+ }
+
+ writeWatches.clear();
+ paths = config.getWriteWatches();
+ if (paths != null) {
+ for (String path : paths) {
+ watchWrites(path, null);
+ }
+ }
+ }
+
+ /**
+ * Log registrations to this subtree of the md-sal.
+ * @param iidString the iid path of the root of the subtree
+ * @param store Which LogicalDataStore? or null for both
+ */
+ public void watchRegistrations(String iidString, LogicalDatastoreType store) {
+ LOG.info("Watching registrations to {} in {}", iidString, store);
+ registrationWatches.add(new Watch(iidString, store));
+ }
+
+ /**
+ * Log writes to this subtree of the md-sal.
+ * @param iidString the iid path of the root of the subtree
+ * @param store Which LogicalDataStore? or null for both
+ */
+ public void watchWrites(String iidString, LogicalDatastoreType store) {
+ LOG.info("Watching writes to {} in {}", iidString, store);
+ Watch watch = new Watch(iidString, store);
+ writeWatches.add(watch);
+ }
+
+ private boolean isRegistrationWatched(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+ if (registrationWatches.isEmpty()) {
+ return true;
+ }
+
+ for (Watch regInterest : registrationWatches) {
+ if (regInterest.subtreesOverlap(iid, store)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ boolean isWriteWatched(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+ if (writeWatches.isEmpty()) {
+ return true;
+ }
+
+ for (Watch watch : writeWatches) {
+ if (watch.eventIsOfInterest(iid, store)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static void toPathString(InstanceIdentifier<? extends DataObject> iid, StringBuilder builder) {
+ for (InstanceIdentifier.PathArgument pathArg : iid.getPathArguments()) {
+ builder.append('/').append(pathArg.getType().getSimpleName());
+ }
+ }
+
+ String toPathString(YangInstanceIdentifier yiid) {
+ StringBuilder sb = new StringBuilder();
+ toPathString(yiid, sb);
+ return sb.toString();
+ }
+
+
+ private void toPathString(YangInstanceIdentifier yiid, StringBuilder sb) {
+ InstanceIdentifier<?> iid = codec.fromYangInstanceIdentifier(yiid);
+ if (null == iid) {
+ reconstructIidPathString(yiid, sb);
+ } else {
+ toPathString(iid, sb);
+ }
+ }
+
+ private void reconstructIidPathString(YangInstanceIdentifier yiid, StringBuilder sb) {
+ sb.append("<RECONSTRUCTED FROM: \"").append(yiid.toString()).append("\">");
+ for (YangInstanceIdentifier.PathArgument pathArg : yiid.getPathArguments()) {
+ if (pathArg instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+ sb.append('/').append("AUGMENTATION");
+ continue;
+ }
+ sb.append('/').append(pathArg.getNodeType().getLocalName());
+ }
+ }
+
+ String getStackSummary() {
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = STACK_TRACE_FIRST_RELEVANT_FRAME; i < stack.length; i++) {
+ StackTraceElement frame = stack[i];
+ sb.append("\n\t(TracingBroker)\t").append(frame.getClassName()).append('.').append(frame.getMethodName());
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public DOMDataReadWriteTransaction newReadWriteTransaction() {
+ return new TracingReadWriteTransaction(delegate.newReadWriteTransaction(), this, readWriteTransactionsRegistry);
+ }
+
+ @Override
+ public DOMDataWriteTransaction newWriteOnlyTransaction() {
+ return new TracingWriteTransaction(delegate.newWriteOnlyTransaction(), this, writeTransactionsRegistry);
+ }
+
+ @Override
+ public DOMTransactionChain createTransactionChain(TransactionChainListener transactionChainListener) {
+ return new TracingTransactionChain(
+ delegate.createTransactionChain(transactionChainListener), this, transactionChainsRegistry);
+ }
+
+ @Override
+ public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+ return new TracingReadOnlyTransaction(delegate.newReadOnlyTransaction(), readOnlyTransactionsRegistry);
+ }
+
+ @Nonnull
+ @Override
+ public Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> getSupportedExtensions() {
+ Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> res = delegate.getSupportedExtensions();
+ DOMDataTreeChangeService treeChangeSvc = (DOMDataTreeChangeService) res.get(DOMDataTreeChangeService.class);
+ if (treeChangeSvc == null) {
+ return res;
+ }
+
+ res = new HashMap<>(res);
+
+ res.put(DOMDataTreeChangeService.class, new DOMDataTreeChangeService() {
+ @Nonnull
+ @Override
+ public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerDataTreeChangeListener(
+ @Nonnull DOMDataTreeIdentifier domDataTreeIdentifier, @Nonnull L listener) {
+ if (isRegistrationWatched(domDataTreeIdentifier.getRootIdentifier(),
+ domDataTreeIdentifier.getDatastoreType())) {
+ 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, int minOpenTXs) {
+ if (transactionChainsRegistry.getAllUnique().isEmpty()
+ && readOnlyTransactionsRegistry.getAllUnique().isEmpty()
+ && writeTransactionsRegistry.getAllUnique().isEmpty()
+ && readWriteTransactionsRegistry.getAllUnique().isEmpty()) {
+
+ ps.println(type + ": No open transactions, great!");
+ return false;
+ }
+
+ ps.println(type + ": " + 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();
+ // Flag to track if we really found any real leaks with more (or equal) to minOpenTXs
+ boolean hasFound = print(readOnlyTransactionsRegistry, ps, " ", minOpenTXs);
+ hasFound |= print(writeTransactionsRegistry, ps, " ", minOpenTXs);
+ hasFound |= print(readWriteTransactionsRegistry, ps, " ", minOpenTXs);
+
+ // 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());
+ }
+ for (CloseTrackedRegistryReportEntry<TracingTransactionChain> entry : entries) {
+ ps.println(" " + entry.getNumberAddedNotRemoved() + "x TransactionChains opened but not closed here:");
+ printStackTraceElements(ps, " ", entry.getStackTraceElements());
+ @SuppressWarnings("resource")
+ TracingTransactionChain txChain = (TracingTransactionChain) entry
+ .getExampleCloseTracked().getRealCloseTracked();
+ hasFound |= print(txChain.getReadOnlyTransactionsRegistry(), ps, " ", minOpenTXs);
+ hasFound |= print(txChain.getWriteTransactionsRegistry(), ps, " ", minOpenTXs);
+ hasFound |= print(txChain.getReadWriteTransactionsRegistry(), ps, " ", minOpenTXs);
+ }
+ ps.println();
+
+ return hasFound;
+ }
+
+ private <T extends CloseTracked<T>> boolean print(
+ CloseTrackedRegistry<T> registry, PrintStream ps, String indent, int minOpenTransactions) {
+ Set<CloseTrackedRegistryReportEntry<T>> unsorted = registry.getAllUnique();
+ if (unsorted.size() < minOpenTransactions) {
+ return false;
+ }
+
+ List<CloseTrackedRegistryReportEntry<T>> entries = new ArrayList<>(unsorted);
+ entries.sort((o1, o2) -> Long.compare(o2.getNumberAddedNotRemoved(), o1.getNumberAddedNotRemoved()));
+
+ 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();
+ }
+ return true;
+ }
+
+ 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");
+ }
+}
--- /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.dom.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.AbstractCloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+class TracingReadOnlyTransaction
+ extends AbstractCloseTracked<TracingReadOnlyTransaction>
+ implements DOMDataReadOnlyTransaction {
+
+ private final DOMDataReadOnlyTransaction delegate;
+
+ TracingReadOnlyTransaction(DOMDataReadOnlyTransaction delegate,
+ CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry) {
+ super(readOnlyTransactionsRegistry);
+ this.delegate = delegate;
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(LogicalDatastoreType store,
+ YangInstanceIdentifier path) {
+ return delegate.read(store, path);
+ }
+
+ @Override
+ public CheckedFuture<Boolean, ReadFailedException> exists(LogicalDatastoreType store, YangInstanceIdentifier path) {
+ return delegate.exists(store, path);
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return delegate.getIdentifier();
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ super.removeFromTrackedRegistry();
+ }
+
+
+ // https://jira.opendaylight.org/browse/CONTROLLER-1792
+
+ @Override
+ public final boolean equals(Object object) {
+ return object == this || delegate.equals(object);
+ }
+
+ @Override
+ public final int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public final String toString() {
+ return getClass().getName() + "; delegate=" + delegate;
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2016 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.dom.impl;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.Objects;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+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.CloseTrackedTrait;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+class TracingReadWriteTransaction
+ extends AbstractTracingWriteTransaction
+ implements DOMDataReadWriteTransaction, CloseTracked<TracingReadWriteTransaction> {
+
+ private final CloseTrackedTrait<TracingReadWriteTransaction> closeTracker;
+ private final DOMDataReadWriteTransaction delegate;
+
+ TracingReadWriteTransaction(DOMDataReadWriteTransaction delegate, TracingBroker tracingBroker,
+ CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry) {
+ super(delegate, tracingBroker);
+ this.closeTracker = new CloseTrackedTrait<>(readWriteTransactionsRegistry, this);
+ this.delegate = Objects.requireNonNull(delegate);
+ }
+
+ @Override
+ public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+ LogicalDatastoreType store, YangInstanceIdentifier yiid) {
+ return delegate.read(store, yiid);
+ }
+
+ @Override
+ public CheckedFuture<Boolean, ReadFailedException> exists(LogicalDatastoreType store, YangInstanceIdentifier yiid) {
+ return delegate.exists(store, yiid);
+ }
+
+ @Override
+ public FluentFuture<? extends CommitInfo> commit() {
+ closeTracker.removeFromTrackedRegistry();
+ return super.commit();
+ }
+
+ @Override
+ public boolean cancel() {
+ closeTracker.removeFromTrackedRegistry();
+ return super.cancel();
+ }
+
+ @Override
+ public StackTraceElement[] getAllocationContextStackTrace() {
+ return closeTracker.getAllocationContextStackTrace();
+ }
+
+ @Override
+ public CloseTracked<TracingReadWriteTransaction> getRealCloseTracked() {
+ return this;
+ }
+}
--- /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.dom.impl;
+
+import java.util.Objects;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+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.closetracker.impl.AbstractCloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+
+class TracingTransactionChain extends AbstractCloseTracked<TracingTransactionChain> implements DOMTransactionChain {
+
+ private final DOMTransactionChain delegate;
+ private final TracingBroker tracingBroker;
+ private final CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry;
+ private final CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry;
+ private final CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry;
+
+ TracingTransactionChain(DOMTransactionChain delegate, TracingBroker tracingBroker,
+ CloseTrackedRegistry<TracingTransactionChain> transactionChainsRegistry) {
+ super(transactionChainsRegistry);
+ this.delegate = Objects.requireNonNull(delegate);
+ this.tracingBroker = Objects.requireNonNull(tracingBroker);
+
+ final boolean isDebug = transactionChainsRegistry.isDebugContextEnabled();
+ String anchor = "TransactionChain@" + Integer.toHexString(hashCode());
+ this.readOnlyTransactionsRegistry = new CloseTrackedRegistry<>(anchor, "newReadOnlyTransaction()", isDebug);
+ this.writeTransactionsRegistry = new CloseTrackedRegistry<>(anchor, "newWriteOnlyTransaction()", isDebug);
+ this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(anchor, "newReadWriteTransaction()", isDebug);
+ }
+
+ @Override
+ @SuppressWarnings("resource")
+ public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+ final DOMDataReadOnlyTransaction tx = delegate.newReadOnlyTransaction();
+ return new TracingReadOnlyTransaction(tx, readOnlyTransactionsRegistry);
+ }
+
+ @Override
+ public DOMDataReadWriteTransaction newReadWriteTransaction() {
+ return new TracingReadWriteTransaction(delegate.newReadWriteTransaction(), tracingBroker,
+ readWriteTransactionsRegistry);
+ }
+
+ @Override
+ public DOMDataWriteTransaction newWriteOnlyTransaction() {
+ final DOMDataWriteTransaction tx = delegate.newWriteOnlyTransaction();
+ return new TracingWriteTransaction(tx, tracingBroker, writeTransactionsRegistry);
+ }
+
+ @Override
+ public void close() {
+ delegate.close();
+ super.removeFromTrackedRegistry();
+ }
+
+ public CloseTrackedRegistry<TracingReadOnlyTransaction> getReadOnlyTransactionsRegistry() {
+ return readOnlyTransactionsRegistry;
+ }
+
+ public CloseTrackedRegistry<TracingReadWriteTransaction> getReadWriteTransactionsRegistry() {
+ return readWriteTransactionsRegistry;
+ }
+
+ public CloseTrackedRegistry<TracingWriteTransaction> getWriteTransactionsRegistry() {
+ return writeTransactionsRegistry;
+ }
+
+
+ // https://jira.opendaylight.org/browse/CONTROLLER-1792
+
+ @Override
+ public final boolean equals(Object object) {
+ return object == this || delegate.equals(object);
+ }
+
+ @Override
+ public final int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public final String toString() {
+ return getClass().getName() + "; delegate=" + delegate;
+ }
+
+}
--- /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.dom.impl;
+
+import com.google.common.util.concurrent.FluentFuture;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+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.CloseTrackedTrait;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+
+class TracingWriteTransaction extends AbstractTracingWriteTransaction
+ implements CloseTracked<TracingWriteTransaction> {
+
+ private final CloseTrackedTrait<TracingWriteTransaction> closeTracker;
+
+ TracingWriteTransaction(DOMDataWriteTransaction delegate, TracingBroker tracingBroker,
+ CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry) {
+ super(delegate, tracingBroker);
+ this.closeTracker = new CloseTrackedTrait<>(writeTransactionsRegistry, this);
+ }
+
+ @Override
+ public FluentFuture<? extends CommitInfo> commit() {
+ closeTracker.removeFromTrackedRegistry();
+ return super.commit();
+ }
+
+ @Override
+ public boolean cancel() {
+ closeTracker.removeFromTrackedRegistry();
+ return super.cancel();
+ }
+
+ @Override
+ public StackTraceElement[] getAllocationContextStackTrace() {
+ return closeTracker.getAllocationContextStackTrace();
+ }
+
+ @Override
+ public CloseTracked<TracingWriteTransaction> getRealCloseTracked() {
+ return this;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+ odl:use-default-for-reference-types="true">
+
+ <odl:clustered-app-config id="mdsalConfig"
+ binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.Config">
+ </odl:clustered-app-config>
+
+ <reference id="codec"
+ interface="org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer"
+ odl:type="default" />
+
+ <!-- Tracing Binding DataBroker -->
+
+ <reference id="realDefaultDOMBroker"
+ interface="org.opendaylight.controller.md.sal.dom.api.DOMDataBroker"
+ odl:type="default" />
+
+ <bean id="tracingDefaultDOMBroker" class="org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker">
+ <argument value="default" />
+ <argument ref="realDefaultDOMBroker" />
+ <argument ref="mdsalConfig" />
+ <argument ref="codec" />
+ </bean>
+
+ <service id="tracingDefaultDOMBrokerSvc" ref="tracingDefaultDOMBroker" ranking="10" odl:type="default">
+ <interfaces>
+ <value>org.opendaylight.controller.md.sal.dom.api.DOMDataBroker</value>
+ <value>org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker</value>
+ </interfaces>
+ </service>
+
+ <!-- Tracing Binding PingPong DataBroker -->
+
+ <reference id="realPingPongDOMDataBroker"
+ interface="org.opendaylight.controller.md.sal.dom.api.DOMDataBroker"
+ odl:type="pingpong"/>
+
+ <bean id="tracingPingPongDOMBroker" class="org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker">
+ <argument value="pingpong" />
+ <argument ref="realPingPongDOMDataBroker" />
+ <argument ref="mdsalConfig" />
+ <argument ref="codec" />
+ </bean>
+
+ <service id="tracingPingPongDOMBrokerSvc" ref="tracingPingPongDOMBroker" ranking="10" odl:type="pingpong">
+ <interfaces>
+ <value>org.opendaylight.controller.md.sal.dom.api.DOMDataBroker</value>
+ <value>org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker</value>
+ </interfaces>
+ </service>
+</blueprint>
--- /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.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+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 {
+
+ 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);
+
+ 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 if (entry.getNumberAddedNotRemoved() == 1) {
+ assertThatIterableContains(entry.getStackTraceElements(),
+ element -> element.getMethodName().equals("testDuplicateAllocationContexts"));
+ } else {
+ fail("Unexpected number of added, not removed: " + entry.getNumberAddedNotRemoved());
+ }
+ });
+ }
+
+ // 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);
+
+ 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.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("mock", domDataBroker, config, codec);
+
+ for (int i = 0; i < 3; i++) {
+ DOMDataReadWriteTransaction tx = tracingBroker.newReadWriteTransaction();
+ }
+ DOMDataReadWriteTransaction anotherTx = tracingBroker.newReadWriteTransaction();
+
+ DOMTransactionChain txChain = tracingBroker.createTransactionChain(null);
+ DOMDataReadWriteTransaction txFromChain = txChain.newReadWriteTransaction();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos);
+ boolean printReturnValue = tracingBroker.printOpenTransactions(ps, 1);
+ String output = new String(baos.toByteArray(), UTF_8);
+
+ assertThat(printReturnValue).isTrue();
+ // Assert expectations about stack trace
+ assertThat(output).contains("testPrintOpenTransactions(TracingBrokerTest.java");
+ assertThat(output).doesNotContain(TracingBroker.class.getName());
+
+ String previousLine = "";
+ for (String line : output.split("\n")) {
+ if (line.contains("(...")) {
+ assertThat(previousLine.contains("(...)")).isFalse();
+ }
+ previousLine = line;
+ }
+
+ // assert that the sorting works - the x3 is shown before the x1
+ assertThat(output).contains(" DataBroker : newReadWriteTransaction()\n 3x");
+
+ // 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).
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>feature-repo-parent</artifactId>
+ <version>3.1.3</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>features-mdsal-trace</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <name>OpenDaylight :: TracingBroker</name>
+ <packaging>feature</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>odl-mdsal-trace</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>single-feature-parent</artifactId>
+ <version>3.1.3</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>odl-mdsal-trace</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <name>OpenDaylight :: TracingBroker</name>
+ <packaging>feature</packaging>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-artifacts</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>odl-mdsal-broker</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>odl-mdsal-broker-local</artifactId>
+ <version>${project.version}</version>
+ <type>xml</type>
+ <classifier>features</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-dom-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-binding-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="odl-mdsal-trace-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0">
+ <feature name="odl-mdsal-trace" version="${project.version}">
+ <configfile finalname="etc/opendaylight/datastore/initial/config/mdsaltrace_config.xml">mvn:org.opendaylight.controller/mdsal-trace-api/${project.version}/xml/config</configfile>
+ </feature>
+</features>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2016 Red Hat 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 INTERNAL
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>odlparent-lite</artifactId>
+ <version>3.1.3</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-feature-aggregator</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>features-mdsal-trace</module>
+ <module>odl-mdsal-trace</module>
+ </modules>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2016 Red Hat 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 INTERNAL
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>odlparent-lite</artifactId>
+ <version>3.1.3</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>mdsal-trace-aggregator</artifactId>
+ <version>1.9.0-SNAPSHOT</version>
+ <name>mdsaltrace</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>api</module>
+ <module>dom-impl</module>
+ <module>binding-impl</module>
+ <module>cli</module>
+ <module>features</module>
+ </modules>
+
+ <!-- DO NOT install or deploy the repo root pom as it's only needed to initiate a build -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <!--
+ This profile is to ensure we only build javadocs reports
+ when we plan to deploy Maven site for our project.
+ -->
+ <id>maven-site</id>
+ <activation>
+ <file>
+ <exists>${user.dir}/deploy-site.xml</exists>
+ </file>
+ </activation>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <inherited>false</inherited>
+ <executions>
+ <execution>
+ <id>aggregate</id>
+ <goals>
+ <goal>aggregate</goal>
+ </goals>
+ <phase>package</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>