From: Alexandre Montplaisir Date: Fri, 17 Feb 2017 20:22:39 +0000 (-0500) Subject: Support generic globbing patterns in the Java agent X-Git-Tag: v2.10.0-rc1~12 X-Git-Url: https://git.lttng.org./?a=commitdiff_plain;h=c0e418b5f350c9a4a70184c418f4a46ee299508b;p=lttng-ust.git Support generic globbing patterns in the Java agent 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 Signed-off-by: Mathieu Desnoyers --- 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 a5880364..0d04a30f 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 @@ -19,13 +19,14 @@ 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 implements ILttngAgent, 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 /** * 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 enabledEvents = new ConcurrentHashMap(); + private final Map enabledPatterns = new HashMap(); /** - * 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 enabledEventPrefixes = - new ConcurrentSkipListMap(); + private final Map enabledEventNamesCache = new ConcurrentHashMap(); - /** 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 */ FilterChangeNotifier fcn = FilterChangeNotifier.getInstance(); - for (Map.Entry 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 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 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 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 /* 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 @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 return enabledAppContexts.entrySet(); } - private static boolean incrementRefCount(String key, Map refCountMap) { + private static boolean incrementRefCount(T key, Map refCountMap) { synchronized (refCountMap) { Integer count = refCountMap.get(key); if (count == null) { @@ -366,7 +366,7 @@ public abstract class AbstractLttngAgent } } - private static boolean decrementRefCount(String key, Map refCountMap) { + private static boolean decrementRefCount(T key, Map 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 index 00000000..f89d74f4 --- /dev/null +++ b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/EventNamePattern.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2017 - EfficiOS Inc., Philippe Proulx + * + * 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()); + } +}