2 * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
3 * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
5 * This library is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License, version 2.1 only,
7 * as published by the Free Software Foundation.
9 * This library is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 package org
.lttng
.ust
.agent
;
21 import java
.util
.Collection
;
22 import java
.util
.HashMap
;
23 import java
.util
.HashSet
;
26 import java
.util
.concurrent
.ConcurrentHashMap
;
27 import java
.util
.concurrent
.locks
.Lock
;
28 import java
.util
.concurrent
.locks
.ReentrantLock
;
29 import java
.util
.regex
.Matcher
;
31 import org
.lttng
.ust
.agent
.client
.ILttngTcpClientListener
;
32 import org
.lttng
.ust
.agent
.client
.LttngTcpSessiondClient
;
33 import org
.lttng
.ust
.agent
.filter
.FilterChangeNotifier
;
34 import org
.lttng
.ust
.agent
.session
.EventRule
;
35 import org
.lttng
.ust
.agent
.utils
.LttngUstAgentLogger
;
38 * Base implementation of a {@link ILttngAgent}.
40 * @author Alexandre Montplaisir
42 * The type of logging handler that should register to this agent
44 public abstract class AbstractLttngAgent
<T
extends ILttngHandler
>
45 implements ILttngAgent
<T
>, ILttngTcpClientListener
{
47 private static final int INIT_TIMEOUT
= 3; /* Seconds */
49 /** The handlers registered to this agent */
50 private final Set
<T
> registeredHandlers
= new HashSet
<T
>();
53 * The trace events currently enabled in the sessions.
55 * The key is the {@link EventNamePattern} that comes from the event name.
56 * The value is the ref count (how many different sessions currently have
57 * this event enabled). Once the ref count falls to 0, this means we can
58 * avoid sending log events through JNI because nobody wants them.
60 * Its accesses should be protected by the {@link #enabledEventNamesLock}
63 private final Map
<EventNamePattern
, Integer
> enabledPatterns
= new HashMap
<EventNamePattern
, Integer
>();
66 * Cache of already-checked event names. As long as enabled/disabled events
67 * don't change in the session, we can avoid re-checking events that were
68 * previously checked against all known enabled patterns.
70 * Its accesses should be protected by the {@link #enabledEventNamesLock}
71 * below, with the exception of concurrent get operations.
73 private final Map
<String
, Boolean
> enabledEventNamesCache
= new ConcurrentHashMap
<String
, Boolean
>();
76 * Lock protecting accesses to the {@link #enabledPatterns} and
77 * {@link #enabledEventNamesCache} maps.
79 private final Lock enabledEventNamesLock
= new ReentrantLock();
82 * The application contexts currently enabled in the tracing sessions.
84 * It is first indexed by context retriever, then by context name. This
85 * allows to efficiently query all the contexts for a given retriever.
87 * Works similarly as {@link #enabledEvents}, but for app contexts (and with
88 * an extra degree of indexing).
90 * TODO Could be changed to a Guava Table once/if we start using it.
92 private final Map
<String
, Map
<String
, Integer
>> enabledAppContexts
= new ConcurrentHashMap
<String
, Map
<String
, Integer
>>();
94 /** Tracing domain. Defined by the sub-classes via the constructor. */
95 private final Domain domain
;
97 /* Lazy-loaded sessiond clients and their thread objects */
98 private LttngTcpSessiondClient rootSessiondClient
= null;
99 private LttngTcpSessiondClient userSessiondClient
= null;
100 private Thread rootSessiondClientThread
= null;
101 private Thread userSessiondClientThread
= null;
103 /** Indicates if this agent has been initialized. */
104 private boolean initialized
= false;
107 * Constructor. Should only be called by sub-classes via super(...);
110 * The tracing domain of this agent.
112 protected AbstractLttngAgent(Domain domain
) {
113 this.domain
= domain
;
117 public Domain
getDomain() {
122 public void registerHandler(T handler
) {
123 synchronized (registeredHandlers
) {
124 if (registeredHandlers
.isEmpty()) {
126 * This is the first handler that registers, we will initialize
131 registeredHandlers
.add(handler
);
136 public void unregisterHandler(T handler
) {
137 synchronized (registeredHandlers
) {
138 registeredHandlers
.remove(handler
);
139 if (registeredHandlers
.isEmpty()) {
140 /* There are no more registered handlers, close the connection. */
146 private void init() {
148 * Only called from a synchronized (registeredHandlers) block, should
149 * not need additional synchronization.
155 LttngUstAgentLogger
.log(AbstractLttngAgent
.class, "Initializing Agent for domain: " + domain
.name());
157 String rootClientThreadName
= "Root sessiond client started by agent: " + this.getClass().getSimpleName();
159 rootSessiondClient
= new LttngTcpSessiondClient(this, getDomain().value(), true);
160 rootSessiondClientThread
= new Thread(rootSessiondClient
, rootClientThreadName
);
161 rootSessiondClientThread
.setDaemon(true);
162 rootSessiondClientThread
.start();
164 String userClientThreadName
= "User sessiond client started by agent: " + this.getClass().getSimpleName();
166 userSessiondClient
= new LttngTcpSessiondClient(this, getDomain().value(), false);
167 userSessiondClientThread
= new Thread(userSessiondClient
, userClientThreadName
);
168 userSessiondClientThread
.setDaemon(true);
169 userSessiondClientThread
.start();
171 /* Give the threads' registration a chance to end. */
172 if (!rootSessiondClient
.waitForConnection(INIT_TIMEOUT
)) {
173 userSessiondClient
.waitForConnection(INIT_TIMEOUT
);
182 private void dispose() {
183 LttngUstAgentLogger
.log(AbstractLttngAgent
.class, "Disposing Agent for domain: " + domain
.name());
186 * Only called from a synchronized (registeredHandlers) block, should
187 * not need additional synchronization.
189 rootSessiondClient
.close();
190 userSessiondClient
.close();
193 rootSessiondClientThread
.join();
194 userSessiondClientThread
.join();
196 } catch (InterruptedException e
) {
199 rootSessiondClient
= null;
200 rootSessiondClientThread
= null;
201 userSessiondClient
= null;
202 userSessiondClientThread
= null;
205 * Send filter change notifications for all event rules currently
206 * active, then clear them.
208 FilterChangeNotifier fcn
= FilterChangeNotifier
.getInstance();
210 enabledEventNamesLock
.lock();
212 for (Map
.Entry
<EventNamePattern
, Integer
> entry
: enabledPatterns
.entrySet()) {
213 String eventName
= entry
.getKey().getEventName();
214 Integer nb
= entry
.getValue();
215 for (int i
= 0; i
< nb
.intValue(); i
++) {
216 fcn
.removeEventRules(eventName
);
219 enabledPatterns
.clear();
220 enabledEventNamesCache
.clear();
222 enabledEventNamesLock
.unlock();
226 * Also clear tracked app contexts (no filter notifications sent for
229 enabledAppContexts
.clear();
235 public boolean eventEnabled(EventRule eventRule
) {
236 /* Notify the filter change manager of the command */
237 FilterChangeNotifier
.getInstance().addEventRule(eventRule
);
239 String eventName
= eventRule
.getEventName();
240 EventNamePattern pattern
= new EventNamePattern(eventName
);
242 enabledEventNamesLock
.lock();
244 boolean ret
= incrementRefCount(pattern
, enabledPatterns
);
245 enabledEventNamesCache
.clear();
248 enabledEventNamesLock
.unlock();
253 public boolean eventDisabled(String eventName
) {
254 /* Notify the filter change manager of the command */
255 FilterChangeNotifier
.getInstance().removeEventRules(eventName
);
257 EventNamePattern pattern
= new EventNamePattern(eventName
);
259 enabledEventNamesLock
.lock();
261 boolean ret
= decrementRefCount(pattern
, enabledPatterns
);
262 enabledEventNamesCache
.clear();
265 enabledEventNamesLock
.unlock();
270 public boolean appContextEnabled(String contextRetrieverName
, String contextName
) {
271 synchronized (enabledAppContexts
) {
272 Map
<String
, Integer
> retrieverMap
= enabledAppContexts
.get(contextRetrieverName
);
273 if (retrieverMap
== null) {
274 /* There is no submap for this retriever, let's create one. */
275 retrieverMap
= new ConcurrentHashMap
<String
, Integer
>();
276 enabledAppContexts
.put(contextRetrieverName
, retrieverMap
);
279 return incrementRefCount(contextName
, retrieverMap
);
284 public boolean appContextDisabled(String contextRetrieverName
, String contextName
) {
285 synchronized (enabledAppContexts
) {
286 Map
<String
, Integer
> retrieverMap
= enabledAppContexts
.get(contextRetrieverName
);
287 if (retrieverMap
== null) {
288 /* There was no submap for this retriever, invalid command? */
292 boolean ret
= decrementRefCount(contextName
, retrieverMap
);
294 /* If the submap is now empty we can remove it from the main map. */
295 if (retrieverMap
.isEmpty()) {
296 enabledAppContexts
.remove(contextRetrieverName
);
304 * Implementation of this method is domain-specific.
307 public abstract Collection
<String
> listAvailableEvents();
310 public boolean isEventEnabled(String eventName
) {
311 Boolean cachedEnabled
= enabledEventNamesCache
.get(eventName
);
312 if (cachedEnabled
!= null) {
313 /* We have seen this event previously */
315 * Careful! enabled == null could also mean that the null value is
316 * associated with the key. But we should have never inserted null
319 return cachedEnabled
.booleanValue();
323 * We have not previously checked this event. Run it against all known
324 * enabled event patterns to determine if it should pass or not.
326 enabledEventNamesLock
.lock();
328 boolean enabled
= false;
329 for (EventNamePattern enabledPattern
: enabledPatterns
.keySet()) {
330 Matcher matcher
= enabledPattern
.getPattern().matcher(eventName
);
331 if (matcher
.matches()) {
337 /* Add the result to the cache */
338 enabledEventNamesCache
.put(eventName
, Boolean
.valueOf(enabled
));
342 enabledEventNamesLock
.unlock();
347 public Collection
<Map
.Entry
<String
, Map
<String
, Integer
>>> getEnabledAppContexts() {
348 return enabledAppContexts
.entrySet();
351 private static <T
> boolean incrementRefCount(T key
, Map
<T
, Integer
> refCountMap
) {
352 synchronized (refCountMap
) {
353 Integer count
= refCountMap
.get(key
);
355 /* This is the first instance of this event being enabled */
356 refCountMap
.put(key
, Integer
.valueOf(1));
359 if (count
.intValue() <= 0) {
360 /* It should not have been in the map in the first place! */
361 throw new IllegalStateException();
363 /* The event was already enabled, increment its refcount */
364 refCountMap
.put(key
, Integer
.valueOf(count
.intValue() + 1));
369 private static <T
> boolean decrementRefCount(T key
, Map
<T
, Integer
> refCountMap
) {
370 synchronized (refCountMap
) {
371 Integer count
= refCountMap
.get(key
);
372 if (count
== null || count
.intValue() <= 0) {
374 * The sessiond asked us to disable an event that was not
375 * enabled previously. Command error?
379 if (count
.intValue() == 1) {
381 * This is the last instance of this event being disabled,
382 * remove it from the map so that we stop sending it.
384 refCountMap
.remove(key
);
388 * Other sessions are still looking for this event, simply decrement
391 refCountMap
.put(key
, Integer
.valueOf(count
.intValue() - 1));