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