Add filter change notification mechanism to the Java agent
authorAlexandre Montplaisir <alexmonthy@efficios.com>
Thu, 27 Aug 2015 23:50:36 +0000 (19:50 -0400)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Thu, 22 Oct 2015 20:54:54 +0000 (16:54 -0400)
Java applications can now register to receive notifications of event
filtering rules being changed in the tracing session. This can be
used to implement application-specific filtering on the Java side,
to reduce the amount of events sent through JNI.

To do so, they need to implement a IFilterChangeListener and register
it to the FilterChangeNotifier. The listener's callbacks will
be invoked by the LTTng agent when the tracing session(s) change.

A new example file is provided to demo this usage.

Signed-off-by: Alexandre Montplaisir <alexmonthy@efficios.com>
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
doc/examples/java-jul/FilerChangeListenerExample.java [new file with mode: 0644]
doc/examples/java-jul/Makefile
liblttng-ust-java-agent/java/lttng-ust-agent-common/Makefile.am
liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java
liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/FilterChangeNotifier.java [new file with mode: 0644]
liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/IFilterChangeListener.java [new file with mode: 0644]

diff --git a/doc/examples/java-jul/FilerChangeListenerExample.java b/doc/examples/java-jul/FilerChangeListenerExample.java
new file mode 100644 (file)
index 0000000..7e1ab5c
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.lttng.ust.agent.ILttngHandler;
+import org.lttng.ust.agent.filter.FilterChangeNotifier;
+import org.lttng.ust.agent.filter.IFilterChangeListener;
+import org.lttng.ust.agent.jul.LttngLogHandler;
+import org.lttng.ust.agent.session.EventRule;
+import org.lttng.ust.agent.session.LogLevelSelector;
+
+/**
+ * Example usage of a {@link IFilterChangeListener}.
+ *
+ * This listener will simply print to stdout the notifications it receives. To
+ * try it, run the program, then issue "lttng enable-event" and
+ * "lttng disable-event" commands for the JUL domain.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class FilerChangeListenerExample {
+
+       private static class ExampleFilterChangeListener implements IFilterChangeListener {
+
+               @Override
+               public void eventRuleAdded(EventRule rule) {
+                       System.out.println();
+                       System.out.println("New event rule enabled:");
+                       System.out.println("Event name: " + rule.getEventName());
+                       System.out.println(printLogLevel(rule.getLogLevelSelector()));
+                       System.out.println("Filter string: " + rule.getFilterString());
+               }
+
+               @Override
+               public void eventRuleRemoved(EventRule rule) {
+                       System.out.println();
+                       System.out.println("Event rule disabled:");
+                       System.out.println("Event name: " + rule.getEventName());
+                       System.out.println(printLogLevel(rule.getLogLevelSelector()));
+                       System.out.println("Filter string: " + rule.getFilterString());
+               }
+
+               /**
+                * Convenience method to print integer log level values into their JUL
+                * equivalent.
+                */
+               private static String printLogLevel(LogLevelSelector lls) {
+                       String llname = Level.parse(String.valueOf(lls.getLogLevel())).getName();
+                       return "Log level: " + llname + ", filter type: " + lls.getLogLevelType().toString();
+               }
+       }
+
+       /**
+        * Run the program.
+        *
+        * @param args
+        *            Command-line arguments (not used)
+        * @throws IOException
+        */
+       public static void main(String args[]) throws IOException {
+               /* We need at least one log handler to activate the LTTng agent */
+               ILttngHandler handler = new LttngLogHandler();
+
+               /* Create a listener and register it to the manager */
+               IFilterChangeListener listener = new ExampleFilterChangeListener();
+               FilterChangeNotifier.getInstance().registerListener(listener);
+
+               System.out.println("Press Enter to finish.");
+               System.in.read();
+
+               /* Unregister the listener */
+               FilterChangeNotifier.getInstance().unregisterListener(listener);
+
+               /* Cleanup the log handler we created */
+               handler.close();
+       }
+}
\ No newline at end of file
index 154078bf095f75ba44390f1a2b21f0a8ab3920ac..8cbc23b35f70a621e625ed68d3db178a3442f012 100644 (file)
@@ -40,7 +40,7 @@ JC = javac -classpath "$(CLASSPATH):."
 .java.class:
        $(JC) $(JFLAGS) $*.java
 
-CLASSES = Hello.java
+CLASSES = Hello.java FilerChangeListenerExample.java
 
 all: classes
 
index 6b961b55e247ed7592fdd07a946e9047f4852b8e..67c3e1aa1337e53fff5ac81023bbbb9eddc59032 100644 (file)
@@ -22,6 +22,8 @@ dist_noinst_JAVA = $(pkgpath)/AbstractLttngAgent.java \
                                   $(pkgpath)/client/SessiondDisableEventCommand.java \
                                   $(pkgpath)/client/SessiondEnableEventCommand.java \
                                   $(pkgpath)/client/SessiondListLoggersCommand.java \
