X-Git-Url: http://git.lttng.org/?a=blobdiff_plain;f=liblttng-ust-java-agent%2Fjava%2Flttng-ust-agent-common%2Forg%2Flttng%2Fust%2Fagent%2Fcontext%2FContextInfoSerializer.java;h=ae65060789eccd44f95b82df8d0530b8a464c91b;hb=c0c0989ab70574e09b2f7e8b48c2da6af664a849;hp=57382bd1c7d3613f5ad4393a0d3f75038b17e22a;hpb=8ab5c06b92ac9a06ba2743470a38e4e1cfc6a3c9;p=lttng-ust.git diff --git a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java index 57382bd1..ae650607 100644 --- a/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java +++ b/liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java @@ -1,22 +1,14 @@ /* - * Copyright (C) 2016 - EfficiOS Inc., Alexandre Montplaisir + * SPDX-License-Identifier: LGPL-2.1-only * - * This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License, version 2.1 only, - * as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * Copyright (C) 2016 EfficiOS Inc. + * Copyright (C) 2016 Alexandre Montplaisir */ 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; @@ -24,22 +16,40 @@ 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 a single byte array parameter. This byte array consists - * of a series of fixed-size entries, where each entry contains the following - * elements (with their size in bytes in parenthesis): + * 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): * * * - * So the total size of each entry is 513 bytes. All unused bytes will be - * zero'ed. + * 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 */ @@ -67,12 +77,48 @@ public class ContextInfoSerializer { } } + /** + * 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 ELEMENT_LENGTH = 256; - private static final int ENTRY_LENGTH = 513; + 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 byte[] EMPTY_ARRAY = new byte[0]; + 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 @@ -86,16 +132,10 @@ public class ContextInfoSerializer { * @return The byte array representing the intersection of the requested and * available contexts. */ - public static byte[] queryAndSerializeRequestedContexts(Collection>> enabledContexts) { + public static SerializedContexts queryAndSerializeRequestedContexts(Collection>> enabledContexts) { if (enabledContexts.isEmpty()) { /* Early return if there is no requested context information */ - return EMPTY_ARRAY; - } - - /* Compute the total number of contexts (flatten the map) */ - int totalArraySize = 0; - for (Map.Entry> contexts : enabledContexts) { - totalArraySize += contexts.getValue().size() * ENTRY_LENGTH; + return EMPTY_CONTEXTS; } ContextInfoManager contextManager; @@ -106,106 +146,140 @@ public class ContextInfoSerializer { * The JNI library is not available, do not send any context * information. No retriever could have been defined anyways. */ - return EMPTY_ARRAY; + return EMPTY_CONTEXTS; } - ByteBuffer buffer = ByteBuffer.allocate(totalArraySize); - buffer.order(NATIVE_ORDER); - buffer.clear(); - - for (Map.Entry> entry : enabledContexts) { - String requestedRetrieverName = entry.getKey(); - Map 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. - */ - } + /* Compute the total number of contexts (flatten the map) */ + int totalArraySize = 0; + for (Map.Entry> contexts : enabledContexts) { + totalArraySize += contexts.getValue().size() * ENTRY_LENGTH; + } - /* Serialize the result to the buffer */ - // FIXME Eventually pass the retriever name only once? - String fullContextName = (UST_APP_CTX_PREFIX + requestedRetrieverName + ':' + requestedContext); - byte[] strArray = fullContextName.getBytes(UTF8_CHARSET); - int remainingBytes = ELEMENT_LENGTH - strArray.length; - // FIXME Handle case where name is too long... - buffer.put(strArray); - buffer.position(buffer.position() + remainingBytes); + /* 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> entry : enabledContexts) { + String requestedRetrieverName = entry.getKey(); + Map requestedContexts = entry.getValue(); - serializeContextInfo(buffer, contextInfo); + 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(); } - return buffer.array(); + + byte[] entriesArray = entriesBuffer.array(); + byte[] stringsArray = stringsBaos.toByteArray(); + return new SerializedContexts(entriesArray, stringsArray); } - private static void serializeContextInfo(ByteBuffer buffer, Object contextInfo) { + 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) { - buffer.put(DataType.NULL.getValue()); - remainingBytes = ELEMENT_LENGTH; + entriesBuffer.put(DataType.NULL.getValue()); + remainingBytes = CONTEXT_VALUE_LENGTH; } else if (contextInfo instanceof Integer) { - buffer.put(DataType.INTEGER.getValue()); - buffer.putInt(((Integer) contextInfo).intValue()); - remainingBytes = ELEMENT_LENGTH - 4; + entriesBuffer.put(DataType.INTEGER.getValue()); + entriesBuffer.putInt(((Integer) contextInfo).intValue()); + remainingBytes = CONTEXT_VALUE_LENGTH - 4; } else if (contextInfo instanceof Long) { - buffer.put(DataType.LONG.getValue()); - buffer.putLong(((Long) contextInfo).longValue()); - remainingBytes = ELEMENT_LENGTH - 8; + entriesBuffer.put(DataType.LONG.getValue()); + entriesBuffer.putLong(((Long) contextInfo).longValue()); + remainingBytes = CONTEXT_VALUE_LENGTH - 8; } else if (contextInfo instanceof Double) { - buffer.put(DataType.DOUBLE.getValue()); - buffer.putDouble(((Double) contextInfo).doubleValue()); - remainingBytes = ELEMENT_LENGTH - 8; + entriesBuffer.put(DataType.DOUBLE.getValue()); + entriesBuffer.putDouble(((Double) contextInfo).doubleValue()); + remainingBytes = CONTEXT_VALUE_LENGTH - 8; } else if (contextInfo instanceof Float) { - buffer.put(DataType.FLOAT.getValue()); - buffer.putFloat(((Float) contextInfo).floatValue()); - remainingBytes = ELEMENT_LENGTH - 4; + entriesBuffer.put(DataType.FLOAT.getValue()); + entriesBuffer.putFloat(((Float) contextInfo).floatValue()); + remainingBytes = CONTEXT_VALUE_LENGTH - 4; } else if (contextInfo instanceof Byte) { - buffer.put(DataType.BYTE.getValue()); - buffer.put(((Byte) contextInfo).byteValue()); - remainingBytes = ELEMENT_LENGTH - 1; + entriesBuffer.put(DataType.BYTE.getValue()); + entriesBuffer.put(((Byte) contextInfo).byteValue()); + remainingBytes = CONTEXT_VALUE_LENGTH - 1; } else if (contextInfo instanceof Short) { - buffer.put(DataType.SHORT.getValue()); - buffer.putShort(((Short) contextInfo).shortValue()); - remainingBytes = ELEMENT_LENGTH - 2; + entriesBuffer.put(DataType.SHORT.getValue()); + entriesBuffer.putShort(((Short) contextInfo).shortValue()); + remainingBytes = CONTEXT_VALUE_LENGTH - 2; } else if (contextInfo instanceof Boolean) { - buffer.put(DataType.BOOLEAN.getValue()); + entriesBuffer.put(DataType.BOOLEAN.getValue()); boolean b = ((Boolean) contextInfo).booleanValue(); /* Converted to one byte, write 1 for true, 0 for false */ - buffer.put((byte) (b ? 1 : 0)); - remainingBytes = ELEMENT_LENGTH - 1; + entriesBuffer.put((byte) (b ? 1 : 0)); + remainingBytes = CONTEXT_VALUE_LENGTH - 1; } else { - /* We'll write the object as a string. Also includes the case of Character. */ + /* 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); - buffer.put(DataType.STRING.getValue()); - if (strArray.length >= ELEMENT_LENGTH) { - /* Trim the string to the max allowed length */ - buffer.put(strArray, 0, ELEMENT_LENGTH); - remainingBytes = 0; - } else { - buffer.put(strArray); - remainingBytes = ELEMENT_LENGTH - strArray.length; - } + entriesBuffer.put(DataType.STRING.getValue()); + + entriesBuffer.putInt(stringsDos.size()); + stringsDos.write(strArray); + stringsDos.writeChar('\0'); + + remainingBytes = CONTEXT_VALUE_LENGTH - 4; } - buffer.position(buffer.position() + remainingBytes); + entriesBuffer.position(entriesBuffer.position() + remainingBytes); } }