20 Create Event Stream from External Process

The sample StreamExternalEventsWithAttachAPISample.java creates an event stream from a separate Java process, the sample SleepOneSecondIntervals.java.

SleepOneSecondIntervals repeatedly sleeps for 1 second intervals; as demonstrated in Create Event Stream in Process, Active, every time Thread.sleep() is called, a jdk.ThreadSleep event occurs.

public class SleepOneSecondIntervals {

    public static void main(String... args) throws Exception {
        long pid = ProcessHandle.current().pid();
        System.out.println("Process ID: " + pid);
        while(true) {
            System.out.println("Sleeping for 1s...");
            Thread.sleep(1000);
        }
    }
}

StreamExternalEventsWithAttachAPISample uses the Attach API to obtain the virtual machine in which SleepOneSecondIntervals is running. From this virtual machine, StreamExternalEventsWithAttachAPISample obtains the location of its Flight Recorder repository though the jdk.jfr.repository property. It then creates an EventStream with this repository through the EventStream::openRepository(Paths) method.

import java.nio.file.Paths;
import java.util.Optional;
import java.util.Properties;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

import jdk.jfr.consumer.EventStream;

public class StreamExternalEventsWithAttachAPISample {
    public static void main(String... args) throws Exception {

        Optional<VirtualMachineDescriptor> vmd =
            VirtualMachine.list().stream()
            .filter(v -> v.displayName()
                .contains("SleepOneSecondIntervals"))
            .findFirst();

        if (vmd.isEmpty()) {
            throw new RuntimeException("Cannot find VM for SleepOneSecondIntervals");
        }

        VirtualMachine vm = VirtualMachine.attach(vmd.get());

        // Get system properties from attached VM

        Properties props = vm.getSystemProperties();
        String repository = props.getProperty("jdk.jfr.repository");
        System.out.println("jdk.jfr.repository: " + repository);

        try (EventStream es = EventStream
            .openRepository(Paths.get(repository))) {
            System.out.println("Found repository ...");
            es.onEvent("jdk.ThreadSleep", System.out::println);
            es.start();
        }
    }
}

Compile SleepOneSecondIntervals.java and StreamExternalEventsWithAttachAPISample.java. Then run SleepOneSecondIntervals with this command:

java -XX:StartFlightRecording SleepOneSecondIntervals

In a new command shell, run StreamExternalEventsWithAttachAPISample:

java StreamExternalEventsWithAttachAPISample

It prints output similar to the following:

jdk.jfr.repository: C:\Users\<your user name>\AppData\Local\Temp\2019_12_08_23_32_47_5100
Found repository ...
jdk.ThreadSleep {
  startTime = 00:15:31.643
  duration = 1.04 s
  time = 1.00 s
  eventThread = "main" (javaThreadId = 1)
  stackTrace = [
    java.lang.Thread.sleep(long)
    SleepOneSecondIntervals.main(String[]) line: 8
  ]
}

jdk.ThreadSleep {
  startTime = 00:15:32.689
  duration = 1.05 s
  time = 1.00 s
  eventThread = "main" (javaThreadId = 1)
  stackTrace = [
    java.lang.Thread.sleep(long)
    SleepOneSecondIntervals.main(String[]) line: 8
  ]
}
...

The sample StreamExternalEventsWithJcmdSample.java is similar to StreamExternalEventsWithAttachAPISample except it starts Flight Recorder for SleepOneSecondIntervals with the Attach API. With this API, the sample runs the command jcmd <PID> JFR.start with the PID of SleepOneSecondIntervals:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Paths;
import java.util.Properties;

import com.sun.tools.attach.VirtualMachine;

import jdk.jfr.consumer.EventStream;

public class StreamExternalEventsWithJcmdSample {
    public static void main(String... args) throws Exception {
        if (args[0] == null) {
            System.err.println("Requires PID of process as argument");
            System.exit(1);
        }

        String pid = args[0];

        Process p = Runtime.getRuntime().exec(
            "jcmd " + pid + " JFR.start");

        printOutput(p);

        // Wait for jcmd to start the recording
        Thread.sleep(1000);

        VirtualMachine vm = VirtualMachine.attach(pid);
        Properties props = vm.getSystemProperties();
        String repository = props.getProperty("jdk.jfr.repository");
        System.out.println("jdk.jfr.repository: " + repository);

        try (EventStream es = EventStream
            .openRepository(Paths.get(repository))) {
            System.out.println("Found repository ...");
            es.onEvent("jdk.ThreadSleep", System.out::println);
            es.start();
        }
    }

    private static void printOutput(Process proc) throws IOException {
        BufferedReader stdInput = new BufferedReader(
            new InputStreamReader(proc.getInputStream()));

        BufferedReader stdError = new BufferedReader(
            new InputStreamReader(proc.getErrorStream()));

        // Read the output from the command
        System.out.println(
            "Here is the standard output of the command:\n");
        String s = null;
        while ((s = stdInput.readLine()) != null) {
            System.out.println(s);
        }

        // Read any errors from the attempted command
        System.out.println(
            "Here is the standard error of the " + "command (if any):\n");
        while ((s = stdError.readLine()) != null) {
            System.out.println(s);
        }
    }
}

Compile SleepOneSecondIntervals.java and StreamExternalEventsWithJcmdSample.java. Then run SleepOneSecondIntervals with this command:

java -XX:StartFlightRecording SleepOneSecondIntervals

It prints output similar to the following:

Started recording 1. No limit specified, using maxsize=250MB as default.

Use jcmd 5100 JFR.dump name=1 filename=FILEPATH to copy recording data to file.
Process ID: 5100
Sleeping for 1s...
Sleeping for 1s...
Sleeping for 1s...
...

Note the PID for SleepOneSecondIntervals (in this example, it's 5100). While this sample is running, in a new command shell, run StreamExternalEventsWithJcmdSample with this command.

java StreamExternalEventsWithJcmdSample <PID of SleepOneSecondIntervals>

It prints output similar to StreamExternalEventsWithAttachAPISample.