2 * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
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.
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
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
18 package org
.lttng
.ust
.jul
;
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
;
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
;
40 import java
.util
.Timer
;
41 import java
.util
.TimerTask
;
42 import java
.util
.logging
.Logger
;
43 import java
.util
.Collections
;
45 class USTRegisterMsg
{
46 public static int pid
;
49 public class LTTngTCPSessiondClient
{
50 /* Command header from the session deamon. */
51 private LTTngSessiondCmd2_4
.sessiond_hdr headerCmd
=
52 new LTTngSessiondCmd2_4
.sessiond_hdr();
54 private final String sessiondHost
;
55 private Socket sessiondSock
;
56 private boolean quit
= false;
58 private DataInputStream inFromSessiond
;
59 private DataOutputStream outToSessiond
;
61 private LTTngLogHandler handler
;
63 private Semaphore registerSem
;
65 private Timer eventTimer
;
66 private Set
<LTTngEvent
> enabledEventSet
=
67 Collections
.synchronizedSet(new HashSet
<LTTngEvent
>());
69 * Map of Logger objects that have been enabled. They are indexed by name.
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
;
76 private static final String rootPortFile
= "/var/run/lttng/jul.port";
77 private static final String userPortFile
= "/.lttng/jul.port";
79 /* Indicate if we've already release the semaphore. */
80 private boolean sem_posted
= false;
82 public LTTngTCPSessiondClient(String host
, Semaphore sem
) {
83 this.sessiondHost
= host
;
84 this.registerSem
= sem
;
85 this.eventTimer
= new Timer();
86 this.timerInitialized
= false;
89 private void setupEventTimer() {
90 if (this.timerInitialized
) {
94 this.eventTimer
.scheduleAtFixedRate(new TimerTask() {
97 synchronized (enabledEventSet
) {
98 LTTngSessiondCmd2_4
.sessiond_enable_handler enableCmd
= new
99 LTTngSessiondCmd2_4
.sessiond_enable_handler();
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.
106 Set
<LTTngEvent
> modifiedEvents
= new HashSet
<LTTngEvent
>();
107 Iterator
<LTTngEvent
> it
= enabledEventSet
.iterator();
109 while (it
.hasNext()) {
112 LTTngEvent event
= it
.next();
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...
124 logger
= enabledLoggers
.get(event
.name
);
125 if (logger
!= null) {
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.
134 if (handler
.logLevelUseAll
== 1) {
136 event
.logLevels
.addAll(handler
.logLevelsAll
);
137 modifiedEvents
.add(event
);
141 * The all event is a special case since we have to iterate
142 * over every Logger to see which one was not enabled.
144 if (event
.name
.equals("*")) {
145 enableCmd
.name
= event
.name
;
146 /* Tell the command NOT to add the loglevel. */
147 enableCmd
.lttngLogLevel
= -1;
149 * The return value is irrelevant since the * event is
150 * always kept in the set.
152 enableCmd
.execute(handler
, enabledLoggers
);
156 ret
= enableCmd
.enableLogger(handler
, event
, enabledLoggers
);
158 /* Enabled so remove the event from the set. */
159 if (!modifiedEvents
.remove(event
)) {
161 * event can only be present in one of
168 enabledEventSet
.addAll(modifiedEvents
);
172 }, this.timerDelay
, this.timerDelay
);
174 this.timerInitialized
= true;
178 * Try to release the registerSem if it's not already done.
180 private void tryReleaseSem()
182 /* Release semaphore so we unblock the agent. */
183 if (!this.sem_posted
) {
184 this.registerSem
.release();
185 this.sem_posted
= true;
190 * Cleanup Agent state.
192 private void cleanupState() {
193 enabledEventSet
.clear();
194 enabledLoggers
.clear();
195 if (this.handler
!= null) {
196 this.handler
.clear();
200 public void init(LTTngLogHandler handler
) throws InterruptedException
{
201 this.handler
= handler
;
208 /* Cleanup Agent state before trying to connect or reconnect. */
214 * Connect to the session daemon before anything else.
219 * Register to the session daemon as the Java component of the
222 registerToSessiond();
227 * Block on socket receive and wait for command from the
228 * session daemon. This will return if and only if there is a
229 * fatal error or the socket closes.
232 } catch (UnknownHostException uhe
) {
234 System
.out
.println(uhe
);
235 } catch (IOException ioe
) {
238 } catch (Exception e
) {
245 public void destroy() {
247 this.eventTimer
.cancel();
250 if (this.sessiondSock
!= null) {
251 this.sessiondSock
.close();
253 } catch (Exception e
) {
259 * Receive header data from the session daemon using the LTTng command
260 * static buffer of the right size.
262 private void recvHeader() throws Exception
{
264 byte data
[] = new byte[this.headerCmd
.SIZE
];
266 read_len
= this.inFromSessiond
.read(data
, 0, data
.length
);
267 if (read_len
!= data
.length
) {
268 throw new IOException();
270 this.headerCmd
.populate(data
);
274 * Receive payload from the session daemon. This MUST be done after a
275 * recvHeader() so the header value of a command are known.
277 * The caller SHOULD use isPayload() before which returns true if a payload
278 * is expected after the header.
280 private byte[] recvPayload() throws Exception
{
281 byte payload
[] = new byte[(int) this.headerCmd
.data_size
];
283 /* Failsafe check so we don't waste our time reading 0 bytes. */
284 if (payload
.length
== 0) {
288 this.inFromSessiond
.read(payload
, 0, payload
.length
);
293 * Handle session command from the session daemon.
295 private void handleSessiondCmd() throws Exception
{
300 /* Get header from session daemon. */
303 if (headerCmd
.data_size
> 0) {
304 data
= recvPayload();
307 switch (headerCmd
.cmd
) {
311 * Release semaphore so meaning registration is done and we
312 * can proceed to continue tracing.
316 * We don't send any reply to the registration done command.
317 * This just marks the end of the initial session setup.
323 LTTngSessiondCmd2_4
.sessiond_list_logger listLoggerCmd
=
324 new LTTngSessiondCmd2_4
.sessiond_list_logger();
325 listLoggerCmd
.execute(this.handler
);
326 data
= listLoggerCmd
.getBytes();
332 LTTngSessiondCmd2_4
.sessiond_enable_handler enableCmd
=
333 new LTTngSessiondCmd2_4
.sessiond_enable_handler();
335 enableCmd
.code
= LTTngSessiondCmd2_4
.lttng_jul_ret_code
.CODE_INVALID_CMD
;
338 enableCmd
.populate(data
);
339 event
= enableCmd
.execute(this.handler
, this.enabledLoggers
);
342 * Add the event to the set so it can be enabled if
343 * the logger appears at some point in time.
345 enabledEventSet
.add(event
);
347 data
= enableCmd
.getBytes();
352 LTTngSessiondCmd2_4
.sessiond_disable_handler disableCmd
=
353 new LTTngSessiondCmd2_4
.sessiond_disable_handler();
355 disableCmd
.code
= LTTngSessiondCmd2_4
.lttng_jul_ret_code
.CODE_INVALID_CMD
;
358 disableCmd
.populate(data
);
359 disableCmd
.execute(this.handler
);
360 data
= disableCmd
.getBytes();
366 ByteBuffer buf
= ByteBuffer
.wrap(data
);
367 buf
.order(ByteOrder
.BIG_ENDIAN
);
368 LTTngSessiondCmd2_4
.lttng_jul_ret_code code
=
369 LTTngSessiondCmd2_4
.lttng_jul_ret_code
.CODE_INVALID_CMD
;
370 buf
.putInt(code
.getCode());
375 /* Send payload to session daemon. */
376 this.outToSessiond
.write(data
, 0, data
.length
);
377 this.outToSessiond
.flush();
381 private String
getHomePath() {
382 return System
.getProperty("user.home");
386 * Read port number from file created by the session daemon.
388 * @return port value if found else 0.
390 private int getPortFromFile(String path
) throws IOException
{
395 br
= new BufferedReader(new FileReader(path
));
396 String line
= br
.readLine();
397 port
= Integer
.parseInt(line
, 10);
398 if (port
< 0 || port
> 65535) {
399 /* Invalid value. Ignore. */
403 } catch (FileNotFoundException e
) {
404 /* No port available. */
411 private void connectToSessiond() throws Exception
{
414 if (this.handler
.is_root
== 1) {
415 port
= getPortFromFile(rootPortFile
);
417 /* No session daemon available. Stop and retry later. */
418 throw new IOException();
421 port
= getPortFromFile(getHomePath() + userPortFile
);
423 /* No session daemon available. Stop and retry later. */
424 throw new IOException();
428 this.sessiondSock
= new Socket(this.sessiondHost
, port
);
429 this.inFromSessiond
= new DataInputStream(
430 sessiondSock
.getInputStream());
431 this.outToSessiond
= new DataOutputStream(
432 sessiondSock
.getOutputStream());
435 private void registerToSessiond() throws Exception
{
436 byte data
[] = new byte[4];
437 ByteBuffer buf
= ByteBuffer
.wrap(data
);
438 String pid
= ManagementFactory
.getRuntimeMXBean().getName().split("@")[0];
440 buf
.putInt(Integer
.parseInt(pid
));
441 this.outToSessiond
.write(data
, 0, data
.length
);
442 this.outToSessiond
.flush();