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(); | |
43e5396b DG |
191 | |
192 | setupEventTimer(); | |
193 | ||
194 | /* | |
195 | * Block on socket receive and wait for command from the | |
196 | * session daemon. This will return if and only if there is a | |
197 | * fatal error or the socket closes. | |
198 | */ | |
199 | handleSessiondCmd(); | |
200 | } catch (UnknownHostException uhe) { | |
201 | this.registerSem.release(); | |
202 | System.out.println(uhe); | |
203 | } catch (IOException ioe) { | |
204 | this.registerSem.release(); | |
205 | Thread.sleep(3000); | |
206 | } catch (Exception e) { | |
207 | this.registerSem.release(); | |
208 | e.printStackTrace(); | |
209 | } | |
210 | } | |
211 | } | |
212 | ||
213 | public void destroy() { | |
214 | this.quit = true; | |
215 | this.eventTimer.cancel(); | |
216 | ||
217 | try { | |
218 | if (this.sessiondSock != null) { | |
219 | this.sessiondSock.close(); | |
220 | } | |
221 | } catch (Exception e) { | |
222 | e.printStackTrace(); | |
223 | } | |
224 | } | |
225 | ||
226 | /* | |
227 | * Receive header data from the session daemon using the LTTng command | |
228 | * static buffer of the right size. | |
229 | */ | |
230 | private void recvHeader() throws Exception { | |
231 | int read_len; | |
232 | byte data[] = new byte[this.headerCmd.SIZE]; | |
233 | ||
234 | read_len = this.inFromSessiond.read(data, 0, data.length); | |
235 | if (read_len != data.length) { | |
236 | throw new IOException(); | |
237 | } | |
238 | this.headerCmd.populate(data); | |
239 | } | |
240 | ||
241 | /* | |
242 | * Receive payload from the session daemon. This MUST be done after a | |
243 | * recvHeader() so the header value of a command are known. | |
244 | * | |
245 | * The caller SHOULD use isPayload() before which returns true if a payload | |
246 | * is expected after the header. | |
247 | */ | |
248 | private byte[] recvPayload() throws Exception { | |
249 | byte payload[] = new byte[(int) this.headerCmd.data_size]; | |
250 | ||
251 | /* Failsafe check so we don't waste our time reading 0 bytes. */ | |
252 | if (payload.length == 0) { | |
253 | return null; | |
254 | } | |
255 | ||
256 | this.inFromSessiond.read(payload, 0, payload.length); | |
257 | return payload; | |
258 | } | |
259 | ||
260 | /* | |
261 | * Handle session command from the session daemon. | |
262 | */ | |
263 | private void handleSessiondCmd() throws Exception { | |
264 | int ret_code; | |
265 | byte data[] = null; | |
266 | ||
267 | while (true) { | |
268 | /* Get header from session daemon. */ | |
269 | recvHeader(); | |
270 | ||
271 | if (headerCmd.data_size > 0) { | |
272 | data = recvPayload(); | |
273 | } | |
274 | ||
275 | switch (headerCmd.cmd) { | |
f08bb871 DG |
276 | case CMD_REG_DONE: |
277 | { | |
278 | /* | |
279 | * Release semaphore so meaning registration is done and we | |
280 | * can proceed to continue tracing. | |
281 | */ | |
282 | this.registerSem.release(); | |
283 | break; | |
284 | } | |
43e5396b DG |
285 | case CMD_LIST: |
286 | { | |
287 | LTTngSessiondCmd2_4.sessiond_list_logger listLoggerCmd = | |
288 | new LTTngSessiondCmd2_4.sessiond_list_logger(); | |
289 | listLoggerCmd.execute(this.handler); | |
290 | data = listLoggerCmd.getBytes(); | |
291 | break; | |
292 | } | |
293 | case CMD_ENABLE: | |
294 | { | |
5b5ffa03 | 295 | LTTngEvent event; |
43e5396b DG |
296 | LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = |
297 | new LTTngSessiondCmd2_4.sessiond_enable_handler(); | |
298 | if (data == null) { | |
299 | enableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
300 | break; | |
301 | } | |
302 | enableCmd.populate(data); | |
5b5ffa03 DG |
303 | event = enableCmd.execute(this.handler, this.enabledLoggers); |
304 | if (event != null) { | |
43e5396b | 305 | /* |
e614d916 | 306 | * Add the event to the set so it can be enabled if |
43e5396b DG |
307 | * the logger appears at some point in time. |
308 | */ | |
e614d916 | 309 | enabledEventSet.add(event); |
43e5396b DG |
310 | } |
311 | data = enableCmd.getBytes(); | |
312 | break; | |
313 | } | |
314 | case CMD_DISABLE: | |
315 | { | |
316 | LTTngSessiondCmd2_4.sessiond_disable_handler disableCmd = | |
317 | new LTTngSessiondCmd2_4.sessiond_disable_handler(); | |
318 | if (data == null) { | |
319 | disableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
320 | break; | |
321 | } | |
322 | disableCmd.populate(data); | |
323 | disableCmd.execute(this.handler); | |
324 | data = disableCmd.getBytes(); | |
325 | break; | |
326 | } | |
327 | default: | |
328 | { | |
329 | data = new byte[4]; | |
330 | ByteBuffer buf = ByteBuffer.wrap(data); | |
331 | buf.order(ByteOrder.BIG_ENDIAN); | |
332 | LTTngSessiondCmd2_4.lttng_jul_ret_code code = | |
333 | LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
334 | buf.putInt(code.getCode()); | |
335 | break; | |
336 | } | |
337 | } | |
338 | ||
339 | /* Send payload to session daemon. */ | |
340 | this.outToSessiond.write(data, 0, data.length); | |
341 | this.outToSessiond.flush(); | |
342 | } | |
343 | } | |
344 | ||
345 | private void connectToSessiond() throws Exception { | |
346 | this.sessiondSock = new Socket(this.sessiondHost, this.sessiondPort); | |
347 | this.inFromSessiond = new DataInputStream( | |
348 | sessiondSock.getInputStream()); | |
349 | this.outToSessiond = new DataOutputStream( | |
350 | sessiondSock.getOutputStream()); | |
351 | } | |
352 | ||
353 | private void registerToSessiond() throws Exception { | |
354 | byte data[] = new byte[4]; | |
355 | ByteBuffer buf = ByteBuffer.wrap(data); | |
356 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; | |
357 | ||
358 | buf.putInt(Integer.parseInt(pid)); | |
359 | this.outToSessiond.write(data, 0, data.length); | |
360 | this.outToSessiond.flush(); | |
361 | } | |
362 | } |