Support generic globbing patterns in the Java agent
authorAlexandre Montplaisir <alexmonthy@efficios.com>
Fri, 17 Feb 2017 20:22:39 +0000 (15:22 -0500)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Fri, 10 Mar 2017 16:30:36 +0000 (11:30 -0500)
Replace the separate eventNames and eventNamePrefixes maps by
one map tracking generic Patterns instead. This will allow
matching against patterns containing more than one wildcard
character, which is now supported by UST.

Signed-off-by: Alexandre Montplaisir <alexmonthy@efficios.com>
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
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/EventNamePattern.java [new file with mode: 0644]

index a58803640f79c15e931fd51ce23e39c31aca394d..0d04a30f4203eed073669a8b804a66371e6a2c65 100644 (file)
 package org.lttng.ust.agent;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 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;
@@ -43,7 +44,6 @@ import org.lttng.ust.agent.utils.LttngUstAgentLogger;
 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 */
@@ -52,29 +52,31 @@ 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 NavigableMap<String, Integer> enabledEventPrefixes =
-                       new ConcurrentSkipListMap<String, Integer>();
+       private final Map<String, Boolean> enabledEventNamesCache = new ConcurrentHashMap<String, Boolean>();
 
-       /** Number of sessions currently enabling the wildcard "*" event */
-       private final AtomicInteger enabledWildcards = new AtomicInteger(0);
+       /**
+        * Lock protecting accesses to the {@link #enabledPatterns} and
+        * {@link #enabledEventNamesCache} maps.
+        */
+       private final Lock enabledEventNamesLock = new ReentrantLock();
 
        /**
         * The application contexts currently enabled in the tracing sessions.
@@ -205,28 +207,19 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                 */
                FilterChangeNotifier fcn = FilterChangeNotifier.getInstance();
 
-               for (Map.Entry<String, Integer> entry : enabledEvents.entrySet()) {
-                       String eventName = entry.getKey();
-                       Integer nb = entry.getValue();
-                       for (int i = 0; i < nb.intValue(); i++) {
-                               fcn.removeEventRules(eventName);
-                       }
-               }
-               enabledEvents.clear();
-
-               for (Map.Entry<String, Integer> entry : enabledEventPrefixes.entrySet()) {
-                       /* Re-add the * at the end, the FCN tracks the rules that way */
-                       String eventName = (entry.getKey() + "*");
-                       Integer nb = entry.getValue();
-                       for (int i = 0; i < nb.intValue(); i++) {
-                               fcn.removeEventRules(eventName);
+               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);
+                               }
                        }
-               }
-               enabledEventPrefixes.clear();
-
-               int wildcardRules = enabledWildcards.getAndSet(0);
-               for (int i = 0; i < wildcardRules; i++) {
-                       fcn.removeEventRules(WILDCARD);
+                       enabledPatterns.clear();
+                       enabledEventNamesCache.clear();
+               } finally {
+                       enabledEventNamesLock.unlock();
                }
 
                /*
@@ -244,18 +237,16 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                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 incrementRefCount(prefix, enabledEventPrefixes);
+               enabledEventNamesLock.lock();
+               try {
+                       boolean ret = incrementRefCount(pattern, enabledPatterns);
+                       enabledEventNamesCache.clear();
+                       return ret;
+               } finally {
+                       enabledEventNamesLock.unlock();
                }
-
-               return incrementRefCount(eventName, enabledEvents);
        }
 
        @Override
@@ -263,23 +254,16 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                /* Notify the filter change manager of the command */
                FilterChangeNotifier.getInstance().removeEventRules(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;
-               }
+               EventNamePattern pattern = new EventNamePattern(eventName);
 
-               if (eventName.endsWith(WILDCARD)) {
-                       /* Strip the "*" from the name. */
-                       String prefix = eventName.substring(0, eventName.length() - 1);
-                       return decrementRefCount(prefix, enabledEventPrefixes);
+               enabledEventNamesLock.lock();
+               try {
+                       boolean ret = decrementRefCount(pattern, enabledPatterns);
+                       enabledEventNamesCache.clear();
+                       return ret;
+               } finally {
+                       enabledEventNamesLock.unlock();
                }
-
-               return decrementRefCount(eventName, enabledEvents);
        }
 
        @Override
@@ -324,23 +308,39 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
 
        @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;
 
-               return false;
+               } finally {
+                       enabledEventNamesLock.unlock();
+               }
        }
 
        @Override
