Commit | Line | Data |
---|---|---|
43e5396b DG |
1 | /* |
2 | * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com> | |
3 | * | |
4 | * This library is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU Lesser General Public License, version 2.1 only, | |
6 | * as published by the Free Software Foundation. | |
7 | * | |
8 | * This library is distributed in the hope that it will be useful, but WITHOUT | |
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
11 | * for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU Lesser General Public License | |
14 | * along with this library; if not, write to the Free Software Foundation, | |
15 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
16 | */ | |
17 | ||
18 | package org.lttng.ust.jul; | |
19 | ||
20 | import java.util.concurrent.Semaphore; | |
21 | import java.nio.ByteBuffer; | |
22 | import java.nio.ByteOrder; | |
23 | import java.lang.Integer; | |
24 | import java.io.IOException; | |
25 | import java.io.BufferedOutputStream; | |
26 | import java.io.ByteArrayOutputStream; | |
27 | import java.io.DataOutputStream; | |
28 | import java.io.DataInputStream; | |
29 | import java.net.*; | |
30 | import java.lang.management.ManagementFactory; | |
31 | import java.util.ArrayList; | |
529e6def | 32 | import java.util.HashMap; |
e614d916 JG |
33 | import java.util.HashSet; |
34 | import java.util.Iterator; | |
43e5396b | 35 | import java.util.List; |
e614d916 | 36 | import java.util.Set; |
43e5396b DG |
37 | import java.util.Timer; |
38 | import java.util.TimerTask; | |
529e6def | 39 | import java.util.logging.Logger; |
e614d916 | 40 | import java.util.Collections; |
43e5396b DG |
41 | |
42 | class USTRegisterMsg { | |
43 | public static int pid; | |
44 | } | |
45 | ||
46 | public class LTTngTCPSessiondClient { | |
47 | /* Command header from the session deamon. */ | |
48 | private LTTngSessiondCmd2_4.sessiond_hdr headerCmd = | |
49 | new LTTngSessiondCmd2_4.sessiond_hdr(); | |
50 | ||
51 | private final String sessiondHost; | |
52 | private final int sessiondPort; | |
53 | private Socket sessiondSock; | |
54 | private boolean quit = false; | |
55 | ||
56 | private DataInputStream inFromSessiond; | |
57 | private DataOutputStream outToSessiond; | |
58 | ||
59 | private LTTngLogHandler handler; | |
60 | ||
61 | private Semaphore registerSem; | |
62 | ||
63 | private Timer eventTimer; | |
e614d916 JG |
64 | private Set<LTTngEvent> enabledEventSet = |
65 | Collections.synchronizedSet(new HashSet<LTTngEvent>()); | |
529e6def DG |
66 | /* |
67 | * Map of Logger objects that have been enabled. They are indexed by name. | |
68 | */ | |
69 | private HashMap<String, Logger> enabledLoggers = new HashMap<String, Logger>(); | |
43e5396b DG |
70 | /* Timer delay at each 5 seconds. */ |
71 | private final static long timerDelay = 5 * 1000; | |
72 | private static boolean timerInitialized; | |
73 | ||
74 | public LTTngTCPSessiondClient(String host, int port, Semaphore sem) { | |
75 | this.sessiondHost = host; | |
76 | this.sessiondPort = port; | |
77 | this.registerSem = sem; | |
78 | this.eventTimer = new Timer(); | |
79 | this.timerInitialized = false; | |
80 | } | |
81 | ||
82 | private void setupEventTimer() { | |
83 | if (this.timerInitialized) { | |
84 | return; | |
85 | } | |
86 | ||
87 | this.eventTimer.scheduleAtFixedRate(new TimerTask() { | |
88 | @Override | |
89 | public void run() { | |
e614d916 JG |
90 | synchronized (enabledEventSet) { |
91 | LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = new | |
92 | LTTngSessiondCmd2_4.sessiond_enable_handler(); | |
529e6def | 93 | /* |
e614d916 JG |
94 | * Modifying events in a Set will raise a |
95 | * ConcurrentModificationException. Thus, we remove an event | |
96 | * and add its modified version to modifiedEvents when a | |
97 | * modification is necessary. | |
529e6def | 98 | */ |
e614d916 JG |
99 | Set<LTTngEvent> modifiedEvents = new HashSet<LTTngEvent>(); |
100 | Iterator<LTTngEvent> it = enabledEventSet.iterator(); | |
5b5ffa03 | 101 | |
e614d916 JG |
102 | while (it.hasNext()) { |
103 | int ret; | |
104 | Logger logger; | |
105 | LTTngEvent event = it.next(); | |
5b5ffa03 | 106 | |
5b5ffa03 | 107 | /* |
e614d916 JG |
108 | * Check if this Logger name has been enabled already. Note |
109 | * that in the case of "*", it's never added in that hash | |
110 | * table thus the enable command does a lookup for each | |
111 | * logger name in that hash table for the * case in order | |
112 | * to make sure we don't enable twice the same logger | |
113 | * because JUL apparently accepts that the *same* | |
114 | * LogHandler can be added twice on a Logger object... | |
115 | * don't ask... | |
5b5ffa03 | 116 | */ |
e614d916 JG |
117 | logger = enabledLoggers.get(event.name); |
118 | if (logger != null) { | |
119 | continue; | |
120 | } | |
529e6def | 121 | |
e614d916 JG |
122 | /* |
123 | * Set to one means that the enable all event has been seen | |
124 | * thus event from that point on must use loglevel for all | |
125 | * events. Else the object has its own loglevel. | |
126 | */ | |
127 | if (handler.logLevelUseAll == 1) { | |
128 | it.remove(); | |
129 | event.logLevel.level = handler.logLevelAll; | |
130 | event.logLevel.type = handler.logLevelTypeAll; | |
131 | modifiedEvents.add(event); | |
132 | } | |
133 | ||
134 | /* | |
135 | * The all event is a special case since we have to iterate | |
136 | * over every Logger to see which one was not enabled. | |
137 | */ | |
138 | if (event.name.equals("*")) { | |
139 | enableCmd.name = event.name; | |
140 | enableCmd.lttngLogLevel = event.logLevel.level; | |
141 | enableCmd.lttngLogLevelType = event.logLevel.type; | |
142 | /* | |
143 | * The return value is irrelevant since the * event is | |
144 | * always kept in the set. | |
145 | */ | |
146 | enableCmd.execute(handler, enabledLoggers); | |
147 | continue; | |
148 | } | |
149 | ||
150 | ret = enableCmd.enableLogger(handler, event, enabledLoggers); | |
151 | if (ret == 1) { | |
152 | /* Enabled so remove the event from the set. */ | |
153 | if (!modifiedEvents.remove(event)) { | |
154 | /* | |
155 | * event can only be present in one of | |
156 | * the sets. | |
157 | */ | |
158 | it.remove(); | |
159 | } | |
160 | } | |
43e5396b | 161 | } |
e614d916 | 162 | enabledEventSet.addAll(modifiedEvents); |
43e5396b | 163 | } |
e614d916 | 164 | |
43e5396b DG |
165 | } |
166 | }, this.timerDelay, this.timerDelay); | |
167 | ||
168 | this.timerInitialized = true; | |
169 | } | |
170 | ||
171 | public void init(LTTngLogHandler handler) throws InterruptedException { | |
172 | this.handler = handler; | |
173 | ||
174 | for (;;) { | |
175 | if (this.quit) { | |
176 | break; | |
177 | } | |
178 | ||
179 | try { | |
180 | ||
181 | /* | |
182 | * Connect to the session daemon before anything else. | |
183 | */ | |
184 | connectToSessiond(); | |
185 | ||
186 | /* | |
187 | * Register to the session daemon as the Java component of the | |
188 | * UST application. | |
189 | */ | |
190 | registerToSessiond(); | |
191 | this.registerSem.release(); | |
192 | ||
193 | setupEventTimer(); | |
194 | ||
195 | /* | |
196 | * Block on socket receive and wait for command from the | |
197 | * session daemon. This will return if and only if there is a | |
198 | * fatal error or the socket closes. | |
199 | */ | |
200 | handleSessiondCmd(); | |
201 | } catch (UnknownHostException uhe) { | |
202 | this.registerSem.release(); | |
203 | System.out.println(uhe); | |
204 | } catch (IOException ioe) { | |
205 | this.registerSem.release(); | |
206 | Thread.sleep(3000); | |
207 | } catch (Exception e) { | |
208 | this.registerSem.release(); | |
209 | e.printStackTrace(); | |
210 | } | |
211 | } | |
212 | } | |
213 | ||
214 | public void destroy() { | |
215 | this.quit = true; | |
216 | this.eventTimer.cancel(); | |
217 | ||
218 | try { | |
219 | if (this.sessiondSock != null) { | |
220 | this.sessiondSock.close(); | |
221 | } | |
222 | } catch (Exception e) { | |
223 | e.printStackTrace(); | |
224 | } | |
225 | } | |
226 | ||
227 | /* | |
228 | * Receive header data from the session daemon using the LTTng command | |
229 | * static buffer of the right size. | |
230 | */ | |
231 | private void recvHeader() throws Exception { | |
232 | int read_len; | |
233 | byte data[] = new byte[this.headerCmd.SIZE]; | |
234 | ||
235 | read_len = this.inFromSessiond.read(data, 0, data.length); | |
236 | if (read_len != data.length) { | |
237 | throw new IOException(); | |
238 | } | |
239 | this.headerCmd.populate(data); | |
240 | } | |
241 | ||
242 | /* | |
243 | * Receive payload from the session daemon. This MUST be done after a | |
244 | * recvHeader() so the header value of a command are known. | |
245 | * | |
246 | * The caller SHOULD use isPayload() before which returns true if a payload | |
247 | * is expected after the header. | |
248 | */ | |
249 | private byte[] recvPayload() throws Exception { | |
250 | byte payload[] = new byte[(int) this.headerCmd.data_size]; | |
251 | ||
252 | /* Failsafe check so we don't waste our time reading 0 bytes. */ | |
253 | if (payload.length == 0) { | |
254 | return null; | |
255 | } | |
256 | ||
257 | this.inFromSessiond.read(payload, 0, payload.length); | |
258 | return payload; | |
259 | } | |
260 | ||
261 | /* | |
262 | * Handle session command from the session daemon. | |
263 | */ | |
264 | private void handleSessiondCmd() throws Exception { | |
265 | int ret_code; | |
266 | byte data[] = null; | |
267 | ||
268 | while (true) { | |
269 | /* Get header from session daemon. */ | |
270 | recvHeader(); | |
271 | ||
272 | if (headerCmd.data_size > 0) { | |
273 | data = recvPayload(); | |
274 | } | |
275 | ||
276 | switch (headerCmd.cmd) { | |
277 | case CMD_LIST: | |
278 | { | |
279 | LTTngSessiondCmd2_4.sessiond_list_logger listLoggerCmd = | |
280 | new LTTngSessiondCmd2_4.sessiond_list_logger(); | |
281 | listLoggerCmd.execute(this.handler); | |
282 | data = listLoggerCmd.getBytes(); | |
283 | break; | |
284 | } | |
285 | case CMD_ENABLE: | |
286 | { | |
5b5ffa03 | 287 | LTTngEvent event; |
43e5396b DG |
288 | LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = |
289 | new LTTngSessiondCmd2_4.sessiond_enable_handler(); | |
290 | if (data == null) { | |
291 | enableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
292 | break; | |
293 | } | |
294 | enableCmd.populate(data); | |
5b5ffa03 DG |
295 | event = enableCmd.execute(this.handler, this.enabledLoggers); |
296 | if (event != null) { | |
43e5396b | 297 | /* |
e614d916 | 298 | * Add the event to the set so it can be enabled if |
43e5396b DG |
299 | * the logger appears at some point in time. |
300 | */ | |
e614d916 | 301 | enabledEventSet.add(event); |
43e5396b DG |
302 | } |
303 | data = enableCmd.getBytes(); | |
304 | break; | |
305 | } | |
306 | case CMD_DISABLE: | |
307 | { | |
308 | LTTngSessiondCmd2_4.sessiond_disable_handler disableCmd = | |
309 | new LTTngSessiondCmd2_4.sessiond_disable_handler(); | |
310 | if (data == null) { | |
311 | disableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
312 | break; | |
313 | } | |
314 | disableCmd.populate(data); | |
315 | disableCmd.execute(this.handler); | |
316 | data = disableCmd.getBytes(); | |
317 | break; | |
318 | } | |
319 | default: | |
320 | { | |
321 | data = new byte[4]; | |
322 | ByteBuffer buf = ByteBuffer.wrap(data); | |
323 | buf.order(ByteOrder.BIG_ENDIAN); | |
324 | LTTngSessiondCmd2_4.lttng_jul_ret_code code = | |
325 | LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
326 | buf.putInt(code.getCode()); | |
327 | break; | |
328 | } | |
329 | } | |
330 | ||
331 | /* Send payload to session daemon. */ | |
332 | this.outToSessiond.write(data, 0, data.length); | |
333 | this.outToSessiond.flush(); | |
334 | } | |
335 | } | |
336 | ||
337 | private void connectToSessiond() throws Exception { | |
338 | this.sessiondSock = new Socket(this.sessiondHost, this.sessiondPort); | |
339 | this.inFromSessiond = new DataInputStream( | |
340 | sessiondSock.getInputStream()); | |
341 | this.outToSessiond = new DataOutputStream( | |
342 | sessiondSock.getOutputStream()); | |
343 | } | |
344 | ||
345 | private void registerToSessiond() throws Exception { | |
346 | byte data[] = new byte[4]; | |
347 | ByteBuffer buf = ByteBuffer.wrap(data); | |
348 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; | |
349 | ||
350 | buf.putInt(Integer.parseInt(pid)); | |
351 | this.outToSessiond.write(data, 0, data.length); | |
352 | this.outToSessiond.flush(); | |
353 | } | |
354 | } |