Add liblttng-ust-jul for JUL support
authorDavid Goulet <dgoulet@efficios.com>
Thu, 7 Nov 2013 21:34:23 +0000 (16:34 -0500)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Tue, 12 Nov 2013 23:48:31 +0000 (18:48 -0500)
The build system creates a jar file named liblttng-ust-jul.jar to be
linked with the Java application. A public library named
liblttng-ust-jul.so is also created and must be in the library path of
the Java application in order for the LTTngAgent to use it for the
tracer's JNI call.

A unit test is also added and integrated with the "make check" command.

Signed-off-by: David Goulet <dgoulet@efficios.com>
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
18 files changed:
.gitignore
Makefile.am
configure.ac
liblttng-ust-jul/LTTngUst.c [new file with mode: 0644]
liblttng-ust-jul/Makefile.am [new file with mode: 0644]
liblttng-ust-jul/README [new file with mode: 0644]
liblttng-ust-jul/lttng_ust_jul.h [new file with mode: 0644]
liblttng-ust-jul/org/lttng/ust/jul/LTTngAgent.java [new file with mode: 0644]
liblttng-ust-jul/org/lttng/ust/jul/LTTngLogHandler.java [new file with mode: 0644]
liblttng-ust-jul/org/lttng/ust/jul/LTTngSessiondCmd2_4.java [new file with mode: 0644]
liblttng-ust-jul/org/lttng/ust/jul/LTTngTCPSessiondClient.java [new file with mode: 0644]
liblttng-ust-jul/org/lttng/ust/jul/LTTngThread.java [new file with mode: 0644]
liblttng-ust-jul/org/lttng/ust/jul/LTTngUst.java [new file with mode: 0644]
tests/Makefile.am
tests/java-jul/JULTest.java [new file with mode: 0644]
tests/java-jul/Makefile.am [new file with mode: 0644]
tests/java-jul/test_jul [new file with mode: 0755]
tests/unit_tests

index 07d13062dbdefcdd79c3cb4c405523f7fa696646..ce7c5729fab55ecbe05b00565528ce32c2b0c7e2 100644 (file)
@@ -48,3 +48,8 @@ tests/tracepoint/tracepoint_test
 tests/snprintf/prog
 tests/benchmark/bench1
 tests/benchmark/bench2
+
+# Java JUL library
+*.class
+liblttng-ust-jul.jar
+org_lttng_ust_jul_LTTngUst.h
index dc88c46ea07b47ba17026b4757383c2d8e45e540..edb1a8571ee92fb9e2844095c69efc7fa0b34b61 100644 (file)
@@ -7,13 +7,14 @@ SUBDIRS = . include snprintf libringbuffer liblttng-ust-comm \
                liblttng-ust-libc-wrapper \
                liblttng-ust-cyg-profile \
                tools \
-               tests \
                doc
 
 if BUILD_JNI_INTERFACE
-SUBDIRS += liblttng-ust-java
+SUBDIRS += liblttng-ust-java liblttng-ust-jul
 endif
 
+SUBDIRS += tests
+
 #temporarily disabled
 # liblttng-ust-malloc
 
index cc28205c625f8e6e0d546b96338a12e789272080..c5802ee26af95f4ff4495545c0c4238a26747042 100644 (file)
@@ -209,6 +209,9 @@ AC_ARG_WITH([java-jdk],
        [JAVA_JDK=$withval],
        [JAVA_JDK=""]
 )
