Move to kernel style SPDX license identifiers
[lttng-ust.git] / liblttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / AbstractLttngAgent.java
index ef3b1dbe9afddddd5922a0e91d9999254c40209e..acbdc4f124512225828f900369522db6b81348d8 100644 (file)
@@ -1,36 +1,28 @@
 /*
- * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
- * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ * SPDX-License-Identifier: LGPL-2.1-only
  *
- * 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
+ * Copyright (C) 2015 EfficiOS Inc.
+ * Copyright (C) 2015 Alexandre Montplaisir <alexmonthy@efficios.com>
+ * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
  */
 
 package org.lttng.ust.agent;
 
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
-import java.util.NavigableMap;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentSkipListMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Matcher;
 
 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;
+import org.lttng.ust.agent.utils.LttngUstAgentLogger;
 
 /**
  * Base implementation of a {@link ILttngAgent}.
@@ -42,7 +34,6 @@ import org.lttng.ust.agent.session.EventRule;
 public abstract class AbstractLttngAgent<T extends ILttngHandler>
                implements ILttngAgent<T>, ILttngTcpClientListener {
 
-       private static final String WILDCARD = "*";
        private static final int INIT_TIMEOUT = 3; /* Seconds */
 
        /** The handlers registered to this agent */
@@ -51,29 +42,44 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
        /**
         * The trace events currently enabled in the sessions.
         *
-        * The key represents the event name, the value is the ref count (how many
-        * different sessions currently have this event enabled). Once the ref count
-        * falls to 0, this means we can avoid sending log events through JNI
-        * because nobody wants them.
+        * The key is the {@link EventNamePattern} that comes from the event name.
+        * The value is the ref count (how many different sessions currently have
+        * this event enabled). Once the ref count falls to 0, this means we can
+        * avoid sending log events through JNI because nobody wants them.
         *
-        * It uses a concurrent hash map, so that the {@link #isEventEnabled} and
-        * read methods do not need to take a synchronization lock.
+        * Its accesses should be protected by the {@link #enabledEventNamesLock}
+        * below.
         */
-       private final Map<String, Integer> enabledEvents = new ConcurrentHashMap<String, Integer>();
+       private final Map<EventNamePattern, Integer> enabledPatterns = new HashMap<EventNamePattern, Integer>();
 
        /**
-        * The trace events prefixes currently enabled in the sessions, which means
-        * the event names finishing in *, like "abcd*". We track them separately
-        * from the standard event names, so that we can use {@link String#equals}
-        * and {@link String#startsWith} appropriately.
+        * Cache of already-checked event names. As long as enabled/disabled events
+        * don't change in the session, we can avoid re-checking events that were
+        * previously checked against all known enabled patterns.
         *
-        * We track the lone wildcard "*" separately, in {@link #enabledWildcards}.
+        * Its accesses should be protected by the {@link #enabledEventNamesLock}
+        * below, with the exception of concurrent get operations.
+        */
+       private final Map<String, Boolean> enabledEventNamesCache = new ConcurrentHashMap<String, Boolean>();
+
+       /**
+        * Lock protecting accesses to the {@link #enabledPatterns} and
+        * {@link #enabledEventNamesCache} maps.
         */
-       private final NavigableMap<String, Integer> enabledEventPrefixes =
-                       new ConcurrentSkipListMap<String, Integer>();
+       private final Lock enabledEventNamesLock = new ReentrantLock();
 
-       /** Number of sessions currently enabling the wildcard "*" event */
-       private final AtomicInteger enabledWildcards = new AtomicInteger(0);
+       /**
+        * The application contexts currently enabled in the tracing sessions.
+        *
+        * It is first indexed by context retriever, then by context name. This
+        * allows to efficiently query all the contexts for a given retriever.
+        *
+        * Works similarly as {@link #enabledEvents}, but for app contexts (and with
+        * an extra degree of indexing).
+        *
+        * TODO Could be changed to a Guava Table once/if we start using it.
+        */
+       private final Map<String, Map<String, Integer>> enabledAppContexts = new ConcurrentHashMap<String, Map<String, Integer>>();
 
        /** Tracing domain. Defined by the sub-classes via the constructor. */
        private final Domain domain;
@@ -135,6 +141,9 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                if (initialized) {
                        return;
                }
+
+               LttngUstAgentLogger.log(AbstractLttngAgent.class, "Initializing Agent for domain: " + domain.name());
+
                String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName();
 
                rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true);
