{"id":16690263,"url":"https://github.com/btraceio/jafar","last_synced_at":"2026-04-27T10:00:31.833Z","repository":{"id":252189291,"uuid":"839604818","full_name":"btraceio/jafar","owner":"btraceio","description":"Experimental JFR parser","archived":false,"fork":false,"pushed_at":"2026-04-23T23:06:38.000Z","size":5600,"stargazers_count":49,"open_issues_count":3,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-04-24T01:38:14.516Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/btraceio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2024-08-08T00:45:37.000Z","updated_at":"2026-04-23T23:03:04.000Z","dependencies_parsed_at":"2025-06-30T13:27:37.657Z","dependency_job_id":"889c82dd-746b-416e-8a4d-97abeebd403a","html_url":"https://github.com/btraceio/jafar","commit_stats":null,"previous_names":["jbachorik/jafar","btraceio/jafar"],"tags_count":58,"template":false,"template_full_name":null,"purl":"pkg:github/btraceio/jafar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btraceio%2Fjafar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btraceio%2Fjafar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btraceio%2Fjafar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btraceio%2Fjafar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/btraceio","download_url":"https://codeload.github.com/btraceio/jafar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btraceio%2Fjafar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32331305,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T23:26:28.701Z","status":"online","status_checked_at":"2026-04-27T02:00:06.769Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-12T15:50:55.194Z","updated_at":"2026-04-27T10:00:31.574Z","avatar_url":"https://github.com/btraceio.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JAFAR\n\nFast, modern JFR (Java Flight Recorder) parser for the JVM with a small, focused API.\n\n**Status**: Early public release (v0.11.0) - API may evolve based on feedback. See [CHANGELOG.md](CHANGELOG.md) for details.\n\nJAFAR provides both typed (interface-based) and untyped (Map-based) APIs for parsing JFR recordings with minimal ceremony. It emphasizes performance, low allocation, and ease of use.\n\n## Requirements\n- Java 21+\n\n## Build\n1) Fetch binary resources: `./get_resources.sh`\n2) Build all modules: `./gradlew build`\n3) Build shadow JARs (optional): `./gradlew shadowJar`\n\n## Quick start (typed API)\nDefine a Java interface per JFR type and annotate with `@JfrType`. Methods correspond to event fields; use `@JfrField` to map differing names and `@JfrIgnore` to skip fields.\n\n```java\nimport io.jafar.parser.api.*;\nimport java.nio.file.Paths;\n\n@JfrType(\"custom.MyEvent\")\npublic interface MyEvent { // no base interface required\n  String myfield();\n}\n\ntry (TypedJafarParser p = JafarParser.newTypedParser(Paths.get(\"/path/to/recording.jfr\"))) {\n  HandlerRegistration\u003cMyEvent\u003e reg = p.handle(MyEvent.class, (e, ctl) -\u003e {\n    System.out.println(e.myfield());\n    long pos = ctl.stream().position(); // current byte position while in handler\n    // ctl.abort(); // optionally stop parsing immediately without throwing\n  });\n  p.run();\n  reg.destroy(p); // deregister\n}\n```\n\nNotes:\n- Handlers run synchronously on the parser thread. Keep work small or offload.\n- Exceptions thrown from a handler stop parsing and propagate from `run()`.\n- Call `ctl.abort()` inside a handler to stop parsing early without an exception.\n\n## Untyped API\nReceive events as `Map\u003cString, Object\u003e` with nested maps/arrays when applicable.\n\n```java\nimport io.jafar.parser.api.*;\nimport java.nio.file.Paths;\n\ntry (UntypedJafarParser p = JafarParser.newUntypedParser(Paths.get(\"/path/to/recording.jfr\"))) {\n  HandlerRegistration\u003c?\u003e reg = p.handle((type, value) -\u003e {\n    if (\"jdk.ExecutionSample\".equals(type.getName())) {\n      // You can retrieve the value by providing 'path' -\u003e \"eventThread\", \"javaThreadId\"\n      Object threadId = Values.get(value, \"eventThread\", \"javaThreadId\");\n      // You can also get the value conveniently typed - for primitive values you need to use the boxed type in the call\n      long threadIdLong = Values.as(value, Long.class, \"eventThread\", \"javaThreadId\");\n      // use threadId ...\n    }\n  });\n  p.run();\n  reg.destroy(p);\n}\n```\n\n### Complex and array values in untyped events\n- **ComplexType**: Complex fields may appear either inline as `Map\u003cString, Object\u003e` or as a wrapper implementing `io.jafar.parser.api.ComplexType` (e.g., constant-pool backed references). Use `getValue()` on a `ComplexType` to obtain the resolved `Map\u003cString, Object\u003e`.\n- **ArrayType**: When a field is an array, the value implements `io.jafar.parser.api.ArrayType`. Use `getType()` to inspect the array class (e.g., `int[].class`, `Object[].class`) and `getArray()` to access the underlying Java array.\n\nExamples:\n\n```java\nimport io.jafar.parser.api.*;\nimport java.util.Map;\n\ntry (UntypedJafarParser p = JafarParser.newUntypedParser(Paths.get(\"/path/to/recording.jfr\"))) {\n  p.handle((type, value) -\u003e {\n    // ComplexType: constant-pool backed references (e.g., eventThread)\n    Map\u003cString, Object\u003e thread = Values.as(value, Map.class, \"eventThread\").orElse(null);\n    if (thread != null) {\n      System.out.println(\"thread id=\" + thread.get(\"javaThreadId\") + \", name=\" + thread.get(\"name\"));\n    }\n\n    // ArrayType: arrays of primitives, Strings, maps, or ComplexType elements\n    Object framesVal = Values.get(value, \"stackTrace\", \"frames\");\n    // You can also reference the array elements directly\n    Object firstFrame = Values.get(value, \"stackTrace\", \"frames\", 0);\n    if (framesVal instanceof ArrayType at) {\n      Object arr = at.getArray();\n      if (arr instanceof Object[] objs) {\n        for (Object el : objs) {\n          if (el instanceof ComplexType cpx) {\n            Map\u003cString, Object\u003e m = cpx.getValue();\n            // use fields from the resolved element\n          } else if (el instanceof Map) {\n            Map\u003cString, Object\u003e m = (Map\u003cString, Object\u003e) el; // inline complex value\n          } else {\n            // primitive wrapper or String\n          }\n        }\n      } else if (arr instanceof int[] ints) {\n        for (int i : ints) { /* ... */ }\n      } else if (arr instanceof long[] longs) {\n        for (long l : longs) { /* ... */ }\n      }\n    }\n  });\n  p.run();\n}\n```\n\n## Build-Time Handler Generation\n\nJAFAR now supports **build-time handler generation** via annotation processor, providing massive performance benefits for production applications.\n\n### Why Build-Time Generation?\n\n**Benchmark Results:**\n- **85% less memory allocation** (35.5 MB/sec vs 237.2 MB/sec)\n- **Eliminates GC collections** (0 vs 3 GC pauses per benchmark)\n- **Equivalent throughput** (no performance penalty)\n- **Predictable latency** (no GC jitter)\n\n[→ See Full Performance Report](doc/performance/PerformanceReport.md)\n\n### How It Works\n\n1. **Compile-time**: Annotation processor scans `@JfrType` interfaces and generates:\n   - Handler implementation classes\n   - Factory classes with thread-local caching\n   - ServiceLoader registration (META-INF/services)\n\n2. **Runtime**: Parser auto-discovers factories via ServiceLoader, handlers are reused via thread-local cache\n\n### Usage\n\n#### 1. Add Annotation Processor Dependency\n\n**Gradle:**\n```gradle\ndependencies {\n    implementation 'io.btrace:jafar-parser:0.11.0'\n    annotationProcessor 'io.btrace:jafar-processor:0.11.0'\n}\n```\n\n**Maven:**\n```xml\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003eio.btrace\u003c/groupId\u003e\n        \u003cartifactId\u003ejafar-parser\u003c/artifactId\u003e\n        \u003cversion\u003e0.11.0\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n            \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n            \u003cconfiguration\u003e\n                \u003cannotationProcessorPaths\u003e\n                    \u003cpath\u003e\n                        \u003cgroupId\u003eio.btrace\u003c/groupId\u003e\n                        \u003cartifactId\u003ejafar-processor\u003c/artifactId\u003e\n                        \u003cversion\u003e0.11.0\u003c/version\u003e\n                    \u003c/path\u003e\n                \u003c/annotationProcessorPaths\u003e\n            \u003c/configuration\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\n#### 2. Define Event Interfaces (Top-Level Only)\n\n```java\n// Must be top-level interfaces (not nested/inner classes)\n@JfrType(\"jdk.ExecutionSample\")\npublic interface JFRExecutionSample {\n    @JfrField(\"startTime\")\n    long startTime();\n\n    @JfrField(\"sampledThread\")\n    JFRThread sampledThread();\n}\n\n@JfrType(\"java.lang.Thread\")\npublic interface JFRThread {\n    @JfrField(\"javaThreadId\")\n    long javaThreadId();\n\n    @JfrField(\"javaName\")\n    String javaName();\n}\n```\n\n**Note:** Annotation processor only processes **top-level interfaces**. Nested/inner classes with `@JfrType` are skipped and use runtime generation instead.\n\n#### 3. Parse Events (Factories Auto-Discovered)\n\n```java\ntry (TypedJafarParser p = JafarParser.newTypedParser(Paths.get(\"/path/to/recording.jfr\"))) {\n    // Factories automatically discovered via ServiceLoader - no registration needed!\n\n    // Handle events (uses thread-local cached handlers)\n    p.handle(JFRExecutionSample.class, (event, ctl) -\u003e {\n        JFRThread thread = event.sampledThread();\n        if (thread != null) {\n            System.out.println(\"Thread: \" + thread.javaName());\n        }\n    });\n\n    p.run();\n}\n```\n\n**That's it!** The annotation processor generates factories and registers them via ServiceLoader. No manual registration required.\n\n### Runtime Generation (Default)\n\nIf you don't register factories, JAFAR falls back to **runtime bytecode generation** (existing behavior):\n\n```java\ntry (TypedJafarParser p = JafarParser.newTypedParser(Paths.get(\"/path/to/recording.jfr\"))) {\n    // No factory registration - handlers generated at runtime via ASM\n    p.handle(JFRExecutionSample.class, (event, ctl) -\u003e {\n        // Handler generated on first use, cached globally\n        System.out.println(\"Event: \" + event.startTime());\n    });\n\n    p.run();\n}\n```\n\n### When to Use Build-Time Generation\n\n✅ **Use build-time generation when:**\n- Processing large JFR files or streams (millions of events)\n- Memory allocation is a bottleneck\n- Running in memory-constrained environments (containers)\n- GC pauses affect latency SLAs\n- Deploying to GraalVM native images\n- Event types are known at compile time\n\n✅ **Use runtime generation when:**\n- Building JFR analysis tools (unknown event types)\n- Rapid prototyping and exploration\n- Processing arbitrary JFR recordings\n- No build-time configuration desired\n\n### Performance Impact\n\nFor processing **1 million ExecutionSample events:**\n\n| Metric | Runtime Generation | Build-Time Generation | Benefit |\n|--------|-------------------|----------------------|---------|\n| Total Allocations | ~223 GB | ~37 GB | **-186 GB** |\n| GC Collections | ~600-800 | ~50-100 | **-750 GC pauses** |\n| GC Pause Time | ~2-3 seconds | ~200-300ms | **-2.7 seconds** |\n| Throughput | ~189k events/sec | ~187k events/sec | Equivalent |\n\n[→ Full Benchmark Results](doc/performance/BuildTimeBenchmarks.md)\n\n## Core API overview\n\nFor the architecture of the `parser` module, see the [Parser Architecture](parser/ARCHITECTURE.md).\n- `JafarParser`\n  - `newTypedParser(Path)` / `newUntypedParser(Path)`: start a session.\n  - `withParserListener(ChunkParserListener)`: observe low-level parse events (advanced, see below).\n  - `run()`: parse and invoke registered handlers.\n- `TypedJafarParser`\n  - `handle(Class\u003cT\u003e, JFRHandler\u003cT\u003e) -\u003e HandlerRegistration\u003cT\u003e`\n  - Static `open(String|Path[, ParsingContext])` are also available, but prefer `JafarParser.newTypedParser(Path)`.\n- `UntypedJafarParser`\n  - `handle(UntypedJafarParser.EventHandler) -\u003e HandlerRegistration\u003c?\u003e`\n  - Static `open(String|Path[, ParsingContext])` also available.\n- Data wrappers\n  - `ArrayType`: wrapper around arrays. `getType()` returns the array class; `getArray()` returns the backing Java array.\n  - `ComplexType`: wrapper around complex values. `getValue()` resolves to a `Map\u003cString, Object\u003e`. Note that some complex fields may be provided inline as a `Map` without a wrapper.\n- `ParsingContext`\n  - `create()`: build a reusable context.\n  - `newTypedParser(Path)` / `newUntypedParser(Path)`: create parsers bound to the shared context.\n  - `uptime()`: cumulative processing time across sessions using the context.\n- `Control`\n  - `stream().position()`: current byte position while a handler executes.\n  - `abort()`: stop parsing immediately (no exception thrown).\n  - `chunkInfo()`: chunk metadata with `startTime()`, `duration()`, `size()`, and `convertTicks(long, TimeUnit)`.\n    - Why `convertTicks(...)`? JFR records many time values in chunk-relative ticks. Converting on demand avoids creating `Instant`/`Duration` objects for every event, minimizing allocation and GC pressure when a scalar value suffices. Convert only when needed and to the unit you need.\n\n### Typed runtime (JDK support)\n- The typed parser defines small, generated classes at runtime. It automatically picks the best available strategy for the running JDK:\n  - JDK 15+: hidden classes via `MethodHandles.Lookup#defineHiddenClass` (fastest, unloadable)\n  - JDK 9–14: `MethodHandles.Lookup#defineClass(byte[])` (good)\n  - JDK 8: `sun.misc.Unsafe#defineAnonymousClass` (compatible; slightly heavier)\n- Selection is automatic based on capability probes; no flags required. Enable debug logs to see the chosen strategy.\n\n### Multi‑Release JAR (parser)\n- The `parser` artifact is a Multi‑Release JAR:\n  - Base classes target Java 8 for broad compatibility.\n  - Java 21 overrides live under `META-INF/versions/21` and restore faster implementations (e.g., zero‑copy `ByteBuffer` slicing, `Arrays.equals` range checks, `Files.writeString`, etc.).\n- On Java 21+, the JVM loads these optimized classes automatically. On older JVMs, the Java 8 fallbacks are used.\n- Annotations\n  - `@JfrType(\"\u003cfq.type\u003e\")`: declare the JFR type an interface represents.\n  - `@JfrField(\"\u003cjfrField\u003e\", raw = false)`: map differing names or request raw representation.\n  - `@JfrIgnore`: exclude a method from mapping.\n\n## Advanced usage\n- Reusing context across many recordings\n\n```java\nParsingContext ctx = ParsingContext.create();\ntry (TypedJafarParser p = ctx.newTypedParser(Paths.get(\"/path/to.a.jfr\"))) {\n  p.handle(MyEvent.class, (e, ctl) -\u003e {/*...*/});\n  p.run();\n}\ntry (TypedJafarParser p = ctx.newTypedParser(Paths.get(\"/path/to.b.jfr\"))) {\n  p.handle(MyEvent.class, (e, ctl) -\u003e {/*...*/});\n  p.run();\n}\nSystem.out.println(\"uptime(ns)=\" + ctx.uptime());\n```\n\n- Early termination with Control\n\n```java\nAtomicInteger seen = new AtomicInteger();\ntry (TypedJafarParser p = JafarParser.newTypedParser(Paths.get(\"/path/to.jfr\"))) {\n  HandlerRegistration\u003cJFRJdkExecutionSample\u003e reg =\n      p.handle(JFRJdkExecutionSample.class, (e, ctl) -\u003e {\n        if (seen.incrementAndGet() \u003e= 1000) {\n          ctl.abort(); // stop without throwing\n        }\n      });\n  p.run();\n  reg.destroy(p);\n}\n```\n\n- Converting JFR ticks to time units\n\n```java\n// Some fields are expressed in JFR ticks. Convert them only when needed.\ntry (UntypedJafarParser p = JafarParser.newUntypedParser(Paths.get(\"/file.jfr\"))) {\n  p.handle((type, value, ctl) -\u003e {\n    long ticksObj = value.get(\"startTime\"); // example field holding ticks\n    long nanos = ctl.chunkInfo().convertTicks(n.longValue(), TimeUnit.NANOSECONDS);\n    // Use nanos directly, or wrap as Instant only when necessary\n    Instant startTs = ctl.chunkInfo().startTime().plusNanos(nanos);\n    // use the startTs instant ...\n  });\n  p.run();\n}\n```\n\n- Observing parse lifecycle (low-level)\n\n```java\nimport io.jafar.parser.internal_api.ChunkParserListener;\nimport io.jafar.parser.internal_api.metadata.MetadataEvent;\n\np.withParserListener(new ChunkParserListener() {\n  @Override public boolean onMetadata(ParserContext c, MetadataEvent md) {\n    // inspect metadata per chunk\n    return true; // continue\n  }\n}).run();\n```\n\n## Gradle plugin: generate Jafar type interfaces\nPlugin id: `io.btrace.jafar-gradle-plugin`\n\nAdds task `generateJafarTypes` and wires it to `compileJava`. It can generate interfaces for selected JFR types from either the current JVM metadata (default) or a `.jfr` file.\n\n**Generated class naming**: The generator includes the full namespace in generated interface names to avoid collisions. For example:\n- `jdk.ExecutionSample` → `JFRJdkExecutionSample`\n- `datadog.ExecutionSample` → `JFRDatadogExecutionSample`\n- `jdk.gc.HeapSummary` → `JFRJdkGcHeapSummary`\n\nThis ensures that events with the same simple name but different namespaces generate distinct interfaces.\n\n```gradle\nplugins {\n  id 'io.btrace.jafar-gradle-plugin' version '0.11.0'\n}\n\nrepositories {\n  mavenCentral()\n  mavenLocal()\n  maven { url \"https://oss.sonatype.org/content/repositories/snapshots/\" }\n}\n\ngenerateJafarTypes {\n  // Optional: use a JFR file to derive metadata; otherwise JVM runtime metadata is used\n  inputFile = file('/path/to/recording.jfr')\n\n  // Optional: where to generate sources (default: build/generated/sources/jafar/src/main)\n  outputDir = project.file('src/main/java')\n\n  // Optional: do not overwrite existing files (default: false)\n  overwrite = false\n\n  // Optional: filter event types by name (closure gets fully-qualified JFR type name)\n  eventTypeFilter {\n    it.startsWith('jdk.') \u0026\u0026 it != 'jdk.SomeExcludedType'\n  }\n\n  // Package for generated interfaces (default: io.jafar.parser.api.types)\n  targetPackage = 'com.acme.jfr.types'\n}\n```\n\nYou can also provide the input file via a project property: `-Pjafar.input=/path/to/recording.jfr`.\n\n## Tools: Scrubbing sensitive fields in a `.jfr`\n`io.jafar.tools.Scrubber` can scrub selected string fields in-place while copying to a new file.\n\nExample: scrub the value of `jdk.InitialSystemProperty.value` when `key == 'java.home'`.\n\n```java\nimport static io.jafar.tools.Scrubber.scrubFile;\nimport io.jafar.tools.Scrubber.ScrubField;\nimport java.nio.file.Paths;\n\nscrubFile(Paths.get(\"/in.jfr\"), Paths.get(\"/out-scrubbed.jfr\"),\n  clz -\u003e {\n    if (clz.equals(\"jdk.InitialSystemProperty\")) {\n      return new ScrubField(\"key\", \"value\", (k, v) -\u003e \"java.home\".equals(k));\n    }\n    return null; // no scrubbing for other classes\n  }\n);\n```\n\n## Demo\nBuild and run the demo application:\n\n```shell\n# First you need to publish parser, tools and plugin to local maven\ncd demo\n./build.sh\njava -jar build/libs/jafar-demo-all.jar [jafar|jmc|jfr|jfr-stream] /path/to/recording.jfr\n```\n\nOn an M1 and a ~600MiB JFR, the Jafar parser completes in ~1s vs ~7s with JMC (anecdotal). The stock `jfr` tool may OOM when printing all events.\n\n## JFR Shell\n\nJAFAR includes `jfr-shell`, an interactive CLI for exploring and analyzing JFR files with a powerful query language. See **[jfr-shell/README.md](jfr-shell/README.md)** for features and installation.\n\n### Key Features\n\n- **Interactive REPL** with intelligent tab completion\n- **JfrPath query language** for filtering, projection, and aggregation\n- **Flame graphs**: interactive HTML flame graphs from stack trace events\n- **Scripting support**: record, save, and replay analysis workflows with variable substitution\n- **Event decoration** for correlating and joining events (time-based and key-based)\n- **Multiple output formats**: table and JSON\n- **Multi-session support**: work with multiple recordings simultaneously\n- **Non-interactive mode**: execute queries from command line for scripting/CI\n- **Pluggable backends**: Jafar parser (full-featured) and JDK JFR API (limited, broadly compatible)\n\n### Quick Example\n\n```bash\n# Install via JBang (easiest)\njbang app install jfr-shell@btraceio\n\n# Open and analyze a recording\njfr-shell recording.jfr\n\njfr\u003e events/jdk.ExecutionSample | groupBy(thread/name)\njfr\u003e events/jdk.FileRead | top(10, by=bytes)\njfr\u003e events/jdk.ExecutionSample | flamegraph()\n\n# Event decoration: correlate samples with lock waits\njfr\u003e events/jdk.ExecutionSample | decorateByTime(jdk.JavaMonitorWait, fields=monitorClass)\n```\n\nSee **[Event Decoration and Joining](doc/cli/Tutorial.md#event-decoration-and-joining)** for advanced correlation and joining capabilities.\n\n## MCP Server\n\nJAFAR includes an MCP (Model Context Protocol) server that enables AI agents like Claude to analyze JFR recordings. See **[jfr-mcp/README.md](jfr-mcp/README.md)** for details.\n\n### Quick Install\n\n```bash\ncurl -Ls https://raw.githubusercontent.com/btraceio/jafar/main/jfr-mcp/install.sh | bash\n```\n\nThis installs [JBang](https://www.jbang.dev) (if needed) and the `jfr-mcp` command in one step.\n\n### Claude Code\n\n```bash\nclaude mcp add jafar -- jbang jfr-mcp@btraceio --stdio\n```\n\n### Claude Desktop\n\n```json\n{\n  \"mcpServers\": {\n    \"jafar\": {\n      \"command\": \"jbang\",\n      \"args\": [\"jfr-mcp@btraceio\", \"--stdio\"]\n    }\n  }\n}\n```\n\n## Heap Dump Analysis (NEW)\n\nJAFAR now supports **heap dump (HPROF) analysis** using the same interactive shell. Query objects, classes, and GC roots with the HdumpPath query language.\n\n### Quick Example\n\n```bash\n# Open a heap dump\njfr-shell dump.hprof\n\njfr\u003e objects | count\n| count   |\n|---------|\n| 1923456 |\n\njfr\u003e objects | groupBy(class, agg=count) | top(10, count)\n| class                      | count   |\n|---------------------------|---------|\n| java.util.HashMap$Node    | 249,734 |\n| java.lang.String          | 238,750 |\n| java.lang.Object[]        | 156,234 |\n\njfr\u003e objects/java.lang.String[shallow \u003e 1KB] | stats(shallow)\n| count | sum       | min  | max    | avg    |\n|-------|-----------|------|--------|--------|\n| 1,234 | 2,456,789 | 1024 | 65,536 | 1,991  |\n\njfr\u003e gcroots | groupBy(type) | sortBy(count desc)\n| type         | count |\n|-------------|-------|\n| JAVA_FRAME  | 2,345 |\n| THREAD_OBJ  | 1,234 |\n```\n\n### Key Features\n\n- **Object queries**: Filter by class, size, type hierarchy\n- **Class analysis**: Instance counts, memory footprint\n- **GC root inspection**: Thread roots, JNI references, stack frames\n- **Aggregations**: count, sum, stats, groupBy, top\n- **Memory analysis**: pathToRoot, retentionPaths, retainedBreakdown, dominators\n- **Leak detection**: Built-in detectors for common leak patterns\n- **Heap diff**: `join(session=id)` compares two heap dumps side-by-side\n- **Size units**: Use `1KB`, `1MB`, `1GB` in predicates\n- **Instanceof support**: Query all implementations of interfaces\n\n### HdumpPath Query Language\n\n```\n# Objects by class\nobjects/java.lang.String | top(10, shallow)\n\n# Include subclasses\nobjects/instanceof/java.util.Map | groupBy(class)\n\n# Filter with predicates\nobjects[shallow \u003e 1MB and class ~ \"com.myapp.*\"]\n\n# Class metadata\nclasses[instanceCount \u003e 1000] | sortBy(instanceCount desc)\n\n# GC roots\ngcroots/THREAD_OBJ | select(type, object, threadSerial)\n```\n\nSee **[doc/hdump-shell-quickstart.md](doc/hdump-shell-quickstart.md)** for quick start and **[doc/hdumppath.md](doc/hdumppath.md)** for complete reference.\n\n## Documentation\n\n### JFR Shell\n- **[jfr-shell/README.md](jfr-shell/README.md)** - Interactive JFR analysis tool\n- **[doc/cli/Architecture.md](doc/cli/Architecture.md)** - Architecture overview with diagrams\n- **[doc/cli/Tutorial.md](doc/cli/Tutorial.md)** - Complete JFR Shell tutorial with event decoration\n- **[doc/cli/Scripting.md](doc/cli/Scripting.md)** - Scripting guide: automate analysis workflows\n- **[doc/cli/ScriptExecution.md](doc/cli/ScriptExecution.md)** - Script execution tutorial\n- **[doc/cli/CommandRecording.md](doc/cli/CommandRecording.md)** - Command recording tutorial\n- **[doc/cli/JFRPath.md](doc/cli/JFRPath.md)** - JfrPath query language reference\n- **[doc/cli/Backends.md](doc/cli/Backends.md)** - Backend plugin guide and TCK (Technology Compatibility Kit)\n- **[doc/cli/BackendQuickstart.md](doc/cli/BackendQuickstart.md)** - Build a custom backend in 10 minutes\n\n### MCP Server\n- **[jfr-mcp/README.md](jfr-mcp/README.md)** - MCP server overview and quick install\n- **[doc/mcp/Tutorial.md](doc/mcp/Tutorial.md)** - Full MCP server tutorial\n- **[doc/mcp/JBANGUsage.md](doc/mcp/JBANGUsage.md)** - JBang installation and usage\n\n### Heap Dump Analysis\n- **[doc/hdump-shell-quickstart.md](doc/hdump-shell-quickstart.md)** - Quick start guide for heap dump analysis\n- **[doc/cli/hdump-shell-tutorial.md](doc/cli/hdump-shell-tutorial.md)** - Complete heap dump analysis tutorial\n- **[doc/hdumppath.md](doc/hdumppath.md)** - HdumpPath query language reference\n\n### General\n- **[CHANGELOG.md](CHANGELOG.md)** - Version history and release notes\n- **[LIMITATIONS.md](LIMITATIONS.md)** - Known limitations and workarounds\n- **[PERFORMANCE.md](PERFORMANCE.md)** - Performance benchmarks and tuning tips\n- **[CONTRIBUTING.md](CONTRIBUTING.md)** - How to contribute to JAFAR\n- **[SECURITY.md](SECURITY.md)** - Security policy and vulnerability reporting\n\n## Contributing\n\nContributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\nTo report security vulnerabilities, see [SECURITY.md](SECURITY.md) (do not create public issues).\n\n## License\nApache 2.0 (see `LICENSE`).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbtraceio%2Fjafar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbtraceio%2Fjafar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbtraceio%2Fjafar/lists"}