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 | ||
21 | import java.util.HashSet; | |
22 | import java.util.LinkedList; | |
23 | import java.util.List; | |
24 | import java.util.Map; | |
25 | import java.util.NavigableMap; | |
26 | import java.util.Set; | |
27 | import java.util.concurrent.ConcurrentHashMap; | |
28 | import java.util.concurrent.ConcurrentSkipListMap; | |
29 | import java.util.concurrent.atomic.AtomicInteger; | |
30 | ||
31 | import org.lttng.ust.agent.client.LttngTcpSessiondClient; | |
32 | ||
33 | /** | |
34 | * Base implementation of a {@link ILttngAgent}. | |
35 | * | |
36 | * @author Alexandre Montplaisir | |
37 | * @param <T> | |
38 | * The type of logging handler that should register to this agent | |
39 | */ | |
40 | public abstract class AbstractLttngAgent<T extends ILttngHandler> implements ILttngAgent<T> { | |
41 | ||
42 | private static final String WILDCARD = "*"; | |
43 | private static final int INIT_TIMEOUT = 3; /* Seconds */ | |
44 | ||
45 | /** The handlers registered to this agent */ | |
46 | private final Set<T> registeredHandlers = new HashSet<T>(); | |
47 | ||
48 | /** | |
49 | * The trace events currently enabled in the sessions. | |
50 | * | |
51 | * The key represents the event name, the value is the ref count (how many | |
52 | * different sessions currently have this event enabled). Once the ref count | |
53 | * falls to 0, this means we can avoid sending log events through JNI | |
54 | * because nobody wants them. | |
55 | * | |
56 | * It uses a concurrent hash set", so that the {@link #isEventEnabled} and | |
57 | * read methods do not need to take a synchronization lock. | |
58 | */ | |
59 | private final Map<String, Integer> enabledEvents = new ConcurrentHashMap<String, Integer>(); | |
60 | ||
61 | /** | |
62 | * The trace events prefixes currently enabled in the sessions, which means | |
63 | * the event names finishing in *, like "abcd*". We track them separately | |
64 | * from the standard event names, so that we can use {@link String#equals} | |
65 | * and {@link String#startsWith} appropriately. | |
66 | * | |
67 | * We track the lone wildcard "*" separately, in {@link #enabledWildcards}. | |
68 | */ | |
69 | private final NavigableMap<String, Integer> enabledEventPrefixes = | |
70 | new ConcurrentSkipListMap<String, Integer>(); | |
71 | ||
72 | /** Number of sessions currently enabling the wildcard "*" event */ | |
73 | private final AtomicInteger enabledWildcards = new AtomicInteger(0); | |
74 | ||
75 | /** Tracing domain. Defined by the sub-classes via the constructor. */ | |
76 | private final Domain domain; | |
77 | ||
78 | /* Lazy-loaded sessiond clients and their thread objects */ | |
79 | private LttngTcpSessiondClient rootSessiondClient = null; | |
80 | private LttngTcpSessiondClient userSessiondClient = null; | |
81 | private Thread rootSessiondClientThread = null; | |
82 | private Thread userSessiondClientThread = null; | |
83 | ||
84 | /** Indicates if this agent has been initialized. */ | |
85 | private boolean initialized = false; | |
86 | ||
87 | /** | |
88 | * Constructor. Should only be called by sub-classes via super(...); | |
89 | * | |
90 | * @param domain | |
91 | * The tracing domain of this agent. | |
92 | */ | |
93 | protected AbstractLttngAgent(Domain domain) { | |
94 | this.domain = domain; | |
95 | } | |
96 | ||
97 | @Override | |
98 | public Domain getDomain() { | |
99 | return domain; | |
100 | } | |
101 | ||
102 | @Override | |
103 | public void registerHandler(T handler) { | |
104 | synchronized (registeredHandlers) { | |
105 | if (registeredHandlers.isEmpty()) { | |
106 | /* | |
107 | * This is the first handler that registers, we will initialize | |
108 | * the agent. | |
109 | */ | |
110 | init(); | |
111 | } | |
112 | registeredHandlers.add(handler); | |
113 | } | |
114 | } | |
115 | ||
116 | @Override | |
117 | public void unregisterHandler(T handler) { | |
118 | synchronized (registeredHandlers) { | |
119 | registeredHandlers.remove(handler); | |
120 | if (registeredHandlers.isEmpty()) { | |
121 | /* There are no more registered handlers, close the connection. */ | |
122 | dispose(); | |
123 | } | |
124 | } | |
125 | } | |
126 | ||
127 | private void init() { | |
128 | /* | |
129 | * Only called from a synchronized (registeredHandlers) block, should | |
130 | * not need additional synchronization. | |
131 | */ | |
132 | if (initialized) { | |
133 | return; | |
134 | } | |
135 | String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName(); | |
136 | ||
137 | rootSessiondClient = new LttngTcpSessiondClient(this, true); | |
138 | rootSessiondClientThread = new Thread(rootSessiondClient, rootClientThreadName); | |
139 | rootSessiondClientThread.setDaemon(true); | |
140 | rootSessiondClientThread.start(); | |
141 | ||
142 | String userClientThreadName = "User sessiond client started by agent: " + this.getClass().getSimpleName(); | |
143 | ||
144 | userSessiondClient = new LttngTcpSessiondClient(this, false); | |
145 | userSessiondClientThread = new Thread(userSessiondClient, userClientThreadName); | |
146 | userSessiondClientThread.setDaemon(true); | |
147 | userSessiondClientThread.start(); | |
148 | ||
149 | /* Give the threads' registration a chance to end. */ | |
150 | if (!rootSessiondClient.waitForConnection(INIT_TIMEOUT)) { | |
151 | userSessiondClient.waitForConnection(INIT_TIMEOUT); | |
152 | } | |
153 | ||
154 | initialized = true; | |
155 | } | |
156 | ||
157 | /** | |
158 | * Dispose the agent | |
159 | */ | |
160 | private void dispose() { | |
161 | /* | |
162 | * Only called from a synchronized (registeredHandlers) block, should | |
163 | * not need additional synchronization. | |
164 | */ | |
165 | rootSessiondClient.close(); | |
166 | userSessiondClient.close(); | |
167 | ||
168 | try { | |
169 | rootSessiondClientThread.join(); | |
170 | userSessiondClientThread.join(); | |
171 | ||
172 | } catch (InterruptedException e) { | |
173 | e.printStackTrace(); | |
174 | } | |
175 | rootSessiondClient = null; | |
176 | rootSessiondClientThread = null; | |
177 | userSessiondClient = null; | |
178 | userSessiondClientThread = null; | |
179 | ||
180 | /* Reset all enabled event counts to 0 */ | |
181 | enabledEvents.clear(); | |
182 | enabledEventPrefixes.clear(); | |
183 | enabledWildcards.set(0); | |
184 | ||
185 | initialized = false; | |
186 | ||
187 | } | |
188 | ||
189 | /** | |
190 | * Callback for the TCP clients to notify the agent that a request for | |
191 | * enabling an event was sent from the session daemon. | |
192 | * | |
193 | * @param eventName | |
194 | * The name of the event that was requested to be enabled. | |
195 | * @return Since we do not track individual sessions, right now this command | |
196 | * cannot fail. It will always return true. | |
197 | */ | |
198 | public boolean eventEnabled(String eventName) { | |
199 | if (eventName.equals(WILDCARD)) { | |
200 | enabledWildcards.incrementAndGet(); | |
201 | return true; | |
202 | } | |
203 | ||
204 | if (eventName.endsWith(WILDCARD)) { | |
205 | /* Strip the "*" from the name. */ | |
206 | String prefix = eventName.substring(0, eventName.length() - 1); | |
207 | return incrementEventCount(prefix, enabledEventPrefixes); | |
208 | } | |
209 | ||
210 | return incrementEventCount(eventName, enabledEvents); | |
211 | } | |
212 | ||
213 | /** | |
214 | * Callback for the TCP clients to notify the agent that a request for | |
215 | * disabling an event was sent from the session daemon. | |
216 | * | |
217 | * @param eventName | |
218 | * The name of the event that was requested to be disabled. | |
219 | * @return True if the command completed successfully, false if we should | |
220 | * report an error (event was not enabled, etc.) | |
221 | */ | |
222 | public boolean eventDisabled(String eventName) { | |
223 | if (eventName.equals(WILDCARD)) { | |
224 | int newCount = enabledWildcards.decrementAndGet(); | |
225 | if (newCount < 0) { | |
226 | /* Event was not enabled, bring the count back to 0 */ | |
227 | enabledWildcards.incrementAndGet(); | |
228 | return false; | |
229 | } | |
230 | } | |
231 | ||
232 | if (eventName.endsWith(WILDCARD)) { | |
233 | /* Strip the "*" from the name. */ | |
234 | String prefix = eventName.substring(0, eventName.length() - 1); | |
235 | return decrementEventCount(prefix, enabledEventPrefixes); | |
236 | } | |
237 | ||
238 | return decrementEventCount(eventName, enabledEvents); | |
239 | } | |
240 | ||
241 | @Override | |
242 | public boolean isEventEnabled(String eventName) { | |
243 | /* If at least one session enabled the "*" wildcard, send the event */ | |
244 | if (enabledWildcards.get() > 0) { | |
245 | return true; | |
246 | } | |
247 | ||
248 | /* Check if at least one session wants this exact event name */ | |
249 | if (enabledEvents.containsKey(eventName)) { | |
250 | return true; | |
251 | } | |
252 | ||
253 | /* Look in the enabled prefixes if one of them matches the event */ | |
254 | String potentialMatch = enabledEventPrefixes.floorKey(eventName); | |
255 | if (potentialMatch != null && eventName.startsWith(potentialMatch)) { | |
256 | return true; | |
257 | } | |
258 | ||
259 | return false; | |
260 | } | |
261 | ||
262 | @Override | |
263 | public Iterable<String> listEnabledEvents() { | |
264 | List<String> events = new LinkedList<String>(); | |
265 | ||
266 | if (enabledWildcards.get() > 0) { | |
267 | events.add(WILDCARD); | |
268 | } | |
269 | for (String prefix : enabledEventPrefixes.keySet()) { | |
270 | events.add(new String(prefix + WILDCARD)); | |
271 | } | |
272 | events.addAll(enabledEvents.keySet()); | |
273 | return events; | |
274 | } | |
275 | ||
276 | private static boolean incrementEventCount(String eventName, Map<String, Integer> eventMap) { | |
277 | synchronized (eventMap) { | |
278 | Integer count = eventMap.get(eventName); | |
279 | if (count == null) { | |
280 | /* This is the first instance of this event being enabled */ | |
281 | eventMap.put(eventName, Integer.valueOf(1)); | |
282 | return true; | |
283 | } | |
284 | if (count.intValue() <= 0) { | |
285 | /* It should not have been in the map in the first place! */ | |
286 | throw new IllegalStateException(); | |
287 | } | |
288 | /* The event was already enabled, increment its refcount */ | |
289 | eventMap.put(eventName, Integer.valueOf(count.intValue() + 1)); | |
290 | return true; | |
291 | } | |
292 | } | |
293 | ||
294 | private static boolean decrementEventCount(String eventName, Map<String, Integer> eventMap) { | |
295 | synchronized (eventMap) { | |
296 | Integer count = eventMap.get(eventName); | |
297 | if (count == null || count.intValue() <= 0) { | |
298 | /* | |
299 | * The sessiond asked us to disable an event that was not | |
300 | * enabled previously. Command error? | |
301 | */ | |
302 | return false; | |
303 | } | |
304 | if (count.intValue() == 1) { | |
305 | /* | |
306 | * This is the last instance of this event being disabled, | |
307 | * remove it from the map so that we stop sending it. | |
308 | */ | |
309 | eventMap.remove(eventName); | |
310 | return true; | |
311 | } | |
312 | /* | |
313 | * Other sessions are still looking for this event, simply decrement | |
314 | * its refcount. | |
315 | */ | |
316 | eventMap.put(eventName, Integer.valueOf(count.intValue() - 1)); | |
317 | return true; | |
318 | } | |
319 | } | |
320 | } | |
321 |