Move liblttng-ust-java* to 'src/lib/'
[lttng-ust.git] / src / lib / lttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / AbstractLttngAgent.java
diff --git a/src/lib/lttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java b/src/lib/lttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/AbstractLttngAgent.java
new file mode 100644 (file)
index 0000000..acbdc4f
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * 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.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+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}.
+ *
+ * @author Alexandre Montplaisir
+ * @param <T>
+ *            The type of logging handler that should register to this agent
+ */
+public abstract class AbstractLttngAgent<T extends ILttngHandler>
+               implements ILttngAgent<T>, ILttngTcpClientListener {
+
+       private static final int INIT_TIMEOUT = 3; /* Seconds */
+
+       /** The handlers registered to this agent */
+       private final Set<T> registeredHandlers = new HashSet<T>();
+
+       /**
+        * The trace events currently enabled in the sessions.
+        *
+        * 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.
+        *
+        * Its accesses should be protected by the {@link #enabledEventNamesLock}
+        * below.
+        */
+       private final Map<EventNamePattern, Integer> enabledPatterns = new HashMap<EventNamePattern, Integer>();
+
+       /**
+        * 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.
+        *
+        * 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 Lock enabledEventNamesLock = new ReentrantLock();
+
+       /**
+        * 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;
+
+       /* Lazy-loaded sessiond clients and their thread objects */
+       private LttngTcpSessiondClient rootSessiondClient = null;
+       private LttngTcpSessiondClient userSessiondClient = null;
+       private Thread rootSessiondClientThread = null;
+       private Thread userSessiondClientThread = null;
+
+       /** Indicates if this agent has been initialized. */
+       private boolean initialized = false;
+
+       /**
+        * Constructor. Should only be called by sub-classes via super(...);
+        *
+        * @param domain
+        *            The tracing domain of this agent.
+        */
+       protected AbstractLttngAgent(Domain domain) {
+               this.domain = domain;
+       }
+
+       @Override
+       public Domain getDomain() {
+               return domain;
+       }
+
+       @Override
+       public void registerHandler(T handler) {
+               synchronized (registeredHandlers) {
+                       if (registeredHandlers.isEmpty()) {
+                               /*
+                                * This is the first handler that registers, we will initialize
+                                * the agent.
+                                */
+                               init();
+                       }
+                       registeredHandlers.add(handler);
+               }
+       }
+
+       @Override
+       public void unregisterHandler(T handler) {
+               synchronized (registeredHandlers) {
+                       registeredHandlers.remove(handler);
+                       if (registeredHandlers.isEmpty()) {
+                               /* There are no more registered handlers, close the connection. */
+                               dispose();
+                       }
+               }
+       }
+
+       private void init() {
+               /*
+                * Only called from a synchronized (registeredHandlers) block, should
+                * not need additional synchronization.
+                */
+               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);
+               rootSessiondClientThread = new Thread(rootSessiondClient, rootClientThreadName);
+               rootSessiondClientThread.setDaemon(true);
+               rootSessiondClientThread.start();
+
+               String userClientThreadName = "User sessiond client started by agent: " + this.getClass().getSimpleName();
+
+               userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false);
+               userSessiondClientThread = new Thread(userSessiondClient, userClientThreadName);
+               userSessiondClientThread.setDaemon(true);
+               userSessiondClientThread.start();
+
+               /* Give the threads' registration a chance to end. */
+               if (!rootSessiondClient.waitForConnection(INIT_TIMEOUT)) {
+                       userSessiondClient.waitForConnection(INIT_TIMEOUT);
+               }
+
+               initialized = true;
+       }
+
+       /**
+        * 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.
+                */
+               rootSessiondClient.close();
+               userSessiondClient.close();
+
+               try {
+                       rootSessiondClientThread.join();
+                       userSessiondClientThread.join();
+
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+               rootSessiondClient = null;
+               rootSessiondClientThread = null;
+               userSessiondClient = null;
+               userSessiondClientThread = null;
+
+               /*
+                * Send filter change notifications for all event rules currently
+                * active, then clear them.
+                */
+               FilterChangeNotifier fcn = FilterChangeNotifier.getInstance();
+
+               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);
+
+               enabledEventNamesLock.lock();
+               try {
+                       boolean ret = incrementRefCount(pattern, enabledPatterns);
+                       enabledEventNamesCache.clear();
+                       return ret;
+               } finally {
+                       enabledEventNamesLock.unlock();
+               }
+       }
+
+       @Override
+       public boolean eventDisabled(String eventName) {
+               /* Notify the filter change manager of the command */
+               FilterChangeNotifier.getInstance().removeEventRules(eventName);
+
+               EventNamePattern pattern = new EventNamePattern(eventName);
+
+               enabledEventNamesLock.lock();
+               try {
+                       boolean ret = decrementRefCount(pattern, enabledPatterns);
+                       enabledEventNamesCache.clear();
+                       return ret;
+               } finally {
+                       enabledEventNamesLock.unlock();
+               }
+       }
+
+       @Override
+       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);
+                       }
+
+                       return incrementRefCount(contextName, retrieverMap);
+               }
+       }
+
+       @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;
+               }
+       }
+
+       /*
+        * Implementation of this method is domain-specific.
+        */
+       @Override
+       public abstract Collection<String> listAvailableEvents();
+
+       @Override
+       public boolean isEventEnabled(String eventName) {
+               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();
+               }
+
+               /*
+                * 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;
+                               }
+                       }
+
+                       /* Add the result to the cache */
+                       enabledEventNamesCache.put(eventName, Boolean.valueOf(enabled));
+                       return enabled;
+
+               } finally {
+                       enabledEventNamesLock.unlock();
+               }
+       }
+
+       @Override
+       public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() {
+               return enabledAppContexts.entrySet();
+       }
+
+       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 */
+                               refCountMap.put(key, Integer.valueOf(1));
+                               return true;
+                       }
+                       if (count.intValue() <= 0) {
+                               /* It should not have been in the map in the first place! */
+                               throw new IllegalStateException();
+                       }
+                       /* The event was already enabled, increment its refcount */
+                       refCountMap.put(key, Integer.valueOf(count.intValue() + 1));
+                       return true;
+               }
+       }
+
+       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
+                                * enabled previously. Command error?
+                                */
+                               return false;
+                       }
+                       if (count.intValue() == 1) {
+                               /*
+                                * This is the last instance of this event being disabled,
+                                * remove it from the map so that we stop sending it.
+                                */
+                               refCountMap.remove(key);
+                               return true;
+                       }
+                       /*
+                        * Other sessions are still looking for this event, simply decrement
+                        * its refcount.
+                        */
+                       refCountMap.put(key, Integer.valueOf(count.intValue() - 1));
+                       return true;
+               }
+       }
+}
+
This page took 0.028184 seconds and 4 git commands to generate.