Move to kernel style SPDX license identifiers
[lttng-ust.git] / liblttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / context / ContextInfoSerializer.java
1 /*
2 * SPDX-License-Identifier: LGPL-2.1-only
3 *
4 * Copyright (C) 2016 EfficiOS Inc.
5 * Copyright (C) 2016 Alexandre Montplaisir <alexmonthy@efficios.com>
6 */
7
8 package org.lttng.ust.agent.context;
9
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;
17 import java.util.Map;
18
19 import org.lttng.ust.agent.utils.LttngUstAgentLogger;
20
21 /**
22 * This class is used to serialize the list of "context info" objects to pass
23 * through JNI.
24 *
25 * The protocol expects two byte array parameters, which are contained here in
26 * the {@link SerializedContexts} inner class.
27 *
28 * The first byte array is called the "entries array", and contains fixed-size
29 * entries, one per context element.
30 *
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.
36 *
37 * The fixed-size entries in the entries array contain the following elements
38 * (size in bytes in parentheses):
39 *
40 * <ul>
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>
45 * </ul>
46 *
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
49 * the strings array.
50 *
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.
53 *
54 * @author Alexandre Montplaisir
55 */
56 public class ContextInfoSerializer {
57
58 private enum DataType {
59 NULL(0),
60 INTEGER(1),
61 LONG(2),
62 DOUBLE(3),
63 FLOAT(4),
64 BYTE(5),
65 SHORT(6),
66 BOOLEAN(7),
67 STRING(8);
68
69 private final byte value;
70
71 private DataType(int value) {
72 this.value = (byte) value;
73 }
74
75 public byte getValue() {
76 return value;
77 }
78 }
79
80 /**
81 * Class used to wrap the two byte arrays returned by
82 * {@link #queryAndSerializeRequestedContexts}.
83 */
84 public static class SerializedContexts {
85
86 private final byte[] contextEntries;
87 private final byte[] contextStrings;
88
89 /**
90 * Constructor
91 *
92 * @param entries
93 * Arrays for the fixed-size context entries.
94 * @param strings
95 * Arrays for variable-length strings
96 */
97 public SerializedContexts(byte[] entries, byte[] strings) {
98 contextEntries = entries;
99 contextStrings = strings;
100 }
101
102 /**
103 * @return The entries array
104 */
105 public byte[] getEntriesArray() {
106 return contextEntries;
107 }
108
109 /**
110 * @return The strings array
111 */
112 public byte[] getStringsArray() {
113 return contextStrings;
114 }
115 }
116
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]);
122
123 /**
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.
127 *
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.
134 */
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;
139 }
140
141 ContextInfoManager contextManager;
142 try {
143 contextManager = ContextInfoManager.getInstance();
144 } catch (IOException e) {
145 /*
146 * The JNI library is not available, do not send any context
147 * information. No retriever could have been defined anyways.
148 */
149 return EMPTY_CONTEXTS;
150 }
151
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;
156 }
157
158 /* Prepare the ByteBuffer that will generate the "entries" array */
159 ByteBuffer entriesBuffer = ByteBuffer.allocate(totalArraySize);
160 entriesBuffer.order(NATIVE_ORDER);
161 entriesBuffer.clear();
162
163 /* Prepare the streams that will generate the "strings" array */
164 ByteArrayOutputStream stringsBaos = new ByteArrayOutputStream();
165 DataOutputStream stringsDos = new DataOutputStream(stringsBaos);
166
167 try {
168 for (Map.Entry<String, Map<String, Integer>> entry : enabledContexts) {
169 String requestedRetrieverName = entry.getKey();
170 Map<String, Integer> requestedContexts = entry.getValue();
171
172 IContextInfoRetriever retriever = contextManager.getContextInfoRetriever(requestedRetrieverName);
173
174 for (String requestedContext : requestedContexts.keySet()) {
175 Object contextInfo;
176 if (retriever == null) {
177 contextInfo = null;
178 } else {
179 contextInfo = retriever.retrieveContextInfo(requestedContext);
180 /*
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.
185 */
186 }
187
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);
192
193 entriesBuffer.putInt(stringsDos.size());
194 stringsDos.write(strArray);
195 stringsDos.writeChar('\0');
196
197 LttngUstAgentLogger.log(ContextInfoSerializer.class,
198 "ContextInfoSerializer: Context to be sent through JNI: " + fullContextName + '=' +
199 (contextInfo == null ? "null" : contextInfo.toString()));
200
201 serializeContextInfo(entriesBuffer, stringsDos, contextInfo);
202 }
203 }
204
205 stringsDos.flush();
206 stringsBaos.flush();
207
208 } catch (IOException e) {
209 /*
210 * Should not happen because we are wrapping a
211 * ByteArrayOutputStream, which writes to memory
212 */
213 e.printStackTrace();
214 }
215
216 byte[] entriesArray = entriesBuffer.array();
217 byte[] stringsArray = stringsBaos.toByteArray();
218 return new SerializedContexts(entriesArray, stringsArray);
219 }
220
221 private static final int CONTEXT_VALUE_LENGTH = 8;
222
223 private static void serializeContextInfo(ByteBuffer entriesBuffer, DataOutputStream stringsDos, Object contextInfo) throws IOException {
224 int remainingBytes;
225 if (contextInfo == null) {
226 entriesBuffer.put(DataType.NULL.getValue());
227 remainingBytes = CONTEXT_VALUE_LENGTH;
228
229 } else if (contextInfo instanceof Integer) {
230 entriesBuffer.put(DataType.INTEGER.getValue());
231 entriesBuffer.putInt(((Integer) contextInfo).intValue());
232 remainingBytes = CONTEXT_VALUE_LENGTH - 4;
233
234 } else if (contextInfo instanceof Long) {
235 entriesBuffer.put(DataType.LONG.getValue());
236 entriesBuffer.putLong(((Long) contextInfo).longValue());
237 remainingBytes = CONTEXT_VALUE_LENGTH - 8;
238
239 } else if (contextInfo instanceof Double) {
240 entriesBuffer.put(DataType.DOUBLE.getValue());
241 entriesBuffer.putDouble(((Double) contextInfo).doubleValue());
242 remainingBytes = CONTEXT_VALUE_LENGTH - 8;
243
244 } else if (contextInfo instanceof Float) {
245 entriesBuffer.put(DataType.FLOAT.getValue());
246 entriesBuffer.putFloat(((Float) contextInfo).floatValue());
247 remainingBytes = CONTEXT_VALUE_LENGTH - 4;
248
249 } else if (contextInfo instanceof Byte) {
250 entriesBuffer.put(DataType.BYTE.getValue());
251 entriesBuffer.put(((Byte) contextInfo).byteValue());
252 remainingBytes = CONTEXT_VALUE_LENGTH - 1;
253
254 } else if (contextInfo instanceof Short) {
255 entriesBuffer.put(DataType.SHORT.getValue());
256 entriesBuffer.putShort(((Short) contextInfo).shortValue());
257 remainingBytes = CONTEXT_VALUE_LENGTH - 2;
258
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;
265
266 } else {
267 /* Also includes the case of Character. */
268 /*
269 * We'll write the object as a string, into the strings array. We
270 * will write the corresponding offset to the entries array.
271 */
272 String str = contextInfo.toString();
273 byte[] strArray = str.getBytes(UTF8_CHARSET);
274
275 entriesBuffer.put(DataType.STRING.getValue());
276
277 entriesBuffer.putInt(stringsDos.size());
278 stringsDos.write(strArray);
279 stringsDos.writeChar('\0');
280
281 remainingBytes = CONTEXT_VALUE_LENGTH - 4;
282 }
283 entriesBuffer.position(entriesBuffer.position() + remainingBytes);
284 }
285 }
This page took 0.034463 seconds and 4 git commands to generate.