Move to kernel style SPDX license identifiers
[lttng-ust.git] / liblttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / AbstractLttngAgent.java
CommitLineData
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
9package org.lttng.ust.agent;
10
68a1ef73 11import java.util.Collection;
c0e418b5 12import java.util.HashMap;
d60dfbe4 13import java.util.HashSet;
d60dfbe4 14import java.util.Map;
d60dfbe4
AM
15import java.util.Set;
16import java.util.concurrent.ConcurrentHashMap;
c0e418b5
AM
17import java.util.concurrent.locks.Lock;
18import java.util.concurrent.locks.ReentrantLock;
19import java.util.regex.Matcher;
d60dfbe4 20
3165c2f5 21import org.lttng.ust.agent.client.ILttngTcpClientListener;
d60dfbe4 22import org.lttng.ust.agent.client.LttngTcpSessiondClient;
4fb826b1 23import org.lttng.ust.agent.filter.FilterChangeNotifier;
3daf5cba 24import org.lttng.ust.agent.session.EventRule;
e1357790 25import 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
34public 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
This page took 0.043807 seconds and 4 git commands to generate.