+AM_CONDITIONAL([HAVE_JAVA_JDK], [test $JAVA_JDK], [Java JDK path])
+AC_SUBST([JAVA_JDK])
+
 AS_IF([test $JAVA_JDK],[
        AS_IF([test -d $JAVA_JDK],[
                 AC_MSG_RESULT([using Java includes in $JAVA_SDK])
@@ -283,6 +286,7 @@ AC_CONFIG_FILES([
        liblttng-ust-ctl/Makefile
        liblttng-ust-fork/Makefile
        liblttng-ust-java/Makefile
+       liblttng-ust-jul/Makefile
        liblttng-ust-libc-wrapper/Makefile
        liblttng-ust-cyg-profile/Makefile
        tools/Makefile
@@ -293,6 +297,7 @@ AC_CONFIG_FILES([
        tests/snprintf/Makefile
        tests/benchmark/Makefile
        tests/utils/Makefile
+       tests/java-jul/Makefile
        lttng-ust.pc
 ])
 
diff --git a/liblttng-ust-jul/LTTngUst.c b/liblttng-ust-jul/LTTngUst.c
new file mode 100644 (file)
index 0000000..69bd83d
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011-2012 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; only
+ * version 2.1 of the License.
+ *
+ * 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
+ */
+
+#include "org_lttng_ust_jul_LTTngUst.h"
+
+#define TRACEPOINT_DEFINE
+#define TRACEPOINT_CREATE_PROBES
+#include "lttng_ust_jul.h"
+
+JNIEXPORT void JNICALL Java_org_lttng_ust_LTTngUst_tracepoint(JNIEnv *env,
+                                               jobject jobj,
+                                               jstring msg,
+                                               jstring logger_name,
+                                               jstring class_name,
+                                               jstring method_name,
+                                               jlong millis,
+                                               jint log_level,
+                                               jint thread_id)
+{
+       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);
+
+       tracepoint(lttng_jul, jul_event, msg_cstr, logger_name_cstr,
+                       class_name_cstr, method_name_cstr, millis, log_level, thread_id);
+
+       (*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);
+}
diff --git a/liblttng-ust-jul/Makefile.am b/liblttng-ust-jul/Makefile.am
new file mode 100644 (file)
index 0000000..22ba89c
--- /dev/null
@@ -0,0 +1,43 @@
+if BUILD_JNI_INTERFACE
+
+AM_CPPFLAGS = -I$(top_srcdir)/include
+
+lib_LTLIBRARIES = liblttng-ust-jul-jni.la
+liblttng_ust_jul_jni_la_SOURCES = LTTngUst.c lttng_ust_jul.h
+nodist_liblttng_ust_jul_jni_la_SOURCES = org_lttng_ust_LTTngUst.h
+dist_noinst_DATA = $(LTTNG_JUL_SRCDIR)/LTTngUst.java
+liblttng_ust_jul_jni_la_LIBADD = -lc -L$(top_builddir)/liblttng-ust/.libs -llttng-ust
+
+LTTNG_JUL_SRCDIR = $(srcdir)/org/lttng/ust/jul
+LTTNG_JUL_DESTDIR = $(builddir)/org/lttng/ust/jul
+
+if HAVE_JAVA_JDK
+JCC=$(JAVA_JDK)/bin
+else
+JCC=javac
+endif
+
+all-local: $(LTTNG_JUL_DESTDIR)/LTTngAgent.class $(LTTNG_JUL_DESTDIR)/LTTngUst.class \
+               org_lttng_ust_LTTngUst.h liblttng-ust-jul.jar
+
+clean-local:
+       rm -f org_lttng_ust_jul_LTTngUst.h
+       rm -f liblttng-ust-jul.jar
+       rm -f org/lttng/ust/jul/*.class
+
+LTTngUst.c: org_lttng_ust_LTTngUst.h
+
+$(LTTNG_JUL_DESTDIR)/LTTngUst.class: $(LTTNG_JUL_SRCDIR)/LTTngUst.java
+       $(JCC)/javac -d "$(builddir)" "$(LTTNG_JUL_SRCDIR)/LTTngUst.java"
+
+$(LTTNG_JUL_DESTDIR)/LTTngAgent.class: $(LTTNG_JUL_SRCDIR)/LTTngAgent.java
+       $(JCC)/javac -d "$(builddir)" "$(LTTNG_JUL_SRCDIR)/LTTngAgent.java"
+
+org_lttng_ust_LTTngUst.h: $(LTTNG_JUL_DESTDIR)/LTTngUst.class
+       $(JCC)/javah org.lttng.ust.jul.LTTngUst
+
+liblttng-ust-jul.jar: $(LTTNG_JUL_DESTDIR)/LTTngUst.class $(LTTNG_JUL_DESTDIR)/LTTngAgent.class
+       $(JCC)/jar cf liblttng-ust-jul.jar \
+               $(LTTNG_JUL_DESTDIR)/*.class
+
+endif
diff --git a/liblttng-ust-jul/README b/liblttng-ust-jul/README
new file mode 100644 (file)
index 0000000..6e2ee98
--- /dev/null
@@ -0,0 +1,38 @@
+This directory contains the LTTng Java Agent for JUL support.
+
+Configuration examples to build this library:
+
+dependency: openjdk-7-jdk
+    ./configure --with-java-jdk=/usr/lib/jvm/java-7-openjdk --with-jni-interface
+
+On Debian system for instance you can simply use the "default-java" path:
+
+    ./configure --with-java-jdk=/usr/lib/jvm/default-java --with-jni-interface
+
+Note that the OpenJDK 7 is used for development and continuous integration thus
+we directly support that version for this library. However, it has been tested
+with OpenJDK 6 also. Please let us know if other Java version (commercial or
+not) work with this library.
+
+After building, you can use the "liblttng-ust-jul.jar" file in a Java project.
+It requires "liblttng-ust-jul.so" which is installed by the build system when
+doing "make install". Make sure that your Java application can find this shared
+object with the "java.library.path".
+
+In order to enable the agent in your Java application, you simply have to add
+this as early as you can in the runtime process.
+
+import org.lttng.ust.jul.LTTngAgent;
+[...]
+       private static LTTngAgent lttngAgent;
+       [...]
+       lttngAgent = LTTngAgent.getLTTngAgent();
+
+This will initialize automatically the singleton LTTngAgent, it will stall
+your application until the session daemon registration is done. If no session
+daemon is available, the execution will continue and the agent will retry at
+each 3 seconds.
+
+Once registered, it is adds a thread inside your Java application and will be
+able to automatically use every Logger object and map them to the jul_event
+tracepoint of the JNI interface (see LTTngUst.c/.java).
diff --git a/liblttng-ust-jul/lttng_ust_jul.h b/liblttng-ust-jul/lttng_ust_jul.h
new file mode 100644 (file)
index 0000000..442eda5
--- /dev/null
@@ -0,0 +1,53 @@
+#undef TRACEPOINT_PROVIDER
+#define TRACEPOINT_PROVIDER lttng_jul
+
+#if !defined(_TRACEPOINT_LTTNG_UST_JUL_H) || defined(TRACEPOINT_HEADER_MULTI_READ)
+#define _TRACEPOINT_LTTNG_UST_JUL_H
+
+/*
+ * Copyright (C) 2011  Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; version 2.1 of
+ * the License.
+ *
+ * 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
+ */
+
+#include <lttng/tracepoint.h>
+
+TRACEPOINT_EVENT(lttng_jul, jul_event,
+       TP_ARGS(
+               const char *, msg,
+               const char *, logger_name,
+               const char *, class_name,
+               const char *, method_name,
+               long, millis,
+               int, log_level,
+               int, thread_id),
+       TP_FIELDS(
+               ctf_string(msg, msg)
+               ctf_string(logger_name, logger_name)
+               ctf_string(class_name, class_name)
+               ctf_string(method_name, method_name)
+               ctf_integer(long, long_millis, millis)
+               ctf_integer(int, int_loglevel, log_level)
+               ctf_integer(int, int_threadid, thread_id)
+       )
+)
+
+#endif /* _TRACEPOINT_LTTNG_UST_JUL_H */
+
+#undef TRACEPOINT_INCLUDE
+#define TRACEPOINT_INCLUDE "./lttng_ust_jul.h"
+
+/* This part must be outside protection */
+#include <lttng/tracepoint-event.h>
diff --git a/liblttng-ust-jul/org/lttng/ust/jul/LTTngAgent.java b/liblttng-ust-jul/org/lttng/ust/jul/LTTngAgent.java
new file mode 100644 (file)
index 0000000..c1834f5
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ *
+ * 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
+ */
+
+package org.lttng.ust.jul;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.concurrent.Semaphore;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.LogManager;
+import java.util.Enumeration;
+
+public class LTTngAgent {
+       private static LTTngLogHandler lttngHandler;
+       private static LogManager logManager;
+       private static LTTngThread lttngThread;
+       private static Thread sessiondTh;
+
+       /* Singleton agent object. */
+       private static LTTngAgent curAgent = null;
+
+       /* Indicate if this object has been initialized. */
+       private static boolean initialized = false;
+
+       private static Semaphore registerSem;
+
+       private static final String sessiondAddr = "127.0.0.1";
+       private static final int sessiondPort = 5345;
+
+       private static final String rootPortFile = "/var/run/lttng/jul.port";
+       private static final String userPortFile = "/.lttng/jul.port";
+
+       /*
+        * Constructor is private. This is a singleton and a reference should be
+        * acquired using getLTTngAgent().
+        */
+       private LTTngAgent() throws IOException {
+               this.logManager = LogManager.getLogManager();
+               this.lttngHandler = new LTTngLogHandler(this.logManager);
+               this.registerSem = new Semaphore(0, true);
+       }
+
+       private void removeHandlers() throws SecurityException, IOException {
+               String loggerName;
+               Logger logger;
+
+               Enumeration list = this.logManager.getLoggerNames();
+               while (list.hasMoreElements()) {
+                       loggerName = list.nextElement().toString();
+                       /* Somehow there is always an empty string at the end. */
+                       if (loggerName == "") {
+                               continue;
+                       }
+
+                       logger = this.logManager.getLogger(loggerName);
+                       logger.removeHandler(this.lttngHandler);
+               }
+       }
+
+       private int getUID() throws IOException {
+               int uid;
+               byte b[] = new byte[4];
+               String userName = System.getProperty("user.name");
+               String command = "id -u " + userName;
+               Process child = Runtime.getRuntime().exec(command);
+               InputStream in = child.getInputStream();
+
+               in.read(b);
+               uid = Integer.parseInt(new String(b).trim(), 10);
+               in.close();
+
+               return uid;
+       }
+
+       private String getHomePath() {
+               return System.getProperty("user.home");
+       }
+
+       private int getPortFromFile() throws IOException {
+               int port;
+               int uid = getUID();
+               String path;
+               BufferedReader br;
+
+               /* Check if root or not, it tells where to get the port file. */
+               if (uid == 0) {
+                       path = rootPortFile;
+               } else {
+                       path = new String(getHomePath() + userPortFile);
+               }
+
+               try {
+                       br = new BufferedReader(new FileReader(path));
+                       String line = br.readLine();
+                       port = Integer.parseInt(line, 10);
+                       if (port < 0 || port > 65535) {
+                               port = sessiondPort;
+                       }
+                       br.close();
+               } catch (FileNotFoundException e) {
+                       port = sessiondPort;
+               }
+
+               return port;
+       }
+
+       /*
+        * Public getter to acquire a reference to this singleton object.
+        */
+       public static synchronized LTTngAgent getLTTngAgent() throws IOException {
+               if (curAgent == null) {
+                       curAgent = new LTTngAgent();
+                       curAgent.init();
+               }
+
+               return curAgent;
+       }
+
+       /*
+        * Initialize LTTngAgent. This will attach the log handler to all Logger
+        * returned by the logManager.
+        */
+       private synchronized void init() throws SecurityException, IOException {
+               if (this.initialized) {
+                       return;
+               }
+
+               this.lttngThread = new LTTngThread(this.sessiondAddr,
+                               getPortFromFile(), this.lttngHandler, this.registerSem);
+               this.sessiondTh = new Thread(lttngThread);
+               this.sessiondTh.start();
+
+               this.initialized = true;
+
+               /* Wait for the registration to end. */
+               try {
+                       this.registerSem.acquire();
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void dispose() throws IOException {
+               this.lttngThread.dispose();
+
+               /* Make sure there is no more LTTng handler attach to logger(s). */
+               this.removeHandlers();
+
+               try {
+                       this.sessiondTh.join();
+               } catch (InterruptedException e) {
+                       e.printStackTrace();
+               }
+       }
+}
diff --git a/liblttng-ust-jul/org/lttng/ust/jul/LTTngLogHandler.java b/liblttng-ust-jul/org/lttng/ust/jul/LTTngLogHandler.java
new file mode 100644 (file)
index 0000000..dfc15b3
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ *
+ * 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
+ */
+
+package org.lttng.ust.jul;
+
+import java.lang.String;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.LogManager;
+
+import org.lttng.ust.jul.LTTngUst;
+
+public class LTTngLogHandler extends Handler {
+       public LogManager logManager;
+
+       public LTTngLogHandler(LogManager logManager) {
+               super();
+
+               this.logManager = logManager;
+
+               /* Initialize LTTng UST tracer. */
+               LTTngUst.init();
+       }
+
+       @Override
+       public void close() throws SecurityException {}
+
+       @Override
+       public void flush() {}
+
+       @Override
+       public void publish(LogRecord record) {
+               /*
+                * Specific tracepoing designed for JUL events. The source class of the
+                * caller is used for the event name, the raw message is taken, the
+                * loglevel of the record and the thread ID.
+                */
+               LTTngUst.tracepoint(record.getMessage(), record.getLoggerName(),
+                               record.getSourceClassName(), record.getSourceMethodName(),
+                               record.getMillis(), record.getLevel().intValue(),
+                               record.getThreadID());
+       }
+}
diff --git a/liblttng-ust-jul/org/lttng/ust/jul/LTTngSessiondCmd2_4.java b/liblttng-ust-jul/org/lttng/ust/jul/LTTngSessiondCmd2_4.java
new file mode 100644 (file)
index 0000000..4b893e0
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ *
+ *
+ * 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
+ */
+
+package org.lttng.ust.jul;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.logging.Logger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Enumeration;
+
+public interface LTTngSessiondCmd2_4 {
+       /**
+        * Maximum name length for a logger name to be send to sessiond.
+        */
+       final static int NAME_MAX = 255;
+
+       public interface SessiondResponse {
+               /**
+                * Gets a byte array of the command so that it may be streamed
+                *
+                * @return the byte array of the command
+                */
+               public byte[] getBytes();
+       }
+
+       public interface SessiondCommand {
+               /**
+                * Populate the class from a byte array
+                *
+                * @param data
+                *              the byte array containing the streamed command
+                */
+               public void populate(byte[] data);
+       }
+
+       public enum lttng_jul_command {
+               /** List logger(s). */
+               CMD_LIST(1),
+               /** Enable logger by name. */
+               CMD_ENABLE(2),
+               /** Disable logger by name. */
+               CMD_DISABLE(3);
+               private int code;
+
+               private lttng_jul_command(int c) {
+                       code = c;
+               }
+
+               public int getCommand() {
+                       return code;
+               }
+       }
+
+       enum lttng_jul_ret_code {
+               CODE_SUCCESS_CMD(1),
+               CODE_INVALID_CMD(2),
+               CODE_UNK_LOGGER_NAME(3);
+               private int code;
+
+               private lttng_jul_ret_code(int c) {
+                       code = c;
+               }
+
+               public int getCode() {
+                       return code;
+               }
+       }
+
+       public class sessiond_hdr implements SessiondCommand {
+               /** ABI size of command header. */
+               public final static int SIZE = 16;
+               /** Payload size in bytes following this header.  */
+               public long data_size;
+               /** Command type. */
+               public lttng_jul_command cmd;
+               /** Command version. */
+               public int cmd_version;
+
+               public void populate(byte[] data) {
+                       ByteBuffer buf = ByteBuffer.wrap(data);
+                       buf.order(ByteOrder.BIG_ENDIAN);
+
+                       data_size = buf.getLong();
+                       cmd = lttng_jul_command.values()[buf.getInt() - 1];
+                       cmd_version = buf.getInt();
+               }
+       }
+
+       public class sessiond_enable_handler implements SessiondResponse, SessiondCommand {
+               private final static int SIZE = 4;
+               public String name;
+
+               /** Return status code to the session daemon. */
+               public lttng_jul_ret_code code;
+
+               @Override
+               public void populate(byte[] data) {
+                       ByteBuffer buf = ByteBuffer.wrap(data);
+                       buf.order(ByteOrder.LITTLE_ENDIAN);
+                       name = new String(data, 0, data.length);
+               }
+
+               @Override
+               public byte[] getBytes() {
+                       byte data[] = new byte[SIZE];
+                       ByteBuffer buf = ByteBuffer.wrap(data);
+                       buf.order(ByteOrder.BIG_ENDIAN);
+                       buf.putInt(code.getCode());
+                       return data;
+               }
+
+               /**
+                * Execute enable handler action which is to enable the given handler
+                * to the received name.
+                *
+                * @return Event name as a string if the event is NOT found thus was
+                * not enabled.
+                */
+               public String execute(LTTngLogHandler handler) {
+                       Logger logger;
+
+                       if (name == null) {
+                               this.code = lttng_jul_ret_code.CODE_INVALID_CMD;
+                               return null;
+                       }
+
+                       /* Wild card to enable ALL logger. */
+                       if (name.trim().equals("*")) {
+                               String loggerName;
+                               Enumeration loggers = handler.logManager.getLoggerNames();
+                               while (loggers.hasMoreElements()) {
+                                       loggerName = loggers.nextElement().toString();
+                                       /* Somehow there is always an empty string at the end. */
+                                       if (loggerName == "") {
+                                               continue;
+                                       }
+
+                                       logger = handler.logManager.getLogger(loggerName);
+                                       logger.addHandler(handler);
+                               }
+                               this.code = lttng_jul_ret_code.CODE_SUCCESS_CMD;
+                               return null;
+                       }
+
+                       this.code = lttng_jul_ret_code.CODE_SUCCESS_CMD;
+                       logger = handler.logManager.getLogger(name.trim());
+                       if (logger != null) {
+                               logger.addHandler(handler);
+                               return null;
+                       } else {
+                               return new String(name);
+                       }
+               }
+       }
+
+       public class sessiond_disable_handler implements SessiondResponse, SessiondCommand {
+               private final static int SIZE = 4;
+               public String name;
+
+               /** Return status code to the session daemon. */
+               public lttng_jul_ret_code code;
+
+               @Override
+               public void populate(byte[] data) {
+                       ByteBuffer buf = ByteBuffer.wrap(data);
+                       buf.order(ByteOrder.BIG_ENDIAN);
+                       name = new String(data, 0, data.length);
+               }
+
+               @Override
+               public byte[] getBytes() {
+                       byte data[] = new byte[SIZE];
+                       ByteBuffer buf = ByteBuffer.wrap(data);
+                       buf.order(ByteOrder.BIG_ENDIAN);
+                       buf.putInt(code.getCode());
+                       return data;
+               }
+
+               /**
+                * Execute disable handler action which is to disable the given handler
+                * to the received name.
+                */
+               public void execute(LTTngLogHandler handler) {
+                       Logger logger;
+
+                       if (name == null) {
+                               this.code = lttng_jul_ret_code.CODE_INVALID_CMD;
+                               return;
+                       }
+
+                       /* Wild card to disable ALL logger. */
+                       if (name.trim().equals("*")) {
+                               String loggerName;
+                               Enumeration loggers = handler.logManager.getLoggerNames();
+                               while (loggers.hasMoreElements()) {
+                                       loggerName = loggers.nextElement().toString();
+                                       /* Somehow there is always an empty string at the end. */
+                                       if (loggerName == "") {
+                                               continue;
+                                       }
+
+                                       logger = handler.logManager.getLogger(loggerName);
+                                       logger.removeHandler(handler);
+                               }
+                               this.code = lttng_jul_ret_code.CODE_SUCCESS_CMD;
+                               return;
+                       }
+
+                       logger = handler.logManager.getLogger(name.trim());
+                       if (logger == null) {
+                               this.code = lttng_jul_ret_code.CODE_UNK_LOGGER_NAME;
+                       } else {
+                               logger.removeHandler(handler);
+                               this.code = lttng_jul_ret_code.CODE_SUCCESS_CMD;
+                       }
+               }
+       }
+
+       public class sessiond_list_logger implements SessiondResponse {
+               private final static int SIZE = 12;
+
+               private int data_size = 0;
+               private int nb_logger = 0;
+
+               List<String> logger_list = new ArrayList<String>();
+
+               /** Return status code to the session daemon. */
+               public lttng_jul_ret_code code;
+
+               @Override
+               public byte[] getBytes() {
+                       byte data[] = new byte[SIZE + data_size];
+                       ByteBuffer buf = ByteBuffer.wrap(data);
+                       buf.order(ByteOrder.BIG_ENDIAN);
+
+                       /* Returned code */
+                       buf.putInt(code.getCode());
+                       buf.putInt(data_size);
+                       buf.putInt(nb_logger);
+
+                       for (String logger: logger_list) {
+                               buf.put(logger.getBytes());
+                               /* NULL terminated byte after the logger name. */
+                               buf.put((byte) 0x0);
+                       }
+                       return data;
+               }
+
+               /**
+                * Execute enable handler action which is to enable the given handler
+                * to the received name.
+                */
+               public void execute(LTTngLogHandler handler) {
+                       String loggerName;
+
+                       Enumeration loggers = handler.logManager.getLoggerNames();
+                       while (loggers.hasMoreElements()) {
+                               loggerName = loggers.nextElement().toString();
+                               /* Somehow there is always an empty string at the end. */
+                               if (loggerName == "") {
+                                       continue;
+                               }
+
+                               this.logger_list.add(loggerName);
+                               this.nb_logger++;
+                               this.data_size += loggerName.length() + 1;
+                       }
+
+                       this.code = lttng_jul_ret_code.CODE_SUCCESS_CMD;
+               }
+       }
+}
diff --git a/liblttng-ust-jul/org/lttng/ust/jul/LTTngTCPSessiondClient.java b/liblttng-ust-jul/org/lttng/ust/jul/LTTngTCPSessiondClient.java
new file mode 100644 (file)
index 0000000..b89deb8
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ *
+ * 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
+ */
+
+package org.lttng.ust.jul;
+
+import java.util.concurrent.Semaphore;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.lang.Integer;
+import java.io.IOException;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.net.*;
+import java.lang.management.ManagementFactory;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+class USTRegisterMsg {
+       public static int pid;
+}
+
+public class LTTngTCPSessiondClient {
+       /* Command header from the session deamon. */
+       private LTTngSessiondCmd2_4.sessiond_hdr headerCmd =
+               new LTTngSessiondCmd2_4.sessiond_hdr();
+
+       private final String sessiondHost;
+       private final int sessiondPort;
+       private Socket sessiondSock;
+       private boolean quit = false;
+
+       private DataInputStream inFromSessiond;
+       private DataOutputStream outToSessiond;
+
+       private LTTngLogHandler handler;
+
+       private Semaphore registerSem;
+
+       private Timer eventTimer;
+       private List<String> enabledEventList = new ArrayList<String>();
+       /* Timer delay at each 5 seconds. */
+       private final static long timerDelay = 5 * 1000;
+       private static boolean timerInitialized;
+
+       public LTTngTCPSessiondClient(String host, int port, Semaphore sem) {
+               this.sessiondHost = host;
+               this.sessiondPort = port;
+               this.registerSem = sem;
+               this.eventTimer = new Timer();
+               this.timerInitialized = false;
+       }
+
+       private void setupEventTimer() {
+               if (this.timerInitialized) {
+                       return;
+               }
+
+               this.eventTimer.scheduleAtFixedRate(new TimerTask() {
+                       @Override
+                       public void run() {
+                               /*
+                                * We have to make a copy here since it is possible that the
+                                * enabled event list is changed during an iteration on it.
+                                */
+                               List<String> tmpList = new ArrayList<String>(enabledEventList);
+
+                               LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd = new
+                                       LTTngSessiondCmd2_4.sessiond_enable_handler();
+                               for (String strEventName: tmpList) {
+                                       enableCmd.name = strEventName;
+                                       if (enableCmd.execute(handler) == null) {
+                                               enabledEventList.remove(strEventName);
+                                       }
+                               }
+                       }
+               }, this.timerDelay, this.timerDelay);
+
+               this.timerInitialized = true;
+       }
+
+       public void init(LTTngLogHandler handler) throws InterruptedException {
+               this.handler = handler;
+
+               for (;;) {
+                       if (this.quit) {
+                               break;
+                       }
+
+                       try {
+
+                               /*
+                                * Connect to the session daemon before anything else.
+                                */
+                               connectToSessiond();
+
+                               /*
+                                * Register to the session daemon as the Java component of the
+                                * UST application.
+                                */
+                               registerToSessiond();
+                               this.registerSem.release();
+
+                               setupEventTimer();
+
+                               /*
+                                * Block on socket receive and wait for command from the
+                                * session daemon. This will return if and only if there is a
+                                * fatal error or the socket closes.
+                                */
+                               handleSessiondCmd();
+                       } catch (UnknownHostException uhe) {
+                               this.registerSem.release();
+                               System.out.println(uhe);
+                       } catch (IOException ioe) {
+                               this.registerSem.release();
+                               Thread.sleep(3000);
+                       } catch (Exception e) {
+                               this.registerSem.release();
+                               e.printStackTrace();
+                       }
+               }
+       }
+
+       public void destroy() {
+               this.quit = true;
+               this.eventTimer.cancel();
+
+               try {
+                       if (this.sessiondSock != null) {
+                               this.sessiondSock.close();
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       /*
+        * Receive header data from the session daemon using the LTTng command
+        * static buffer of the right size.
+        */
+       private void recvHeader() throws Exception {
+               int read_len;
+               byte data[] = new byte[this.headerCmd.SIZE];
+
+               read_len = this.inFromSessiond.read(data, 0, data.length);
+               if (read_len != data.length) {
+                       throw new IOException();
+               }
+               this.headerCmd.populate(data);
+       }
+
+       /*
+        * Receive payload from the session daemon. This MUST be done after a
+        * recvHeader() so the header value of a command are known.
+        *
+        * The caller SHOULD use isPayload() before which returns true if a payload
+        * is expected after the header.
+        */
+       private byte[] recvPayload() throws Exception {
+               byte payload[] = new byte[(int) this.headerCmd.data_size];
+
+               /* Failsafe check so we don't waste our time reading 0 bytes. */
+               if (payload.length == 0) {
+                       return null;
+               }
+
+               this.inFromSessiond.read(payload, 0, payload.length);
+               return payload;
+       }
+
+       /*
+        * Handle session command from the session daemon.
+        */
+       private void handleSessiondCmd() throws Exception {
+               int ret_code;
+               byte data[] = null;
+
+               while (true) {
+                       /* Get header from session daemon. */
+                       recvHeader();
+
+                       if (headerCmd.data_size > 0) {
+                               data = recvPayload();
+                       }
+
+                       switch (headerCmd.cmd) {
+                               case CMD_LIST:
+                               {
+                                       LTTngSessiondCmd2_4.sessiond_list_logger listLoggerCmd =
+                                               new LTTngSessiondCmd2_4.sessiond_list_logger();
+                                       listLoggerCmd.execute(this.handler);
+                                       data = listLoggerCmd.getBytes();
+                                       break;
+                               }
+                               case CMD_ENABLE:
+                               {
+                                       String event_name;
+                                       LTTngSessiondCmd2_4.sessiond_enable_handler enableCmd =
+                                               new LTTngSessiondCmd2_4.sessiond_enable_handler();
+                                       if (data == null) {
+                                               enableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
+                                               break;
+                                       }
+                                       enableCmd.populate(data);
+                                       event_name = enableCmd.execute(this.handler);
+                                       if (event_name != null) {
+                                               /*
+                                                * Add the event to the list so it can be enabled if
+                                                * the logger appears at some point in time.
+                                                */
+                                               enabledEventList.add(event_name);
+                                       }
+                                       data = enableCmd.getBytes();
+                                       break;
+                               }
+                               case CMD_DISABLE:
+                               {
+                                       LTTngSessiondCmd2_4.sessiond_disable_handler disableCmd =
+                                               new LTTngSessiondCmd2_4.sessiond_disable_handler();
+                                       if (data == null) {
+                                               disableCmd.code = LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
+                                               break;
+                                       }
+                                       disableCmd.populate(data);
+                                       disableCmd.execute(this.handler);
+                                       data = disableCmd.getBytes();
+                                       break;
+                               }
+                               default:
+                               {
+                                       data = new byte[4];
+                                       ByteBuffer buf = ByteBuffer.wrap(data);
+                                       buf.order(ByteOrder.BIG_ENDIAN);
+                                       LTTngSessiondCmd2_4.lttng_jul_ret_code code =
+                                               LTTngSessiondCmd2_4.lttng_jul_ret_code.CODE_INVALID_CMD;
+                                       buf.putInt(code.getCode());
+                                       break;
+                               }
+                       }
+
+                       /* Send payload to session daemon. */
+                       this.outToSessiond.write(data, 0, data.length);
+                       this.outToSessiond.flush();
+               }
+       }
+
+       private void connectToSessiond() throws Exception {
+               this.sessiondSock = new Socket(this.sessiondHost, this.sessiondPort);
+               this.inFromSessiond = new DataInputStream(
+                               sessiondSock.getInputStream());
+               this.outToSessiond = new DataOutputStream(
+                               sessiondSock.getOutputStream());
+       }
+
+       private void registerToSessiond() throws Exception {
+               byte data[] = new byte[4];
+               ByteBuffer buf = ByteBuffer.wrap(data);
+               String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
+
+               buf.putInt(Integer.parseInt(pid));
+               this.outToSessiond.write(data, 0, data.length);
+               this.outToSessiond.flush();
+       }
+}
diff --git a/liblttng-ust-jul/org/lttng/ust/jul/LTTngThread.java b/liblttng-ust-jul/org/lttng/ust/jul/LTTngThread.java
new file mode 100644 (file)
index 0000000..a64c6a9
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ *
+ * 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
+ */
+
+package org.lttng.ust.jul;
+
+import java.util.concurrent.Semaphore;
+
+public class LTTngThread implements Runnable {
+       private LTTngLogHandler handler;
+       private LTTngTCPSessiondClient sessiondClient;
+
+       public LTTngThread(String host, int port, LTTngLogHandler handler,
+                       Semaphore registerSem) {
+               this.handler = handler;
+               this.sessiondClient = new LTTngTCPSessiondClient(host, port,
+                               registerSem);
+       }
+
+       @Override
+       public void run() {
+               try {
+                       this.sessiondClient.init(this.handler);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void dispose() {
+               this.sessiondClient.destroy();
+       }
+}
diff --git a/liblttng-ust-jul/org/lttng/ust/jul/LTTngUst.java b/liblttng-ust-jul/org/lttng/ust/jul/LTTngUst.java
new file mode 100644 (file)
index 0000000..33c15eb
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (C) 2011-2012 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ * Copyright (C) 2012 Alexandre Montplaisir <alexandre.montplaisir@polymtl.ca>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; only
+ * version 2.1 of the License.
+ *
+ * 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
+ */
+
+package org.lttng.ust.jul;
+
+/**
+ * This class implements the the Java side of the LTTng-UST Java interface.
+ *
+ * First, make sure you have installed "liblttng-ust-java.so" where the linker
+ * can find it. You can then call LTTngUst.init() from your Java program to
+ * connect the methods exposed here to the native library.
+ *
+ * Because of limitations in the probe declaration, all trace events generated
+ * by this library will have "lttng_ust_java" for domain, and "<type>_event" for
+ * event name in the CTF trace files. The "name" parameter will instead appear
+ * as the first element of the event's payload.
+ *
+ * @author Mathieu Desnoyers
+ * @author Alexandre Montplaisir
+ * @author David Goulet
+ *
+ */
+public abstract class LTTngUst {
+       /**
+        * Initialize the UST tracer. This should always be called first, before any
+        * tracepoint* method.
+        */
+       public static void init() {
+               System.loadLibrary("lttng-ust-jul-jni"); //$NON-NLS-1$
+       }
+
+       /**
+        * Insert a tracepoint for JUL event.
+        *
+        * @param msg
+        *            Raw message provided by the JUL API.
+        * @param logger_name
+        *            Logger name that trigger this event.
+        * @param class_name
+        *            Name of the class that (allegedly) issued the logging request.
+        * @param method_name
+        *            Name of the method that (allegedly) issued the logging request.
+        * @param millis
+        *            Event time in milliseconds since 1970.
+        * @param log_level
+        *            Log level of the event from JUL.
+        * @param thread_id
+        *            Identifier for the thread where the message originated.
+        */
+    public static native void tracepoint(String msg, String logger_name, String class_name,
+                       String method_name, long millis, int log_level, int thread_id);
+}
index 30b0e9a0f23897d6588bf37d8059c84c9004958a..b5166f9887860878b42f34ef183ee64900d2a193 100644 (file)
@@ -4,6 +4,10 @@ if CXX_WORKS
 SUBDIRS += hello.cxx
 endif
 
+if BUILD_JNI_INTERFACE
+SUBDIRS += java-jul
+endif
+
 SCRIPT_LIST = test_loop run.sh unit_tests
 
 dist_noinst_SCRIPTS = $(SCRIPT_LIST)
diff --git a/tests/java-jul/JULTest.java b/tests/java-jul/JULTest.java
new file mode 100644 (file)
index 0000000..0a2e854
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 - David Goulet <dgoulet@efficios.com>
+ *
+ * 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
+ */
+
+import java.io.IOException;
+import java.util.concurrent.Semaphore;
+import java.util.logging.LogManager;
+
+import org.lttng.ust.jul.*;
+
+public class JULTest {
+       private final static int NUM_TESTS = 5;
+       private static int testCount = 1;
+
+       /* Singleton agent object. */
+       private static LTTngAgent agent;
+       private static LTTngLogHandler handler;
+       private static LTTngTCPSessiondClient client;
+
+       private static Semaphore sem;
+
+       private static void ok(String desc) {
+               System.out.println("ok " + testCount + " - " + desc);
+               testCount++;
+       }
+
+       public static void go() throws IOException {
+               handler = new LTTngLogHandler(LogManager.getLogManager());
+               assert handler.logManager == LogManager.getLogManager();
+               ok("Log handler logManager is valid");
+
+               client = new LTTngTCPSessiondClient("127.0.0.1", 5345, sem);
+               assert client != null;
+               ok("TCP client is valid");
+               client.destroy();
+               ok("TCP client destroyed");
+
+               agent = LTTngAgent.getLTTngAgent();
+               assert agent != null;
+               ok("LTTngAgent is valid");
+               agent.dispose();
+               ok("LTTngAgent disposed");
+       }
+
+       public static void main(String args[]) throws Exception {
+               System.out.println("1.." + NUM_TESTS);
+               go();
+       }
+}
diff --git a/tests/java-jul/Makefile.am b/tests/java-jul/Makefile.am
new file mode 100644 (file)
index 0000000..6d3e2aa
--- /dev/null
@@ -0,0 +1,25 @@
+if BUILD_JNI_INTERFACE
+
+if HAVE_JAVA_JDK
+JCC=$(JAVA_JDK)/bin/javac
+else
+JCC=javac
+endif
+
+AM_CPPFLAGS = -I$(top_srcdir)/include
+
+EXTRA_DIST = test_jul
+
+JUL_jar_file = "$(srcdir)/../../liblttng-ust-jul/liblttng-ust-jul.jar"
+
+default: all
+
+all: JULTest.class
+
+clean-local:
+       rm -f *.class
+
+JULTest.class: JULTest.java
+       $(JCC) -cp $(JUL_jar_file) -d "$(builddir)" "$(srcdir)/JULTest.java"
+
+endif
diff --git a/tests/java-jul/test_jul b/tests/java-jul/test_jul
new file mode 100755 (executable)
index 0000000..192dfda
--- /dev/null
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+CURDIR=$(dirname $0)/
+TESTDIR=$CURDIR/..
+TESTCLASS="JULTest"
+
+if [ ! -f  "$CURDIR/$TESTCLASS.class" ]; then
+       echo "1..0 # Skipped: Java support not build"
+       exit 0
+fi
+
+java -ea -cp "$CURDIR:$TESTDIR/../liblttng-ust-jul/liblttng-ust-jul.jar" -Djava.library.path="$TESTDIR/../liblttng-ust-jul/.libs" $TESTCLASS
index 911013834e2e736eb0108780c8637c456d3f75d8..2520c0d820626784bef078e2ecc8e1127d20a7b3 100644 (file)
@@ -1 +1,2 @@
 snprintf/test_snprintf
+java-jul/test_jul
This page took 0.041313 seconds and 4 git commands to generate.