2 * Copyright (C) 2016 - EfficiOS Inc., Alexandre Montplaisir <alexmonthy@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
.agent
.context
;
20 import java
.io
.ByteArrayOutputStream
;
21 import java
.io
.DataOutputStream
;
22 import java
.io
.IOException
;
23 import java
.nio
.ByteBuffer
;
24 import java
.nio
.ByteOrder
;
25 import java
.nio
.charset
.Charset
;
26 import java
.util
.Collection
;
29 import org
.lttng
.ust
.agent
.utils
.LttngUstAgentLogger
;
32 * This class is used to serialize the list of "context info" objects to pass
35 * The protocol expects two byte array parameters, which are contained here in
36 * the {@link SerializedContexts} inner class.
38 * The first byte array is called the "entries array", and contains fixed-size
39 * entries, one per context element.
41 * The second one is the "strings array", it is of variable length and used to
42 * hold the variable-length strings. Each one of these strings is formatted as a
43 * UTF-8 C-string, meaning in will end with a "\0" byte to indicate its end.
44 * Entries in the first array may refer to offsets in the second array to point
45 * to relevant strings.
47 * The fixed-size entries in the entries array contain the following elements
48 * (size in bytes in parentheses):
51 * <li>The offset in the strings array pointing to the full context name, like
52 * "$app.myprovider:mycontext" (4)</li>
53 * <li>The context value type (1)</li>
54 * <li>The context value itself (8)</li>
57 * The context value type will indicate how many bytes are used for the value.
58 * If the it is of String type, then we use 4 bytes to represent the offset in
61 * So the total size of each entry is 13 bytes. All unused bytes (for context
62 * values shorter than 8 bytes for example) will be zero'ed.
64 * @author Alexandre Montplaisir
66 public class ContextInfoSerializer
{
68 private enum DataType
{
79 private final byte value
;
81 private DataType(int value
) {
82 this.value
= (byte) value
;
85 public byte getValue() {
91 * Class used to wrap the two byte arrays returned by
92 * {@link #queryAndSerializeRequestedContexts}.
94 public static class SerializedContexts
{
96 private final byte[] contextEntries
;
97 private final byte[] contextStrings
;
103 * Arrays for the fixed-size context entries.
105 * Arrays for variable-length strings
107 public SerializedContexts(byte[] entries
, byte[] strings
) {
108 contextEntries
= entries
;
109 contextStrings
= strings
;
113 * @return The entries array
115 public byte[] getEntriesArray() {
116 return contextEntries
;
120 * @return The strings array
122 public byte[] getStringsArray() {
123 return contextStrings
;
127 private static final String UST_APP_CTX_PREFIX
= "$app.";
128 private static final int ENTRY_LENGTH
= 13;
129 private static final ByteOrder NATIVE_ORDER
= ByteOrder
.nativeOrder();
130 private static final Charset UTF8_CHARSET
= Charset
.forName("UTF-8");
131 private static final SerializedContexts EMPTY_CONTEXTS
= new SerializedContexts(new byte[0], new byte[0]);
134 * From the list of requested contexts in the tracing session, look them up
135 * in the {@link ContextInfoManager}, retrieve the available ones, and
136 * serialize them into a byte array.
138 * @param enabledContexts
139 * The contexts that are enabled in the tracing session (indexed
140 * first by retriever name, then by index names). Should come
141 * from the LTTng Agent.
142 * @return The byte array representing the intersection of the requested and
143 * available contexts.
145 public static SerializedContexts
queryAndSerializeRequestedContexts(Collection
<Map
.Entry
<String
, Map
<String
, Integer
>>> enabledContexts
) {
146 if (enabledContexts
.isEmpty()) {
147 /* Early return if there is no requested context information */
148 return EMPTY_CONTEXTS
;
151 ContextInfoManager contextManager
;
153 contextManager
= ContextInfoManager
.getInstance();
154 } catch (IOException e
) {
156 * The JNI library is not available, do not send any context
157 * information. No retriever could have been defined anyways.
159 return EMPTY_CONTEXTS
;
162 /* Compute the total number of contexts (flatten the map) */
163 int totalArraySize
= 0;
164 for (Map
.Entry
<String
, Map
<String
, Integer
>> contexts
: enabledContexts
) {
165 totalArraySize
+= contexts
.getValue().size() * ENTRY_LENGTH
;
168 /* Prepare the ByteBuffer that will generate the "entries" array */
169 ByteBuffer entriesBuffer
= ByteBuffer
.allocate(totalArraySize
);
170 entriesBuffer
.order(NATIVE_ORDER
);
171 entriesBuffer
.clear();
173 /* Prepare the streams that will generate the "strings" array */
174 ByteArrayOutputStream stringsBaos
= new ByteArrayOutputStream();
175 DataOutputStream stringsDos
= new DataOutputStream(stringsBaos
);
178 for (Map
.Entry
<String
, Map
<String
, Integer
>> entry
: enabledContexts
) {
179 String requestedRetrieverName
= entry
.getKey();
180 Map
<String
, Integer
> requestedContexts
= entry
.getValue();
182 IContextInfoRetriever retriever
= contextManager
.getContextInfoRetriever(requestedRetrieverName
);
184 for (String requestedContext
: requestedContexts
.keySet()) {
186 if (retriever
== null) {
189 contextInfo
= retriever
.retrieveContextInfo(requestedContext
);
191 * 'contextInfo' can still be null here, which would
192 * indicate the retriever does not supply this context.
193 * We will still write this information so that the
194 * tracer can know about it.
198 /* Serialize the result to the buffers */
199 // FIXME Eventually pass the retriever name only once?
200 String fullContextName
= (UST_APP_CTX_PREFIX
+ requestedRetrieverName
+ ':' + requestedContext
);
201 byte[] strArray
= fullContextName
.getBytes(UTF8_CHARSET
);
203 entriesBuffer
.putInt(stringsDos
.size());
204 stringsDos
.write(strArray
);
205 stringsDos
.writeChar('\0');
207 LttngUstAgentLogger
.log(ContextInfoSerializer
.class,
208 "ContextInfoSerializer: Context to be sent through JNI: " + fullContextName
+ '=' +
209 (contextInfo
== null ?
"null" : contextInfo
.toString()));
211 serializeContextInfo(entriesBuffer
, stringsDos
, contextInfo
);
218 } catch (IOException e
) {
220 * Should not happen because we are wrapping a
221 * ByteArrayOutputStream, which writes to memory
226 byte[] entriesArray
= entriesBuffer
.array();
227 byte[] stringsArray
= stringsBaos
.toByteArray();
228 return new SerializedContexts(entriesArray
, stringsArray
);
231 private static final int CONTEXT_VALUE_LENGTH
= 8;
233 private static void serializeContextInfo(ByteBuffer entriesBuffer
, DataOutputStream stringsDos
, Object contextInfo
) throws IOException
{
235 if (contextInfo
== null) {
236 entriesBuffer
.put(DataType
.NULL
.getValue());
237 remainingBytes
= CONTEXT_VALUE_LENGTH
;
239 } else if (contextInfo
instanceof Integer
) {
240 entriesBuffer
.put(DataType
.INTEGER
.getValue());
241 entriesBuffer
.putInt(((Integer
) contextInfo
).intValue());
242 remainingBytes
= CONTEXT_VALUE_LENGTH
- 4;
244 } else if (contextInfo
instanceof Long
) {
245 entriesBuffer
.put(DataType
.LONG
.getValue());
246 entriesBuffer
.putLong(((Long
) contextInfo
).longValue());
247 remainingBytes
= CONTEXT_VALUE_LENGTH
- 8;
249 } else if (contextInfo
instanceof Double
) {
250 entriesBuffer
.put(DataType
.DOUBLE
.getValue());
251 entriesBuffer
.putDouble(((Double
) contextInfo
).doubleValue());
252 remainingBytes
= CONTEXT_VALUE_LENGTH
- 8;
254 } else if (contextInfo
instanceof Float
) {
255 entriesBuffer
.put(DataType
.FLOAT
.getValue());
256 entriesBuffer
.putFloat(((Float
) contextInfo
).floatValue());
257 remainingBytes
= CONTEXT_VALUE_LENGTH
- 4;
259 } else if (contextInfo
instanceof Byte
) {
260 entriesBuffer
.put(DataType
.BYTE
.getValue());
261 entriesBuffer
.put(((Byte
) contextInfo
).byteValue());
262 remainingBytes
= CONTEXT_VALUE_LENGTH
- 1;
264 } else if (contextInfo
instanceof Short
) {
265 entriesBuffer
.put(DataType
.SHORT
.getValue());
266 entriesBuffer
.putShort(((Short
) contextInfo
).shortValue());
267 remainingBytes
= CONTEXT_VALUE_LENGTH
- 2;
269 } else if (contextInfo
instanceof Boolean
) {
270 entriesBuffer
.put(DataType
.BOOLEAN
.getValue());
271 boolean b
= ((Boolean
) contextInfo
).booleanValue();
272 /* Converted to one byte, write 1 for true, 0 for false */
273 entriesBuffer
.put((byte) (b ?
1 : 0));
274 remainingBytes
= CONTEXT_VALUE_LENGTH
- 1;
277 /* Also includes the case of Character. */
279 * We'll write the object as a string, into the strings array. We
280 * will write the corresponding offset to the entries array.
282 String str
= contextInfo
.toString();
283 byte[] strArray
= str
.getBytes(UTF8_CHARSET
);
285 entriesBuffer
.put(DataType
.STRING
.getValue());
287 entriesBuffer
.putInt(stringsDos
.size());
288 stringsDos
.write(strArray
);
289 stringsDos
.writeChar('\0');
291 remainingBytes
= CONTEXT_VALUE_LENGTH
- 4;
293 entriesBuffer
.position(entriesBuffer
.position() + remainingBytes
);