Commit | Line | Data |
---|---|---|
d60dfbe4 | 1 | /* |
c0c0989a | 2 | * SPDX-License-Identifier: LGPL-2.1-only |
d60dfbe4 | 3 | * |
c0c0989a MJ |
4 | * Copyright (C) 2015 EfficiOS Inc. |
5 | * Copyright (C) 2015 Alexandre Montplaisir <alexmonthy@efficios.com> | |
6 | * Copyright (C) 2013 David Goulet <dgoulet@efficios.com> | |
d60dfbe4 AM |
7 | */ |
8 | ||
9 | package org.lttng.ust.agent; | |
10 | ||
68a1ef73 | 11 | import java.util.Collection; |
c0e418b5 | 12 | import java.util.HashMap; |
d60dfbe4 | 13 | import java.util.HashSet; |
d60dfbe4 | 14 | import java.util.Map; |
d60dfbe4 AM |
15 | import java.util.Set; |
16 | import java.util.concurrent.ConcurrentHashMap; | |
c0e418b5 AM |
17 | import java.util.concurrent.locks.Lock; |
18 | import java.util.concurrent.locks.ReentrantLock; | |
19 | import java.util.regex.Matcher; | |
d60dfbe4 | 20 | |
3165c2f5 | 21 | import org.lttng.ust.agent.client.ILttngTcpClientListener; |
d60dfbe4 | 22 | import org.lttng.ust.agent.client.LttngTcpSessiondClient; |
4fb826b1 | 23 | import org.lttng.ust.agent.filter.FilterChangeNotifier; |
3daf5cba | 24 | import org.lttng.ust.agent.session.EventRule; |
e1357790 | 25 | import org.lttng.ust.agent.utils.LttngUstAgentLogger; |
d60dfbe4 AM |
26 | |
27 | /** | |
28 | * Base implementation of a {@link ILttngAgent}. | |
29 | * | |
30 | * @author Alexandre Montplaisir | |
31 | * @param <T> | |
32 | * The type of logging handler that should register to this agent | |
33 | */ | |
3165c2f5 AM |
34 | public abstract class AbstractLttngAgent<T extends ILttngHandler> |
35 | implements ILttngAgent<T>, ILttngTcpClientListener { | |
d60dfbe4 | 36 | |
d60dfbe4 AM |
37 | private static final int INIT_TIMEOUT = 3; /* Seconds */ |
38 | ||
39 | /** The handlers registered to this agent */ | |
40 | private final Set<T> registeredHandlers = new HashSet<T>(); | |
41 | ||
42 | /** | |
43 | * The trace events currently enabled in the sessions. | |
44 | * | |
c0e418b5 AM |
45 | * The key is the {@link EventNamePattern} that comes from the event name. |
46 | * The value is the ref count (how many different sessions currently have | |
47 | * this event enabled). Once the ref count falls to 0, this means we can | |
48 | * avoid sending log events through JNI because nobody wants them. | |
d60dfbe4 | 49 | * |
c0e418b5 AM |
50 | * Its accesses should be protected by the {@link #enabledEventNamesLock} |
51 | * below. | |
d60dfbe4 | 52 | */ |
c0e418b5 | 53 | private final Map<EventNamePattern, Integer> enabledPatterns = new HashMap<EventNamePattern, Integer>(); |
d60dfbe4 AM |
54 | |
55 | /** | |
c0e418b5 AM |
56 | * Cache of already-checked event names. As long as enabled/disabled events |
57 | * don't change in the session, we can avoid re-checking events that were | |
58 | * previously checked against all known enabled patterns. | |
d60dfbe4 | 59 | * |
c0e418b5 AM |
60 | * Its accesses should be protected by the {@link #enabledEventNamesLock} |
61 | * below, with the exception of concurrent get operations. | |
d60dfbe4 | 62 | */ |
c0e418b5 | 63 | private final Map<String, Boolean> enabledEventNamesCache = new ConcurrentHashMap<String, Boolean>(); |
d60dfbe4 | 64 | |
c0e418b5 AM |
65 | /** |
66 | * Lock protecting accesses to the {@link #enabledPatterns} and | |
67 | * {@link #enabledEventNamesCache} maps. | |
68 | */ | |
69 | private final Lock enabledEventNamesLock = new ReentrantLock(); | |
d60dfbe4 | 70 | |
8ab5c06b AM |
71 | /** |
72 | * The application contexts currently enabled in the tracing sessions. | |
73 | * | |
74 | * It is first indexed by context retriever, then by context name. This | |
75 | * allows to efficiently query all the contexts for a given retriever. | |
76 | * | |
77 | * Works similarly as {@link #enabledEvents}, but for app contexts (and with | |
78 | * an extra degree of indexing). | |
79 | * | |
80 | * TODO Could be changed to a Guava Table once/if we start using it. | |
81 | */ | |
82 | private final Map<String, Map<String, Integer>> enabledAppContexts = new ConcurrentHashMap<String, Map<String, Integer>>(); | |
83 | ||
d60dfbe4 AM |
84 | /** Tracing domain. Defined by the sub-classes via the constructor. */ |
85 | private final Domain domain; | |
86 | ||
87 | /* Lazy-loaded sessiond clients and their thread objects */ | |
88 | private LttngTcpSessiondClient rootSessiondClient = null; | |
89 | private LttngTcpSessiondClient userSessiondClient = null; | |
90 | private Thread rootSessiondClientThread = null; | |
91 | private Thread userSessiondClientThread = null; | |
92 | ||
93 | /** Indicates if this agent has been initialized. */ | |
94 | private boolean initialized = false; | |
95 | ||
96 | /** | |
97 | * Constructor. Should only be called by sub-classes via super(...); | |
98 | * | |
99 | * @param domain | |
100 | * The tracing domain of this agent. | |
101 | */ | |
102 | protected AbstractLttngAgent(Domain domain) { | |
103 | this.domain = domain; | |
104 | } | |
105 | ||
106 | @Override | |
107 | public Domain getDomain() { | |
108 | return domain; | |
109 | } | |
110 | ||
111 | @Override | |
112 | public void registerHandler(T handler) { | |
113 | synchronized (registeredHandlers) { | |
114 | if (registeredHandlers.isEmpty()) { | |
115 | /* | |
116 | * This is the first handler that registers, we will initialize | |
117 | * the agent. | |
118 | */ | |
119 | init(); | |
120 | } | |
121 | registeredHandlers.add(handler); | |
122 | } | |
123 | } | |
124 | ||
125 | @Override | |
126 | public void unregisterHandler(T handler) { | |
127 | synchronized (registeredHandlers) { | |
128 | registeredHandlers.remove(handler); | |
129 | if (registeredHandlers.isEmpty()) { | |
130 | /* There are no more registered handlers, close the connection. */ | |
131 | dispose(); | |
132 | } | |
133 | } | |
134 | } | |
135 | ||
136 | private void init() { | |
137 | /* | |
138 | * Only called from a synchronized (registeredHandlers) block, should | |
139 | * not need additional synchronization. | |
140 | */ | |
141 | if (initialized) { | |
142 | return; | |
143 | } | |
e1357790 AM |
144 | |
145 | LttngUstAgentLogger.log(AbstractLttngAgent.class, "Initializing Agent for domain: " + domain.name()); | |
146 | ||
d60dfbe4 AM |
147 | String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName(); |
148 | ||
3165c2f5 | 149 | rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true); |
d60dfbe4 AM |
150 | rootSessiondClientThread = new Thread(rootSessiondClient, rootClientThreadName); |
151 | rootSessiondClientThread.setDaemon(true); | |
152 | rootSessiondClientThread.start(); | |
153 | ||
154 | String userClientThreadName = "User sessiond client started by agent: " + this.getClass().getSimpleName(); | |
155 | ||
3165c2f5 | 156 | userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false); |
d60dfbe4 AM |
157 | userSessiondClientThread = new Thread(userSessiondClient, userClientThreadName); |
158 | userSessiondClientThread.setDaemon(true); | |
159 | userSessiondClientThread.start(); | |
160 | ||
161 | /* Give the threads' registration a chance to end. */ | |
162 | if (!rootSessiondClient.waitForConnection(INIT_TIMEOUT)) { | |
163 | userSessiondClient.waitForConnection(INIT_TIMEOUT); | |
164 | } | |
165 | ||
166 | initialized = true; | |
167 | } | |
168 | ||
169 | /** | |
170 | * Dispose the agent | |
171 | */ | |
172 | private void dispose() { | |
e1357790 AM |
173 | LttngUstAgentLogger.log(AbstractLttngAgent.class, "Disposing Agent for domain: " + domain.name()); |
174 | ||
d60dfbe4 AM |
175 | /* |
176 | * Only called from a synchronized (registeredHandlers) block, should | |
177 | * not need additional synchronization. | |
178 | */ | |
179 | rootSessiondClient.close(); | |
180 | userSessiondClient.close(); | |
181 | ||
182 | try { | |
183 | rootSessiondClientThread.join(); | |
184 | userSessiondClientThread.join(); | |
185 | ||
186 | } catch (InterruptedException e) { | |
187 | e.printStackTrace(); | |
188 | } | |
189 | rootSessiondClient = null; | |
190 | rootSessiondClientThread = null; | |
191 | userSessiondClient = null; | |
192 | userSessiondClientThread = null; | |
193 | ||
5c76231c AM |
194 | /* |
195 | * Send filter change notifications for all event rules currently | |
196 | * active, then clear them. | |
197 | */ | |
198 | FilterChangeNotifier fcn = FilterChangeNotifier.getInstance(); | |
199 | ||
c0e418b5 AM |
200 | enabledEventNamesLock.lock(); |
201 | try { | |
202 | for (Map.Entry<EventNamePattern, Integer> entry : enabledPatterns.entrySet()) { | |
203 | String eventName = entry.getKey().getEventName(); | |
204 | Integer nb = entry.getValue(); | |
205 | for (int i = 0; i < nb.intValue(); i++) { | |
206 | fcn.removeEventRules(eventName); | |
207 | } | |
5c76231c | 208 | } |
c0e418b5 AM |
209 | enabledPatterns.clear(); |
210 | enabledEventNamesCache.clear(); | |
211 | } finally { | |
212 | enabledEventNamesLock.unlock(); | |
5c76231c AM |
213 | } |
214 | ||
215 | /* | |
216 | * Also clear tracked app contexts (no filter notifications sent for | |
217 | * those currently). | |
218 | */ | |
e7a87e50 AM |
219 | enabledAppContexts.clear(); |
220 | ||
d60dfbe4 | 221 | initialized = false; |
d60dfbe4 AM |
222 | } |
223 | ||
3165c2f5 | 224 | @Override |
3daf5cba | 225 | public boolean eventEnabled(EventRule eventRule) { |
4fb826b1 AM |
226 | /* Notify the filter change manager of the command */ |
227 | FilterChangeNotifier.getInstance().addEventRule(eventRule); | |
228 | ||
3daf5cba | 229 | String eventName = eventRule.getEventName(); |
c0e418b5 | 230 | EventNamePattern pattern = new EventNamePattern(eventName); |
3daf5cba | 231 | |
c0e418b5 AM |
232 | enabledEventNamesLock.lock(); |
233 | try { | |
234 | boolean ret = incrementRefCount(pattern, enabledPatterns); | |
235 | enabledEventNamesCache.clear(); | |
236 | return ret; | |
237 | } finally { | |
238 | enabledEventNamesLock.unlock(); | |
d60dfbe4 | 239 | } |
d60dfbe4 AM |
240 | } |
241 | ||
3165c2f5 | 242 | @Override |
d60dfbe4 | 243 | public boolean eventDisabled(String eventName) { |
4fb826b1 AM |
244 | /* Notify the filter change manager of the command */ |
245 | FilterChangeNotifier.getInstance().removeEventRules(eventName); | |
246 | ||
c0e418b5 | 247 | EventNamePattern pattern = new EventNamePattern(eventName); |
d60dfbe4 | 248 | |
c0e418b5 AM |
249 | enabledEventNamesLock.lock(); |
250 | try { | |
251 | boolean ret = decrementRefCount(pattern, enabledPatterns); | |
252 | enabledEventNamesCache.clear(); | |
253 | return ret; | |
254 | } finally { | |
255 | enabledEventNamesLock.unlock(); | |
8ab5c06b | 256 | } |
8ab5c06b AM |
257 | } |
258 | ||
259 | @Override | |
260 | public boolean appContextEnabled(String contextRetrieverName, String contextName) { | |
261 | synchronized (enabledAppContexts) { | |
262 | Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName); | |
263 | if (retrieverMap == null) { | |
264 | /* There is no submap for this retriever, let's create one. */ | |
265 | retrieverMap = new ConcurrentHashMap<String, Integer>(); | |
266 | enabledAppContexts.put(contextRetrieverName, retrieverMap); | |
267 | } | |
268 | ||
269 | return incrementRefCount(contextName, retrieverMap); | |
d60dfbe4 | 270 | } |
8ab5c06b AM |
271 | } |
272 | ||
273 | @Override | |
274 | public boolean appContextDisabled(String contextRetrieverName, String contextName) { | |
275 | synchronized (enabledAppContexts) { | |
276 | Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName); | |
277 | if (retrieverMap == null) { | |
278 | /* There was no submap for this retriever, invalid command? */ | |
279 | return false; | |
280 | } | |
281 | ||
282 | boolean ret = decrementRefCount(contextName, retrieverMap); | |
d60dfbe4 | 283 | |
8ab5c06b AM |
284 | /* If the submap is now empty we can remove it from the main map. */ |
285 | if (retrieverMap.isEmpty()) { | |
286 | enabledAppContexts.remove(contextRetrieverName); | |
287 | } | |
288 | ||
289 | return ret; | |
290 | } | |
d60dfbe4 AM |
291 | } |
292 | ||
68a1ef73 AM |
293 | /* |
294 | * Implementation of this method is domain-specific. | |
295 | */ | |
3165c2f5 | 296 | @Override |
68a1ef73 | 297 | public abstract Collection<String> listAvailableEvents(); |
3165c2f5 | 298 | |
d60dfbe4 AM |
299 | @Override |
300 | public boolean isEventEnabled(String eventName) { | |
c0e418b5 AM |
301 | Boolean cachedEnabled = enabledEventNamesCache.get(eventName); |
302 | if (cachedEnabled != null) { | |
303 | /* We have seen this event previously */ | |
304 | /* | |
305 | * Careful! enabled == null could also mean that the null value is | |
306 | * associated with the key. But we should have never inserted null | |
307 | * values in the map. | |
308 | */ | |
309 | return cachedEnabled.booleanValue(); | |
d60dfbe4 AM |
310 | } |
311 | ||
c0e418b5 AM |
312 | /* |
313 | * We have not previously checked this event. Run it against all known | |
314 | * enabled event patterns to determine if it should pass or not. | |
315 | */ | |
316 | enabledEventNamesLock.lock(); | |
317 | try { | |
318 | boolean enabled = false; | |
319 | for (EventNamePattern enabledPattern : enabledPatterns.keySet()) { | |
320 | Matcher matcher = enabledPattern.getPattern().matcher(eventName); | |
321 | if (matcher.matches()) { | |
322 | enabled = true; | |
323 | break; | |
324 | } | |
325 | } | |
d60dfbe4 | 326 | |
c0e418b5 AM |
327 | /* Add the result to the cache */ |
328 | enabledEventNamesCache.put(eventName, Boolean.valueOf(enabled)); | |
329 | return enabled; | |
d60dfbe4 | 330 | |
c0e418b5 AM |
331 | } finally { |
332 | enabledEventNamesLock.unlock(); | |
333 | } | |
d60dfbe4 AM |
334 | } |
335 | ||
8ab5c06b AM |
336 | @Override |
337 | public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() { | |
338 | return enabledAppContexts.entrySet(); | |
339 | } | |
340 | ||
c0e418b5 | 341 | private static <T> boolean incrementRefCount(T key, Map<T, Integer> refCountMap) { |
8ab5c06b AM |
342 | synchronized (refCountMap) { |
343 | Integer count = refCountMap.get(key); | |
d60dfbe4 AM |
344 | if (count == null) { |
345 | /* This is the first instance of this event being enabled */ | |
8ab5c06b | 346 | refCountMap.put(key, Integer.valueOf(1)); |
d60dfbe4 AM |
347 | return true; |
348 | } | |
349 | if (count.intValue() <= 0) { | |
350 | /* It should not have been in the map in the first place! */ | |
351 | throw new IllegalStateException(); | |
352 | } | |
353 | /* The event was already enabled, increment its refcount */ | |
8ab5c06b | 354 | refCountMap.put(key, Integer.valueOf(count.intValue() + 1)); |
d60dfbe4 AM |
355 | return true; |
356 | } | |
357 | } | |
358 | ||
c0e418b5 | 359 | private static <T> boolean decrementRefCount(T key, Map<T, Integer> refCountMap) { |
8ab5c06b AM |
360 | synchronized (refCountMap) { |
361 | Integer count = refCountMap.get(key); | |
d60dfbe4 AM |
362 | if (count == null || count.intValue() <= 0) { |
363 | /* | |
364 | * The sessiond asked us to disable an event that was not | |
365 | * enabled previously. Command error? | |
366 | */ | |
367 | return false; | |
368 | } | |
369 | if (count.intValue() == 1) { | |
370 | /* | |
371 | * This is the last instance of this event being disabled, | |
372 | * remove it from the map so that we stop sending it. | |
373 | */ | |
8ab5c06b | 374 | refCountMap.remove(key); |
d60dfbe4 AM |
375 | return true; |
376 | } | |
377 | /* | |
378 | * Other sessions are still looking for this event, simply decrement | |
379 | * its refcount. | |
380 | */ | |
8ab5c06b | 381 | refCountMap.put(key, Integer.valueOf(count.intValue() - 1)); |
d60dfbe4 AM |
382 | return true; |
383 | } | |
384 | } | |
385 | } | |
386 |