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
.HashSet
;
24 import java
.util
.NavigableMap
;
26 import java
.util
.concurrent
.ConcurrentHashMap
;
27 import java
.util
.concurrent
.ConcurrentSkipListMap
;
28 import java
.util
.concurrent
.atomic
.AtomicInteger
;
30 import org
.lttng
.ust
.agent
.client
.ILttngTcpClientListener
;
31 import org
.lttng
.ust
.agent
.client
.LttngTcpSessiondClient
;
32 import org
.lttng
.ust
.agent
.filter
.FilterChangeNotifier
;
33 import org
.lttng
.ust
.agent
.session
.EventRule
;
34 import org
.lttng
.ust
.agent
.utils
.LttngUstAgentLogger
;
37 * Base implementation of a {@link ILttngAgent}.
39 * @author Alexandre Montplaisir
41 * The type of logging handler that should register to this agent
43 public abstract class AbstractLttngAgent
<T
extends ILttngHandler
>
44 implements ILttngAgent
<T
>, ILttngTcpClientListener
{
46 private static final String WILDCARD
= "*";
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 represents the event name, the value is the ref count (how many
56 * different sessions currently have this event enabled). Once the ref count
57 * falls to 0, this means we can avoid sending log events through JNI
58 * because nobody wants them.
60 * It uses a concurrent hash map, so that the {@link #isEventEnabled} and
61 * read methods do not need to take a synchronization lock.
63 private final Map
<String
, Integer
> enabledEvents
= new ConcurrentHashMap
<String
, Integer
>();
66 * The trace events prefixes currently enabled in the sessions, which means
67 * the event names finishing in *, like "abcd*". We track them separately
68 * from the standard event names, so that we can use {@link String#equals}
69 * and {@link String#startsWith} appropriately.
71 * We track the lone wildcard "*" separately, in {@link #enabledWildcards}.
73 private final NavigableMap
<String
, Integer
> enabledEventPrefixes
=
74 new ConcurrentSkipListMap
<String
, Integer
>();
76 /** Number of sessions currently enabling the wildcard "*" event */
77 private final AtomicInteger enabledWildcards
= new AtomicInteger(0);
80 * The application contexts currently enabled in the tracing sessions.
82 * It is first indexed by context retriever, then by context name. This
83 * allows to efficiently query all the contexts for a given retriever.
85 * Works similarly as {@link #enabledEvents}, but for app contexts (and with
86 * an extra degree of indexing).
88 * TODO Could be changed to a Guava Table once/if we start using it.
90 private final Map
<String
, Map
<String
, Integer
>> enabledAppContexts
= new ConcurrentHashMap
<String
, Map
<String
, Integer
>>();
92 /** Tracing domain. Defined by the sub-classes via the constructor. */
93 private final Domain domain
;
95 /* Lazy-loaded sessiond clients and their thread objects */
96 private LttngTcpSessiondClient rootSessiondClient
= null;
97 private LttngTcpSessiondClient userSessiondClient
= null;
98 private Thread rootSessiondClientThread
= null;
99 private Thread userSessiondClientThread
= null;
101 /** Indicates if this agent has been initialized. */
102 private boolean initialized
= false;
105 * Constructor. Should only be called by sub-classes via super(...);
108 * The tracing domain of this agent.
110 protected AbstractLttngAgent(Domain domain
) {
111 this.domain
= domain
;
115 public Domain
getDomain() {
120 public void registerHandler(T handler
) {
121 synchronized (registeredHandlers
) {
122 if (registeredHandlers
.isEmpty()) {
124 * This is the first handler that registers, we will initialize
129 registeredHandlers
.add(handler
);
134 public void unregisterHandler(T handler
) {
135 synchronized (registeredHandlers
) {
136 registeredHandlers
.remove(handler
);
137 if (registeredHandlers
.isEmpty()) {
138 /* There are no more registered handlers, close the connection. */
144 private void init() {
146 * Only called from a synchronized (registeredHandlers) block, should
147 * not need additional synchronization.
153 LttngUstAgentLogger
.log(AbstractLttngAgent
.class, "Initializing Agent for domain: " + domain
.name());
155 String rootClientThreadName
= "Root sessiond client started by agent: " + this.getClass().getSimpleName();
157 rootSessiondClient
= new LttngTcpSessiondClient(this, getDomain().value(), true);
158 rootSessiondClientThread
= new Thread(rootSessiondClient
, rootClientThreadName
);
159 rootSessiondClientThread
.setDaemon(true);
160 rootSessiondClientThread
.start();
162 String userClientThreadName
= "User sessiond client started by agent: " + this.getClass().getSimpleName();
164 userSessiondClient
= new LttngTcpSessiondClient(this, getDomain().value(), false);
165 userSessiondClientThread
= new Thread(userSessiondClient
, userClientThreadName
);
166 userSessiondClientThread
.setDaemon(true);
167 userSessiondClientThread
.start();
169 /* Give the threads' registration a chance to end. */
170 if (!rootSessiondClient
.waitForConnection(INIT_TIMEOUT
)) {
171 userSessiondClient
.waitForConnection(INIT_TIMEOUT
);
180 private void dispose() {
181 LttngUstAgentLogger
.log(AbstractLttngAgent
.class, "Disposing Agent for domain: " + domain
.name());
184 * Only called from a synchronized (registeredHandlers) block, should
185 * not need additional synchronization.
187 rootSessiondClient
.close();
188 userSessiondClient
.close();
191 rootSessiondClientThread
.join();
192 userSessiondClientThread
.join();
194 } catch (InterruptedException e
) {
197 rootSessiondClient
= null;
198 rootSessiondClientThread
= null;
199 userSessiondClient
= null;
200 userSessiondClientThread
= null;
203 * Send filter change notifications for all event rules currently
204 * active, then clear them.
206 FilterChangeNotifier fcn
= FilterChangeNotifier
.getInstance();
208 for (Map
.Entry
<String
, Integer
> entry
: enabledEvents
.entrySet()) {
209 String eventName
= entry
.getKey();
210 Integer nb
= entry
.getValue();
211 for (int i
= 0; i
< nb
.intValue(); i
++) {
212 fcn
.removeEventRules(eventName
);
215 enabledEvents
.clear();
217 for (Map
.Entry
<String
, Integer
> entry
: enabledEventPrefixes
.entrySet()) {
218 /* Re-add the * at the end, the FCN tracks the rules that way */
219 String eventName
= (entry
.getKey() + "*");
220 Integer nb
= entry
.getValue();
221 for (int i
= 0; i
< nb
.intValue(); i
++) {
222 fcn
.removeEventRules(eventName
);
225 enabledEventPrefixes
.clear();
227 int wildcardRules
= enabledWildcards
.getAndSet(0);
228 for (int i
= 0; i
< wildcardRules
; i
++) {
229 fcn
.removeEventRules(WILDCARD
);
233 * Also clear tracked app contexts (no filter notifications sent for
236 enabledAppContexts
.clear();
242 public boolean eventEnabled(EventRule eventRule
) {
243 /* Notify the filter change manager of the command */
244 FilterChangeNotifier
.getInstance().addEventRule(eventRule
);
246 String eventName
= eventRule
.getEventName();
248 if (eventName
.equals(WILDCARD
)) {
249 enabledWildcards
.incrementAndGet();
252 if (eventName
.endsWith(WILDCARD
)) {
253 /* Strip the "*" from the name. */
254 String prefix
= eventName
.substring(0, eventName
.length() - 1);
255 return incrementRefCount(prefix
, enabledEventPrefixes
);
258 return incrementRefCount(eventName
, enabledEvents
);
262 public boolean eventDisabled(String eventName
) {
263 /* Notify the filter change manager of the command */
264 FilterChangeNotifier
.getInstance().removeEventRules(eventName
);
266 if (eventName
.equals(WILDCARD
)) {
267 int newCount
= enabledWildcards
.decrementAndGet();
269 /* Event was not enabled, bring the count back to 0 */
270 enabledWildcards
.incrementAndGet();
276 if (eventName
.endsWith(WILDCARD
)) {
277 /* Strip the "*" from the name. */
278 String prefix
= eventName
.substring(0, eventName
.length() - 1);
279 return decrementRefCount(prefix
, enabledEventPrefixes
);
282 return decrementRefCount(eventName
, enabledEvents
);
286 public boolean appContextEnabled(String contextRetrieverName
, String contextName
) {
287 synchronized (enabledAppContexts
) {
288 Map
<String
, Integer
> retrieverMap
= enabledAppContexts
.get(contextRetrieverName
);
289 if (retrieverMap
== null) {
290 /* There is no submap for this retriever, let's create one. */
291 retrieverMap
= new ConcurrentHashMap
<String
, Integer
>();
292 enabledAppContexts
.put(contextRetrieverName
, retrieverMap
);
295 return incrementRefCount(contextName
, retrieverMap
);
300 public boolean appContextDisabled(String contextRetrieverName
, String contextName
) {
301 synchronized (enabledAppContexts
) {
302 Map
<String
, Integer
> retrieverMap
= enabledAppContexts
.get(contextRetrieverName
);
303 if (retrieverMap
== null) {
304 /* There was no submap for this retriever, invalid command? */
308 boolean ret
= decrementRefCount(contextName
, retrieverMap
);
310 /* If the submap is now empty we can remove it from the main map. */
311 if (retrieverMap
.isEmpty()) {
312 enabledAppContexts
.remove(contextRetrieverName
);
320 * Implementation of this method is domain-specific.
323 public abstract Collection
<String
> listAvailableEvents();
326 public boolean isEventEnabled(String eventName
) {
327 /* If at least one session enabled the "*" wildcard, send the event */
328 if (enabledWildcards
.get() > 0) {
332 /* Check if at least one session wants this exact event name */
333 if (enabledEvents
.containsKey(eventName
)) {
337 /* Look in the enabled prefixes if one of them matches the event */
338 String potentialMatch
= enabledEventPrefixes
.floorKey(eventName
);
339 if (potentialMatch
!= null && eventName
.startsWith(potentialMatch
)) {
347 public Collection
<Map
.Entry
<String
, Map
<String
, Integer
>>> getEnabledAppContexts() {
348 return enabledAppContexts
.entrySet();
351 private static boolean incrementRefCount(String key
, Map
<String
, 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 boolean decrementRefCount(String key
, Map
<String
, 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));