Commit | Line | Data |
---|---|---|
d60dfbe4 AM |
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 | ||
68a1ef73 | 21 | import java.util.Collection; |
c0e418b5 | 22 | import java.util.HashMap; |
d60dfbe4 | 23 | import java.util.HashSet; |
d60dfbe4 | 24 | import java.util.Map; |
d60dfbe4 AM |
25 | import java.util.Set; |
26 | import java.util.concurrent.ConcurrentHashMap; | |
c0e418b5 AM |
27 | import java.util.concurrent.locks.Lock; |
28 | import java.util.concurrent.locks.ReentrantLock; | |
29 | import java.util.regex.Matcher; | |
d60dfbe4 | 30 | |
3165c2f5 | 31 | import org.lttng.ust.agent.client.ILttngTcpClientListener; |
d60dfbe4 | 32 | import org.lttng.ust.agent.client.LttngTcpSessiondClient; |
4fb826b1 | 33 | import org.lttng.ust.agent.filter.FilterChangeNotifier; |
3daf5cba | 34 | import org.lttng.ust.agent.session.EventRule; |
e1357790 | 35 | import org.lttng.ust.agent.utils.LttngUstAgentLogger; |
d60dfbe4 AM |
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 | */ | |
3165c2f5 AM |
44 | public abstract class AbstractLttngAgent<T extends ILttngHandler> |
45 | implements ILttngAgent<T>, ILttngTcpClientListener { | |
d60dfbe4 | 46 | |
d60dfbe4 AM |
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 | * | |
c0e418b5 AM |
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. | |
d60dfbe4 | 59 | * |
c0e418b5 AM |
60 | * Its accesses should be protected by the {@link #enabledEventNamesLock} |
61 | * below. | |
d60dfbe4 | 62 | */ |
c0e418b5 | 63 | private final Map<EventNamePattern, Integer> enabledPatterns = new HashMap<EventNamePattern, Integer>(); |
d60dfbe4 AM |
64 | |
65 | /** | |
c0e418b5 AM |
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. | |
d60dfbe4 | 69 | * |
c0e418b5 AM |
70 | * Its accesses should be protected by the {@link #enabledEventNamesLock} |
71 | * below, with the exception of concurrent get operations. | |
d60dfbe4 | 72 | */ |
c0e418b5 | 73 | private final Map<String, Boolean> enabledEventNamesCache = new ConcurrentHashMap<String, Boolean>(); |
d60dfbe4 | 74 | |
c0e418b5 AM |
75 | /** |
76 | * Lock protecting accesses to the {@link #enabledPatterns} and | |
77 | * {@link #enabledEventNamesCache} maps. | |
78 | */ | |
79 | private final Lock enabledEventNamesLock = new ReentrantLock(); | |
d60dfbe4 | 80 | |
8ab5c06b AM |
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 | ||
d60dfbe4 AM |
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 | } | |
e1357790 AM |
154 | |
155 | LttngUstAgentLogger.log(AbstractLttngAgent.class, "Initializing Agent for domain: " + domain.name()); | |
156 | ||
d60dfbe4 AM |
157 | String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName(); |
158 | ||
3165c2f5 | 159 | rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true); |
d60dfbe4 AM |
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 | ||
3165c2f5 | 166 | userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false); |
d60dfbe4 AM |
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() { | |
e1357790 AM |
183 | LttngUstAgentLogger.log(AbstractLttngAgent.class, "Disposing Agent for domain: " + domain.name()); |
184 | ||
d60dfbe4 AM |
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 | ||
5c76231c AM |
204 | /* |
205 | * Send filter change notifications for all event rules currently | |
206 | * active, then clear them. | |
207 | */ | |
208 | FilterChangeNotifier fcn = FilterChangeNotifier.getInstance(); | |
209 | ||
c0e418b5 AM |
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 | } | |
5c76231c | 218 | } |
c0e418b5 AM |
219 | enabledPatterns.clear(); |
220 | enabledEventNamesCache.clear(); | |
221 | } finally { | |
222 | enabledEventNamesLock.unlock(); | |
5c76231c AM |
223 | } |
224 | ||
225 | /* | |
226 | * Also clear tracked app contexts (no filter notifications sent for | |
227 | * those currently). | |
228 | */ | |
e7a87e50 AM |
229 | enabledAppContexts.clear(); |
230 | ||
d60dfbe4 | 231 | initialized = false; |
d60dfbe4 AM |
232 | } |
233 | ||
3165c2f5 | 234 | @Override |
3daf5cba | 235 | public boolean eventEnabled(EventRule eventRule) { |
4fb826b1 AM |
236 | /* Notify the filter change manager of the command */ |
237 | FilterChangeNotifier.getInstance().addEventRule(eventRule); | |
238 | ||
3daf5cba | 239 | String eventName = eventRule.getEventName(); |
c0e418b5 | 240 | EventNamePattern pattern = new EventNamePattern(eventName); |
3daf5cba | 241 | |
c0e418b5 AM |
242 | enabledEventNamesLock.lock(); |
243 | try { | |
244 | boolean ret = incrementRefCount(pattern, enabledPatterns); | |
245 | enabledEventNamesCache.clear(); | |
246 | return ret; | |
247 | } finally { | |
248 | enabledEventNamesLock.unlock(); | |
d60dfbe4 | 249 | } |
d60dfbe4 AM |
250 | } |
251 | ||
3165c2f5 | 252 | @Override |
d60dfbe4 | 253 | public boolean eventDisabled(String eventName) { |
4fb826b1 AM |
254 | /* Notify the filter change manager of the command */ |
255 | FilterChangeNotifier.getInstance().removeEventRules(eventName); | |
256 | ||
c0e418b5 | 257 | EventNamePattern pattern = new EventNamePattern(eventName); |
d60dfbe4 | 258 | |
c0e418b5 AM |
259 | enabledEventNamesLock.lock(); |
260 | try { | |
261 | boolean ret = decrementRefCount(pattern, enabledPatterns); | |
262 | enabledEventNamesCache.clear(); | |
263 | return ret; | |
264 | } finally { | |
265 | enabledEventNamesLock.unlock(); | |
8ab5c06b | 266 | } |
8ab5c06b AM |
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); | |
d60dfbe4 | 280 | } |
8ab5c06b AM |
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); | |
d60dfbe4 | 293 | |
8ab5c06b AM |
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 | } | |
d60dfbe4 AM |
301 | } |
302 | ||
68a1ef73 AM |
303 | /* |
304 | * Implementation of this method is domain-specific. | |
305 | */ | |
3165c2f5 | 306 | @Override |
68a1ef73 | 307 | public abstract Collection<String> listAvailableEvents(); |
3165c2f5 | 308 | |
d60dfbe4 AM |
309 | @Override |
310 | public boolean isEventEnabled(String eventName) { | |
c0e418b5 AM |
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(); | |
d60dfbe4 AM |
320 | } |
321 | ||
c0e418b5 AM |
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 | } | |
d60dfbe4 | 336 | |
c0e418b5 AM |
337 | /* Add the result to the cache */ |
338 | enabledEventNamesCache.put(eventName, Boolean.valueOf(enabled)); | |
339 | return enabled; | |
d60dfbe4 | 340 | |
c0e418b5 AM |
341 | } finally { |
342 | enabledEventNamesLock.unlock(); | |
343 | } | |
d60dfbe4 AM |
344 | } |
345 | ||
8ab5c06b AM |
346 | @Override |
347 | public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() { | |
348 | return enabledAppContexts.entrySet(); | |
349 | } | |
350 | ||
c0e418b5 | 351 | private static <T> boolean incrementRefCount(T key, Map<T, Integer> refCountMap) { |
8ab5c06b AM |
352 | synchronized (refCountMap) { |
353 | Integer count = refCountMap.get(key); | |
d60dfbe4 AM |
354 | if (count == null) { |
355 | /* This is the first instance of this event being enabled */ | |
8ab5c06b | 356 | refCountMap.put(key, Integer.valueOf(1)); |
d60dfbe4 AM |
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 */ | |
8ab5c06b | 364 | refCountMap.put(key, Integer.valueOf(count.intValue() + 1)); |
d60dfbe4 AM |
365 | return true; |
366 | } | |
367 | } | |
368 | ||
c0e418b5 | 369 | private static <T> boolean decrementRefCount(T key, Map<T, Integer> refCountMap) { |
8ab5c06b AM |
370 | synchronized (refCountMap) { |
371 | Integer count = refCountMap.get(key); | |
d60dfbe4 AM |
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 | */ | |
8ab5c06b | 384 | refCountMap.remove(key); |
d60dfbe4 AM |
385 | return true; |
386 | } | |
387 | /* | |
388 | * Other sessions are still looking for this event, simply decrement | |
389 | * its refcount. | |
390 | */ | |
8ab5c06b | 391 | refCountMap.put(key, Integer.valueOf(count.intValue() - 1)); |
d60dfbe4 AM |
392 | return true; |
393 | } | |
394 | } | |
395 | } | |
396 |