Introduce a verbose mode for the 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
201 initialized = false;
d60dfbe4
AM
202 }
203
3165c2f5 204 @Override
3daf5cba 205 public boolean eventEnabled(EventRule eventRule) {
4fb826b1
AM
206 /* Notify the filter change manager of the command */
207 FilterChangeNotifier.getInstance().addEventRule(eventRule);
208
3daf5cba
AM
209 String eventName = eventRule.getEventName();
210
d60dfbe4
AM
211 if (eventName.equals(WILDCARD)) {
212 enabledWildcards.incrementAndGet();
213 return true;
214 }
d60dfbe4
AM
215 if (eventName.endsWith(WILDCARD)) {
216 /* Strip the "*" from the name. */
217 String prefix = eventName.substring(0, eventName.length() - 1);
8ab5c06b 218 return incrementRefCount(prefix, enabledEventPrefixes);
d60dfbe4
AM
219 }
220
8ab5c06b 221 return incrementRefCount(eventName, enabledEvents);
d60dfbe4
AM
222 }
223
3165c2f5 224 @Override
d60dfbe4 225 public boolean eventDisabled(String eventName) {
4fb826b1
AM
226 /* Notify the filter change manager of the command */
227 FilterChangeNotifier.getInstance().removeEventRules(eventName);
228
d60dfbe4
AM
229 if (eventName.equals(WILDCARD)) {
230 int newCount = enabledWildcards.decrementAndGet();
231 if (newCount < 0) {
232 /* Event was not enabled, bring the count back to 0 */
233 enabledWildcards.incrementAndGet();
234 return false;
235 }
cbaa5167 236 return true;
d60dfbe4
AM
237 }
238
239 if (eventName.endsWith(WILDCARD)) {
240 /* Strip the "*" from the name. */
241 String prefix = eventName.substring(0, eventName.length() - 1);
8ab5c06b
AM
242 return decrementRefCount(prefix, enabledEventPrefixes);
243 }
244
245 return decrementRefCount(eventName, enabledEvents);
246 }
247
248 @Override
249 public boolean appContextEnabled(String contextRetrieverName, String contextName) {
250 synchronized (enabledAppContexts) {
251 Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName);
252 if (retrieverMap == null) {
253 /* There is no submap for this retriever, let's create one. */
254 retrieverMap = new ConcurrentHashMap<String, Integer>();
255 enabledAppContexts.put(contextRetrieverName, retrieverMap);
256 }
257
258 return incrementRefCount(contextName, retrieverMap);
d60dfbe4 259 }
8ab5c06b
AM
260 }
261
262 @Override
263 public boolean appContextDisabled(String contextRetrieverName, String contextName) {
264 synchronized (enabledAppContexts) {
265 Map<String, Integer> retrieverMap = enabledAppContexts.get(contextRetrieverName);
266 if (retrieverMap == null) {
267 /* There was no submap for this retriever, invalid command? */
268 return false;
269 }
270
271 boolean ret = decrementRefCount(contextName, retrieverMap);
d60dfbe4 272
8ab5c06b
AM
273 /* If the submap is now empty we can remove it from the main map. */
274 if (retrieverMap.isEmpty()) {
275 enabledAppContexts.remove(contextRetrieverName);
276 }
277
278 return ret;
279 }
d60dfbe4
AM
280 }
281
68a1ef73
AM
282 /*
283 * Implementation of this method is domain-specific.
284 */
3165c2f5 285 @Override
68a1ef73 286 public abstract Collection<String> listAvailableEvents();
3165c2f5 287
d60dfbe4
AM
288 @Override
289 public boolean isEventEnabled(String eventName) {
290 /* If at least one session enabled the "*" wildcard, send the event */
291 if (enabledWildcards.get() > 0) {
292 return true;
293 }
294
295 /* Check if at least one session wants this exact event name */
296 if (enabledEvents.containsKey(eventName)) {
297 return true;
298 }
299
300 /* Look in the enabled prefixes if one of them matches the event */
301 String potentialMatch = enabledEventPrefixes.floorKey(eventName);
302 if (potentialMatch != null && eventName.startsWith(potentialMatch)) {
303 return true;
304 }
305
306 return false;
307 }
308
8ab5c06b
AM
309 @Override
310 public Collection<Map.Entry<String, Map<String, Integer>>> getEnabledAppContexts() {
311 return enabledAppContexts.entrySet();
312 }
313
314 private static boolean incrementRefCount(String key, Map<String, Integer> refCountMap) {
315 synchronized (refCountMap) {
316 Integer count = refCountMap.get(key);
d60dfbe4
AM
317 if (count == null) {
318 /* This is the first instance of this event being enabled */
8ab5c06b 319 refCountMap.put(key, Integer.valueOf(1));
d60dfbe4
AM
320 return true;
321 }
322 if (count.intValue() <= 0) {
323 /* It should not have been in the map in the first place! */
324 throw new IllegalStateException();
325 }
326 /* The event was already enabled, increment its refcount */
8ab5c06b 327 refCountMap.put(key, Integer.valueOf(count.intValue() + 1));
d60dfbe4
AM
328 return true;
329 }
330 }
331
8ab5c06b
AM
332 private static boolean decrementRefCount(String key, Map<String, Integer> refCountMap) {
333 synchronized (refCountMap) {
334 Integer count = refCountMap.get(key);
d60dfbe4
AM
335 if (count == null || count.intValue() <= 0) {
336 /*
337 * The sessiond asked us to disable an event that was not
338 * enabled previously. Command error?
339 */
340 return false;
341 }
342 if (count.intValue() == 1) {
343 /*
344 * This is the last instance of this event being disabled,
345 * remove it from the map so that we stop sending it.
346 */
8ab5c06b 347 refCountMap.remove(key);
d60dfbe4
AM
348 return true;
349 }
350 /*
351 * Other sessions are still looking for this event, simply decrement
352 * its refcount.
353 */
8ab5c06b 354 refCountMap.put(key, Integer.valueOf(count.intValue() - 1));
d60dfbe4
AM
355 return true;
356 }
357 }
358}
359
This page took 0.037395 seconds and 4 git commands to generate.