f3ac80dc6e78860ae8ae4fb72cfb182da65a445d
[lttng-ust.git] / liblttng-ust-jul / org / lttng / ust / jul / LTTngTCPSessiondClient.java
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.BufferedReader;
27 import java.io.ByteArrayOutputStream;
28 import java.io.DataOutputStream;
29 import java.io.DataInputStream;
30 import java.io.FileReader;
31 import java.io.FileNotFoundException;
32 import java.net.*;
33 import java.lang.management.ManagementFactory;
34 import java.util.ArrayList;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Set;
40 import java.util.Timer;
41 import java.util.TimerTask;
42 import java.util.logging.Logger;
43 import java.util.Collections;
44
45 class USTRegisterMsg {
46 public static int pid;
47 }
48
49 public class LTTngTCPSessiondClient {
50 /* Command header from the session deamon. */
51 private LTTngSessiondCmd2_4.sessiond_hdr headerCmd =
52 new LTTngSessiondCmd2_4.sessiond_hdr();
53
54 private final String sessiondHost;
55 private Socket sessiondSock;
56 private boolean quit = false;
57
58 private DataInputStream inFromSessiond;
59 private DataOutputStream outToSessiond;
60
61 private LTTngLogHandler handler;
62
63 private Semaphore registerSem;
64
65 private Timer eventTimer;
66 private Set<LTTngEvent> enabledEventSet =
67 Collections.synchronizedSet(new HashSet<LTTngEvent>());
68 /*
69 * Map of Logger objects that have been enabled. They are indexed by name.
70 */
71 private HashMap<String, Logger> enabledLoggers = new HashMap<String, Logger>();
72 /* Timer delay at each 5 seconds. */
73 private final static long timerDelay = 5 * 1000;
74 private static boolean timerInitialized;
75
76 private static final String rootPortFile = "/var/run/lttng/jul.port";
77 private static final String userPortFile = "/.lttng/jul.port";
78
79 /* Indicate if we've already release the semaphore. */
80 private boolean sem_posted = false;
81
82 public LTTngTCPSessiondClient(String host, Semaphore sem) {
83 this.sessiondHost = host;
84 this.registerSem = sem;
85 this.eventTimer = new Timer();
86 this.timerInitialized = false;
87 }
88
89 private void setupEventTimer() {
90 if (this.timerInitialized) {
91 return;
92 }
93
94 this.eventTimer.scheduleAtFixedRate(new TimerTask() {
95 @Override
96 public void run() {
97 synchronized (enabledEventSet) {
98 LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = new
99 LTTngSessiondCmd2_4.sessiond_enable_handler();
100 /*
101 * Modifying events in a Set will raise a
102 * ConcurrentModificationException. Thus, we remove an event
103 * and add its modified version to modifiedEvents when a
104 * modification is necessary.
105 */
106 Set<LTTngEvent> modifiedEvents = new HashSet<LTTngEvent>();
107 Iterator<LTTngEvent> it = enabledEventSet.iterator();
108
109 while (it.hasNext()) {
110 int ret;
111 Logger logger;
112 LTTngEvent event = it.next();
113
114 /*
115 * Check if this Logger name has been enabled already. Note
116 * that in the case of "*", it's never added in that hash
117 * table thus the enable command does a lookup for each
118 * logger name in that hash table for the * case in order
119 * to make sure we don't enable twice the same logger
120 * because JUL apparently accepts that the *same*
121 * LogHandler can be added twice on a Logger object...
122 * don't ask...
123 */
124 logger = enabledLoggers.get(event.name);
125 if (logger != null) {
126 continue;
127 }
128
129 /*
130 * Set to one means that the enable all event has been seen
131 * thus event from that point on must use loglevel for all
132 * events. Else the object has its own loglevel.
133 */
134 if (handler.logLevelUseAll == 1) {
135 it.remove();
136 event.logLevels.addAll(handler.logLevelsAll);
137 modifiedEvents.add(event);
138 }
139
140 /*
141 * The all event is a special case since we have to iterate
142 * over every Logger to see which one was not enabled.
143 */
144 if (event.name.equals("*")) {
145 enableCmd.name = event.name;
146 /* Tell the command NOT to add the loglevel. */
147 enableCmd.lttngLogLevel = -1;
148 /*
149 * The return value is irrelevant since the * event is
150 * always kept in the set.
151 */
152 enableCmd.execute(handler, enabledLoggers);
153 continue;
154 }
155
156 ret = enableCmd.enableLogger(handler, event, enabledLoggers);
157 if (ret == 1) {
158 /* Enabled so remove the event from the set. */
159 if (!modifiedEvents.remove(event)) {
160 /*
161 * event can only be present in one of
162 * the sets.
163 */
164 it.remove();
165 }
166 }
167 }
168 enabledEventSet.addAll(modifiedEvents);
169 }
170
171 }
172 }, this.timerDelay, this.timerDelay);
173
174 this.timerInitialized = true;
175 }
176
177 /*
178 * Try to release the registerSem if it's not already done.
179 */
180 private void tryReleaseSem()
181 {
182 /* Release semaphore so we unblock the agent. */
183 if (!this.sem_posted) {
184 this.registerSem.release();
185 this.sem_posted = true;
186 }
187 }
188
189 public void init(LTTngLogHandler handler) throws InterruptedException {
190 this.handler = handler;
191
192 for (;;) {
193 if (this.quit) {
194 break;
195 }
196
197 try {
198
199 /*
200 * Connect to the session daemon before anything else.
201 */
202 connectToSessiond();
203
204 /*
205 * Register to the session daemon as the Java component of the
206 * UST application.
207 */
208 registerToSessiond();
209
210 setupEventTimer();
211
212 /*
213 * Block on socket receive and wait for command from the
214 * session daemon. This will return if and only if there is a
215 * fatal error or the socket closes.
216 */
217 handleSessiondCmd();
218 } catch (UnknownHostException uhe) {
219 tryReleaseSem();
220 System.out.println(uhe);
221 } catch (IOException ioe) {
222 tryReleaseSem();
223 Thread.sleep(3000);
224 } catch (Exception e) {
225 tryReleaseSem();
226 e.printStackTrace();
227 }
228 }
229 }
230
231 public void destroy() {
232 this.quit = true;
233 this.eventTimer.cancel();
234
235 try {
236 if (this.sessiondSock != null) {
237 this.sessiondSock.close();
238 }
239 } catch (Exception e) {
240 e.printStackTrace();
241 }
242 }
243
244 /*
245 * Receive header data from the session daemon using the LTTng command
246 * static buffer of the right size.
247 */
248 private void recvHeader() throws Exception {
249 int read_len;
250 byte data[] = new byte[this.headerCmd.SIZE];
251
252 read_len = this.inFromSessiond.read(data, 0, data.length);
253 if (read_len != data.length) {
254 throw new IOException();
255 }
256 this.headerCmd.populate(data);
257 }
258
259 /*
260 * Receive payload from the session daemon. This MUST be done after a
261 * recvHeader() so the header value of a command are known.
262 *
263 * The caller SHOULD use isPayload() before which returns true if a payload
264 * is expected after the header.
265 */
266 private byte[] recvPayload() throws Exception {
267 byte payload[] = new byte[(int) this.headerCmd.data_size];
268
269 /* Failsafe check so we don't waste our time reading 0 bytes. */
270 if (payload.length == 0) {
271 return null;
272 }
273
274 this.inFromSessiond.read(payload, 0, payload.length);
275 return payload;
276 }
277
278 /*
279 * Handle session command from the session daemon.
280 */
281 private void handleSessiondCmd() throws Exception {
282 int ret_code;
283 byte data[] = null;
284
285 while (true) {
286 /* Get header from session daemon. */
287 recvHeader();
288
289 if (headerCmd.data_size > 0) {
290 data = recvPayload();
291 }
292
293 switch (headerCmd.cmd) {
294 case CMD_REG_DONE:
295 {
296 /*
297 * Release semaphore so meaning registration is done and we
298 * can proceed to continue tracing.
299 */
300 tryReleaseSem();
301 /*
302 * We don't send any reply to the registration done command.
303 * This just marks the end of the initial session setup.
304 */
305 continue;
306 }
307 case CMD_LIST:
308 {
309 LTTngSessiondCmd2_4.sessiond_list_logger listLoggerCmd =
310 new LTTngSessiondCmd2_4.sessiond_list_logger();
311 listLoggerCmd.execute(this.handler);
312 data = listLoggerCmd.getBytes();
313 break;
314 }
315 case CMD_ENABLE:
316 {
317 LTTngEvent event;
318 LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd =
319 new LTTngSessiondCmd2_4.sessiond_enable_handler();
320 if (data == null) {
321 enableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
322 break;
323 }
324 enableCmd.populate(data);
325 event = enableCmd.execute(this.handler, this.enabledLoggers);
326 if (event != null) {
327 /*
328 * Add the event to the set so it can be enabled if
329 * the logger appears at some point in time.
330 */
331 enabledEventSet.add(event);
332 }
333 data = enableCmd.getBytes();
334 break;
335 }
336 case CMD_DISABLE:
337 {
338 LTTngSessiondCmd2_4.sessiond_disable_handler disableCmd =
339 new LTTngSessiondCmd2_4.sessiond_disable_handler();
340 if (data == null) {
341 disableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
342 break;
343 }
344 disableCmd.populate(data);
345 disableCmd.execute(this.handler);
346 data = disableCmd.getBytes();
347 break;
348 }
349 default:
350 {
351 data = new byte[4];
352 ByteBuffer buf = ByteBuffer.wrap(data);
353 buf.order(ByteOrder.BIG_ENDIAN);
354 LTTngSessiondCmd2_4.lttng_jul_ret_code code =
355 LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
356 buf.putInt(code.getCode());
357 break;
358 }
359 }
360
361 /* Send payload to session daemon. */
362 this.outToSessiond.write(data, 0, data.length);
363 this.outToSessiond.flush();
364 }
365 }
366
367 private String getHomePath() {
368 return System.getProperty("user.home");
369 }
370
371 /**
372 * Read port number from file created by the session daemon.
373 *
374 * @return port value if found else 0.
375 */
376 private int getPortFromFile(String path) throws IOException {
377 int port;
378 BufferedReader br;
379
380 try {
381 br = new BufferedReader(new FileReader(path));
382 String line = br.readLine();
383 port = Integer.parseInt(line, 10);
384 if (port < 0 || port > 65535) {
385 /* Invalid value. Ignore. */
386 port = 0;
387 }
388 br.close();
389 } catch (FileNotFoundException e) {
390 /* No port available. */
391 port = 0;
392 }
393
394 return port;
395 }
396
397 private void connectToSessiond() throws Exception {
398 int port;
399
400 if (this.handler.is_root == 1) {
401 port = getPortFromFile(rootPortFile);
402 if (port == 0) {
403 /* No session daemon available. Stop and retry later. */
404 throw new IOException();
405 }
406 } else {
407 port = getPortFromFile(getHomePath() + userPortFile);
408 if (port == 0) {
409 /* No session daemon available. Stop and retry later. */
410 throw new IOException();
411 }
412 }
413
414 this.sessiondSock = new Socket(this.sessiondHost, port);
415 this.inFromSessiond = new DataInputStream(
416 sessiondSock.getInputStream());
417 this.outToSessiond = new DataOutputStream(
418 sessiondSock.getOutputStream());
419 }
420
421 private void registerToSessiond() throws Exception {
422 byte data[] = new byte[4];
423 ByteBuffer buf = ByteBuffer.wrap(data);
424 String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
425
426 buf.putInt(Integer.parseInt(pid));
427 this.outToSessiond.write(data, 0, data.length);
428 this.outToSessiond.flush();
429 }
430 }
This page took 0.03762 seconds and 3 git commands to generate.