+                                  $(pkgpath)/filter/FilterChangeNotifier.java \
+                                  $(pkgpath)/filter/IFilterChangeListener.java \
                                   $(pkgpath)/session/EventRule.java \
                                   $(pkgpath)/session/LogLevelSelector.java
 
@@ -30,7 +32,7 @@ dist_noinst_DATA = $(jarfile_manifest)
 
 jar_DATA = $(jarfile)
 
-classes = $(pkgpath)/*.class $(pkgpath)/client/*.class $(pkgpath)/session/*.class
+classes = $(pkgpath)/*.class $(pkgpath)/client/*.class $(pkgpath)/filter/*.class $(pkgpath)/session/*.class
 
 $(jarfile): classnoinst.stamp
        $(JAR) cfm $(JARFLAGS) $@ $(jarfile_manifest) $(classes) && rm -f $(jarfile_symlink) && $(LN_S) $@ $(jarfile_symlink)
@@ -43,4 +45,4 @@ install-data-hook:
 uninstall-hook:
        cd $(DESTDIR)/$(jardir) && rm -f $(jarfile_symlink)
 
-CLEANFILES = $(jarfile) $(pkgpath)/*.class $(pkgpath)/client/*.class $(pkgpath)/session/*.class
+CLEANFILES = $(jarfile) $(pkgpath)/*.class $(pkgpath)/client/*.class $(pkgpath)/filter/*.class $(pkgpath)/session/*.class
index ef3b1dbe9afddddd5922a0e91d9999254c40209e..8531eaec7c3b8ddab23f0b7aa1e668ffc3376124 100644 (file)
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.lttng.ust.agent.client.ILttngTcpClientListener;
 import org.lttng.ust.agent.client.LttngTcpSessiondClient;
+import org.lttng.ust.agent.filter.FilterChangeNotifier;
 import org.lttng.ust.agent.session.EventRule;
 
 /**
@@ -191,6 +192,9 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
 
        @Override
        public boolean eventEnabled(EventRule eventRule) {
+               /* Notify the filter change manager of the command */
+               FilterChangeNotifier.getInstance().addEventRule(eventRule);
+
                String eventName = eventRule.getEventName();
 
                if (eventName.equals(WILDCARD)) {
@@ -208,6 +212,9 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
 
        @Override
        public boolean eventDisabled(String eventName) {
+               /* Notify the filter change manager of the command */
+               FilterChangeNotifier.getInstance().removeEventRules(eventName);
+
                if (eventName.equals(WILDCARD)) {
                        int newCount = enabledWildcards.decrementAndGet();
                        if (newCount < 0) {
diff --git a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/FilterChangeNotifier.java b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/FilterChangeNotifier.java
new file mode 100644 (file)
index 0000000..1a67587
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License, version 2.1 only,
+ * as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.lttng.ust.agent.filter;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.lttng.ust.agent.session.EventRule;
+
+/**
+ * Singleton class managing the filter notifications.
+ *
+ * Applications can register a {@link IFilterChangeListener} to be notified when
+ * event filtering rules change in the tracing sessions.
+ *
+ * @author Alexandre Montplaisir
+ */
+public final class FilterChangeNotifier {
+
+       /** Lazy-loaded singleton instance object */
+       private static FilterChangeNotifier instance = null;
+
+       private final Map<EventRule, Integer> enabledEventRules = new HashMap<EventRule, Integer>();
+       private final Collection<IFilterChangeListener> registeredListeners = new LinkedList<IFilterChangeListener>();
+
+
+       /**
+        * Private constructor, singleton class should not be instantiated directly.
+        */
+       private FilterChangeNotifier() {
+       }
+
+       /**
+        * Get the singleton instance, initializing it if needed.
+        *
+        * @return The singleton instance
+        */
+       public static synchronized FilterChangeNotifier getInstance() {
+               if (instance == null) {
+                       instance = new FilterChangeNotifier();
+               }
+               return instance;
+       }
+
+       /**
+        * Notify the filter manager that a new rule was enabled in a tracing
+        * session ("lttng enable-event ...")
+        *
+        * This is meant to be called by the LTTng Agent only. External Java
+        * applications should not call this.
+        *
+        * @param rule
+        *            The rule that was added
+        */
+       public synchronized void addEventRule(EventRule rule) {
+               Integer count = enabledEventRules.get(rule);
+               if (count == null) {
+                       /*
+                        * This is the first instance of this rule being enabled. Add it to
+                        * the map and send notifications to the registered notifiers.
+                        */
+                       enabledEventRules.put(rule, Integer.valueOf(1));
+                       notifyForAddedRule(rule);
+                       return;
+               }
+               if (count.intValue() <= 0) {
+                       /* It should not have been in the map! */
+                       throw new IllegalStateException();
+               }
+               /*
+                * This exact event rule was already enabled, just increment its
+                * refcount without sending notifications
+                */
+               enabledEventRules.put(rule, Integer.valueOf(count.intValue() + 1));
+       }
+
+       /**
+        * Notify the filter manager that an event name was disabled in the tracing
+        * sessions ("lttng disable-event ...").
+        *
+        * The "disable-event" only specifies an event name. This means all the
+        * rules containing this event name are to be disabled.
+        *
+        * This is meant to be called by the LTTng Agent only. External Java
+        * applications should not call this.
+        *
+        * @param eventName
+        *            The event name to disable
+        */
+       public synchronized void removeEventRules(String eventName) {
+               List<EventRule> rulesToRemove = new LinkedList<EventRule>();
+
+               for (EventRule eventRule : enabledEventRules.keySet()) {
+                       if (eventRule.getEventName().equals(eventName)) {
+                               rulesToRemove.add(eventRule);
+                       }
+               }
+               /*
+                * We cannot modify the map while iterating on it. We have to do the
+                * removal separately from the iteration above.
+                */
+               for (EventRule rule : rulesToRemove) {
+                       removeEventRule(rule);
+               }
+       }
+
+       private synchronized void removeEventRule(EventRule eventRule) {
+               Integer count = enabledEventRules.get(eventRule);
+               if (count == null || count.intValue() <= 0) {
+                       /*
+                        * We were asked us to disable an event rule that was not enabled
+                        * previously. Command error?
+                        */
+                       throw new IllegalStateException();
+               }
+               if (count.intValue() == 1) {
+                       /*
+                        * This is the last instance of this event rule being disabled,
+                        * remove it from the map and send notifications of this rule being
+                        * gone.
+                        */
+                       enabledEventRules.remove(eventRule);
+                       notifyForRemovedRule(eventRule);
+                       return;
+               }
+               /*
+                * Other sessions/daemons are still looking for this event rule, simply
+                * decrement its refcount, and do not send notifications.
+                */
+               enabledEventRules.put(eventRule, Integer.valueOf(count.intValue() - 1));
+
+       }
+
+       /**
+        * Register a new listener to the manager.
+        *
+        * @param listener
+        *            The listener to add
+        */
+       public synchronized void registerListener(IFilterChangeListener listener) {
+               registeredListeners.add(listener);
+
+               /* Send the current rules to the new listener ("statedump") */
+               for (EventRule rule : enabledEventRules.keySet()) {
+                       listener.eventRuleAdded(rule);
+               }
+       }
+
+       /**
+        * Unregister a listener from the manager.
+        *
+        * @param listener
+        *            The listener to remove
+        */
+       public synchronized void unregisterListener(IFilterChangeListener listener) {
+               registeredListeners.remove(listener);
+       }
+
+       private void notifyForAddedRule(final EventRule rule) {
+               for (IFilterChangeListener notifier : registeredListeners) {
+                       notifier.eventRuleAdded(rule);
+               }
+       }
+
+       private void notifyForRemovedRule(final EventRule rule) {
+               for (IFilterChangeListener notifier : registeredListeners) {
+                       notifier.eventRuleRemoved(rule);
+               }
+       }
+}
diff --git a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/IFilterChangeListener.java b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/IFilterChangeListener.java
new file mode 100644 (file)
index 0000000..90883c7
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License, version 2.1 only,
+ * as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package org.lttng.ust.agent.filter;
+
+import org.lttng.ust.agent.session.EventRule;
+
+/**
+ * Filter notification listener interface.
+ * <p>
+ * Applications wanting to be notified of event filtering rule changes should
+ * implement this interface, then register their listener using
+ * {@link FilterChangeNotifier#registerListener}.
+ * </p>
+ * <p>
+ * The callbacks defined in this interface will be called whenever an event rule
+ * is added or removed. The manager will take care of the reference-counting in
+ * case multiple tracing sessions enable the exact same rules. For example, the
+ * {@link #eventRuleRemoved} callback is only called when there are no more
+ * session interested into it.
+ * </p>
+ * <p>
+ * Do not forget to unregister the listener after use, using
+ * {@link FilterChangeNotifier#unregisterListener}. If you do not, or if
+ * you use an anonymous listener for example, these will remain attached until
+ * the complete shutdown of the application.
+ * </p>
+ * <p>
+ * Only one thread is used to dispatch notifications, sequentially. This means
+ * that if a callback hangs it will prevent other listeners from receiving
+ * notifications. Please take care of not blocking inside the listener
+ * callbacks, and use separate threads for potentially long or blocking
+ * operations.
+ * </p>
+ *
+ * @author Alexandre Montplaisir
+ */
+public interface IFilterChangeListener {
+
+       /**
+        * Notification that a new event rule is now enabled in the tracing
+        * sessions.
+        *
+        * @param rule
+        *            The event rule that was enabled
+        */
+       void eventRuleAdded(EventRule rule);
+
+       /**
+        * Notification that an existing event rule is now disabled in the tracing
+        * sessions.
+        *
+        * @param rule
+        *            The event rule that was disabled
+        */
+       void eventRuleRemoved(EventRule rule);
+}
This page took 0.030903 seconds and 4 git commands to generate.