0d04a30f4203eed073669a8b804a66371e6a2c65
[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.HashMap;
23 import java.util.HashSet;
24 import java.util.Map;
25 import java.util.Set;
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;
30
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;
36
37 /**
38 * Base implementation of a {@link ILttngAgent}.
39 *
40 * @author Alexandre Montplaisir
41 * @param <T>
42 * The type of logging handler that should register to this agent
43 */
44 public abstract class AbstractLttngAgent<T extends ILttngHandler>
45 implements ILttngAgent<T>, ILttngTcpClientListener {
46
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 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.
59 *
60 * Its accesses should be protected by the {@link #enabledEventNamesLock}
61 * below.
62 */
63 private final Map<EventNamePattern, Integer> enabledPatterns = new HashMap<EventNamePattern, Integer>();
64
65 /**
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.
69 *
70 * Its accesses should be protected by the {@link #enabledEventNamesLock}
71 * below, with the exception of concurrent get operations.
72 */
73 private final Map<String, Boolean> enabledEventNamesCache = new ConcurrentHashMap<String, Boolean>();
74
75 /**
76 * Lock protecting accesses to the {@link #enabledPatterns} and
77 * {@link #enabledEventNamesCache} maps.
78 */
79 private final Lock enabledEventNamesLock = new ReentrantLock();
80
81 /**
82 * The application contexts currently enabled in the tracing sessions.
83 *
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.
86 *
87 * Works similarly as {@link #enabledEvents}, but for app contexts (and with
88 * an extra degree of indexing).
89 *
90 * TODO Could be changed to a Guava Table once/if we start using it.
91 */
92 private final Map<String, Map<String, Integer>> enabledAppContexts = new ConcurrentHashMap<String, Map<String, Integer>>();
93
94 /** Tracing domain. Defined by the sub-classes via the constructor. */
95 private final Domain domain;
96
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;
102
103 /** Indicates if this agent has been initialized. */
104 private boolean initialized = false;
105
106 /**
107 * Constructor. Should only be called by sub-classes via super(...);
108 *
109 * @param domain
110 * The tracing domain of this agent.
111 */
112 protected AbstractLttngAgent(Domain domain) {
113 this.domain = domain;
114 }
115
116 @Override
117 public Domain getDomain() {
118 return domain;
119 }
120
121 @Override
122 public void registerHandler(T handler) {
123 synchronized (registeredHandlers) {
124 if (registeredHandlers.isEmpty()) {
125 /*
126 * This is the first handler that registers, we will initialize
127 * the agent.
128 */
129 init();
130 }
131 registeredHandlers.add(handler);
132 }
133 }
134
135 @Override
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. */
141 dispose();
142 }
143 }
144 }
145
146 private void init() {
147 /*
148 * Only called from a synchronized (registeredHandlers) block, should
149 * not need additional synchronization.
150 */
151 if (initialized) {
152 return;
153 }
154
155 LttngUstAgentLogger.log(AbstractLttngAgent.class, "Initializing Agent for domain: " + domain.name());
156
157 String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName();
158
159 rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true);
160 rootSessiondClientThread = new Thread(rootSessiondClient, rootClientThreadName);
161 rootSessiondClientThread.setDaemon(true);
162 rootSessiondClientThread.start();
163
164 String userClientThreadName = "User sessiond client started by agent: " + this.getClass().getSimpleName();
165
166 userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false);
167 userSessiondClientThread = new Thread(userSessiondClient, userClientThreadName);
168 userSessiondClientThread.setDaemon(true);
169 userSessiondClientThread.start();
170
171 /* Give the threads' registration a chance to end. */
172 if (!rootSessiondClient.waitForConnection(INIT_TIMEOUT)) {
173 userSessiondClient.waitForConnection(INIT_TIMEOUT);
174 }
175
176 initialized = true;
177 }
178
179 /**
180 * Dispose the agent
181 */
182 private void dispose() {
183 LttngUstAgentLogger.log(AbstractLttngAgent.class, "Disposing Agent for domain: " + domain.name());
184
185 /*
186 * Only called from a synchronized (registeredHandlers) block, should
187 * not need additional synchronization.
188 */
189 rootSessiondClient.close();
190 userSessiondClient.close();
191
192 try {
193 rootSessiondClientThread.join();
194 userSessiondClientThread.join();
195
196 } catch (InterruptedException e) {
197 e.printStackTrace();
198 }
199 rootSessiondClient = null;
200 rootSessiondClientThread = null;
201 userSessiondClient = null;
202 userSessiondClientThread = null;
203
204 /*
205 * Send filter change notifications for all event rules currently
206 * active, then clear them.
207 */
208 FilterChangeNotifier fcn = FilterChangeNotifier.getInstance();
209
210 enabledEventNamesLock.lock();
211 try {
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);
217 }
218 }
219 enabledPatterns.clear();
220 enabledEventNamesCache.clear();
221 } finally {
222 enabledEventNamesLock.unlock();
223 }
224
225 /*
226 * Also clear tracked app contexts (no filter notifications sent for
227 * those currently).
228 */
229 enabledAppContexts.clear();
230
231 initialized = false;
232 }
233
234 @Override
235 public boolean eventEnabled(EventRule eventRule) {
236 /* Notify the filter change manager of the command */
237 FilterChangeNotifier.getInstance().addEventRule(eventRule);
238
239 String eventName = eventRule.getEventName();
240 EventNamePattern pattern = new EventNamePattern(eventName);
241
242 enabledEventNamesLock.lock();
243 try {
244 boolean ret = incrementRefCount(pattern, enabledPatterns);
245 enabledEventNamesCache.clear();
246 return ret;
247 } finally {
248 enabledEventNamesLock.unlock();
249 }
250 }
251
252 @Override
253 public boolean eventDisabled(String eventName) {
254 /* Notify the filter change manager of the command */
255 FilterChangeNotifier.getInstance().removeEventRules(eventName);
256
257 EventNamePattern pattern = new EventNamePattern(eventName);
258
259 enabledEventNamesLock.lock();
260 try {
261 boolean ret = decrementRefCount(pattern, enabledPatterns);
262 enabledEventNamesCache.clear();
263 return ret;
264 } finally {
265 enabledEventNamesLock.unlock();
266 }
267 }
268
269 @Override
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);
277 }
278
279 return incrementRefCount(contextName, retrieverMap);
280 }
281 }
282
283 @Override
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? */
289 return false;
290 }
291
292 boolean ret = decrementRefCount(contextName, retrieverMap);
293
294 /* If the submap is now empty we can remove it from the main map. */
295 if (retrieverMap.isEmpty()) {
296 enabledAppContexts.remove(contextRetrieverName);
297 }
298
299 return ret;
300 }
301 }
302
303 /*
304 * Implementation of this method is domain-specific.
305 */
306 @Override
307 public abstract Collection<String> listAvailableEvents();
308
309 @Override
310 public boolean isEventEnabled(String eventName) {
311 Boolean cachedEnabled = enabledEventNamesCache.get(eventName);
312 if (cachedEnabled != null) {
313 /* We have seen this event previously */
314 /*
315 * Careful! enabled == null could also mean that the null value is
316 * associated with the key. But we should have never inserted null
317 * values in the map.
318 */
319 return cachedEnabled.booleanValue();
320 }
321
322 /*
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.
325 */
326 enabledEventNamesLock.lock();
327 try {
328 boolean enabled = false;
329 for (EventNamePattern enabledPattern : enabledPatterns.keySet()) {
330 Matcher matcher = enabledPattern.getPattern().matcher(eventName);
331 if (matcher.matches()) {
332 enabled = true;
333 break;
334 }
335 }
336
337 /* Add the result to the cache */
338 enabledEventNamesCache.put(eventName, Boolean.valueOf(enabled));
339 return enabled;
340
341 } finally {
342 enabledEventNamesLock.unlock();
343 }
344 }
345
346 @Override
347 public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() {
348 return enabledAppContexts.entrySet();
349 }
350
351 private static <T> boolean incrementRefCount(T key, Map<T, 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 <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) {
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.037211 seconds and 3 git commands to generate.