@@ -348,7 +348,7 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                return enabledAppContexts.entrySet();
        }
 
-       private static boolean incrementRefCount(String key, Map<String, Integer> refCountMap) {
+       private static <T> boolean incrementRefCount(T key, Map<T, Integer> refCountMap) {
                synchronized (refCountMap) {
                        Integer count = refCountMap.get(key);
                        if (count == null) {
@@ -366,7 +366,7 @@ public abstract class AbstractLttngAgent<T extends ILttngHandler>
                }
        }
 
-       private static boolean decrementRefCount(String key, Map<String, Integer> refCountMap) {
+       private static <T> boolean decrementRefCount(T key, Map<T, Integer> refCountMap) {
                synchronized (refCountMap) {
                        Integer count = refCountMap.get(key);
                        if (count == null || count.intValue() <= 0) {
diff --git a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/EventNamePattern.java b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/EventNamePattern.java
new file mode 100644 (file)
index 0000000..f89d74f
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 - EfficiOS Inc., Philippe Proulx <pproulx@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;
+
+import java.util.regex.Pattern;
+
+/**
+ * Class encapsulating an event name from the session daemon, and its
+ * corresponding {@link Pattern}. This allows referring back to the original
+ * event name, for example when we receive a disable command.
+ *
+ * @author Philippe Proulx
+ * @author Alexandre Montplaisir
+ */
+class EventNamePattern {
+
+       private final String originalEventName;
+
+       /*
+        * Note that two Patterns coming from the exact same String will not be
+        * equals()! As such, it would be confusing to make the pattern part of this
+        * class's equals/hashCode
+        */
+       private final transient Pattern pattern;
+
+       public EventNamePattern(String eventName) {
+               if (eventName == null) {
+                       throw new IllegalArgumentException();
+               }
+
+               originalEventName = eventName;
+               pattern = patternFromEventName(eventName);
+       }
+
+       public String getEventName() {
+               return originalEventName;
+       }
+
+       public Pattern getPattern() {
+               return pattern;
+       }
+
+       @Override
+       public int hashCode() {
+               final int prime = 31;
+               int result = 1;
+               result = prime * result + originalEventName.hashCode();
+               return result;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (this == obj) {
+                       return true;
+               }
+               if (obj == null) {
+                       return false;
+               }
+               if (getClass() != obj.getClass()) {
+                       return false;
+               }
+               EventNamePattern other = (EventNamePattern) obj;
+               if (!originalEventName.equals(other.originalEventName)) {
+                       return false;
+               }
+               return true;
+       }
+
+       private static Pattern patternFromEventName(String eventName) {
+               /*
+                * The situation here is that `\*` means a literal `*` in the event
+                * name, and `*` is a wildcard star. We check the event name one
+                * character at a time and create a list of tokens to be converter to
+                * partial patterns.
+                */
+               StringBuilder bigBuilder = new StringBuilder("^");
+               StringBuilder smallBuilder = new StringBuilder();
+
+               for (int i = 0; i < eventName.length(); i++) {
+                       char c = eventName.charAt(i);
+
+                       switch (c) {
+                       case '*':
+                               /* Add current quoted builder's string if not empty. */
+                               if (smallBuilder.length() > 0) {
+                                       bigBuilder.append(Pattern.quote(smallBuilder.toString()));
+                                       smallBuilder.setLength(0);
+                               }
+
+                               /* Append the equivalent regex which is `.*`. */
+                               bigBuilder.append(".*");
+                               continue;
+
+                       case '\\':
+                               /* We only escape `*` and `\` here. */
+                               if (i < (eventName.length() - 1)) {
+                                       char nextChar = eventName.charAt(i + 1);
+
+                                       if (nextChar == '*' || nextChar == '\\') {
+                                               smallBuilder.append(nextChar);
+                                       } else {
+                                               smallBuilder.append(c);
+                                               smallBuilder.append(nextChar);
+                                       }
+
+                                       i++;
+                                       continue;
+                               }
+                               break;
+
+                       default:
+                               break;
+                       }
+
+                       smallBuilder.append(c);
+               }
+
+               /* Add current quoted builder's string if not empty. */
+               if (smallBuilder.length() > 0) {
+                       bigBuilder.append(Pattern.quote(smallBuilder.toString()));
+               }
+
+               bigBuilder.append("$");
+
+               return Pattern.compile(bigBuilder.toString());
+       }
+}
This page took 0.029181 seconds and 4 git commands to generate.