Replaying a recording

Requirements

A Linux environment with access to the recording file. Usually it’s simplest if this is the same environment in which the application was recorded. However if the recording environment is transient - e.g. a container or on-demand cloud instance - it will be easier to replay in a more interactive environment such as a physical machine, a VM, WSL2, or a scratch container running a supported Linux distribution.

  • JDK that matches the major version of the JRE used to make the recording.

  • The ability to copy the LiveRecorder for Java software into this environment.

  • (Optionally) a copy in this environment of the same build of jar files as supplied to Java at record time.

  • IntelliJ IDEA running in the same Linux environment as your application, or running on a separate Windows, macOS or Linux machine. Refer to Getting started if you don’t already have the Time Travel Debug for Java plugin installed and setup in IntelliJ.

  • If the environment is a remote machine, or a VM or container running on your local machine:
    A port (default 9000) opened into the environment for IntelliJ to connect over.

Replaying

The Time Travel Debug for Java IntelliJ plugin provides Run/Debug configurations for use with LiveRecorder for Java:

Windows or macOS

  • Open your project in IntelliJ if it’s not already open.

  • Under File › Project Structure… › SDKs check that the JDK version matches the version of the JRE used to make the recording.

  • From the Run menu choose Edit Configurations and press the + button at the top left of the Run/Debug Configurations dialog.

  • Add a Remote JVM Debug configuration

    • Specify the machine running your application as the Host. Use localhost if you’re replaying on Linux, or in a VM or container running on your local machine.

    • Adjust the Port setting to match the port that you opened up into the remote environment above.

    • Ignore the recommended Command line arguments for remote JVM as we are using our own replay tool which emulates a remote JDWP agent

In the replay environment:

  • Unzip the file LR4J-Replay-*.zip. This creates a directory named lr4j.

  • To replay a recording:

/path/to/lr4j/lr4j_replay --input /path/to/recording.undo

Note

Replace /path/to with the full path to the directory where you unzipped the archive.

/path/to/lr4j/lr4j_replay --input /path/to/recording.undo --port PORT

Linux

As an alternative to the above, on Linux there is a Run Configuration which can be used to launch the replay session directly.

  • First, set Settings › Tools › LiveRecorder › Replay tool directory to the location of the unzipped LR4J-Replay-*.zip directory.

  • From the Run menu, choose Edit Configurations and press the + button at the top left of the Run/Debug Configurations dialog.

  • Add a Undo Replay Recording configuration.

  • Set Recording file to the name of the recording you wish to replay.

Refer to Time travel debugging in IntelliJ for next steps.

Extracting classfiles

Sometimes it can be the case that classfiles are present in a recording of which IntelliJ has no knowledge. In order to be able to step into these classes and set breakpoints, IntelliJ needs a jar containing the classfiles. You can extract the classes from the recording using the following command:

/path/to/lr4j/lr4j_extract -i /path/to/recording.undo -o /path/to/output.jar

The resulting output.jar will contain all the classfiles that were loaded during the recording

Profiling commands

The following commands are run offline on a recording.

Method profile

If you suspect that a particular method sometimes takes longer than expected to complete you can run a method profiling command on the recording to display a table showing both the bbcount and the elapsed wall-clock time for each call to that method. The command takes the form:

/path/to/lr4j/lr4j_method_profile -i /path/to/recording.undo -o /path/to/output.csv -m class.method [-v]

where output.csv is csv version of the output. The -v option produces verbose output including the console log. The bbcount can then be entered into the log jump panel to go directly to the start of that method in IntelliJ. For example:

/path/to/lr4j/lr4j_profile -i /path/to/recording.undo -o /path/to/output.csv -m org.springframework.samples.petclinic.customers.web.OwnerResource.findOwner

┌───────────────────────────────────────────────────────────────────────────┬────────────┬─────────────┬───────┬────────────┐
│location                                                                   │start time  │start bbcount│bbcount│milliseconds│
├───────────────────────────────────────────────────────────────────────────┼────────────┼─────────────┼───────┼────────────┤
│org.springframework.samples.petclinic.customers.web.OwnerResource.findOwner│14:01:00.403│7524030      │77816  │109         │
├───────────────────────────────────────────────────────────────────────────┼────────────┼─────────────┼───────┼────────────┤
│org.springframework.samples.petclinic.customers.web.OwnerResource.findOwner│14:02:09.777│32426403     │22216  │4           │
└───────────────────────────────────────────────────────────────────────────┴────────────┴─────────────┴───────┴────────────┘