@@ -161,6 +170,8 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
         * Dispose the agent
         */
        private void dispose() {
+               LttngUstAgentLogger.log(AbstractLttngAgent.class, "Disposing Agent for domain: " + domain.name());
+
                /*
                 * Only called from a synchronized (registeredHandlers) block, should
                 * not need additional synchronization.
@@ -180,94 +191,159 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                userSessiondClient = null;
                userSessiondClientThread = null;
 
-               /* Reset all enabled event counts to 0 */
-               enabledEvents.clear();
-               enabledEventPrefixes.clear();
-               enabledWildcards.set(0);
+               /*
+                * Send filter change notifications for all event rules currently
+                * active, then clear them.
+                */
+               FilterChangeNotifier fcn = FilterChangeNotifier.getInstance();
 
-               initialized = false;
+               enabledEventNamesLock.lock();
+               try {
+                       for (Map.Entry<EventNamePattern, Integer> entry : enabledPatterns.entrySet()) {
+                               String eventName = entry.getKey().getEventName();
+                               Integer nb = entry.getValue();
+                               for (int i = 0; i < nb.intValue(); i++) {
+                                       fcn.removeEventRules(eventName);
+                               }
+                       }
+                       enabledPatterns.clear();
+                       enabledEventNamesCache.clear();
+               } finally {
+                       enabledEventNamesLock.unlock();
+               }
+
+               /*
+                * Also clear tracked app contexts (no filter notifications sent for
+                * those currently).
+                */
+               enabledAppContexts.clear();
 
+               initialized = false;
        }
 
        @Override
        public boolean eventEnabled(EventRule eventRule) {
+               /* Notify the filter change manager of the command */
+               FilterChangeNotifier.getInstance().addEventRule(eventRule);
+
                String eventName = eventRule.getEventName();
+               EventNamePattern pattern = new EventNamePattern(eventName);
 
-               if (eventName.equals(WILDCARD)) {
-                       enabledWildcards.incrementAndGet();
-                       return true;
-               }
-               if (eventName.endsWith(WILDCARD)) {
-                       /* Strip the "*" from the name. */
-                       String prefix = eventName.substring(0, eventName.length() - 1);
-                       return incrementEventCount(prefix, enabledEventPrefixes);
+               enabledEventNamesLock.lock();
+               try {
+                       boolean ret = incrementRefCount(pattern, enabledPatterns);
+                       enabledEventNamesCache.clear();
+                       return ret;
+               } finally {
+                       enabledEventNamesLock.unlock();
                }
-
-               return incrementEventCount(eventName, enabledEvents);
        }
 
        @Override
        public boolean eventDisabled(String eventName) {
-               if (eventName.equals(WILDCARD)) {
-                       int newCount = enabledWildcards.decrementAndGet();
-                       if (newCount < 0) {
-                               /* Event was not enabled, bring the count back to 0 */
-                               enabledWildcards.incrementAndGet();
-                               return false;
-                       }
-                       return true;
-               }
+               /* Notify the filter change manager of the command */
+               FilterChangeNotifier.getInstance().removeEventRules(eventName);
 
-               if (eventName.endsWith(WILDCARD)) {
-                       /* Strip the "*" from the name. */
-                       String prefix = eventName.substring(0, eventName.length() - 1);
-                       return decrementEventCount(prefix, enabledEventPrefixes);
-               }
+               EventNamePattern pattern = new EventNamePattern(eventName);
 
-               return decrementEventCount(eventName, enabledEvents);
+               enabledEventNamesLock.lock();
+               try {
+                       boolean ret = decrementRefCount(pattern, enabledPatterns);
+                       enabledEventNamesCache.clear();
+                       return ret;
+               } finally {
+                       enabledEventNamesLock.unlock();
+               }
        }
 
        @Override
-       public Iterable<String> listEnabledEvents() {
-               List<String> events = new LinkedList<String>();
+       public boolean appContextEnabled(String contextRetrieverName, String contextName) {
+               synchronized (enabledAppContexts) {
+                       Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName);
+                       if (retrieverMap == null) {
+                               /* There is no submap for this retriever, let's create one. */
+                               retrieverMap = new ConcurrentHashMap<String, Integer>();
+                               enabledAppContexts.put(contextRetrieverName, retrieverMap);
+                       }
 
-               if (enabledWildcards.get() > 0) {
-                       events.add(WILDCARD);
+                       return incrementRefCount(contextName, retrieverMap);
                }
-               for (String prefix : enabledEventPrefixes.keySet()) {
-                       events.add(new String(prefix + WILDCARD));
+       }
+
+       @Override
+       public boolean appContextDisabled(String contextRetrieverName, String contextName) {
+               synchronized (enabledAppContexts) {
+                       Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName);
+                       if (retrieverMap == null) {
+                               /* There was no submap for this retriever, invalid command? */
+                               return false;
+                       }
+
+                       boolean ret = decrementRefCount(contextName, retrieverMap);
+
+                       /* If the submap is now empty we can remove it from the main map. */
+                       if (retrieverMap.isEmpty()) {
+                               enabledAppContexts.remove(contextRetrieverName);
+                       }
+
+                       return ret;
                }
-               events.addAll(enabledEvents.keySet());
-               return events;
        }
 
+       /*
+        * Implementation of this method is domain-specific.
+        */
+       @Override
+       public abstract Collection<String> listAvailableEvents();
+
        @Override
        public boolean isEventEnabled(String eventName) {
-               /* If at least one session enabled the "*" wildcard, send the event */
-               if (enabledWildcards.get() > 0) {
-                       return true;
+               Boolean cachedEnabled = enabledEventNamesCache.get(eventName);
+               if (cachedEnabled != null) {
+                       /* We have seen this event previously */
+                       /*
+                        * Careful! enabled == null could also mean that the null value is
+                        * associated with the key. But we should have never inserted null
+                        * values in the map.
+                        */
+                       return cachedEnabled.booleanValue();
                }
 
-               /* Check if at least one session wants this exact event name */
-               if (enabledEvents.containsKey(eventName)) {
-                       return true;
-               }
+               /*
+                * We have not previously checked this event. Run it against all known
+                * enabled event patterns to determine if it should pass or not.
+                */
+               enabledEventNamesLock.lock();
+               try {
+                       boolean enabled = false;
+                       for (EventNamePattern enabledPattern : enabledPatterns.keySet()) {
+                               Matcher matcher = enabledPattern.getPattern().matcher(eventName);
+                               if (matcher.matches()) {
+                                       enabled = true;
+                                       break;
+                               }
+                       }
 
-               /* Look in the enabled prefixes if one of them matches the event */
-               String potentialMatch = enabledEventPrefixes.floorKey(eventName);
-               if (potentialMatch != null && eventName.startsWith(potentialMatch)) {
-                       return true;
+                       /* Add the result to the cache */
+                       enabledEventNamesCache.put(eventName, Boolean.valueOf(enabled));
+                       return enabled;
+
+               } finally {
+                       enabledEventNamesLock.unlock();
                }
+       }
 
-               return false;
+       @Override
+       public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() {
+               return enabledAppContexts.entrySet();
        }
 
-       private static boolean incrementEventCount(String eventName, Map<String, Integer> eventMap) {
-               synchronized (eventMap) {
-                       Integer count = eventMap.get(eventName);
+       private static <T> boolean incrementRefCount(T key, Map<T, Integer> refCountMap) {
+               synchronized (refCountMap) {
+                       Integer count = refCountMap.get(key);
                        if (count == null) {
                                /* This is the first instance of this event being enabled */
-                               eventMap.put(eventName, Integer.valueOf(1));
+                               refCountMap.put(key, Integer.valueOf(1));
                                return true;
                        }
                        if (count.intValue() <= 0) {
@@ -275,14 +351,14 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                                throw new IllegalStateException();
                        }
                        /* The event was already enabled, increment its refcount */
-                       eventMap.put(eventName, Integer.valueOf(count.intValue() + 1));
+                       refCountMap.put(key, Integer.valueOf(count.intValue() + 1));
                        return true;
                }
        }
 
-       private static boolean decrementEventCount(String eventName, Map<String, Integer> eventMap) {
-               synchronized (eventMap) {
-                       Integer count = eventMap.get(eventName);
+       private static <T> boolean decrementRefCount(T key, Map<T, Integer> refCountMap) {
+               synchronized (refCountMap) {
+                       Integer count = refCountMap.get(key);
                        if (count == null || count.intValue() <= 0) {
                                /*
                                 * The sessiond asked us to disable an event that was not
@@ -295,14 +371,14 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                                 * This is the last instance of this event being disabled,
                                 * remove it from the map so that we stop sending it.
                                 */
-                               eventMap.remove(eventName);
+                               refCountMap.remove(key);
                                return true;
                        }
                        /*
                         * Other sessions are still looking for this event, simply decrement
                         * its refcount.
                         */
-                       eventMap.put(eventName, Integer.valueOf(count.intValue() - 1));
+                       refCountMap.put(key, Integer.valueOf(count.intValue() - 1));
                        return true;
                }
        }
This page took 0.029416 seconds and 4 git commands to generate.