Argus Harness

May 8, 2026 · View on GitHub

argus harness <pid> runs a continuous, trend-aware health watch on a target JVM. It samples on a fixed interval, runs the existing 12 doctor health rules plus four trend rules on every tick, and emits a single severity-ranked session report at the end.

The harness is observation-only. It does not mutate the JVM. Findings produce recommendations and JVM-flag suggestions; the user decides whether to act.

When to use it vs. argus doctor

argus doctorargus harness
Single snapshot
Time-window analysis
Catches leaks (sustained heap growth)partial
Catches GC regression vs. earlier in window
Catches thread-pool leakspartial (high count only)
Exit-code semantics0/1/20/1/2
Output formatrich box / JSONrich box / JSON / file

If the user is asking "is my JVM healthy right now?", run doctor. If they're asking "what is happening to my JVM over time?", run harness.

Usage

argus harness <pid>                                # default: profile=deep, duration=30m
argus harness <pid> --profile=quick                # 2s tick, 1m default duration
argus harness <pid> --profile=deep --duration=15m
argus harness <pid> --interval=5s --duration=2m    # explicit, overrides profile
argus harness <pid> --out=/tmp/session.json        # also write JSON to file
argus harness <pid> --format=json                  # emit JSON only (no live ticks)

Subcommand --help prints a usage card.

Profiles

ProfileIntervalDefault durationUse case
quick2 s1 minDemos, fast spot checks, CI smoke.
deep10 s30 minReal investigation; default.

--interval and --duration always win over the profile defaults.

Trend rules

The harness adds four rules on top of the doctor set. Each takes the entire retained sample window and returns zero or more Findings.

HeapGrowthLeakRule (CRITICAL)

Linear regression on heapUsed over time. Fires when:

  • ≥ 5 samples,
  • R² ≥ 0.85 (samples are well-explained by a straight line), and
  • slope ≥ 1 MB/min.

This is the canonical leak signature. A typical warm-up that levels off won't trigger it because R² drops fast once the curve plateaus.

GcOverheadTrendRule (WARNING)

Splits the window in half and compares average gcOverheadPercent. Fires when:

  • ≥ 6 samples,
  • second half ≥ 2× first half, and
  • second half ≥ 2 % absolute.

Catches GC overhead trending up before the doctor's static threshold trips.

ThreadGrowthRule (WARNING)

Compares threadCount between first and last sample, and fits a line for confirmation. Fires when:

  • ≥ 5 samples,
  • last − first ≥ 50 threads, and
  • regression slope > 0 (sustained, not noise).

Almost always means an unbounded executor or connection pool.

PauseTrendRule (WARNING)

Same shape as GcOverheadTrendRule but on maxRecentPauseMs. Fires when:

  • ≥ 6 samples,
  • second half ≥ 2× first half, and
  • second half ≥ 50 ms absolute.

Surfaces pause-time SLO drift the static MaxPauseRule only sees instantaneously. Skipped on the remote-jcmd path when pause data is not populated.

Output

Rich (default)

Header banner with PID / profile / duration / tick count, severity counters (Nx critical, Nx warning, Nx info), then one block per deduplicated finding with the rule's detail line, recommendations, and a "(fired N ticks)" suffix when it triggered repeatedly. A trailing "Suggested JVM Flags" panel collects every suggestedFlags value across findings.

JSON (--format=json or programmatic via --out=<path>)

Stable schema:

{
  "pid": 39113,
  "profile": "deep",
  "startTimeMs": 1715000000000,
  "endTimeMs": 1715001800000,
  "durationMs": 1800000,
  "sampleCount": 180,
  "exitCode": 1,
  "counts": { "critical": 0, "warning": 2, "info": 0 },
  "findings": [
    {
      "severity": "WARNING",
      "category": "GC",
      "title": "GC overhead is trending up",
      "detail": "Average GC overhead rose from 0.40% to 1.10% (2.7x) over the harness window",
      "hits": 12,
      "recommendations": [
        "Capture allocation profile: argus profile <pid> --event=alloc --duration=30",
        "Inspect recent GC log if available: argus gclog <gc.log>",
        "Run argus gcwhy <pid> for a per-event explanation of the worst recent pause"
      ],
      "suggestedFlags": []
    }
  ]
}

hits is the per-rule fire count across the session — a leak rule that triggered every tick still appears once but with a high hits value.

Live ticks

Without --format=json, the harness prints one dim line per tick:

  tick   3  t+24s     heap  41.8%  gc 0.42%  thr   29  -> 0C 1W 0I

That is (tick, elapsed, heap%, GC overhead, thread count, → critical/warning/info from this tick).

Exit codes

CodeMeaning
0No findings — harness window was healthy.
1One or more WARNING findings.
2One or more CRITICAL findings.

Useful for CI and runbooks: argus harness <pid> --duration=2m || alert "JVM not healthy".

Out of scope

The harness intentionally does not:

  • mutate the JVM (no vmset, gcrun, heapdump triggers — those remain the user's call);
  • store data on disk beyond the optional --out=<file> snapshot (in-memory rolling window only);
  • monitor more than one PID at a time;
  • call into LLMs to generate explanations (it uses pattern-matching rules, not free-form text).
  • argus doctor <pid> — single-snapshot equivalent.
  • argus gcwhy <pid> — explain the worst recent pause in plain language.
  • argus gcscore <gc.log> — A–F GC health grade from a log file.
  • argus heapdump <pid> — capture a heap dump for offline analysis.