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 | ||
d60dfbe4 | 18 | package org.lttng.ust.agent.client; |
43e5396b | 19 | |
f1fa0535 | 20 | import java.io.BufferedReader; |
43e5396b | 21 | import java.io.DataInputStream; |
bc7de6d9 | 22 | import java.io.DataOutputStream; |
f1fa0535 | 23 | import java.io.FileNotFoundException; |
bc7de6d9 AM |
24 | import java.io.FileReader; |
25 | import java.io.IOException; | |
43e5396b | 26 | import java.lang.management.ManagementFactory; |
bc7de6d9 AM |
27 | import java.net.Socket; |
28 | import java.net.UnknownHostException; | |
29 | import java.nio.ByteBuffer; | |
30 | import java.nio.ByteOrder; | |
d60dfbe4 AM |
31 | import java.util.concurrent.CountDownLatch; |
32 | import java.util.concurrent.TimeUnit; | |
43e5396b | 33 | |
d60dfbe4 AM |
34 | import org.lttng.ust.agent.AbstractLttngAgent; |
35 | ||
36 | /** | |
37 | * Client for agents to connect to a local session daemon, using a TCP socket. | |
38 | * | |
39 | * @author David Goulet | |
40 | */ | |
41 | public class LttngTcpSessiondClient implements Runnable { | |
43e5396b | 42 | |
08284556 AM |
43 | private static final String SESSION_HOST = "127.0.0.1"; |
44 | private static final String ROOT_PORT_FILE = "/var/run/lttng/agent.port"; | |
45 | private static final String USER_PORT_FILE = "/.lttng/agent.port"; | |
46 | ||
d60dfbe4 AM |
47 | private static int protocolMajorVersion = 1; |
48 | private static int protocolMinorVersion = 0; | |
08284556 | 49 | |
d60dfbe4 | 50 | /** Command header from the session deamon. */ |
d60dfbe4 | 51 | private final CountDownLatch registrationLatch = new CountDownLatch(1); |
43e5396b | 52 | |
43e5396b | 53 | private Socket sessiondSock; |
501f6777 | 54 | private volatile boolean quit = false; |
43e5396b DG |
55 | |
56 | private DataInputStream inFromSessiond; | |
57 | private DataOutputStream outToSessiond; | |
58 | ||
d60dfbe4 AM |
59 | private final AbstractLttngAgent<?> logAgent; |
60 | private final boolean isRoot; | |
501f6777 | 61 | |
f1fa0535 | 62 | |
d60dfbe4 AM |
63 | /** |
64 | * Constructor | |
65 | * | |
66 | * @param logAgent | |
67 | * The logging agent this client will operate on. | |
68 | * @param isRoot | |
69 | * True if this client should connect to the root session daemon, | |
70 | * false if it should connect to the user one. | |
71 | */ | |
72 | public LttngTcpSessiondClient(AbstractLttngAgent<?> logAgent, boolean isRoot) { | |
73 | this.logAgent = logAgent; | |
74 | this.isRoot = isRoot; | |
43e5396b DG |
75 | } |
76 | ||
d60dfbe4 AM |
77 | /** |
78 | * Wait until this client has successfully established a connection to its | |
79 | * target session daemon. | |
80 | * | |
81 | * @param seconds | |
82 | * A timeout in seconds after which this method will return | |
83 | * anyway. | |
84 | * @return True if the the client actually established the connection, false | |
85 | * if we returned because the timeout has elapsed or the thread was | |
86 | * interrupted. | |
f1fa0535 | 87 | */ |
d60dfbe4 AM |
88 | public boolean waitForConnection(int seconds) { |
89 | try { | |
90 | return registrationLatch.await(seconds, TimeUnit.SECONDS); | |
91 | } catch (InterruptedException e) { | |
92 | return false; | |
f1fa0535 DG |
93 | } |
94 | } | |
95 | ||
501f6777 CB |
96 | @Override |
97 | public void run() { | |
43e5396b DG |
98 | for (;;) { |
99 | if (this.quit) { | |
100 | break; | |
101 | } | |
102 | ||
103 | try { | |
104 | ||
105 | /* | |
106 | * Connect to the session daemon before anything else. | |
107 | */ | |
108 | connectToSessiond(); | |
109 | ||
110 | /* | |
111 | * Register to the session daemon as the Java component of the | |
112 | * UST application. | |
113 | */ | |
114 | registerToSessiond(); | |
43e5396b | 115 | |
43e5396b DG |
116 | /* |
117 | * Block on socket receive and wait for command from the | |
118 | * session daemon. This will return if and only if there is a | |
119 | * fatal error or the socket closes. | |
120 | */ | |
121 | handleSessiondCmd(); | |
122 | } catch (UnknownHostException uhe) { | |
d60dfbe4 | 123 | uhe.printStackTrace(); |
43e5396b | 124 | } catch (IOException ioe) { |
501f6777 CB |
125 | try { |
126 | Thread.sleep(3000); | |
127 | } catch (InterruptedException e) { | |
128 | e.printStackTrace(); | |
129 | } | |
43e5396b DG |
130 | } |
131 | } | |
132 | } | |
133 | ||
d60dfbe4 AM |
134 | /** |
135 | * Dispose this client and close any socket connection it may hold. | |
136 | */ | |
137 | public void close() { | |
43e5396b | 138 | this.quit = true; |
43e5396b DG |
139 | |
140 | try { | |
141 | if (this.sessiondSock != null) { | |
142 | this.sessiondSock.close(); | |
143 | } | |
d60dfbe4 | 144 | } catch (IOException e) { |
43e5396b DG |
145 | e.printStackTrace(); |
146 | } | |
147 | } | |
148 | ||
301a3ddb AM |
149 | private void connectToSessiond() throws IOException { |
150 | int port; | |
43e5396b | 151 | |
301a3ddb AM |
152 | if (this.isRoot) { |
153 | port = getPortFromFile(ROOT_PORT_FILE); | |
154 | if (port == 0) { | |
155 | /* No session daemon available. Stop and retry later. */ | |
156 | throw new IOException(); | |
157 | } | |
158 | } else { | |
159 | port = getPortFromFile(getHomePath() + USER_PORT_FILE); | |
160 | if (port == 0) { | |
161 | /* No session daemon available. Stop and retry later. */ | |
162 | throw new IOException(); | |
163 | } | |
43e5396b | 164 | } |
301a3ddb AM |
165 | |
166 | this.sessiondSock = new Socket(SESSION_HOST, port); | |
167 | this.inFromSessiond = new DataInputStream(sessiondSock.getInputStream()); | |
168 | this.outToSessiond = new DataOutputStream(sessiondSock.getOutputStream()); | |
169 | } | |
170 | ||
171 | private static String getHomePath() { | |
172 | return System.getProperty("user.home"); | |
43e5396b DG |
173 | } |
174 | ||
d60dfbe4 | 175 | /** |
301a3ddb | 176 | * Read port number from file created by the session daemon. |
43e5396b | 177 | * |
301a3ddb | 178 | * @return port value if found else 0. |
43e5396b | 179 | */ |
301a3ddb AM |
180 | private static int getPortFromFile(String path) throws IOException { |
181 | int port; | |
182 | BufferedReader br = null; | |
43e5396b | 183 | |
301a3ddb AM |
184 | try { |
185 | br = new BufferedReader(new FileReader(path)); | |
186 | String line = br.readLine(); | |
187 | port = Integer.parseInt(line, 10); | |
188 | if (port < 0 || port > 65535) { | |
189 | /* Invalid value. Ignore. */ | |
190 | port = 0; | |
191 | } | |
192 | } catch (FileNotFoundException e) { | |
193 | /* No port available. */ | |
194 | port = 0; | |
195 | } finally { | |
196 | if (br != null) { | |
197 | br.close(); | |
198 | } | |
43e5396b DG |
199 | } |
200 | ||
301a3ddb AM |
201 | return port; |
202 | } | |
203 | ||
204 | private void registerToSessiond() throws IOException { | |
205 | byte data[] = new byte[16]; | |
206 | ByteBuffer buf = ByteBuffer.wrap(data); | |
207 | String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; | |
208 | ||
209 | buf.putInt(logAgent.getDomain().value()); | |
210 | buf.putInt(Integer.parseInt(pid)); | |
211 | buf.putInt(protocolMajorVersion); | |
212 | buf.putInt(protocolMinorVersion); | |
213 | this.outToSessiond.write(data, 0, data.length); | |
214 | this.outToSessiond.flush(); | |
43e5396b DG |
215 | } |
216 | ||
d60dfbe4 | 217 | /** |
43e5396b DG |
218 | * Handle session command from the session daemon. |
219 | */ | |
d60dfbe4 | 220 | private void handleSessiondCmd() throws IOException { |
301a3ddb AM |
221 | /* Data read from the socket */ |
222 | byte inputData[] = null; | |
223 | /* Reply data written to the socket, sent to the sessiond */ | |
224 | byte responseData[] = null; | |
43e5396b DG |
225 | |
226 | while (true) { | |
227 | /* Get header from session daemon. */ | |
301a3ddb | 228 | SessiondCommandHeader cmdHeader = recvHeader(); |
43e5396b | 229 | |
301a3ddb AM |
230 | if (cmdHeader.getDataSize() > 0) { |
231 | inputData = recvPayload(cmdHeader); | |
43e5396b DG |
232 | } |
233 | ||
301a3ddb | 234 | switch (cmdHeader.getCommandType()) { |
d60dfbe4 AM |
235 | case CMD_REG_DONE: |
236 | { | |
237 | /* | |
238 | * Countdown the registration latch, meaning registration is | |
239 | * done and we can proceed to continue tracing. | |
240 | */ | |
241 | registrationLatch.countDown(); | |
242 | /* | |
243 | * We don't send any reply to the registration done command. | |
244 | * This just marks the end of the initial session setup. | |
245 | */ | |
246 | continue; | |
247 | } | |
248 | case CMD_LIST: | |
249 | { | |
301a3ddb AM |
250 | ISessiondCommand listLoggerCmd = new SessiondListLoggersCommand(); |
251 | ILttngAgentResponse response = listLoggerCmd.execute(logAgent); | |
252 | responseData = response.getBytes(); | |
d60dfbe4 AM |
253 | break; |
254 | } | |
255 | case CMD_ENABLE: | |
256 | { | |
301a3ddb AM |
257 | if (inputData == null) { |
258 | /* Invalid command */ | |
259 | responseData = ILttngAgentResponse.FAILURE_RESPONSE.getBytes(); | |
43e5396b DG |
260 | break; |
261 | } | |
301a3ddb AM |
262 | ISessiondCommand enableCmd = new SessiondEnableEventCommand(inputData); |
263 | ILttngAgentResponse response = enableCmd.execute(logAgent); | |
264 | responseData = response.getBytes(); | |
d60dfbe4 AM |
265 | break; |
266 | } | |
267 | case CMD_DISABLE: | |
268 | { | |
301a3ddb AM |
269 | if (inputData == null) { |
270 | /* Invalid command */ | |
271 | responseData = ILttngAgentResponse.FAILURE_RESPONSE.getBytes(); | |
43e5396b DG |
272 | break; |
273 | } | |
301a3ddb AM |
274 | ISessiondCommand disableCmd = new SessiondDisableEventCommand(inputData); |
275 | ILttngAgentResponse response = disableCmd.execute(logAgent); | |
276 | responseData = response.getBytes(); | |
d60dfbe4 AM |
277 | break; |
278 | } | |
279 | default: | |
280 | { | |
301a3ddb AM |
281 | /* Unknown command, send empty reply */ |
282 | responseData = new byte[4]; | |
283 | ByteBuffer buf = ByteBuffer.wrap(responseData); | |
d60dfbe4 AM |
284 | buf.order(ByteOrder.BIG_ENDIAN); |
285 | break; | |
286 | } | |
43e5396b DG |
287 | } |
288 | ||
301a3ddb AM |
289 | /* Send response to the session daemon. */ |
290 | this.outToSessiond.write(responseData, 0, responseData.length); | |
43e5396b DG |
291 | this.outToSessiond.flush(); |
292 | } | |
293 | } | |
294 | ||
f1fa0535 | 295 | /** |
301a3ddb AM |
296 | * Receive header data from the session daemon using the LTTng command |
297 | * static buffer of the right size. | |
f1fa0535 | 298 | */ |
301a3ddb AM |
299 | private SessiondCommandHeader recvHeader() throws IOException { |
300 | byte data[] = new byte[SessiondCommandHeader.HEADER_SIZE]; | |
f1fa0535 | 301 | |
301a3ddb AM |
302 | int readLen = this.inFromSessiond.read(data, 0, data.length); |
303 | if (readLen != data.length) { | |
304 | throw new IOException(); | |
f1fa0535 | 305 | } |
301a3ddb | 306 | return new SessiondCommandHeader(data); |
f1fa0535 DG |
307 | } |
308 | ||
301a3ddb AM |
309 | /** |
310 | * Receive payload from the session daemon. This MUST be done after a | |
311 | * recvHeader() so the header value of a command are known. | |
312 | * | |
313 | * The caller SHOULD use isPayload() before which returns true if a payload | |
314 | * is expected after the header. | |
315 | */ | |
316 | private byte[] recvPayload(SessiondCommandHeader headerCmd) throws IOException { | |
317 | byte payload[] = new byte[(int) headerCmd.getDataSize()]; | |
f1fa0535 | 318 | |
301a3ddb AM |
319 | /* Failsafe check so we don't waste our time reading 0 bytes. */ |
320 | if (payload.length == 0) { | |
321 | return null; | |
f1fa0535 DG |
322 | } |
323 | ||
301a3ddb AM |
324 | this.inFromSessiond.read(payload, 0, payload.length); |
325 | return payload; | |
43e5396b DG |
326 | } |
327 | ||
43e5396b | 328 | } |