2 * SPDX-License-Identifier: LGPL-2.1-only
4 * Copyright (C) 2016 EfficiOS Inc.
5 * Copyright (C) 2016 Alexandre Montplaisir <alexmonthy@efficios.com>
8 package org
.lttng
.ust
.agent
.context
;
10 import java
.io
.ByteArrayOutputStream
;
11 import java
.io
.DataOutputStream
;
12 import java
.io
.IOException
;
13 import java
.nio
.ByteBuffer
;
14 import java
.nio
.ByteOrder
;
15 import java
.nio
.charset
.Charset
;
16 import java
.util
.Collection
;
19 import org
.lttng
.ust
.agent
.utils
.LttngUstAgentLogger
;
22 * This class is used to serialize the list of "context info" objects to pass
25 * The protocol expects two byte array parameters, which are contained here in
26 * the {@link SerializedContexts} inner class.
28 * The first byte array is called the "entries array", and contains fixed-size
29 * entries, one per context element.
31 * The second one is the "strings array", it is of variable length and used to
32 * hold the variable-length strings. Each one of these strings is formatted as a
33 * UTF-8 C-string, meaning in will end with a "\0" byte to indicate its end.
34 * Entries in the first array may refer to offsets in the second array to point
35 * to relevant strings.
37 * The fixed-size entries in the entries array contain the following elements
38 * (size in bytes in parentheses):
41 * <li>The offset in the strings array pointing to the full context name, like
42 * "$app.myprovider:mycontext" (4)</li>
43 * <li>The context value type (1)</li>
44 * <li>The context value itself (8)</li>
47 * The context value type will indicate how many bytes are used for the value.
48 * If the it is of String type, then we use 4 bytes to represent the offset in
51 * So the total size of each entry is 13 bytes. All unused bytes (for context
52 * values shorter than 8 bytes for example) will be zero'ed.
54 * @author Alexandre Montplaisir
56 public class ContextInfoSerializer
{
58 private enum DataType
{
69 private final byte value
;
71 private DataType(int value
) {
72 this.value
= (byte) value
;
75 public byte getValue() {
81 * Class used to wrap the two byte arrays returned by
82 * {@link #queryAndSerializeRequestedContexts}.
84 public static class SerializedContexts
{
86 private final byte[] contextEntries
;
87 private final byte[] contextStrings
;
93 * Arrays for the fixed-size context entries.
95 * Arrays for variable-length strings
97 public SerializedContexts(byte[] entries
, byte[] strings
) {
98 contextEntries
= entries
;
99 contextStrings
= strings
;
103 * @return The entries array
105 public byte[] getEntriesArray() {
106 return contextEntries
;
110 * @return The strings array
112 public byte[] getStringsArray() {
113 return contextStrings
;
117 private static final String UST_APP_CTX_PREFIX
= "$app.";
118 private static final int ENTRY_LENGTH
= 13;
119 private static final ByteOrder NATIVE_ORDER
= ByteOrder
.nativeOrder();
120 private static final Charset UTF8_CHARSET
= Charset
.forName("UTF-8");
121 private static final SerializedContexts EMPTY_CONTEXTS
= new SerializedContexts(new byte[0], new byte[0]);
124 * From the list of requested contexts in the tracing session, look them up
125 * in the {@link ContextInfoManager}, retrieve the available ones, and
126 * serialize them into a byte array.
128 * @param enabledContexts
129 * The contexts that are enabled in the tracing session (indexed
130 * first by retriever name, then by index names). Should come
131 * from the LTTng Agent.
132 * @return The byte array representing the intersection of the requested and
133 * available contexts.
135 public static SerializedContexts
queryAndSerializeRequestedContexts(Collection
<Map
.Entry
<String
, Map
<String
, Integer
>>> enabledContexts
) {
136 if (enabledContexts
.isEmpty()) {
137 /* Early return if there is no requested context information */
138 return EMPTY_CONTEXTS
;
141 ContextInfoManager contextManager
;
143 contextManager
= ContextInfoManager
.getInstance();
144 } catch (IOException e
) {
146 * The JNI library is not available, do not send any context
147 * information. No retriever could have been defined anyways.
149 return EMPTY_CONTEXTS
;
152 /* Compute the total number of contexts (flatten the map) */
153 int totalArraySize
= 0;
154 for (Map
.Entry
<String
, Map
<String
, Integer
>> contexts
: enabledContexts
) {
155 totalArraySize
+= contexts
.getValue().size() * ENTRY_LENGTH
;
158 /* Prepare the ByteBuffer that will generate the "entries" array */
159 ByteBuffer entriesBuffer
= ByteBuffer
.allocate(totalArraySize
);
160 entriesBuffer
.order(NATIVE_ORDER
);
161 entriesBuffer
.clear();
163 /* Prepare the streams that will generate the "strings" array */
164 ByteArrayOutputStream stringsBaos
= new ByteArrayOutputStream();
165 DataOutputStream stringsDos
= new DataOutputStream(stringsBaos
);
168 for (Map
.Entry
<String
, Map
<String
, Integer
>> entry
: enabledContexts
) {
169 String requestedRetrieverName
= entry
.getKey();
170 Map
<String
, Integer
> requestedContexts
= entry
.getValue();
172 IContextInfoRetriever retriever
= contextManager
.getContextInfoRetriever(requestedRetrieverName
);
174 for (String requestedContext
: requestedContexts
.keySet()) {
176 if (retriever
== null) {
179 contextInfo
= retriever
.retrieveContextInfo(requestedContext
);
181 * 'contextInfo' can still be null here, which would
182 * indicate the retriever does not supply this context.
183 * We will still write this information so that the
184 * tracer can know about it.
188 /* Serialize the result to the buffers */
189 // FIXME Eventually pass the retriever name only once?
190 String fullContextName
= (UST_APP_CTX_PREFIX
+ requestedRetrieverName
+ ':' + requestedContext
);
191 byte[] strArray
= fullContextName
.getBytes(UTF8_CHARSET
);
193 entriesBuffer
.putInt(stringsDos
.size());
194 stringsDos
.write(strArray
);
195 stringsDos
.writeChar('\0');
197 LttngUstAgentLogger
.log(ContextInfoSerializer
.class,
198 "ContextInfoSerializer: Context to be sent through JNI: " + fullContextName
+ '=' +
199 (contextInfo
== null ?
"null" : contextInfo
.toString()));
201 serializeContextInfo(entriesBuffer
, stringsDos
, contextInfo
);
208 } catch (IOException e
) {
210 * Should not happen because we are wrapping a
211 * ByteArrayOutputStream, which writes to memory
216 byte[] entriesArray
= entriesBuffer
.array();
217 byte[] stringsArray
= stringsBaos
.toByteArray();
218 return new SerializedContexts(entriesArray
, stringsArray
);
221 private static final int CONTEXT_VALUE_LENGTH
= 8;
223 private static void serializeContextInfo(ByteBuffer entriesBuffer
, DataOutputStream stringsDos
, Object contextInfo
) throws IOException
{
225 if (contextInfo
== null) {
226 entriesBuffer
.put(DataType
.NULL
.getValue());
227 remainingBytes
= CONTEXT_VALUE_LENGTH
;
229 } else if (contextInfo
instanceof Integer
) {
230 entriesBuffer
.put(DataType
.INTEGER
.getValue());
231 entriesBuffer
.putInt(((Integer
) contextInfo
).intValue());
232 remainingBytes
= CONTEXT_VALUE_LENGTH
- 4;
234 } else if (contextInfo
instanceof Long
) {
235 entriesBuffer
.put(DataType
.LONG
.getValue());
236 entriesBuffer
.putLong(((Long
) contextInfo
).longValue());
237 remainingBytes
= CONTEXT_VALUE_LENGTH
- 8;
239 } else if (contextInfo
instanceof Double
) {
240 entriesBuffer
.put(DataType
.DOUBLE
.getValue());
241 entriesBuffer
.putDouble(((Double
) contextInfo
).doubleValue());
242 remainingBytes
= CONTEXT_VALUE_LENGTH
- 8;
244 } else if (contextInfo
instanceof Float
) {
245 entriesBuffer
.put(DataType
.FLOAT
.getValue());
246 entriesBuffer
.putFloat(((Float
) contextInfo
).floatValue());
247 remainingBytes
= CONTEXT_VALUE_LENGTH
- 4;
249 } else if (contextInfo
instanceof Byte
) {
250 entriesBuffer
.put(DataType
.BYTE
.getValue());
251 entriesBuffer
.put(((Byte
) contextInfo
).byteValue());
252 remainingBytes
= CONTEXT_VALUE_LENGTH
- 1;
254 } else if (contextInfo
instanceof Short
) {
255 entriesBuffer
.put(DataType
.SHORT
.getValue());
256 entriesBuffer
.putShort(((Short
) contextInfo
).shortValue());
257 remainingBytes
= CONTEXT_VALUE_LENGTH
- 2;
259 } else if (contextInfo
instanceof Boolean
) {
260 entriesBuffer
.put(DataType
.BOOLEAN
.getValue());
261 boolean b
= ((Boolean
) contextInfo
).booleanValue();
262 /* Converted to one byte, write 1 for true, 0 for false */
263 entriesBuffer
.put((byte) (b ?
1 : 0));
264 remainingBytes
= CONTEXT_VALUE_LENGTH
- 1;
267 /* Also includes the case of Character. */
269 * We'll write the object as a string, into the strings array. We
270 * will write the corresponding offset to the entries array.
272 String str
= contextInfo
.toString();
273 byte[] strArray
= str
.getBytes(UTF8_CHARSET
);
275 entriesBuffer
.put(DataType
.STRING
.getValue());
277 entriesBuffer
.putInt(stringsDos
.size());
278 stringsDos
.write(strArray
);
279 stringsDos
.writeChar('\0');
281 remainingBytes
= CONTEXT_VALUE_LENGTH
- 4;
283 entriesBuffer
.position(entriesBuffer
.position() + remainingBytes
);