Move liblttng-ust-java* to 'src/lib/'
[lttng-ust.git] / src / lib / lttng-ust-java-agent / java / lttng-ust-agent-common / org / lttng / ust / agent / context / ContextInfoSerializer.java
diff --git a/src/lib/lttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java b/src/lib/lttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java
new file mode 100644 (file)
index 0000000..ae65060
--- /dev/null
@@ -0,0 +1,285 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * Copyright (C) 2016 EfficiOS Inc.
+ * Copyright (C) 2016 Alexandre Montplaisir <alexmonthy@efficios.com>
+ */
+
+package org.lttng.ust.agent.context;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.Map;
+
+import org.lttng.ust.agent.utils.LttngUstAgentLogger;
+
+/**
+ * This class is used to serialize the list of "context info" objects to pass
+ * through JNI.
+ *
+ * The protocol expects two byte array parameters, which are contained here in
+ * the {@link SerializedContexts} inner class.
+ *
+ * The first byte array is called the "entries array", and contains fixed-size
+ * entries, one per context element.
+ *
+ * The second one is the "strings array", it is of variable length and used to
+ * hold the variable-length strings. Each one of these strings is formatted as a
+ * UTF-8 C-string, meaning in will end with a "\0" byte to indicate its end.
+ * Entries in the first array may refer to offsets in the second array to point
+ * to relevant strings.
+ *
+ * The fixed-size entries in the entries array contain the following elements
+ * (size in bytes in parentheses):
+ *
+ * <ul>
+ * <li>The offset in the strings array pointing to the full context name, like
+ * "$app.myprovider:mycontext" (4)</li>
+ * <li>The context value type (1)</li>
+ * <li>The context value itself (8)</li>
+ * </ul>
+ *
+ * The context value type will indicate how many bytes are used for the value.
+ * If the it is of String type, then we use 4 bytes to represent the offset in
+ * the strings array.
+ *
+ * So the total size of each entry is 13 bytes. All unused bytes (for context
+ * values shorter than 8 bytes for example) will be zero'ed.
+ *
+ * @author Alexandre Montplaisir
+ */
+public class ContextInfoSerializer {
+
+       private enum DataType {
+               NULL(0),
+               INTEGER(1),
+               LONG(2),
+               DOUBLE(3),
+               FLOAT(4),
+               BYTE(5),
+               SHORT(6),
+               BOOLEAN(7),
+               STRING(8);
+
+               private final byte value;
+
+               private DataType(int value) {
+                       this.value = (byte) value;
+               }
+
+               public byte getValue() {
+                       return value;
+               }
+       }
+
+       /**
+        * Class used to wrap the two byte arrays returned by
+        * {@link #queryAndSerializeRequestedContexts}.
+        */
+       public static class SerializedContexts {
+
+               private final byte[] contextEntries;
+               private final byte[] contextStrings;
+
+               /**
+                * Constructor
+                *
+                * @param entries
+                *            Arrays for the fixed-size context entries.
+                * @param strings
+                *            Arrays for variable-length strings
+                */
+               public SerializedContexts(byte[] entries, byte[] strings) {
+                       contextEntries = entries;
+                       contextStrings = strings;
+               }
+
+               /**
+                * @return The entries array
+                */
+               public byte[] getEntriesArray() {
+                       return contextEntries;
+               }
+
+               /**
+                * @return The strings array
+                */
+               public byte[] getStringsArray() {
+                       return contextStrings;
+               }
+       }
+
+       private static final String UST_APP_CTX_PREFIX = "$app.";
+       private static final int ENTRY_LENGTH = 13;
+       private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder();
+       private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
+       private static final SerializedContexts EMPTY_CONTEXTS = new SerializedContexts(new byte[0], new byte[0]);
+
+       /**
+        * From the list of requested contexts in the tracing session, look them up
+        * in the {@link ContextInfoManager}, retrieve the available ones, and
+        * serialize them into a byte array.
+        *
+        * @param enabledContexts
+        *            The contexts that are enabled in the tracing session (indexed
+        *            first by retriever name, then by index names). Should come
+        *            from the LTTng Agent.
+        * @return The byte array representing the intersection of the requested and
+        *         available contexts.
+        */
+       public static SerializedContexts queryAndSerializeRequestedContexts(Collection<Map.Entry<String, Map<String, Integer>>> enabledContexts) {
+               if (enabledContexts.isEmpty()) {
+                       /* Early return if there is no requested context information */
+                       return EMPTY_CONTEXTS;
+               }
+
+               ContextInfoManager contextManager;
+               try {
+                       contextManager = ContextInfoManager.getInstance();
+               } catch (IOException e) {
+                       /*
+                        * The JNI library is not available, do not send any context
+                        * information. No retriever could have been defined anyways.
+                        */
+                       return EMPTY_CONTEXTS;
+               }
+
+               /* Compute the total number of contexts (flatten the map) */
+               int totalArraySize = 0;
+               for (Map.Entry<String, Map<String, Integer>> contexts : enabledContexts) {
+                       totalArraySize += contexts.getValue().size() * ENTRY_LENGTH;
+               }
+
+               /* Prepare the ByteBuffer that will generate the "entries" array */
+               ByteBuffer entriesBuffer = ByteBuffer.allocate(totalArraySize);
+               entriesBuffer.order(NATIVE_ORDER);
+               entriesBuffer.clear();
+
+               /* Prepare the streams that will generate the "strings" array */
+               ByteArrayOutputStream stringsBaos = new ByteArrayOutputStream();
+               DataOutputStream stringsDos = new DataOutputStream(stringsBaos);
+
+               try {
+                       for (Map.Entry<String, Map<String, Integer>> entry : enabledContexts) {
+                               String requestedRetrieverName = entry.getKey();
+                               Map<String, Integer> requestedContexts = entry.getValue();
+
+                               IContextInfoRetriever retriever = contextManager.getContextInfoRetriever(requestedRetrieverName);
+
+                               for (String requestedContext : requestedContexts.keySet()) {
+                                       Object contextInfo;
+                                       if (retriever == null) {
+                                               contextInfo = null;
+                                       } else {
+                                               contextInfo = retriever.retrieveContextInfo(requestedContext);
+                                               /*
+                                                * 'contextInfo' can still be null here, which would
+                                                * indicate the retriever does not supply this context.
+                                                * We will still write this information so that the
+                                                * tracer can know about it.
+                                                */
+                                       }
+
+                                       /* Serialize the result to the buffers */
+                                       // FIXME Eventually pass the retriever name only once?
+                                       String fullContextName = (UST_APP_CTX_PREFIX + requestedRetrieverName + ':' + requestedContext);
+                                       byte[] strArray = fullContextName.getBytes(UTF8_CHARSET);
+
+                                       entriesBuffer.putInt(stringsDos.size());
+                                       stringsDos.write(strArray);
+                                       stringsDos.writeChar('\0');
+
+                                       LttngUstAgentLogger.log(ContextInfoSerializer.class,
+                                                       "ContextInfoSerializer: Context to be sent through JNI: " + fullContextName + '=' +
+                                                                       (contextInfo == null ? "null" : contextInfo.toString()));
+
+                                       serializeContextInfo(entriesBuffer, stringsDos, contextInfo);
+                               }
+                       }
+
+                       stringsDos.flush();
+                       stringsBaos.flush();
+
+               } catch (IOException e) {
+                       /*
+                        * Should not happen because we are wrapping a
+                        * ByteArrayOutputStream, which writes to memory
+                        */
+                       e.printStackTrace();
+               }
+
+               byte[] entriesArray = entriesBuffer.array();
+               byte[] stringsArray = stringsBaos.toByteArray();
+               return new SerializedContexts(entriesArray, stringsArray);
+       }
+
+       private static final int CONTEXT_VALUE_LENGTH = 8;
+
+       private static void serializeContextInfo(ByteBuffer entriesBuffer, DataOutputStream stringsDos, Object contextInfo) throws IOException {
+               int remainingBytes;
+               if (contextInfo == null) {
+                       entriesBuffer.put(DataType.NULL.getValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH;
+
+               } else if (contextInfo instanceof Integer) {
+                       entriesBuffer.put(DataType.INTEGER.getValue());
+                       entriesBuffer.putInt(((Integer) contextInfo).intValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 4;
+
+               } else if (contextInfo instanceof Long) {
+                       entriesBuffer.put(DataType.LONG.getValue());
+                       entriesBuffer.putLong(((Long) contextInfo).longValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 8;
+
+               } else if (contextInfo instanceof Double) {
+                       entriesBuffer.put(DataType.DOUBLE.getValue());
+                       entriesBuffer.putDouble(((Double) contextInfo).doubleValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 8;
+
+               } else if (contextInfo instanceof Float) {
+                       entriesBuffer.put(DataType.FLOAT.getValue());
+                       entriesBuffer.putFloat(((Float) contextInfo).floatValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 4;
+
+               } else if (contextInfo instanceof Byte) {
+                       entriesBuffer.put(DataType.BYTE.getValue());
+                       entriesBuffer.put(((Byte) contextInfo).byteValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 1;
+
+               } else if (contextInfo instanceof Short) {
+                       entriesBuffer.put(DataType.SHORT.getValue());
+                       entriesBuffer.putShort(((Short) contextInfo).shortValue());
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 2;
+
+               } else if (contextInfo instanceof Boolean) {
+                       entriesBuffer.put(DataType.BOOLEAN.getValue());
+                       boolean b = ((Boolean) contextInfo).booleanValue();
+                       /* Converted to one byte, write 1 for true, 0 for false */
+                       entriesBuffer.put((byte) (b ? 1 : 0));
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 1;
+
+               } else {
+                       /* Also includes the case of Character. */
+                       /*
+                        * We'll write the object as a string, into the strings array. We
+                        * will write the corresponding offset to the entries array.
+                        */
+                       String str = contextInfo.toString();
+                       byte[] strArray = str.getBytes(UTF8_CHARSET);
+
+                       entriesBuffer.put(DataType.STRING.getValue());
+
+                       entriesBuffer.putInt(stringsDos.size());
+                       stringsDos.write(strArray);
+                       stringsDos.writeChar('\0');
+
+                       remainingBytes = CONTEXT_VALUE_LENGTH - 4;
+               }
+               entriesBuffer.position(entriesBuffer.position() + remainingBytes);
+       }
+}
This page took 0.025727 seconds and 4 git commands to generate.