Blame liblttng-ust-java-agent/java/lttng-ust-agent-common/org/lttng/ust/agent/context/ContextInfoSerializer.java

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