Fix: Clear tracked application contexts upon closing a Java agent
[lttng-ust.git] / liblttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / AbstractLttngAgent.java
CommitLineData
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
19package org.lttng.ust.agent;
20
68a1ef73 21import java.util.Collection;
d60dfbe4 22import java.util.HashSet;
d60dfbe4
AM
23import java.util.Map;
24import java.util.NavigableMap;
25import java.util.Set;
26import java.util.concurrent.ConcurrentHashMap;
27import java.util.concurrent.ConcurrentSkipListMap;
28import java.util.concurrent.atomic.AtomicInteger;
29
3165c2f5 30import org.lttng.ust.agent.client.ILttngTcpClientListener;
d60dfbe4 31import org.lttng.ust.agent.client.LttngTcpSessiondClient;
4fb826b1 32import org.lttng.ust.agent.filter.FilterChangeNotifier;
3daf5cba 33import org.lttng.ust.agent.session.EventRule;
d60dfbe4
AM
34
35/**
36 * Base implementation of a {@link ILttngAgent}.
37 *
38 * @author Alexandre Montplaisir
39 * @param <T>
40 * The type of logging handler that should register to this agent
41 */
3165c2f5
AM
42public abstract class AbstractLttngAgent<T extends ILttngHandler>
43 implements ILttngAgent<T>, ILttngTcpClientListener {
d60dfbe4
AM
44
45 private static final String WILDCARD = "*";
46 private static final int INIT_TIMEOUT = 3; /* Seconds */
47
48 /** The handlers registered to this agent */
49 private final Set<T> registeredHandlers = new HashSet<T>();
50
51 /**
52 * The trace events currently enabled in the sessions.
53 *
54 * The key represents the event name, the value is the ref count (how many
55 * different sessions currently have this event enabled). Once the ref count
56 * falls to 0, this means we can avoid sending log events through JNI
57 * because nobody wants them.
58 *
3daf5cba 59 * It uses a concurrent hash map, so that the {@link #isEventEnabled} and
d60dfbe4
AM
60 * read methods do not need to take a synchronization lock.
61 */
62 private final Map<String, Integer> enabledEvents = new ConcurrentHashMap<String, Integer>();
63
64 /**
65 * The trace events prefixes currently enabled in the sessions, which means
66 * the event names finishing in *, like "abcd*". We track them separately
67 * from the standard event names, so that we can use {@link String#equals}
68 * and {@link String#startsWith} appropriately.
69 *
70 * We track the lone wildcard "*" separately, in {@link #enabledWildcards}.
71 */
72 private final NavigableMap<String, Integer> enabledEventPrefixes =
73 new ConcurrentSkipListMap<String, Integer>();
74
75 /** Number of sessions currently enabling the wildcard "*" event */
76 private final AtomicInteger enabledWildcards = new AtomicInteger(0);
77
8ab5c06b
AM
78 /**
79 * The application contexts currently enabled in the tracing sessions.
80 *
81 * It is first indexed by context retriever, then by context name. This
82 * allows to efficiently query all the contexts for a given retriever.
83 *
84 * Works similarly as {@link #enabledEvents}, but for app contexts (and with
85 * an extra degree of indexing).
86 *
87 * TODO Could be changed to a Guava Table once/if we start using it.
88 */
89 private final Map<String, Map<String, Integer>> enabledAppContexts = new ConcurrentHashMap<String, Map<String, Integer>>();
90
d60dfbe4
AM
91 /** Tracing domain. Defined by the sub-classes via the constructor. */
92 private final Domain domain;
93
94 /* Lazy-loaded sessiond clients and their thread objects */
95 private LttngTcpSessiondClient rootSessiondClient = null;
96 private LttngTcpSessiondClient userSessiondClient = null;
97 private Thread rootSessiondClientThread = null;
98 private Thread userSessiondClientThread = null;
99
100 /** Indicates if this agent has been initialized. */
101 private boolean initialized = false;
102
103 /**
104 * Constructor. Should only be called by sub-classes via super(...);
105 *
106 * @param domain
107 * The tracing domain of this agent.
108 */
109 protected AbstractLttngAgent(Domain domain) {
110 this.domain = domain;
111 }
112
113 @Override
114 public Domain getDomain() {
115 return domain;
116 }
117
118 @Override
119 public void registerHandler(T handler) {
120 synchronized (registeredHandlers) {
121 if (registeredHandlers.isEmpty()) {
122 /*
123 * This is the first handler that registers, we will initialize
124 * the agent.
125 */
126 init();
127 }
128 registeredHandlers.add(handler);
129 }
130 }
131
132 @Override
133 public void unregisterHandler(T handler) {
134 synchronized (registeredHandlers) {
135 registeredHandlers.remove(handler);
136 if (registeredHandlers.isEmpty()) {
137 /* There are no more registered handlers, close the connection. */
138 dispose();
139 }
140 }
141 }
142
143 private void init() {
144 /*
145 * Only called from a synchronized (registeredHandlers) block, should
146 * not need additional synchronization.
147 */
148 if (initialized) {
149 return;
150 }
151 String rootClientThreadName = "Root sessiond client started by agent: " + this.getClass().getSimpleName();
152
3165c2f5 153 rootSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), true);
d60dfbe4
AM
154 rootSessiondClientThread = new Thread(rootSessiondClient, rootClientThreadName);
155 rootSessiondClientThread.setDaemon(true);
156 rootSessiondClientThread.start();
157
158 String userClientThreadName = "User sessiond client started by agent: " + this.getClass().getSimpleName();
159
3165c2f5 160 userSessiondClient = new LttngTcpSessiondClient(this, getDomain().value(), false);
d60dfbe4
AM
161 userSessiondClientThread = new Thread(userSessiondClient, userClientThreadName);
162 userSessiondClientThread.setDaemon(true);
163 userSessiondClientThread.start();
164
165 /* Give the threads' registration a chance to end. */
166 if (!rootSessiondClient.waitForConnection(INIT_TIMEOUT)) {
167 userSessiondClient.waitForConnection(INIT_TIMEOUT);
168 }
169
170 initialized = true;
171 }
172
173 /**
174 * Dispose the agent
175 */
176 private void dispose() {
177 /*
178 * Only called from a synchronized (registeredHandlers) block, should
179 * not need additional synchronization.
180 */
181 rootSessiondClient.close();
182 userSessiondClient.close();
183
184 try {
185 rootSessiondClientThread.join();
186 userSessiondClientThread.join();
187
188 } catch (InterruptedException e) {
189 e.printStackTrace();
190 }
191 rootSessiondClient = null;
192 rootSessiondClientThread = null;
193 userSessiondClient = null;
194 userSessiondClientThread = null;
195
196 /* Reset all enabled event counts to 0 */
197 enabledEvents.clear();
198 enabledEventPrefixes.clear();
199 enabledWildcards.set(0);
200
e7a87e50
AM
201 enabledAppContexts.clear();
202
d60dfbe4 203 initialized = false;
d60dfbe4
AM
204 }
205
3165c2f5 206 @Override
3daf5cba 207 public boolean eventEnabled(EventRule eventRule) {
4fb826b1
AM
208 /* Notify the filter change manager of the command */
209 FilterChangeNotifier.getInstance().addEventRule(eventRule);
210
3daf5cba
AM
211 String eventName = eventRule.getEventName();
212
d60dfbe4
AM
213 if (eventName.equals(WILDCARD)) {
214 enabledWildcards.incrementAndGet();
215 return true;
216 }
d60dfbe4
AM
217 if (eventName.endsWith(WILDCARD)) {
218 /* Strip the "*" from the name. */
219 String prefix = eventName.substring(0, eventName.length() - 1);
8ab5c06b 220 return incrementRefCount(prefix, enabledEventPrefixes);
d60dfbe4
AM
221 }
222
8ab5c06b 223 return incrementRefCount(eventName, enabledEvents);
d60dfbe4
AM
224 }
225
3165c2f5 226 @Override
d60dfbe4 227 public boolean eventDisabled(String eventName) {
4fb826b1
AM
228 /* Notify the filter change manager of the command */
229 FilterChangeNotifier.getInstance().removeEventRules(eventName);
230
d60dfbe4
AM
231 if (eventName.equals(WILDCARD)) {
232 int newCount = enabledWildcards.decrementAndGet();
233 if (newCount < 0) {
234 /* Event was not enabled, bring the count back to 0 */
235 enabledWildcards.incrementAndGet();
236 return false;
237 }
cbaa5167 238 return true;
d60dfbe4
AM
239 }
240
241 if (eventName.endsWith(WILDCARD)) {
242 /* Strip the "*" from the name. */
243 String prefix = eventName.substring(0, eventName.length() - 1);
8ab5c06b
AM
244 return decrementRefCount(prefix, enabledEventPrefixes);
245 }
246
247 return decrementRefCount(eventName, enabledEvents);
248 }
249
250 @Override
251 public boolean appContextEnabled(String contextRetrieverName, String contextName) {
252 synchronized (enabledAppContexts) {
253 Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName);
254 if (retrieverMap == null) {
255 /* There is no submap for this retriever, let's create one. */
256 retrieverMap = new ConcurrentHashMap<String, Integer>();
257 enabledAppContexts.put(contextRetrieverName, retrieverMap);
258 }
259
260 return incrementRefCount(contextName, retrieverMap);
d60dfbe4 261 }
8ab5c06b
AM
262 }
263
264 @Override
265 public boolean appContextDisabled(String contextRetrieverName, String contextName) {
266 synchronized (enabledAppContexts) {
267 Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName);
268 if (retrieverMap == null) {
269 /* There was no submap for this retriever, invalid command? */
270 return false;
271 }
272
273 boolean ret = decrementRefCount(contextName, retrieverMap);
d60dfbe4 274
8ab5c06b
AM
275 /* If the submap is now empty we can remove it from the main map. */
276 if (retrieverMap.isEmpty()) {
277 enabledAppContexts.remove(contextRetrieverName);
278 }
279
280 return ret;
281 }
d60dfbe4
AM
282 }
283
68a1ef73
AM
284 /*
285 * Implementation of this method is domain-specific.
286 */
3165c2f5 287 @Override
68a1ef73 288 public abstract Collection<String> listAvailableEvents();
3165c2f5 289
d60dfbe4
AM
290 @Override
291 public boolean isEventEnabled(String eventName) {
292 /* If at least one session enabled the "*" wildcard, send the event */
293 if (enabledWildcards.get() > 0) {
294 return true;
295 }
296
297 /* Check if at least one session wants this exact event name */
298 if (enabledEvents.containsKey(eventName)) {
299 return true;
300 }
301
302 /* Look in the enabled prefixes if one of them matches the event */
303 String potentialMatch = enabledEventPrefixes.floorKey(eventName);
304 if (potentialMatch != null && eventName.startsWith(potentialMatch)) {
305 return true;
306 }
307
308 return false;
309 }
310
8ab5c06b
AM
311 @Override
312 public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() {
313 return enabledAppContexts.entrySet();
314 }
315
316 private static boolean incrementRefCount(String key, Map<String, Integer> refCountMap) {
317 synchronized (refCountMap) {
318 Integer count = refCountMap.get(key);
d60dfbe4
AM
319 if (count == null) {
320 /* This is the first instance of this event being enabled */
8ab5c06b 321 refCountMap.put(key, Integer.valueOf(1));
d60dfbe4
AM
322 return true;
323 }
324 if (count.intValue() <= 0) {
325 /* It should not have been in the map in the first place! */
326 throw new IllegalStateException();
327 }
328 /* The event was already enabled, increment its refcount */
8ab5c06b 329 refCountMap.put(key, Integer.valueOf(count.intValue() + 1));
d60dfbe4
AM
330 return true;
331 }
332 }
333
8ab5c06b
AM
334 private static boolean decrementRefCount(String key, Map<String, Integer> refCountMap) {
335 synchronized (refCountMap) {
336 Integer count = refCountMap.get(key);
d60dfbe4
AM
337 if (count == null || count.intValue() <= 0) {
338 /*
339 * The sessiond asked us to disable an event that was not
340 * enabled previously. Command error?
341 */
342 return false;
343 }
344 if (count.intValue() == 1) {
345 /*
346 * This is the last instance of this event being disabled,
347 * remove it from the map so that we stop sending it.
348 */
8ab5c06b 349 refCountMap.remove(key);
d60dfbe4
AM
350 return true;
351 }
352 /*
353 * Other sessions are still looking for this event, simply decrement
354 * its refcount.
355 */
8ab5c06b 356 refCountMap.put(key, Integer.valueOf(count.intValue() - 1));
d60dfbe4
AM
357 return true;
358 }
359 }
360}
361
This page took 0.038873 seconds and 4 git commands to generate.