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; |
43e5396b DG |
33 | import java.util.List; |
34 | import java.util.Timer; | |
35 | import java.util.TimerTask; | |
529e6def | 36 | import java.util.logging.Logger; |
43e5396b DG |
37 | |
38 | class USTRegisterMsg { | |
39 | public static int pid; | |
40 | } | |
41 | ||
42 | public class LTTngTCPSessiondClient { | |
43 | /* Command header from the session deamon. */ | |
44 | private LTTngSessiondCmd2_4.sessiond_hdr headerCmd = | |
45 | new LTTngSessiondCmd2_4.sessiond_hdr(); | |
46 | ||
47 | private final String sessiondHost; | |
48 | private final int sessiondPort; | |
49 | private Socket sessiondSock; | |
50 | private boolean quit = false; | |
51 | ||
52 | private DataInputStream inFromSessiond; | |
53 | private DataOutputStream outToSessiond; | |
54 | ||
55 | private LTTngLogHandler handler; | |
56 | ||
57 | private Semaphore registerSem; | |
58 | ||
59 | private Timer eventTimer; | |
5b5ffa03 | 60 | private List<LTTngEvent> enabledEventList = new ArrayList<LTTngEvent>(); |
529e6def DG |
61 | /* |
62 | * Map of Logger objects that have been enabled. They are indexed by name. | |
63 | */ | |
64 | private HashMap<String, Logger> enabledLoggers = new HashMap<String, Logger>(); | |
43e5396b DG |
65 | /* Timer delay at each 5 seconds. */ |
66 | private final static long timerDelay = 5 * 1000; | |
67 | private static boolean timerInitialized; | |
68 | ||
69 | public LTTngTCPSessiondClient(String host, int port, Semaphore sem) { | |
70 | this.sessiondHost = host; | |
71 | this.sessiondPort = port; | |
72 | this.registerSem = sem; | |
73 | this.eventTimer = new Timer(); | |
74 | this.timerInitialized = false; | |
75 | } | |
76 | ||
77 | private void setupEventTimer() { | |
78 | if (this.timerInitialized) { | |
79 | return; | |
80 | } | |
81 | ||
82 | this.eventTimer.scheduleAtFixedRate(new TimerTask() { | |
83 | @Override | |
84 | public void run() { | |
85 | /* | |
86 | * We have to make a copy here since it is possible that the | |
87 | * enabled event list is changed during an iteration on it. | |
88 | */ | |
5b5ffa03 | 89 | List<LTTngEvent> tmpList = new ArrayList<LTTngEvent>(enabledEventList); |
43e5396b DG |
90 | |
91 | LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = new | |
92 | LTTngSessiondCmd2_4.sessiond_enable_handler(); | |
5b5ffa03 DG |
93 | for (LTTngEvent event: tmpList) { |
94 | int ret; | |
95 | Logger logger; | |
96 | ||
529e6def DG |
97 | /* |
98 | * Check if this Logger name has been enabled already. Note | |
99 | * that in the case of "*", it's never added in that hash | |
100 | * table thus the enable command does a lookup for each | |
101 | * logger name in that hash table for the * case in order | |
102 | * to make sure we don't enable twice the same logger | |
103 | * because JUL apparently accepts that the *same* | |
104 | * LogHandler can be added twice on a Logger object... | |
105 | * don't ask... | |
106 | */ | |
5b5ffa03 DG |
107 | logger = enabledLoggers.get(event.name); |
108 | if (logger != null) { | |
109 | continue; | |
110 | } | |
111 | ||
112 | /* | |
113 | * Set to one means that the enable all event has been seen | |
114 | * thus event from that point on must use loglevel for all | |
115 | * events. Else the object has its own loglevel. | |
116 | */ | |
117 | if (handler.logLevelUseAll == 1) { | |
118 | event.logLevel.level = handler.logLevelAll; | |
119 | event.logLevel.type = handler.logLevelTypeAll; | |
120 | } | |
121 | ||
122 | /* | |
123 | * The all event is a special case since we have to iterate | |
124 | * over every Logger to see which one was not enabled. | |
125 | */ | |
126 | if (event.name.equals("*")) { | |
127 | enableCmd.name = event.name; | |
128 | enableCmd.lttngLogLevel = event.logLevel.level; | |
129 | enableCmd.lttngLogLevelType = event.logLevel.type; | |
130 | /* | |
131 | * The return value is irrelevant since the * event is | |
132 | * always kept in the list. | |
133 | */ | |
134 | enableCmd.execute(handler, enabledLoggers); | |
529e6def DG |
135 | continue; |
136 | } | |
137 | ||
5b5ffa03 DG |
138 | ret = enableCmd.enableLogger(handler, event, enabledLoggers); |
139 | if (ret == 1) { | |
140 | /* Enabled so remove the event from the list. */ | |
141 | enabledEventList.remove(event); | |
43e5396b DG |
142 | } |
143 | } | |
144 | } | |
145 | }, this.timerDelay, this.timerDelay); | |
146 | ||
147 | this.timerInitialized = true; | |
148 | } | |
149 | ||
150 | public void init(LTTngLogHandler handler) throws InterruptedException { | |
151 | this.handler = handler; | |
152 | ||
153 | for (;;) { | |
154 | if (this.quit) { | |
155 | break; | |
156 | } | |
157 | ||
158 | try { | |
159 | ||
160 | /* | |
161 | * Connect to the session daemon before anything else. | |
162 | */ | |
163 | connectToSessiond(); | |
164 | ||
165 | /* | |
166 | * Register to the session daemon as the Java component of the | |
167 | * UST application. | |
168 | */ | |
169 | registerToSessiond(); | |
170 | this.registerSem.release(); | |
171 | ||
172 | setupEventTimer(); | |
173 | ||
174 | /* | |
175 | * Block on socket receive and wait for command from the | |
176 | * session daemon. This will return if and only if there is a | |
177 | * fatal error or the socket closes. | |
178 | */ | |
179 | handleSessiondCmd(); | |
180 | } catch (UnknownHostException uhe) { | |
181 | this.registerSem.release(); | |
182 | System.out.println(uhe); | |
183 | } catch (IOException ioe) { | |
184 | this.registerSem.release(); | |
185 | Thread.sleep(3000); | |
186 | } catch (Exception e) { | |
187 | this.registerSem.release(); | |
188 | e.printStackTrace(); | |
189 | } | |
190 | } | |
191 | } | |
192 | ||
193 | public void destroy() { | |
194 | this.quit = true; | |
195 | this.eventTimer.cancel(); | |
196 | ||
197 | try { | |
198 | if (this.sessiondSock != null) { | |
199 | this.sessiondSock.close(); | |
200 | } | |
201 | } catch (Exception e) { | |
202 | e.printStackTrace(); | |
203 | } | |
204 | } | |
205 | ||
206 | /* | |
207 | * Receive header data from the session daemon using the LTTng command | |
208 | * static buffer of the right size. | |
209 | */ | |
210 | private void recvHeader() throws Exception { | |
211 | int read_len; | |
212 | byte data[] = new byte[this.headerCmd.SIZE]; | |
213 | ||
214 | read_len = this.inFromSessiond.read(data, 0, data.length); | |
215 | if (read_len != data.length) { | |
216 | throw new IOException(); | |
217 | } | |
218 | this.headerCmd.populate(data); | |
219 | } | |
220 | ||
221 | /* | |
222 | * Receive payload from the session daemon. This MUST be done after a | |
223 | * recvHeader() so the header value of a command are known. | |
224 | * | |
225 | * The caller SHOULD use isPayload() before which returns true if a payload | |
226 | * is expected after the header. | |
227 | */ | |
228 | private byte[] recvPayload() throws Exception { | |
229 | byte payload[] = new byte[(int) this.headerCmd.data_size]; | |
230 | ||
231 | /* Failsafe check so we don't waste our time reading 0 bytes. */ | |
232 | if (payload.length == 0) { | |
233 | return null; | |
234 | } | |
235 | ||
236 | this.inFromSessiond.read(payload, 0, payload.length); | |
237 | return payload; | |
238 | } | |
239 | ||
240 | /* | |
241 | * Handle session command from the session daemon. | |
242 | */ | |
243 | private void handleSessiondCmd() throws Exception { | |
244 | int ret_code; | |
245 | byte data[] = null; | |
246 | ||
247 | while (true) { | |
248 | /* Get header from session daemon. */ | |
249 | recvHeader(); | |
250 | ||
251 | if (headerCmd.data_size > 0) { | |
252 | data = recvPayload(); | |
253 | } | |
254 | ||
255 | switch (headerCmd.cmd) { | |
256 | case CMD_LIST: | |
257 | { | |
258 | LTTngSessiondCmd2_4.sessiond_list_logger listLoggerCmd = | |
259 | new LTTngSessiondCmd2_4.sessiond_list_logger(); | |
260 | listLoggerCmd.execute(this.handler); | |
261 | data = listLoggerCmd.getBytes(); | |
262 | break; | |
263 | } | |
264 | case CMD_ENABLE: | |
265 | { | |
5b5ffa03 | 266 | LTTngEvent event; |
43e5396b DG |
267 | LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = |
268 | new LTTngSessiondCmd2_4.sessiond_enable_handler(); | |
269 | if (data == null) { | |
270 | enableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
271 | break; | |
272 | } | |
273 | enableCmd.populate(data); | |
5b5ffa03 DG |
274 | event = enableCmd.execute(this.handler, this.enabledLoggers); |
275 | if (event != null) { | |
43e5396b DG |
276 | /* |
277 | * Add the event to the list so it can be enabled if | |
278 | * the logger appears at some point in time. | |
279 | */ | |
5b5ffa03 DG |
280 | if (enabledEventList.contains(event) == false) { |
281 | enabledEventList.add(event); | |
529e6def | 282 | } |
43e5396b DG |
283 | } |
284 | data = enableCmd.getBytes(); | |
285 | break; | |
286 | } | |
287 | case CMD_DISABLE: | |
288 | { | |
289 | LTTngSessiondCmd2_4.sessiond_disable_handler disableCmd = | |
290 | new LTTngSessiondCmd2_4.sessiond_disable_handler(); | |
291 | if (data == null) { | |
292 | disableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
293 | break; | |
294 | } | |
295 | disableCmd.populate(data); | |
296 | disableCmd.execute(this.handler); | |
297 | data = disableCmd.getBytes(); | |
298 | break; | |
299 | } | |
300 | default: | |
301 | { | |
302 | data = new byte[4]; | |
303 | ByteBuffer buf = ByteBuffer.wrap(data); | |
304 | buf.order(ByteOrder.BIG_ENDIAN); | |
305 | LTTngSessiondCmd2_4.lttng_jul_ret_code code = | |
306 | LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD; | |
307 | buf.putInt(code.getCode()); | |
308 | break; | |
309 | } | |
310 | } | |
311 | ||
312 | /* Send payload to session daemon. */ | |
313 | this.outToSessiond.write(data, 0, data.length); | |
314 | this.outToSessiond.flush(); | |
315 | } | |
316 | } | |
317 | ||
318 | private void connectToSessiond() throws Exception { | |
319 | this.sessiondSock = new Socket(this.sessiondHost, this.sessiondPort); | |
320 | this.inFromSessiond = new DataInputStream( | |
321 | sessiondSock.getInputStream()); | |
322 | this.outToSessiond = new DataOutputStream( | |
323 | sessiondSock.getOutputStream()); | |
324 | } | |
325 | ||
326 | private void registerToSessiond() throws Exception { | |
327 | byte data[] = new byte[4]; | |
328 | ByteBuffer buf = ByteBuffer.wrap(data); | |
329 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; | |
330 | ||
331 | buf.putInt(Integer.parseInt(pid)); | |
332 | this.outToSessiond.write(data, 0, data.length); | |
333 | this.outToSessiond.flush(); | |
334 | } | |
335 | } |