diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java index acc3322f1..c44fc1ea1 100644 --- a/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/Client.java @@ -52,14 +52,7 @@ import org.openjdk.btrace.core.BTraceRuntime; import org.openjdk.btrace.core.DebugSupport; import org.openjdk.btrace.core.SharedSettings; -import org.openjdk.btrace.core.comm.CommandListener; -import org.openjdk.btrace.core.comm.ErrorCommand; -import org.openjdk.btrace.core.comm.ExitCommand; -import org.openjdk.btrace.core.comm.InstrumentCommand; -import org.openjdk.btrace.core.comm.MessageCommand; -import org.openjdk.btrace.core.comm.RenameCommand; -import org.openjdk.btrace.core.comm.RetransformationStartNotification; -import org.openjdk.btrace.core.comm.StatusCommand; +import org.openjdk.btrace.core.comm.*; import org.openjdk.btrace.instr.BTraceProbe; import org.openjdk.btrace.instr.BTraceProbeFactory; import org.openjdk.btrace.instr.BTraceProbePersisted; @@ -338,13 +331,6 @@ final Class loadClass(InstrumentCommand instr, boolean canLoadPack) throws IO errorExit(th); return null; } - - if (probe.isClassRenamed()) { - if (isDebug()) { - debugPrint("class renamed to " + probe.getClassName()); - } - onCommand(new RenameCommand(probe.getClassName())); - } if (isDebug()) { debugPrint("creating BTraceRuntime instance for " + probe.getClassName()); } @@ -357,12 +343,18 @@ final Class loadClass(InstrumentCommand instr, boolean canLoadPack) throws IO runtime.handleExit(0); } })); + if (probe.isClassRenamed()) { + if (isDebug()) { + debugPrint("class renamed to " + probe.getClassName()); + } + sendCommand(new RenameCommand(probe.getClassName())); + } if (isDebug()) { debugPrint("created BTraceRuntime instance for " + probe.getClassName()); debugPrint("sending Okay command"); } - onCommand(new StatusCommand()); + sendCommand(new StatusCommand()); boolean entered = false; try { @@ -391,9 +383,9 @@ protected void closeAll() throws IOException { private void errorExit(Throwable th) throws IOException { debugPrint("sending error command"); - onCommand(new ErrorCommand(th)); + sendCommand(new ErrorCommand(th)); debugPrint("sending exit command"); - onCommand(new ExitCommand(1)); + sendCommand(new ExitCommand(1)); closeAll(); } @@ -445,23 +437,15 @@ private final boolean isCandidate(Class c) { } private final void startRetransformClasses(int numClasses) { - try { - onCommand(new RetransformationStartNotification(numClasses)); - if (isDebug()) { - debugPrint("calling retransformClasses (" + numClasses + " classes to be retransformed)"); - } - } catch (IOException e) { - debugPrint(e); + sendCommand(new RetransformationStartNotification(numClasses)); + if (isDebug()) { + debugPrint("calling retransformClasses (" + numClasses + " classes to be retransformed)"); } } final void endRetransformClasses() { - try { - onCommand(new StatusCommand()); - if (isDebug()) debugPrint("finished retransformClasses"); - } catch (IOException e) { - debugPrint(e); - } + sendCommand(new StatusCommand()); + if (isDebug()) debugPrint("finished retransformClasses"); } // Internals only below this point @@ -514,19 +498,13 @@ boolean retransformLoaded() throws UnmodifiableClassException { } catch (ClassFormatError | VerifyError e) { debugPrint("Class '" + c.getName() + "' verification failed"); debugPrint(e); - try { - onCommand( - new MessageCommand( - "[BTRACE WARN] Class verification failed: " - + c.getName() - + " (" - + e.getMessage() - + ")")); - } catch (IOException ioException) { - if (isDebug()) { - debug.debug(ioException); - } - } + sendCommand( + new MessageCommand( + "[BTRACE WARN] Class verification failed: " + + c.getName() + + " (" + + e.getMessage() + + ")")); } } } else { @@ -544,19 +522,13 @@ boolean retransformLoaded() throws UnmodifiableClassException { } catch (ClassFormatError | VerifyError e1) { debugPrint("Class '" + c.getName() + "' verification failed"); debugPrint(e1); - try { - onCommand( - new MessageCommand( - "[BTRACE WARN] Class verification failed: " - + c.getName() - + " (" - + e.getMessage() - + ")")); - } catch (IOException ioException) { - if (isDebug()) { - debug.debug(ioException); - } - } + sendCommand( + new MessageCommand( + "[BTRACE WARN] Class verification failed: " + + c.getName() + + " (" + + e.getMessage() + + ")")); } } } @@ -566,6 +538,10 @@ boolean retransformLoaded() throws UnmodifiableClassException { return true; } + protected void sendCommand(Command command) { + runtime.send(command); + } + static Client findClient(String uuid) { try { UUID id = UUID.fromString(uuid); diff --git a/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java b/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java index 5ed9966be..1dd1a6987 100644 --- a/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java +++ b/btrace-agent/src/main/java/org/openjdk/btrace/agent/RemoteClient.java @@ -32,6 +32,7 @@ import java.net.SocketException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; import org.openjdk.btrace.core.*; import org.openjdk.btrace.core.comm.Command; @@ -70,6 +71,13 @@ public Boolean apply(Command value) { private volatile ObjectInputStream ois; private volatile ObjectOutputStream oos; + private final AtomicReferenceFieldUpdater sockUpdater = + AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, Socket.class, "sock"); + private final AtomicReferenceFieldUpdater oisUpdater = + AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, ObjectInputStream.class, "ois"); + private final AtomicReferenceFieldUpdater oosUpdater = + AtomicReferenceFieldUpdater.newUpdater(RemoteClient.class, ObjectOutputStream.class, "oos"); + private final CircularBuffer delayedCommands = new CircularBuffer<>(5000); static Client getClient(ClientContext ctx, Socket sock, Function> initCallback) @@ -93,7 +101,7 @@ static Client getClient(ClientContext ctx, Socket sock, Function : Send an event with an optional name\n \ + - exit : Terminate the BTrace probe + # usage messages btracec.usage=\ Usage: btracec \n\ @@ -77,6 +82,7 @@ btrace.usage=\ -r Reconnect to an active disconnected probe\n \ \t\t\tExpects PID or app name as the follow-up argument\n \ \t\t\tAll other options are discarded\n \ + -r help Show help on the remote commands\n \ -o The path to store the probe output (will disable showing the output in console)\n \ -u Run in trusted mode\n \ -d Dump the instrumented classes to the specified path\n \ diff --git a/btrace-instr/src/test/java/org/openjdk/btrace/BTraceFunctionalTests.java b/btrace-instr/src/test/java/org/openjdk/btrace/BTraceFunctionalTests.java index f6e6ab6fa..7168f9819 100644 --- a/btrace-instr/src/test/java/org/openjdk/btrace/BTraceFunctionalTests.java +++ b/btrace-instr/src/test/java/org/openjdk/btrace/BTraceFunctionalTests.java @@ -36,6 +36,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; import jdk.jfr.EventType; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingFile; @@ -296,8 +297,6 @@ public void validate(String stdout, String stderr, int retcode, String jfrFile) @Test public void testJfr() throws Exception { - debugBTrace = true; - debugTestApp = true; String rtVersion = System.getProperty("java.runtime.version", ""); String testJavaHome = System.getenv().get("TEST_JAVA_HOME"); if (testJavaHome != null) { @@ -365,18 +364,107 @@ public void validate(String stdout, String stderr, int retcode, String jfrFile) }); } + @Test + public void testOnMethodUnattended() throws Exception { + debugBTrace = true; + debugTestApp = true; + TestApp testApp = launchTestApp("resources.Main"); + File traceFile = locateTrace("btrace/OnMethodTest.java"); + + String pid = String.valueOf(testApp.getPid()); + String[] probeId = new String[1]; + AtomicBoolean hasError = new AtomicBoolean(false); + runBTrace( + new String[] {"-x", pid, traceFile.toString()}, + new ProcessOutputProcessor() { + @Override + public boolean onStdout(int lineno, String line) { + if (lineno > 50) { + return false; + } + System.out.println("[btrace #" + lineno + "] " + line); + if (line.contains("BTrace Probe:")) { + probeId[0] = line.split(":")[1].trim(); + return false; + } + return true; + } + + @Override + public boolean onStderr(int lineno, String line) { + if (lineno > 10) { + return false; + } + System.err.println("[btrace #" + lineno + "] " + line); + hasError.set(true); + return true; + } + }); + + assertFalse(hasError.get()); + assertNotNull(probeId[0]); + + final boolean[] found = new boolean[] {false}; + runBTrace( + new String[] {"-lp", pid}, + new ProcessOutputProcessor() { + @Override + public boolean onStdout(int lineno, String line) { + System.out.println("[btrace #" + lineno + "] " + line); + if (lineno > 3) { + return false; + } + if (line.contains(probeId[0])) { + found[0] = true; + return false; + } + return true; + } + + @Override + public boolean onStderr(int lineno, String line) { + System.err.println("[btrace #" + lineno + "] " + line); + return false; + } + }); + assertTrue(found[0]); + + found[0] = false; + runBTrace( + new String[] {"-r", probeId[0], "exit", pid}, + new ProcessOutputProcessor() { + @Override + public boolean onStdout(int lineno, String line) { + System.out.println("===> " + line); + if (lineno > 3) { + return false; + } + if (line.contains(probeId[0])) { + found[0] = true; + return false; + } + return true; + } + + @Override + public boolean onStderr(int lineno, String line) { + return false; + } + }); + } + private static boolean isVersionSafe(String rtVersion) { String[] versionParts = rtVersion.split("\\+")[0].split("\\."); int major = Integer.parseInt(versionParts[0]); int update = versionParts.length == 3 ? Integer.parseInt(versionParts[2].replace("0_", "")) : 0; if (major == 8) { // before 8u272 there was no JFR support - return update > 272; + return update > 272; } else if (major > 9) { // in JDK 9 the dynamic JFR events are missing if (major == 11) { // 11.0.9 and 11.0.10 are containing a bug causing the JFR initialization from BTrace to go - // into infinte loop - return update < 9 || update > 11; + // into infinite loop + return update < 9 || update > 11; } return true; } diff --git a/btrace-instr/src/test/java/org/openjdk/btrace/RuntimeTest.java b/btrace-instr/src/test/java/org/openjdk/btrace/RuntimeTest.java index 6ff05e115..91efe2b7d 100644 --- a/btrace-instr/src/test/java/org/openjdk/btrace/RuntimeTest.java +++ b/btrace-instr/src/test/java/org/openjdk/btrace/RuntimeTest.java @@ -212,7 +212,7 @@ public void test( String l; while ((l = stdoutReader.readLine()) != null) { if (l.startsWith("ready:")) { - pidStringRef.set(l.split("\\:")[1]); + pidStringRef.set(l.split(":")[1]); testAppLatch.countDown(); } if (debugTestApp) { @@ -274,7 +274,306 @@ public void test( v.validate(stdout.toString(), stderr.toString(), ret.get(), jfrFile); } - private File locateTrace(String trace) { + public static final class TestApp { + private int pid; + private final CountDownLatch testAppLatch = new CountDownLatch(1); + private final Process process; + + public TestApp(Process process, boolean debug) { + this.process = process; + + BufferedReader stdoutReader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + + Thread outT = + new Thread( + () -> { + try { + String l; + while ((l = stdoutReader.readLine()) != null) { + if (l.startsWith("ready:")) { + pid = Integer.parseInt(l.split(":")[1]); + testAppLatch.countDown(); + } + if (debug) { + System.out.println("[traced app] " + l); + } + } + + } catch (Exception e) { + e.printStackTrace(System.err); + } + }, + "STDOUT Reader"); + outT.setDaemon(true); + + BufferedReader stderrReader = + new BufferedReader(new InputStreamReader(process.getErrorStream())); + + Thread errT = + new Thread( + () -> { + try { + String l = null; + while ((l = stderrReader.readLine()) != null) { + if (l.contains("Server VM warning") + || l.contains("XML libraries not available")) { + continue; + } + testAppLatch.countDown(); + if (debug) { + System.err.println("[traced app] " + l); + } + } + } catch (Exception e) { + e.printStackTrace(System.err); + } + }, + "STDERR Reader"); + errT.setDaemon(true); + + outT.start(); + errT.start(); + } + + public void stop() throws InterruptedException { + if (process.isAlive()) { + PrintWriter pw = new PrintWriter(process.getOutputStream()); + pw.println("done"); + pw.flush(); + process.waitFor(); + } + } + + public int getPid() throws InterruptedException { + testAppLatch.await(); + return pid; + } + } + + public TestApp launchTestApp(String testApp, String... cmdArgs) throws Exception { + if (forceDebug) { + // force debug flags + debugBTrace = true; + debugTestApp = true; + } + String jfrFile = null; + List args = new ArrayList<>(Arrays.asList(javaHome + "/bin/java", "-cp", cp)); + if (attachDebugger) { + args.add("-agentlib:jdwp=transport=dt_socket,server=y,address=8000"); + } + args.add("-XX:+AllowRedefinitionToAddDeleteMethods"); + args.add("-XX:+IgnoreUnrecognizedVMOptions"); + // uncomment the following line to get extra JFR logs + // args.add("-Xlog:jfr*=trace"); + args.addAll(extraJvmArgs); + args.addAll( + Arrays.asList( + "-XX:+AllowRedefinitionToAddDeleteMethods", "-XX:+IgnoreUnrecognizedVMOptions")); + if (startJfr) { + jfrFile = Files.createTempFile("btrace-", ".jfr").toString(); + args.add("-XX:StartFlightRecording=settings=default,dumponexit=true,filename=" + jfrFile); + } + args.add(testApp); + + ProcessBuilder pb = new ProcessBuilder(args); + pb.environment().remove("JAVA_TOOL_OPTIONS"); + + return new TestApp(pb.start(), debugTestApp); + } + + public interface ProcessOutputProcessor { + boolean onStdout(int lineno, String line); + + boolean onStderr(int lineno, String line); + } + + public void runBTrace(String[] args, ProcessOutputProcessor outputProcessor) throws Exception { + List argVals = + new ArrayList<>( + Arrays.asList( + javaHome + "/bin/java", + "-cp", + cp, + "org.openjdk.btrace.client.Main", + debugBTrace ? "-v" : "", + "-cp", + eventsClassPath, + "-d", + "/tmp/btrace-test")); + argVals.addAll(Arrays.asList(args)); + if (Files.exists(Paths.get(System.getenv("TEST_JAVA_HOME"), "jmods"))) { + argVals.addAll( + 1, + Arrays.asList("--add-exports", "jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED")); + } + ProcessBuilder pb = new ProcessBuilder(argVals); + + pb.environment().remove("JAVA_TOOL_OPTIONS"); + Process p = pb.start(); + + Thread stderrThread = + new Thread( + () -> { + try { + BufferedReader br = + new BufferedReader( + new InputStreamReader(p.getErrorStream(), StandardCharsets.UTF_8)); + + int lineno = 0; + String line = null; + while ((line = br.readLine()) != null) { + System.out.println("[btrace err] " + line); + if (line.contains("Server VM warning") + || line.contains("XML libraries not available") + || line.contains("Connection reset")) { + // skip JVM generated warnings + continue; + } + if (line.startsWith("[traced app]") || line.startsWith("[btrace out]")) { + // skip test debug lines + continue; + } + if (!outputProcessor.onStderr(++lineno, line)) { + return; + } + ; + } + } catch (Exception e) { + throw new Error(e); + } + }, + "Stderr Reader"); + + Thread stdoutThread = + new Thread( + () -> { + try { + BufferedReader br = + new BufferedReader( + new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8)); + int lineno = 0; + String line = null; + while ((line = br.readLine()) != null) { + if (debugBTrace && line.contains("DEBUG:")) { + continue; + } + if (!outputProcessor.onStdout(++lineno, line)) { + return; + } + } + } catch (Exception e) { + throw new Error(e); + } + }, + "Stdout Reader"); + + stderrThread.setDaemon(true); + stdoutThread.setDaemon(true); + + stderrThread.start(); + stdoutThread.start(); + + stderrThread.join(); + stdoutThread.join(); + } + + public Process runBTrace( + String[] args, int checkLines, StringBuilder stdout, StringBuilder stderr) throws Exception { + List argVals = + new ArrayList<>( + Arrays.asList( + javaHome + "/bin/java", + "-cp", + cp, + "org.openjdk.btrace.client.Main", + debugBTrace ? "-v" : "", + "-cp", + eventsClassPath, + "-d", + "/tmp/btrace-test")); + argVals.addAll(Arrays.asList(args)); + if (Files.exists(Paths.get(System.getenv("TEST_JAVA_HOME"), "jmods"))) { + argVals.addAll( + 1, + Arrays.asList("--add-exports", "jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED")); + } + ProcessBuilder pb = new ProcessBuilder(argVals); + + pb.environment().remove("JAVA_TOOL_OPTIONS"); + Process p = pb.start(); + + CountDownLatch l = new CountDownLatch(checkLines); + + new Thread( + () -> { + try { + BufferedReader br = + new BufferedReader( + new InputStreamReader(p.getErrorStream(), StandardCharsets.UTF_8)); + + String line = null; + while ((line = br.readLine()) != null) { + System.out.println("[btrace err] " + line); + if (line.contains("Server VM warning") + || line.contains("XML libraries not available") + || line.contains("Connection reset")) { + // skip JVM generated warnings + continue; + } + if (line.startsWith("[traced app]") || line.startsWith("[btrace out]")) { + // skip test debug lines + continue; + } + stderr.append(line).append('\n'); + if (line.contains("Exception") || line.contains("Error")) { + for (int i = 0; i < checkLines; i++) { + l.countDown(); + } + } + } + } catch (Exception e) { + for (int i = 0; i < checkLines; i++) { + l.countDown(); + } + throw new Error(e); + } + }, + "Stderr Reader") + .start(); + + new Thread( + () -> { + try { + BufferedReader br = + new BufferedReader( + new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8)); + String line = null; + while ((line = br.readLine()) != null) { + stdout.append(line).append('\n'); + System.out.println("[btrace out] " + line); + if (!(debugBTrace && line.contains("DEBUG:"))) { + l.countDown(); + } + } + } catch (Exception e) { + for (int i = 0; i < checkLines; i++) { + l.countDown(); + } + throw new Error(e); + } + }, + "Stdout Reader") + .start(); + + l.await(timeout, TimeUnit.MILLISECONDS); + + // Thread.sleep(100_000_000L); + + return p; + } + + public File locateTrace(String trace) { Path start = projectRoot.resolve("btrace-instr/src"); AtomicReference tracePath = new AtomicReference<>(); try {