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; |
d60dfbe4 | 22 | import java.util.HashSet; |
d60dfbe4 AM |
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 | ||
3165c2f5 | 30 | import org.lttng.ust.agent.client.ILttngTcpClientListener; |
d60dfbe4 | 31 | import org.lttng.ust.agent.client.LttngTcpSessiondClient; |
4fb826b1 | 32 | import org.lttng.ust.agent.filter.FilterChangeNotifier; |
3daf5cba | 33 | import org.lttng.ust.agent.session.EventRule; |
e1357790 | 34 | import org.lttng.ust.agent.utils.LttngUstAgentLogger; |
d60dfbe4 AM |
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 | */ | |
3165c2f5 AM |
43 | public abstract class AbstractLttngAgent<T extends ILttngHandler> |
44 | implements ILttngAgent<T>, ILttngTcpClientListener { | |
d60dfbe4 AM |
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 | * | |
3daf5cba | 60 | * It uses a concurrent hash map, so that the {@link #isEventEnabled} and |
d60dfbe4 AM |
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 | ||
8ab5c06b AM |
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 | ||
d60dfbe4 AM |
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 | } | |
e1357790 AM |
152 | |
153 | LttngUstAgentLogger.log(AbstractLttngAgent.class, "Initializing Agent for domain: " + domain.name()); | |
154 | ||
d60dfbe4 AM |
155 | String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName(); |
156 | ||
3165c2f5 | 157 | rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true); |
d60dfbe4 AM |
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 | ||
3165c2f5 | 164 | userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false); |
d60dfbe4 AM |
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() { | |
e1357790 AM |
181 | LttngUstAgentLogger.log(AbstractLttngAgent.class, "Disposing Agent for domain: " + domain.name()); |
182 | ||
d60dfbe4 AM |
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 | ||
5c76231c AM |
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 | } | |
d60dfbe4 | 215 | enabledEvents.clear(); |
5c76231c AM |
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 | } | |
d60dfbe4 | 225 | enabledEventPrefixes.clear(); |
d60dfbe4 | 226 | |
5c76231c AM |
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 | */ | |
e7a87e50 AM |
236 | enabledAppContexts.clear(); |
237 | ||
d60dfbe4 | 238 | initialized = false; |
d60dfbe4 AM |
239 | } |
240 | ||
3165c2f5 | 241 | @Override |
3daf5cba | 242 | public boolean eventEnabled(EventRule eventRule) { |
4fb826b1 AM |
243 | /* Notify the filter change manager of the command */ |
244 | FilterChangeNotifier.getInstance().addEventRule(eventRule); | |
245 | ||
3daf5cba AM |
246 | String eventName = eventRule.getEventName(); |
247 | ||
d60dfbe4 AM |
248 | if (eventName.equals(WILDCARD)) { |
249 | enabledWildcards.incrementAndGet(); | |
250 | return true; | |
251 | } | |
d60dfbe4 AM |
252 | if (eventName.endsWith(WILDCARD)) { |
253 | /* Strip the "*" from the name. */ | |
254 | String prefix = eventName.substring(0, eventName.length() - 1); | |
8ab5c06b | 255 | return incrementRefCount(prefix, enabledEventPrefixes); |
d60dfbe4 AM |
256 | } |
257 | ||
8ab5c06b | 258 | return incrementRefCount(eventName, enabledEvents); |
d60dfbe4 AM |
259 | } |
260 | ||
3165c2f5 | 261 | @Override |
d60dfbe4 | 262 | public boolean eventDisabled(String eventName) { |
4fb826b1 AM |
263 | /* Notify the filter change manager of the command */ |
264 | FilterChangeNotifier.getInstance().removeEventRules(eventName); | |
265 | ||
d60dfbe4 AM |
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 | } | |
cbaa5167 | 273 | return true; |
d60dfbe4 AM |
274 | } |
275 | ||
276 | if (eventName.endsWith(WILDCARD)) { | |
277 | /* Strip the "*" from the name. */ | |
278 | String prefix = eventName.substring(0, eventName.length() - 1); | |
8ab5c06b AM |
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); | |
d60dfbe4 | 296 | } |
8ab5c06b AM |
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); | |
d60dfbe4 | 309 | |
8ab5c06b AM |
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 | } | |
d60dfbe4 AM |
317 | } |
318 | ||
68a1ef73 AM |
319 | /* |
320 | * Implementation of this method is domain-specific. | |
321 | */ | |
3165c2f5 | 322 | @Override |
68a1ef73 | 323 | public abstract Collection<String> listAvailableEvents(); |
3165c2f5 | 324 | |
d60dfbe4 AM |
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 | ||
8ab5c06b AM |
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); | |
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 | ||
8ab5c06b AM |
369 | private static boolean decrementRefCount(String key, Map<String, Integer> refCountMap) { |
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 |