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;
* 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):
*
* <ul>
- * <li>The full context name, like "$app.myprovider:mycontext" (256)</li>
+ * <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(256)</li>
+ * <li>The context value itself (8)</li>
* </ul>
*
- * 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
*/
}
}
+ /**
+ * 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
* @return The byte array representing the intersection of the requested and
* available contexts.
*/
- public static byte[] queryAndSerializeRequestedContexts(Collection<Map.Entry<String, Map<String, Integer>>> enabledContexts) {
+ 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_ARRAY;
- }
-
- /* 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;
+ return EMPTY_CONTEXTS;
}
ContextInfoManager contextManager;
* 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<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.
- */
- }
+ /* 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 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);
+ /* 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);
- LttngUstAgentLogger.log(ContextInfoSerializer.class,
- "ContextInfoSerializer: Context to be sent through JNI: " + fullContextName + '=' +
- (contextInfo == null ? "null" : contextInfo.toString()));
+ entriesBuffer.putInt(stringsDos.size());
+ stringsDos.write(strArray);
+ stringsDos.writeChar('\0');
- serializeContextInfo(buffer, contextInfo);
+ 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);
}
}
#include "helper.h"
#include "lttng_ust_context.h"
-#define LTTNG_UST_JNI_CONTEXT_NAME_LEN 256
-/* TODO: the value should be variable length. */
-#define LTTNG_UST_JNI_VALUE_LEN 256
-
enum lttng_ust_jni_type {
JNI_TYPE_NULL = 0,
JNI_TYPE_INTEGER = 1,
JNI_TYPE_STRING = 8,
};
-struct lttng_ust_jni_ctx {
- char context_name[LTTNG_UST_JNI_CONTEXT_NAME_LEN];
+struct lttng_ust_jni_ctx_entry {
+ int32_t context_name_offset;
char type; /* enum lttng_ust_jni_type */
union {
int32_t _integer;
signed char _byte;
int16_t _short;
signed char _boolean;
- char _string[LTTNG_UST_JNI_VALUE_LEN];
+ int32_t _string_offset;
} value;
} __attribute__((packed));
/* TLS passing context info from JNI to callbacks. */
__thread struct lttng_ust_jni_tls lttng_ust_context_info_tls;
-static struct lttng_ust_jni_ctx *lookup_ctx_by_name(const char *ctx_name)
+static const char *get_ctx_string_at_offset(int32_t offset)
+{
+ signed char *ctx_strings_array = lttng_ust_context_info_tls.ctx_strings;
+
+ if (offset < 0 || offset >= lttng_ust_context_info_tls.ctx_strings_len) {
+ return NULL;
+ }
+ return (const char *) (ctx_strings_array + offset);
+}
+
+static struct lttng_ust_jni_ctx_entry *lookup_ctx_by_name(const char *ctx_name)
{
- struct lttng_ust_jni_ctx *ctx_array = lttng_ust_context_info_tls.ctx;
- int i, len = lttng_ust_context_info_tls.len / sizeof(struct lttng_ust_jni_ctx);
+ struct lttng_ust_jni_ctx_entry *ctx_entries_array = lttng_ust_context_info_tls.ctx_entries;
+ int i, len = lttng_ust_context_info_tls.ctx_entries_len / sizeof(struct lttng_ust_jni_ctx_entry);
for (i = 0; i < len; i++) {
- if (strcmp(ctx_array[i].context_name, ctx_name) == 0)
- return &ctx_array[i];
+ int32_t offset = ctx_entries_array[i].context_name_offset;
+ const char *string = get_ctx_string_at_offset(offset);
+
+ if (string && strcmp(string, ctx_name) == 0) {
+ return &ctx_entries_array[i];
+ }
}
return NULL;
-
}
static size_t get_size_cb(struct lttng_ctx_field *field, size_t offset)
{
- struct lttng_ust_jni_ctx *jctx;
+ struct lttng_ust_jni_ctx_entry *jctx;
size_t size = 0;
const char *ctx_name = field->event_field.name;
enum lttng_ust_jni_type jni_type;
+
size += lib_ring_buffer_align(offset, lttng_alignof(char));
size += sizeof(char); /* tag */
jctx = lookup_ctx_by_name(ctx_name);
size += sizeof(char); /* variant */
break;
case JNI_TYPE_STRING:
- size += strlen(jctx->value._string) + 1;
+ {
+ /* The value is an offset, the string is in the "strings" array */
+ int32_t string_offset = jctx->value._string_offset;
+ const char *string = get_ctx_string_at_offset(string_offset);
+
+ if (string) {
+ size += strlen(string) + 1;
+ }
break;
+ }
default:
abort();
}
struct lttng_ust_lib_ring_buffer_ctx *ctx,
struct lttng_channel *chan)
{
- struct lttng_ust_jni_ctx *jctx;
+ struct lttng_ust_jni_ctx_entry *jctx;
const char *ctx_name = field->event_field.name;
enum lttng_ust_jni_type jni_type;
char sel_char;
}
case JNI_TYPE_STRING:
{
- const char *str = jctx->value._string;
-
- sel_char = LTTNG_UST_DYNAMIC_TYPE_STRING;
+ int32_t offset = jctx->value._string_offset;
+ const char *str = get_ctx_string_at_offset(offset);
+
+ if (str) {
+ sel_char = LTTNG_UST_DYNAMIC_TYPE_STRING;
+ } else {
+ sel_char = LTTNG_UST_DYNAMIC_TYPE_NONE;
+ }
lib_ring_buffer_align_ctx(ctx, lttng_alignof(char));
chan->ops->event_write(ctx, &sel_char, sizeof(sel_char));
- chan->ops->event_write(ctx, str, strlen(str) + 1);
+ if (str) {
+ chan->ops->event_write(ctx, str, strlen(str) + 1);
+ }
break;
}
default:
static void get_value_cb(struct lttng_ctx_field *field,
struct lttng_ctx_value *value)
{
- struct lttng_ust_jni_ctx *jctx;
+ struct lttng_ust_jni_ctx_entry *jctx;
const char *ctx_name = field->event_field.name;
enum lttng_ust_jni_type jni_type;
value->u.s64 = (int64_t) jctx->value._boolean;
break;
case JNI_TYPE_STRING:
- value->sel = LTTNG_UST_DYNAMIC_TYPE_STRING;
- value->u.str = jctx->value._string;
+ {
+ int32_t offset = jctx->value._string_offset;
+ const char *str = get_ctx_string_at_offset(offset);
+
+ if (str) {
+ value->sel = LTTNG_UST_DYNAMIC_TYPE_STRING;
+ value->u.str = str;
+ } else {
+ value->sel = LTTNG_UST_DYNAMIC_TYPE_NONE;
+ }
break;
+ }
default:
abort();
}
jlong millis,
jint log_level,
jint thread_id,
- jbyteArray context_info)
+ jbyteArray context_info_entries,
+ jbyteArray context_info_strings)
{
jboolean iscopy;
const char *msg_cstr = (*env)->GetStringUTFChars(env, msg, &iscopy);
const char *logger_name_cstr = (*env)->GetStringUTFChars(env, logger_name, &iscopy);
const char *class_name_cstr = (*env)->GetStringUTFChars(env, class_name, &iscopy);
const char *method_name_cstr = (*env)->GetStringUTFChars(env, method_name, &iscopy);
- signed char *context_info_array;
+ signed char *context_info_entries_array;
+ signed char *context_info_strings_array;
/*
* Write these to the TLS variables, so that the UST callbacks in
* lttng_ust_context.c can access them.
*/
- context_info_array = (*env)->GetByteArrayElements(env, context_info, &iscopy);
- lttng_ust_context_info_tls.ctx = (struct lttng_ust_jni_ctx *) context_info_array;
- lttng_ust_context_info_tls.len = (*env)->GetArrayLength(env, context_info);
+ context_info_entries_array = (*env)->GetByteArrayElements(env, context_info_entries, &iscopy);
+ lttng_ust_context_info_tls.ctx_entries = (struct lttng_ust_jni_ctx_entry *) context_info_entries_array;
+ lttng_ust_context_info_tls.ctx_entries_len = (*env)->GetArrayLength(env, context_info_entries);
+ context_info_strings_array = (*env)->GetByteArrayElements(env, context_info_strings, &iscopy);
+ lttng_ust_context_info_tls.ctx_strings = context_info_strings_array;
+ lttng_ust_context_info_tls.ctx_strings_len = (*env)->GetArrayLength(env, context_info_strings);
tracepoint(lttng_jul, event, msg_cstr, logger_name_cstr,
class_name_cstr, method_name_cstr, millis, log_level, thread_id);
- lttng_ust_context_info_tls.ctx = NULL;
- lttng_ust_context_info_tls.len = 0;
+ lttng_ust_context_info_tls.ctx_entries = NULL;
+ lttng_ust_context_info_tls.ctx_entries_len = 0;
+ lttng_ust_context_info_tls.ctx_strings = NULL;
+ lttng_ust_context_info_tls.ctx_strings_len = 0;
(*env)->ReleaseStringUTFChars(env, msg, msg_cstr);
(*env)->ReleaseStringUTFChars(env, logger_name, logger_name_cstr);
(*env)->ReleaseStringUTFChars(env, class_name, class_name_cstr);
(*env)->ReleaseStringUTFChars(env, method_name, method_name_cstr);
- (*env)->ReleaseByteArrayElements(env, context_info, context_info_array, 0);
+ (*env)->ReleaseByteArrayElements(env, context_info_entries, context_info_entries_array, 0);
+ (*env)->ReleaseByteArrayElements(env, context_info_strings, context_info_strings_array, 0);
}
jlong timestamp,
jint loglevel,
jstring thread_name,
- jbyteArray context_info)
+ jbyteArray context_info_entries,
+ jbyteArray context_info_strings)
{
jboolean iscopy;
const char *msg_cstr = (*env)->GetStringUTFChars(env, msg, &iscopy);
const char *method_name_cstr = (*env)->GetStringUTFChars(env, method_name, &iscopy);
const char *file_name_cstr = (*env)->GetStringUTFChars(env, file_name, &iscopy);
const char *thread_name_cstr = (*env)->GetStringUTFChars(env, thread_name, &iscopy);
- signed char *context_info_array;
+ signed char *context_info_entries_array;
+ signed char *context_info_strings_array;
/*
* Write these to the TLS variables, so that the UST callbacks in
* lttng_ust_context.c can access them.
*/
- context_info_array = (*env)->GetByteArrayElements(env, context_info, &iscopy);
- lttng_ust_context_info_tls.ctx = (struct lttng_ust_jni_ctx *) context_info_array;
- lttng_ust_context_info_tls.len = (*env)->GetArrayLength(env, context_info);
+ context_info_entries_array = (*env)->GetByteArrayElements(env, context_info_entries, &iscopy);
+ lttng_ust_context_info_tls.ctx_entries = (struct lttng_ust_jni_ctx_entry *) context_info_entries_array;
+ lttng_ust_context_info_tls.ctx_entries_len = (*env)->GetArrayLength(env, context_info_entries);
+ context_info_strings_array = (*env)->GetByteArrayElements(env, context_info_strings, &iscopy);
+ lttng_ust_context_info_tls.ctx_strings = context_info_strings_array;
+ lttng_ust_context_info_tls.ctx_strings_len = (*env)->GetArrayLength(env, context_info_strings);
tracepoint(lttng_log4j, event, msg_cstr, logger_name_cstr,
class_name_cstr, method_name_cstr, file_name_cstr,
line_number, timestamp, loglevel, thread_name_cstr);
- lttng_ust_context_info_tls.ctx = NULL;
- lttng_ust_context_info_tls.len = 0;
+ lttng_ust_context_info_tls.ctx_entries = NULL;
+ lttng_ust_context_info_tls.ctx_entries_len = 0;
+ lttng_ust_context_info_tls.ctx_strings = NULL;
+ lttng_ust_context_info_tls.ctx_strings_len = 0;
(*env)->ReleaseStringUTFChars(env, msg, msg_cstr);
(*env)->ReleaseStringUTFChars(env, logger_name, logger_name_cstr);
(*env)->ReleaseStringUTFChars(env, class_name, class_name_cstr);
(*env)->ReleaseStringUTFChars(env, method_name, method_name_cstr);
(*env)->ReleaseStringUTFChars(env, file_name, file_name_cstr);
(*env)->ReleaseStringUTFChars(env, thread_name, thread_name_cstr);
- (*env)->ReleaseByteArrayElements(env, context_info, context_info_array, 0);
+ (*env)->ReleaseByteArrayElements(env, context_info_entries, context_info_entries_array, 0);
+ (*env)->ReleaseByteArrayElements(env, context_info_strings, context_info_strings_array, 0);
}