Recording profile

You can generate a profile showing where the cpu time is spent in a recording by running the following command:

/path/to/lr4j/lr4j_profile [-i|--input <filename>] [-s|--samples <num_samples>] [-l|--min <min_bbcount>] [-h|--max <max_bbcount>]
-i <filename>, --input <filename>

the name of the recording file

-s <num_samples>, --samples <num_samples>

the number of samples to take

-l <min_bbcount>, --min <min_bbcount>

the minimum bbcount to start sampling (default 1)

-h <max_bbcount>, --max <max_bbcount>

the maximum bbcount to start sampling (default the end of the recording)

The command works by dividing the bbcount range (default the whole recording) by the given number of samples and obtains the Java stack at that point in the recording. It then outputs a tree summarising the most commonly called methods. There is also a -v option which produces verbose output containing every stack sampled together with its bbcount. The bbcount could then be entered in the Log Jump panel to investigate further. Note that the recording only contains cpu activity so if for instance a large part of the recorded program was waiting on I/O those stacks would not show.

There are many other types of post-failure analysis that can be run on a recording including Groovy scripting. Contact Undo support for more details.

Groovy scripting

You can create groovy scripts to perform post-failure analysis on a recording. As an example let’s consider trying to reproduce the steps a user would take debugging the ConcurrentModificationExceptionTest demo. The source for the demo is located in /path/to/lr4j/demos/java/io/undo/test/ConcurrentModificationExceptionTest.java. First we record the test:

java -XX:-Inline -XX:TieredStopAtLevel=1 -XX:UseAVX=2 -agentpath:/path/to/lr4j_agent_<arch>.so=save_on=always,output=recording.undo io.undo.test.ConcurrentModificationExceptionTest

Then start a replay session:

/path/to/lr4j/lr4j_replay --input recording.undo

Now we can run a groovy script that will interact with the replay session:

/path/to/lr4j/lr4j_groovy --key license_key --script /path/to/lr4j/demos/groovy/ConcurrentModificationException.groovy

where license_key should be a binary version of your license key (if you only have it in Base64 encoding you can use base64 to convert it to binary)

This is what the script contains:

setBreakpoint("io.undo.test.ConcurrentModificationExceptionTest", 27)
expectBreakpoint("io.undo.test.ConcurrentModificationExceptionTest", 27)

printLocation("breakpoint")

i = getLocalVariable("i")
printf("i = %s\n\n", i)

setReverse(true)

setExceptionBreakpoint("java.util.ConcurrentModificationException")
expectExceptionBreakpoint("java.util.ConcurrentModificationException")

printLocation("exception breakpoint")

setWatchpoint("java.util.AbstractList", "modCount")
expectWatchpoint("java.util.AbstractList", "modCount")

printLocation("watchpoint")

This will typically result in the following output:

hit breakpoint in main at bbcount 14392413

Stacktrace:
    io.undo.test.ConcurrentModificationExceptionTest.main:27

i = 247

hit exception breakpoint in main at bbcount 14387961

Stacktrace:
    java.util.ArrayList$Itr.checkForComodification:1013
    java.util.ArrayList$Itr.next:967
    io.undo.test.ConcurrentModificationExceptionTest.main:18

hit watchpoint in MutatorThread at bbcount 14372311

Stacktrace:
    java.util.ArrayList.add:466
    io.undo.test.ConcurrentModificationExceptionTest$MutatorThread.run:60
    java.lang.Thread.run:840

What it does is:

  • sets a breakpoint at line 27 of the test then resumes and checks that we have reached the expected location

  • prints the stack trace at that location

  • prints the local variable i

  • sets reverse mode for future operations

  • sets an exception breakpoint for ConcurrentModificationException, reverse resumes to it and checks we have reached it

  • prints the stack trace at that location

  • sets a field watchpoint on modCount in the class java.util.AbstractList, reverse resumes to it and checks we have reached it

  • prints the stack trace at that location

See GroovyScript Class Documentation for a full description of the available methods.