| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
JCT is a Java agent that records real method call stacks while your application is running.
If you work in a legacy app and ask things like "Can we remove this?" or "Is this code path still hit in production traffic?", JCT gives you hard runtime evidence instead of guesses.
In older monoliths and large shared platforms, static code search is usually not enough.
JCT helps you reduce that risk by showing what was actually executed.
When you attach JCT via -javaagent, it does this:
This is especially useful before deleting legacy code, splitting modules, or tightening APIs.
If you just want first results quickly:
Minimum commands:
mvn clean package
mkdir -p "$HOME/.jct"
cp doc/config-sample-application-file.yaml "$HOME/.jct/config-sample-file.yaml"
java \
-javaagent:"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" \
-Djct.config="$HOME/.jct/config-sample-file.yaml" \
-Djct.logDir=/tmp/jct \
-noverify \
-jar /path/to/your-application.jarThen check /tmp/jct logs and your configured processor output.
For local experimentation, the recommended setup is:
-javaagent:jct.jar docker compose up
+-------------------+ +-----------------------------------+
| App + JCT Agent | | Logstash :9999 |
| Recorder/Processor|----->| -> Elasticsearch (jct-events-*)|
+-------------------+ | -> Kibana :5601 |
+-----------------------------------+
This gives you a fast feedback loop: run traffic, query traces, validate code paths.
Start here:
If you want stronger analytics, faster aggregations over large event volumes, or a lighter-weight alternative to Elasticsearch, JCT also ships a second Docker Compose stack based on ClickHouse + Vector + Grafana.
-javaagent:jct.jar
+-------------------+
| App + JCT Agent | /tmp/stacks/jct_*.log
| File Processor |--+
+-------------------+ |
| Vector (tail + forward)
+--> ClickHouse: jct_raw
|
Materialized View
|
jct_events (parsed)
|
Grafana :5601
(pre-built dashboard)
Start here: doc/README-ClickHouse-Grafana.md
Both stacks run locally via docker compose and need no cloud setup.
| Criterion | ELK (Elastic + Kibana) | ClickHouse + Grafana |
|---|---|---|
| Setup effort | Medium — three containers, index pattern setup in Kibana UI | Medium — three containers, datasource and dashboard auto-provisioned |
| Query style | KQL / Lucene (full-text search focused) | SQL (aggregation and analytics focused) |
| Best for | Searching for specific stack occurrences, filtering by text | Aggregating, counting, trending over high volumes |
| Event volume | Good up to low millions; indexing is memory-heavy | Excellent for large volumes; columnar storage compresses well |
| Ad-hoc exploration | Kibana Discover is fast for browsing raw events | ClickHouse Play UI or Grafana Explore for SQL queries |
| Dashboard UX | Kibana Lens (good) | Grafana (good, pre-built panels included) |
| Transport | UDP or TCP → Logstash | File processor → Vector tails log files |
| When to pick | You are already familiar with Kibana, or want full-text search | You want SQL analytics, hot-frame counts, or handle high traffic |
Rule of thumb:
This project targets Java 8 bytecode and is currently focused on practical runtime tracing for legacy and monolithic applications.
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.
mvn clean packageThe distributable agent jar is created at:
Create a local config file:
mkdir -p "$HOME/.jct"
cp doc/config-sample-file.yaml "$HOME/.jct/config-sample-file.yaml"Notes:
JCT uses Java regex patterns to decide which classes are instrumented.
Config keys:
How matching works:
Pattern behavior:
Example:
classes:
included:
- ^de.marcelsauer.*
- ^com.example.legacy.*
excluded:
- ^de.marcelsauer.generated.*
- ^com.example.legacy.internal.*In this example:
Practical tips:
JCT has a built-in safety list of package prefixes that are never instrumented, even if your include regex would match them. This prevents self-instrumentation and reduces crash risk in JVM/logging/bytecode internals.
Current hard-skipped prefixes (JVM slash notation):
Source of truth: de.marcelsauer.profiler.transformer.Transformer (HARD_SKIPPED_PREFIXES).
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.jarJCT currently ships with three output processors.
de.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor
de.marcelsauer.profiler.processor.udp.AsyncUdpStackProcessor
de.marcelsauer.profiler.processor.tcp.AsyncTcpStackProcessor
Quick chooser:
On busy systems, writing every single captured stack can create huge event volume. If your main question is only "Was this path hit at least once?", you can report only new stacks.
Processor flags:
Example: report only new stacks (good for high-traffic legacy systems)
processor:
fullQualifiedClass: de.marcelsauer.profiler.processor.udp.AsyncUdpStackProcessor
udpHost: localhost
udpPort: 9999
enableStackDeduplication: true
dedupResetIntervalMillis: 300000Example: report all stacks (full event stream)
processor:
fullQualifiedClass: de.marcelsauer.profiler.processor.file.AsyncFileWritingStackProcessor
stackFolderName: /tmp/stacks/
enableStackDeduplication: false{
"stack": [
"de.marcelsauer.sample.ClassA.methodA_1()",
"de.marcelsauer.sample.ClassB.methodB_2()",
"de.marcelsauer.sample.ClassB.methodB_3()",
"de.marcelsauer.sample.ClassC.methodC_4()",
"de.marcelsauer.sample.ClassD.methodD_5()",
"de.marcelsauer.sample.ClassE.methodE_6()",
"de.marcelsauer.sample.ClassF.methodF_7()",
"de.marcelsauer.sample.ClassG.methodG_8()",
"de.marcelsauer.sample.ClassH.methodH_9()",
"de.marcelsauer.sample.ClassI.methodI_10()",
"de.marcelsauer.sample.ClassJ.methodJ_11()"
],
"timestampMillis": "1528120883697"
}JCT writes logs to the directory configured with -Djct.logDir.
For more instrumentation details, increase log level:
-Djct.loglevel=DEBUGHint: DEBUG is useful when you want to see what happens behind the scenes (for example class matching, instrumentation attempts, and skipped classes).
Build the sample app jar to doc/java-code-tracer-sample-application.jar:
cd sample_application && mvn clean package && cd ..Then run it with the agent from the repository root (java-code-tracer):
java \
-javaagent:"${PWD}/target/java-code-tracer-1.0-SNAPSHOT-jar-with-dependencies.jar" \
-Djct.loglevel=INFO \
-Djct.config="${PWD}/doc/config-sample-application-file.yaml" \
-Djct.logDir=/tmp/jct \
-noverify \
-jar "${PWD}/doc/java-code-tracer-sample-application.jar"Note on class patterns: JCT emits a stack trace only when the outermost tracked frame returns. If you include a class whose method runs forever (like main() or an endless loop driver), no traces will ever be written. Use a pattern that targets the inner chain classes — see doc/config-sample-application-file.yaml for an example using ^de.marcelsauer.sample.Class.*.
Check agent logs:
cat /tmp/jct/jct_agent.logCheck captured stacks:
cat /tmp/stacks/jct_*Pretty-prints a raw JCT stack array into an aligned, human-readable call sequence.
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.pyExample output:
# package class method ───────────────────────────────────────────────────────────────────────── 1 de.marcelsauer.sample ClassA .methodA_1() 2 de.marcelsauer.sample ClassB .methodB_2() 3 .methodB_3() 4 de.marcelsauer.sample ClassC .methodC_4() 5 de.marcelsauer.sample ClassD .methodD_5() 6 de.marcelsauer.sample ClassE .methodE_6() 7 de.marcelsauer.sample ClassF .methodF_7() 8 de.marcelsauer.sample ClassG .methodG_8() 9 de.marcelsauer.sample ClassH .methodH_9() 10 de.marcelsauer.sample ClassI .methodI_10() 11 de.marcelsauer.sample ClassJ .methodJ_11()
For local Elasticsearch + Logstash + Kibana setup (Docker), UI access, data view setup, and log exploration, see the dedicated guide:
Use the following IntelliJ Run/Debug VM options example when attaching JCT as a Java agent:
| Back | FazBrowse Home | New Git URL |