Getting Started
June 9, 2026 · View on GitHub
This guide will help you get up and running with Project Argus in minutes.
Prerequisites
Before you begin, ensure you have the following installed:
- Java 11+ for CLI commands (JDK required)
- Java 17+ for Agent + Dashboard (MXBean polling mode — GC, CPU, Memory)
- Java 21+ for full features (JFR streaming, Virtual Threads, Flame Graph, Allocation Tracking)
- Gradle 8.4+ (optional) - Only needed if building from source
Verify Java Version
java -version
# Should output: openjdk version "21.x.x" or higher
Installation
Option 1: One-line Install (Recommended)
curl -fsSL https://raw.githubusercontent.com/rlaope/argus/master/install.sh | bash
This does three things:
- Downloads
argus-agent.jarandargus-cli.jarto~/.argus/ - Creates a wrapper script at
~/.argus/bin/argus(auto-finds Java 11+ and runs the CLI jar) - Adds
~/.argus/binto your PATH in~/.zshrc(or~/.bashrc)
After installation, restart your terminal (or run source ~/.zshrc), then:
# Just type argus — it works anywhere
argus ps
argus histo 12345
argus report 12345
# Install a specific version
curl -fsSL https://raw.githubusercontent.com/rlaope/argus/master/install.sh | bash -s -- v1.5.0
Option 2: Manual Download
# Download JARs from GitHub Releases
ARGUS_VERSION=1.5.0
curl -LO https://github.com/rlaope/argus/releases/download/v${ARGUS_VERSION}/argus-agent.jar
curl -LO https://github.com/rlaope/argus/releases/download/v${ARGUS_VERSION}/argus-cli-${ARGUS_VERSION}-all.jar
Option 3: Build from Source
git clone https://github.com/rlaope/argus.git
cd argus
./gradlew build
./gradlew :argus-cli:fatJar
# JARs:
# argus-agent/build/libs/argus-agent-1.5.0.jar
# argus-cli/build/libs/argus-cli-1.5.0-all.jar
Quick Start with Sample Project
The easiest way to see Argus in action is to run the included sample project.
Step 1: Build Everything
./gradlew build
Step 2: Run the Sample with Argus
# Basic virtual thread demo
./gradlew :samples:virtual-thread-demo:runWithArgus
# Metrics demo with GC/CPU activity (recommended for testing dashboard)
./gradlew :samples:virtual-thread-simulation:runMetricsDemo
Step 3: Open the Dashboard
Open your browser and navigate to: http://localhost:9202/
The dashboard displays two tabs:
- Virtual Threads: Thread events, active threads, pinning analysis
- JVM Overview: CPU, GC, heap, flame graph, profiling, contention, correlation
Step 4: Use the CLI
# First-time setup (choose language)
argus init
# Diagnose any running JVM (no agent required)
argus ps # List JVM processes
argus histo <pid> # Heap object histogram
argus threads <pid> # Thread dump summary
argus gc <pid> # GC statistics
argus gcutil <pid> # GC generation utilization
argus heap <pid> # Heap memory usage
argus sysprops <pid> # System properties
argus vmflag <pid> # VM flags (view/set)
argus nmt <pid> # Native memory tracking
argus classloader <pid> # Class loader hierarchy
argus jfr <pid> start # Flight Recorder control
argus info <pid> # JVM information
# Real-time monitoring (requires agent)
argus top
# Or run directly
java -jar argus-cli/build/libs/argus-cli-1.5.0-all.jar --help
The CLI auto-detects the best data source: Argus agent (HTTP) when available, JDK tools (jcmd) as fallback. Use --source=agent|jdk to override.
Step 5: Enable Flame Graph
# Run with profiling enabled for flame graph data
./gradlew :samples:virtual-thread-simulation:runMetricsDemoFull
The flame graph appears in the dashboard under JVM Overview tab. It shows which methods consume the most CPU time.
Step 6: View the Output
You'll see output like this:
_____
/ _ \_______ ____ __ __ ______
/ /_\ \_ __ \/ ___\| | \/ ___/
/ | \ | \/ /_/ > | /\___ \
\____|__ /__| \___ /|____//____ >
\/ /_____/ \/
JVM Observability Platform vdev
[Argus] Initializing JFR streaming engine...
[Argus] Agent initialized successfully
[Argus] Ring buffer size: 65536
[Argus] JFR streaming started
============================================================
Virtual Thread Demo - Argus Monitoring Example
============================================================
[Demo 1] Basic Virtual Threads
----------------------------------------
Task 0 started on VirtualThread[#31]/runnable@ForkJoinPool-1-worker-1
Task 1 started on VirtualThread[#33]/runnable@ForkJoinPool-1-worker-7
...
All basic tasks completed.
[Demo 3] Thread Pinning (synchronized blocks)
----------------------------------------
WARNING: This demonstrates thread pinning - avoid in production!
Task 0 acquired lock (PINNED to carrier thread)
...
[Argus] PINNED: thread=61, carrier=-1, duration=204333917ns
[Argus] PINNED: thread=63, carrier=-1, duration=201776666ns
[Argus] PINNED: thread=62, carrier=-1, duration=204995125ns
...
[Argus] Shutting down...
[Argus] JFR streaming stopped. Total events processed: 29
Understanding the Output
Event Types
Virtual Thread Events
| Event | Description | When it Occurs |
|---|---|---|
VIRTUAL_THREAD_START | Thread created | Thread.startVirtualThread() or executor submit |
VIRTUAL_THREAD_END | Thread terminated | Virtual thread completes or is interrupted |
VIRTUAL_THREAD_PINNED | Thread pinned to carrier | Critical! synchronized block or native call |
VIRTUAL_THREAD_SUBMIT_FAILED | Submission failed | Executor rejected task |
GC Events
| Event | Description |
|---|---|
GC_PAUSE | Garbage collection occurred |
GC_HEAP_SUMMARY | Heap usage snapshot |
CPU Events
| Event | Description |
|---|---|
CPU_LOAD | JVM and system CPU utilization |
Reading PINNED Events
[Argus] PINNED: thread=61, carrier=-1, duration=204333917ns
│ │ │ │
│ │ │ └─ Duration pinned (~204ms)
│ │ └─ Carrier thread ID
│ └─ Virtual thread ID
└─ Event type
Why Pinning Matters:
Pinning occurs when a virtual thread cannot be unmounted from its carrier thread. This defeats the purpose of virtual threads and can cause performance issues.
Common causes:
synchronizedblocks (useReentrantLockinstead)- Native method calls
- JNI operations
Using Argus with Your Application
Basic Usage
java -javaagent:~/.argus/argus-agent.jar \
-jar your-application.jar
# Or using the helper command
java -javaagent:$(argus-agent --path) \
-jar your-application.jar
With Full Profiling
java -javaagent:~/.argus/argus-agent.jar \
-Dargus.profiling.enabled=true \
-Dargus.contention.enabled=true \
-jar your-application.jar
With OTLP Export
java -javaagent:~/.argus/argus-agent.jar \
-Dargus.otlp.enabled=true \
-Dargus.otlp.endpoint=http://localhost:4318/v1/metrics \
-jar your-application.jar
Example: Spring Boot Application
java -javaagent:~/.argus/argus-agent.jar \
-Dargus.profiling.enabled=true \
-jar myapp.jar \
--spring.threads.virtual.enabled=true
Sample Project Details
The sample project (samples/virtual-thread-demo) demonstrates four scenarios:
Demo 1: Basic Virtual Threads
// Creates 5 virtual threads that sleep for 100ms
for (int i = 0; i < 5; i++) {
Thread.startVirtualThread(() -> {
Thread.sleep(100);
});
}
What to observe: VIRTUAL_THREAD_START and VIRTUAL_THREAD_END events.
Demo 2: Concurrent HTTP Requests
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// Each HTTP request runs on its own virtual thread
executor.submit(() -> httpClient.send(request, ...));
}
What to observe: Multiple virtual threads handling I/O concurrently.
Demo 3: Thread Pinning
synchronized (lock) {
Thread.sleep(200); // Pinning occurs here!
}
What to observe: VIRTUAL_THREAD_PINNED events with duration.
Warning: Avoid
synchronizedwith virtual threads. UseReentrantLockinstead:lock.lock(); try { Thread.sleep(200); // No pinning! } finally { lock.unlock(); }
Demo 4: High Throughput
// Creates 1000 virtual threads rapidly
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1000).forEach(i ->
executor.submit(() -> computeTask())
);
}
What to observe: Argus handles high event rates efficiently.
Interpreting Results
Healthy Application
[Argus] JFR streaming stopped. Total events processed: 2000
- Many START/END events
- Zero or few PINNED events
- No SUBMIT_FAILED events
Application with Issues
[Argus] PINNED: thread=100, carrier=5, duration=500000000ns
[Argus] PINNED: thread=101, carrier=5, duration=500000000ns
[Argus] PINNED: thread=102, carrier=5, duration=500000000ns
- Frequent PINNED events indicate synchronization issues
- Long durations (>100ms) suggest blocking operations in synchronized blocks
- Same carrier thread repeatedly pinned suggests a contention hotspot
Actions to Take
| Observation | Action |
|---|---|
| Frequent PINNED events | Replace synchronized with ReentrantLock |
| Long PINNED durations | Move blocking operations outside synchronized blocks |
| SUBMIT_FAILED events | Increase executor capacity or check for resource exhaustion |
Next Steps
- Configuration Guide - Learn about all configuration options
- Architecture Overview - Understand how Argus works
- Troubleshooting - Solutions for common issues
- Sample Project - Explore the demo code
Java Version Compatibility
| Feature | Java 11+ | Java 17+ | Java 21+ |
|---|---|---|---|
| CLI (71 commands) | ✅ | ✅ | ✅ |
| Dashboard & Web UI | — | ✅ | ✅ |
| GC Analysis | CLI only | ✅ MXBean | ✅ JFR |
| Virtual Thread Monitoring | — | — | ✅ JFR |
| Flame Graph | — | — | ✅ JFR |
| Micrometer Metrics | — | ✅ | ✅ |
| Spring Boot Starter | — | ✅ | ✅ |
Native Binary (GraalVM)
Argus ships a GraalVM native-image build of the CLI. Its sweet spot is offline analysis — point it at a GC log or a heap dump and it runs as a single self-contained executable, with instant startup and no JVM (not even a JDK) needed on the host:
argus gclog gc.log # GC log analysis + tuning advice
argus gcscore gc.log # A–F GC health score
argus heapanalyze app.hprof # heap-dump histogram + insights
This is also where the fast startup pays off most — in scripts or watch loops
the JVM's warm-up cost would otherwise dominate.
When a release succeeds, pre-built binaries are attached to the release:
argus-linux-amd64— Linux x86_64argus-macos-aarch64— macOS Apple Silicon
The
native-imageworkflow runs after a release is published. If those two assets are missing from a given tag, the native build failed for that tag — use theargus-cli-*-all.jarinstead.
Build from source with GraalVM 21+ installed (the bundled
META-INF/native-image/ reachability metadata is applied automatically):
./gradlew :argus-cli:nativeImage
./argus-cli/build/native/argus --help
Live-target commands
The native binary can also inspect a running JVM, but with caveats — reach for the JAR if you need the full command set against a live process:
- Works: commands that shell out to the JDK tools (
jcmd,jstack,jstat,jps) —gc,heap,threads,threaddump, and friends. Note these still require those JDK tools to be installed on the host, so the "no JDK needed" benefit applies to the offline analyzers above, not here. - JAR only: commands that attach into the target JVM via the Attach API
(
com.sun.tools.attach.VirtualMachine). SubstrateVM ships nolibattach, so they fail withUnsatisfiedLinkError: No attach in java.library.path(the native binary now reports this cleanly instead of crashing). Run them through the JAR:java -jar argus-cli-*-all.jar <command>.
| Commands | Mechanism | Native | JAR |
|---|---|---|---|
gc gcutil gccause gcnew gcrun heap histo metaspace threaddump threads deadlock classloader classstat compiler compilerqueue info ps env sysprops vmflag buffers finalizer perfcounter dynlibs stringtable symboltable sc nmt | jcmd / jstack / jstat subprocess | ✅ | ✅ |
gclog gclogdiff gcscore gcwhy heapanalyze and other file analyzers | file parsing (no target attach) | ✅ | ✅ |
mbean spring pool g1 zgc threads --top | JMX over the Attach API | ❌ | ✅ |
instrument | dynamic Java agent (ByteBuddy) | ❌ | ✅ |