a58803640f79c15e931fd51ce23e39c31aca394d
[lttng-ust.git] / liblttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / AbstractLttngAgent.java
1 /*
2 * Copyright (C) 2015 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@efficios.com>
3 * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
4 *
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.
8 *
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
12 * for more details.
13 *
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
17 */
18
19 package org.lttng.ust.agent;
20
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.NavigableMap;
25 import java.util.Set;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ConcurrentSkipListMap;
28 import java.util.concurrent.atomic.AtomicInteger;
29
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;
35
36 /**
37 * Base implementation of a {@link ILttngAgent}.
38 *
39 * @author Alexandre Montplaisir
40 * @param <T>
41 * The type of logging handler that should register to this agent
42 */
43 public abstract class AbstractLttngAgent<T extends ILttngHandler>
44 implements ILttngAgent<T>, ILttngTcpClientListener {
45
46 private static final String WILDCARD = "*";
47 private static final int INIT_TIMEOUT = 3; /* Seconds */
48
49 /** The handlers registered to this agent */
50 private final Set<T> registeredHandlers = new HashSet<T>();
51
52 /**
53 * The trace events currently enabled in the sessions.
54 *
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.
59 *
60 * It uses a concurrent hash map, so that the {@link #isEventEnabled} and
61 * read methods do not need to take a synchronization lock.
62 */
63 private final Map<String, Integer> enabledEvents = new ConcurrentHashMap<String, Integer>();
64
65 /**
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.
70 *
71 * We track the lone wildcard "*" separately, in {@link #enabledWildcards}.
72 */
73 private final NavigableMap<String, Integer> enabledEventPrefixes =
74 new ConcurrentSkipListMap<String, Integer>();
75
76 /** Number of sessions currently enabling the wildcard "*" event */
77 private final AtomicInteger enabledWildcards = new AtomicInteger(0);
78
79 /**
80 * The application contexts currently enabled in the tracing sessions.
81 *
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.
84 *
85 * Works similarly as {@link #enabledEvents}, but for app contexts (and with
86 * an extra degree of indexing).
87 *
88 * TODO Could be changed to a Guava Table once/if we start using it.
89 */
90 private final Map<String, Map<String, Integer>> enabledAppContexts = new ConcurrentHashMap<String, Map<String, Integer>>();
91
92 /** Tracing domain. Defined by the sub-classes via the constructor. */
93 private final Domain domain;
94
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;
100
101 /** Indicates if this agent has been initialized. */
102 private boolean initialized = false;
103
104 /**
105 * Constructor. Should only be called by sub-classes via super(...);
106 *
107 * @param domain
108 * The tracing domain of this agent.
109 */
110 protected AbstractLttngAgent(Domain domain) {
111 this.domain = domain;
112 }
113
114 @Override
115 public Domain getDomain() {
116 return domain;
117 }
118
119 @Override
120 public void registerHandler(T handler) {
121 synchronized (registeredHandlers) {
122 if (registeredHandlers.isEmpty()) {
123 /*
124 * This is the first handler that registers, we will initialize
125 * the agent.
126 */
127 init();
128 }
129 registeredHandlers.add(handler);
130 }
131 }
132
133 @Override
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. */
139 dispose();
140 }
141 }
142 }
143
144 private void init() {
145 /*
146 * Only called from a synchronized (registeredHandlers) block, should
147 * not need additional synchronization.
148 */
149 if (initialized) {
150 return;
151 }
152
153 LttngUstAgentLogger.log(AbstractLttngAgent.class, "Initializing Agent for domain: " + domain.name());
154
155 String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName();
156
157 rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true);
158 rootSessiondClientThread = new Thread(rootSessiondClient, rootClientThreadName);
159 rootSessiondClientThread.setDaemon(true);
160 rootSessiondClientThread.start();
161
162 String userClientThreadName = "User sessiond client started by agent: " + this.getClass().getSimpleName();
163
164 userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false);
165 userSessiondClientThread = new Thread(userSessiondClient, userClientThreadName);
166 userSessiondClientThread.setDaemon(true);
167 userSessiondClientThread.start();
168
169 /* Give the threads' registration a chance to end. */
170 if (!rootSessiondClient.waitForConnection(INIT_TIMEOUT)) {
171 userSessiondClient.waitForConnection(INIT_TIMEOUT);
172 }
173
174 initialized = true;
175 }
176
177 /**
178 * Dispose the agent
179 */
180 private void dispose() {
181 LttngUstAgentLogger.log(AbstractLttngAgent.class, "Disposing Agent for domain: " + domain.name());
182
183 /*
184 * Only called from a synchronized (registeredHandlers) block, should
185 * not need additional synchronization.
186 */
187 rootSessiondClient.close();
188 userSessiondClient.close();
189
190 try {
191 rootSessiondClientThread.join();
192 userSessiondClientThread.join();
193
194 } catch (InterruptedException e) {
195 e.printStackTrace();
196 }
197 rootSessiondClient = null;
198 rootSessiondClientThread = null;
199 userSessiondClient = null;
200 userSessiondClientThread = null;
201
202 /*
203 * Send filter change notifications for all event rules currently
204 * active, then clear them.
205 */
206 FilterChangeNotifier fcn = FilterChangeNotifier.getInstance();
207
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);
213 }
214 }
215 enabledEvents.clear();
216
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);
223 }
224 }
225 enabledEventPrefixes.clear();
226
227 int wildcardRules = enabledWildcards.getAndSet(0);
228 for (int i = 0; i < wildcardRules; i++) {
229 fcn.removeEventRules(WILDCARD);
230 }
231
232 /*
233 * Also clear tracked app contexts (no filter notifications sent for
234 * those currently).
235 */
236 enabledAppContexts.clear();
237
238 initialized = false;
239 }
240
241 @Override
242 public boolean eventEnabled(EventRule eventRule) {
243 /* Notify the filter change manager of the command */
244 FilterChangeNotifier.getInstance().addEventRule(eventRule);
245
246 String eventName = eventRule.getEventName();
247
248 if (eventName.equals(WILDCARD)) {
249 enabledWildcards.incrementAndGet();
250 return true;
251 }
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);
256 }
257
258 return incrementRefCount(eventName, enabledEvents);
259 }
260
261 @Override
262 public boolean eventDisabled(String eventName) {
263 /* Notify the filter change manager of the command */
264 FilterChangeNotifier.getInstance().removeEventRules(eventName);
265
266 if (eventName.equals(WILDCARD)) {
267 int newCount = enabledWildcards.decrementAndGet();
268 if (newCount < 0) {
269 /* Event was not enabled, bring the count back to 0 */
270 enabledWildcards.incrementAndGet();
271 return false;
272 }
273 return true;
274 }
275
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);
280 }
281
282 return decrementRefCount(eventName, enabledEvents);
283 }
284
285 @Override
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);
293 }
294
295 return incrementRefCount(contextName, retrieverMap);
296 }
297 }
298
299 @Override
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? */
305 return false;
306 }
307
308 boolean ret = decrementRefCount(contextName, retrieverMap);
309
310 /* If the submap is now empty we can remove it from the main map. */
311 if (retrieverMap.isEmpty()) {
312 enabledAppContexts.remove(contextRetrieverName);
313 }
314
315 return ret;
316 }
317 }
318
319 /*
320 * Implementation of this method is domain-specific.
321 */
322 @Override
323 public abstract Collection<String> listAvailableEvents();
324
325 @Override
326 public boolean isEventEnabled(String eventName) {
327 /* If at least one session enabled the "*" wildcard, send the event */
328 if (enabledWildcards.get() > 0) {
329 return true;
330 }
331
332 /* Check if at least one session wants this exact event name */
333 if (enabledEvents.containsKey(eventName)) {
334 return true;
335 }
336
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)) {
340 return true;
341 }
342
343 return false;
344 }
345
346 @Override
347 public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() {
348 return enabledAppContexts.entrySet();
349 }
350
351 private static boolean incrementRefCount(String key, Map<String, Integer> refCountMap) {
352 synchronized (refCountMap) {
353 Integer count = refCountMap.get(key);
354 if (count == null) {
355 /* This is the first instance of this event being enabled */
356 refCountMap.put(key, Integer.valueOf(1));
357 return true;
358 }
359 if (count.intValue() <= 0) {
360 /* It should not have been in the map in the first place! */
361 throw new IllegalStateException();
362 }
363 /* The event was already enabled, increment its refcount */
364 refCountMap.put(key, Integer.valueOf(count.intValue() + 1));
365 return true;
366 }
367 }
368
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) {
373 /*
374 * The sessiond asked us to disable an event that was not
375 * enabled previously. Command error?
376 */
377 return false;
378 }
379 if (count.intValue() == 1) {
380 /*
381 * This is the last instance of this event being disabled,
382 * remove it from the map so that we stop sending it.
383 */
384 refCountMap.remove(key);
385 return true;
386 }
387 /*
388 * Other sessions are still looking for this event, simply decrement
389 * its refcount.
390 */
391 refCountMap.put(key, Integer.valueOf(count.intValue() - 1));
392 return true;
393 }
394 }
395 }
396
This page took 0.039768 seconds and 3 git commands to generate.