2 * Copyright (c) 2016 Red Hat, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.md.sal.trace.dom.impl;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
14 import java.util.Objects;
15 import javax.annotation.Nonnull;
17 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
18 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
19 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
20 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
21 import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
22 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
23 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
24 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
29 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
30 import org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker;
31 import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
32 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.Config;
34 import org.opendaylight.yangtools.concepts.ListenerRegistration;
35 import org.opendaylight.yangtools.yang.binding.DataObject;
36 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
41 @SuppressWarnings("checkstyle:JavadocStyle")
42 //...because otherwise it whines about the elements in the @code block even though it's completely valid Javadoc
45 * TracingBroker logs "write" operations and listener registrations to the md-sal. It logs the instance identifier path,
46 * the objects themselves, as well as the stack trace of the call invoking the registration or write operation.
47 * It works by operating as a "bump on the stack" between the application and actual DataBroker, intercepting write
48 * and registration calls and writing to the log.
50 * <p>In addition, it (optionally) can also keep track of the stack trace of all new transaction allocations
51 * (including TransactionChains, and transactions created in turn from them), in order to detect and report leaks
52 * results from transactions which were not closed.
55 * TracingBroker is designed to be easy to use. In fact, for bundles using Blueprint to inject their DataBroker
56 * TracingBroker can be used without modifying your code at all in two simple steps:
59 * Simply add the dependency "mdsaltrace-features" to
64 * <groupId>org.opendaylight.controller</groupId>
65 * <artifactId>mdsal-trace-features</artifactId>
66 * <classifier>features</classifier>
68 * <scope>runtime</scope>
69 * <version>0.1.6-SNAPSHOT</version>
75 * Then just load the odl-mdsal-trace feature before your feature and you're done.
78 * This works because the mdsaltrace-impl bundle registers its service implementing DOMDataBroker with a higher
79 * rank than sal-binding-broker. As such, any OSGi service lookup for DataBroker will receive the TracingBroker.
81 * <h1>Avoiding log bloat:</h1>
82 * TracingBroker can be configured to only print registrations or write ops pertaining to certain subtrees of the
83 * md-sal. This can be done in the code via the methods of this class or via a config file. TracingBroker uses a more
84 * convenient but non-standard representation of the instance identifiers. Each instance identifier segment's
85 * class.getSimpleName() is used separated by a '/'.
87 * <h1>Known issues</h1>
90 * Filtering by paths. For some registrations the codec that converts back from the DOM to binding paths is
91 * busted. As such, an aproximated path is used in the output. For now it is recommended not to use
92 * watchRegistrations and allow all registrations to be logged.
97 public class TracingBroker implements TracingDOMDataBroker {
99 static final Logger LOG = LoggerFactory.getLogger(TracingBroker.class);
101 private static final int STACK_TRACE_FIRST_RELEVANT_FRAME = 2;
103 private final BindingNormalizedNodeSerializer codec;
104 private final DOMDataBroker delegate;
105 private final List<Watch> registrationWatches = new ArrayList<>();
106 private final List<Watch> writeWatches = new ArrayList<>();
108 private final boolean isDebugging;
109 private final CloseTrackedRegistry<TracingTransactionChain> transactionChainsRegistry;
110 private final CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry;
111 private final CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry;
112 private final CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry;
114 private class Watch {
115 final String iidString;
116 final LogicalDatastoreType store;
118 Watch(String iidString, LogicalDatastoreType storeOrNull) {
119 this.store = storeOrNull;
120 this.iidString = iidString;
123 private String toIidCompString(YangInstanceIdentifier iid) {
124 StringBuilder builder = new StringBuilder();
125 toPathString(iid, builder);
127 return builder.toString();
130 private boolean isParent(String parent, String child) {
131 int parentOffset = 0;
132 if (parent.length() > 0 && parent.charAt(0) == '<') {
133 parentOffset = parent.indexOf('>') + 1;
137 if (child.length() > 0 && child.charAt(0) == '<') {
138 childOffset = child.indexOf('>') + 1;
141 return child.startsWith(parent.substring(parentOffset), childOffset);
144 public boolean subtreesOverlap(YangInstanceIdentifier iid, LogicalDatastoreType store,
145 AsyncDataBroker.DataChangeScope scope) {
146 if (this.store != null && !this.store.equals(store)) {
150 String otherIidString = toIidCompString(iid);
153 return isParent(iidString, otherIidString);
154 case ONE: //for now just treat like SUBTREE, even though it's not
156 return isParent(iidString, otherIidString) || isParent(otherIidString, iidString);
162 public boolean eventIsOfInterest(YangInstanceIdentifier iid, LogicalDatastoreType store) {
163 if (this.store != null && !this.store.equals(store)) {
167 return isParent(iidString, toPathString(iid));
171 public TracingBroker(DOMDataBroker delegate, Config config, BindingNormalizedNodeSerializer codec) {
172 this.delegate = Objects.requireNonNull(delegate);
173 this.codec = Objects.requireNonNull(codec);
176 if (config.isTransactionDebugContextEnabled() != null) {
177 this.isDebugging = config.isTransactionDebugContextEnabled();
179 this.isDebugging = false;
181 this.transactionChainsRegistry = new CloseTrackedRegistry<>(this, "createTransactionChain", isDebugging);
182 this.readOnlyTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadOnlyTransaction", isDebugging);
183 this.writeTransactionsRegistry = new CloseTrackedRegistry<>(this, "newWriteOnlyTransaction", isDebugging);
184 this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(this, "newReadWriteTransaction", isDebugging);
187 private void configure(Config config) {
188 registrationWatches.clear();
189 List<String> paths = config.getRegistrationWatches();
191 for (String path : paths) {
192 watchRegistrations(path, null);
196 writeWatches.clear();
197 paths = config.getWriteWatches();
199 for (String path : paths) {
200 watchWrites(path, null);
206 * Log registrations to this subtree of the md-sal.
207 * @param iidString the iid path of the root of the subtree
208 * @param store Which LogicalDataStore? or null for both
210 public void watchRegistrations(String iidString, LogicalDatastoreType store) {
211 LOG.info("Watching registrations to {} in {}", iidString, store);
212 registrationWatches.add(new Watch(iidString, store));
216 * Log writes to this subtree of the md-sal.
217 * @param iidString the iid path of the root of the subtree
218 * @param store Which LogicalDataStore? or null for both
220 public void watchWrites(String iidString, LogicalDatastoreType store) {
221 LOG.info("Watching writes to {} in {}", iidString, store);
222 Watch watch = new Watch(iidString, store);
223 writeWatches.add(watch);
226 private boolean isRegistrationWatched(YangInstanceIdentifier iid,
227 LogicalDatastoreType store, DataChangeScope scope) {
228 if (registrationWatches.isEmpty()) {
232 for (Watch regInterest : registrationWatches) {
233 if (regInterest.subtreesOverlap(iid, store, scope)) {
241 boolean isWriteWatched(YangInstanceIdentifier iid, LogicalDatastoreType store) {
242 if (writeWatches.isEmpty()) {
246 for (Watch watch : writeWatches) {
247 if (watch.eventIsOfInterest(iid, store)) {
255 static void toPathString(InstanceIdentifier<? extends DataObject> iid, StringBuilder builder) {
256 for (InstanceIdentifier.PathArgument pathArg : iid.getPathArguments()) {
257 builder.append('/').append(pathArg.getType().getSimpleName());
261 String toPathString(YangInstanceIdentifier yiid) {
262 StringBuilder sb = new StringBuilder();
263 toPathString(yiid, sb);
264 return sb.toString();
268 private void toPathString(YangInstanceIdentifier yiid, StringBuilder sb) {
269 InstanceIdentifier<?> iid = codec.fromYangInstanceIdentifier(yiid);
271 reconstructIidPathString(yiid, sb);
273 toPathString(iid, sb);
277 private void reconstructIidPathString(YangInstanceIdentifier yiid, StringBuilder sb) {
278 sb.append("<RECONSTRUCTED FROM: \"").append(yiid.toString()).append("\">");
279 for (YangInstanceIdentifier.PathArgument pathArg : yiid.getPathArguments()) {
280 if (pathArg instanceof YangInstanceIdentifier.AugmentationIdentifier) {
281 sb.append('/').append("AUGMENTATION");
284 sb.append('/').append(pathArg.getNodeType().getLocalName());
288 String getStackSummary() {
289 StackTraceElement[] stack = Thread.currentThread().getStackTrace();
291 StringBuilder sb = new StringBuilder();
292 for (int i = STACK_TRACE_FIRST_RELEVANT_FRAME; i < stack.length; i++) {
293 StackTraceElement frame = stack[i];
294 sb.append("\n\t(TracingBroker)\t").append(frame.getClassName()).append('.').append(frame.getMethodName());
297 return sb.toString();
301 public DOMDataReadWriteTransaction newReadWriteTransaction() {
302 return new TracingReadWriteTransaction(delegate.newReadWriteTransaction(), this, readWriteTransactionsRegistry);
306 public DOMDataWriteTransaction newWriteOnlyTransaction() {
307 return new TracingWriteTransaction(delegate.newWriteOnlyTransaction(), this, writeTransactionsRegistry);
311 public ListenerRegistration<DOMDataChangeListener> registerDataChangeListener(
312 LogicalDatastoreType store, YangInstanceIdentifier yiid,
313 DOMDataChangeListener listener, DataChangeScope scope) {
314 if (isRegistrationWatched(yiid, store, scope)) {
315 LOG.warn("Registration (registerDataChangeListener) for {} from {}",
316 toPathString(yiid), getStackSummary());
318 return delegate.registerDataChangeListener(store, yiid, listener, scope);
322 public DOMTransactionChain createTransactionChain(TransactionChainListener transactionChainListener) {
323 return new TracingTransactionChain(
324 delegate.createTransactionChain(transactionChainListener), this, transactionChainsRegistry);
328 public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
329 return new TracingReadOnlyTransaction(delegate.newReadOnlyTransaction(), this, readOnlyTransactionsRegistry);
334 public Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> getSupportedExtensions() {
335 Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> res = delegate.getSupportedExtensions();
336 DOMDataTreeChangeService treeChangeSvc = (DOMDataTreeChangeService) res.get(DOMDataTreeChangeService.class);
337 if (treeChangeSvc == null) {
341 res = new HashMap<>(res);
343 res.put(DOMDataTreeChangeService.class, new DOMDataTreeChangeService() {
346 public <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerDataTreeChangeListener(
347 @Nonnull DOMDataTreeIdentifier domDataTreeIdentifier, @Nonnull L listener) {
348 if (isRegistrationWatched(domDataTreeIdentifier.getRootIdentifier(),
349 domDataTreeIdentifier.getDatastoreType(), DataChangeScope.SUBTREE)) {
350 LOG.warn("Registration (registerDataTreeChangeListener) for {} from {}",
351 toPathString(domDataTreeIdentifier.getRootIdentifier()), getStackSummary());
353 return treeChangeSvc.registerDataTreeChangeListener(domDataTreeIdentifier, listener);