Skip to content

niesfisch/java-code-tracer

Repository files navigation

Java Code Tracer (JCT) Maven Tests

JCT is a Java agent that instruments method calls at runtime and records executed call stacks. It helps answer one practical question in large systems: "Is this code path still used in real traffic?"

Table of Contents

What JCT Does

  • Instruments selected classes and methods via -javaagent
  • Captures call stacks and timestamps at runtime
  • Supports multiple output processors (file and UDP)
  • Lets you analyze runtime behavior without changing app code

Recommended Local Workflow: ELK

For local experimentation, the recommended setup is:

  -javaagent:jct.jar          Docker Compose
  ┌─────────────────┐     ┌───────────────────────────────────┐
  │   JCT Agent     │     │  Logstash :9999                   │
  │   Recorder      │────▶│    │                              │
  │   Stack         │     │    ▼                              │
  │   Processor     │     │  Elasticsearch  (jct-events-*)    │
  │  UDP / TCP      │     │    │                              │
  └─────────────────┘     │    ▼                              │
                          │  Kibana :5601                     │
                          └───────────────────────────────────┘

This gives you a fast feedback loop with searchable traces and a UI for exploration.

Start here:

Project Status

This project targets Java 8 bytecode and is currently focused on practical runtime tracing for legacy and monolithic applications.

Java Version

Minimum: Java 8

JCT is compiled against Java 8 (-source 8 -target 8) and intentionally uses no APIs beyond that level. This is a deliberate choice — the primary target is legacy and monolithic systems that are often stuck on older JVMs.

It runs fine on newer JVMs (11, 17, 21, …) without any changes.

Build

mvn clean package

The distributable agent jar is created at:

  • target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar

Configure

Create a local config file:

mkdir -p "$HOME/.jct"
cp doc/config-sample-file.yaml "$HOME/.jct/config-sample-file.yaml"

Notes:

  • Default config is loaded from src/main/resources/META-INF/config.yaml
  • If -Djct.config=... is set, custom config is merged with default config

Run an Application with JCT

java \
  -javaagent:"/path/to/java-code-tracer/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" \
  -Djct.loglevel=INFO \
  -Djct.config="$HOME/.jct/config-sample-file.yaml" \
  -Djct.logDir=/tmp/jct \
  -noverify \
  -jar /path/to/your-application.jar

Available Processors

  • de.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor
    • Example config: src/test/resources/integration/test-config-asyncfile.yaml
  • de.marcelsauer.profiler.processor.udp.AsyncUdpStackProcessor
    • Example config: src/test/resources/integration/test-config-asyncudp.yaml
  • de.marcelsauer.profiler.processor.tcp.AsyncTcpStackProcessor
    • Example config: src/test/resources/integration/test-config-asynctcp.yaml

Message Format

{
  "stack": [
    "de.example.Service.doWork()",
    "de.example.Repository.findById()"
  ],
  "timestampMillis": "1528120883697"
}
  • timestampMillis: timestamp in milliseconds when the recorded stack entry started
  • stack: ordered stack frames from entry to exit point

Logging

JCT writes logs to the directory configured with -Djct.logDir.

For more instrumentation details, increase log level:

-Djct.loglevel=DEBUG

Hello World Walkthrough

Use the sample loop jar in doc/helloworld-loop.jar:

java \
  -javaagent:"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" \
  -Djct.loglevel=INFO \
  -Djct.config="${PWD}/doc/config-sample-helloworld.yaml" \
  -Djct.logDir=/tmp/jct \
  -noverify \
  -jar "${PWD}/doc/helloworld-loop.jar"

Check agent logs:

cat /tmp/jct/jct_agent.log

Tools

Stack Formatter (tools/format_stack.py)

Pretty-prints a raw JCT stack array into an aligned, human-readable call sequence. Consecutive calls to the same class are grouped — package names are abbreviated.

Requires Python 3.9+, no dependencies.

# pipe the bracket string directly
echo '[a.b.Foo.bar(), a.b.Foo.baz()]' | python3 tools/format_stack.py

# from a file
python3 tools/format_stack.py stack.txt

# grab from clipboard (Linux)
xclip -o | python3 tools/format_stack.py

Example output:

   #  package        class                  method
  ──────────────────────────────────────────────────────
   1  o.b.s.u.ldap   LdapConnectionFactory  .initialize(LdapConnectionConfigurationDTO)
   2                                        .connect()
   3  o.b.s.u.ldap   LdapConnectionConfigurationDTO  .getLdapServer1()
  ...

ELK Integration Guide

For local Elasticsearch + Logstash + Kibana setup (Docker), UI access, data view setup, and log exploration, see the dedicated guide:

Similar Projects

IntelliJ JVM Options Example

Use the following IntelliJ Run/Debug VM options example when attaching JCT as a Java agent:

IntelliJ IDEA VM options example

License

MIT

For me :)

GIT_SSH_COMMAND='ssh -i ~/.ssh/niesfisch' git pull
GIT_SSH_COMMAND='ssh -i ~/.ssh/niesfisch' git push

About

JCT is a call graph generator that works via byte code instrumentation. it records the flow through your application to gather call statistics. It helps to analyze which code is still used in production and which code can be removed (e.g. for big monolithic legacy applications)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors