From: Alexandre Montplaisir Date: Thu, 27 Aug 2015 23:50:36 +0000 (-0400) Subject: Add filter change notification mechanism to the Java agent X-Git-Url: http://git.efficios.com/?p=deliverable%2Flttng-ust.git;a=commitdiff_plain;h=4fb826b158cbd2cea8f4a91457f49417c261777d Add filter change notification mechanism to the Java agent 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 Signed-off-by: Mathieu Desnoyers --- diff --git a/doc/examples/java-jul/FilerChangeListenerExample.java b/doc/examples/java-jul/FilerChangeListenerExample.java new file mode 100644 index 00000000..7e1ab5cd --- /dev/null +++ b/doc/examples/java-jul/FilerChangeListenerExample.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir + * + * 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 diff --git a/doc/examples/java-jul/Makefile b/doc/examples/java-jul/Makefile index 154078bf..8cbc23b3 100644 --- a/doc/examples/java-jul/Makefile +++ b/doc/examples/java-jul/Makefile @@ -40,7 +40,7 @@ JC = javac -classpath "$(CLASSPATH):." .java.class: $(JC) $(JFLAGS) $*.java -CLASSES = Hello.java +CLASSES = Hello.java FilerChangeListenerExample.java all: classes diff --git a/liblttng-ust-java-agent/java/lttng-ust-agent-common/Makefile.am b/liblttng-ust-java-agent/java/lttng-ust-agent-common/Makefile.am index 6b961b55..67c3e1aa 100644 --- a/liblttng-ust-java-agent/java/lttng-ust-agent-common/Makefile.am +++ b/liblttng-ust-java-agent/java/lttng-ust-agent-common/Makefile.am @@ -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 diff --git a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java index ef3b1dbe..8531eaec 100644 --- a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java +++ b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java @@ -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 @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 @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 index 00000000..1a675876 --- /dev/null +++ b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/FilterChangeNotifier.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir + * + * 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 enabledEventRules = new HashMap(); + private final Collection registeredListeners = new LinkedList(); + + + /** + * 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 rulesToRemove = new LinkedList(); + + 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 index 00000000..90883c7a --- /dev/null +++ b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/filter/IFilterChangeListener.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir + * + * 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. + *

+ * Applications wanting to be notified of event filtering rule changes should + * implement this interface, then register their listener using + * {@link FilterChangeNotifier#registerListener}. + *

+ *

+ * 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. + *

+ *

+ * 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. + *

+ *

+ * 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. + *

+ * + * @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); +}