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.