--- name: analyze-jfr description: "Analyses a JVM profile held by a running Jeffrey Microscope — CPU, wall-clock, allocation, lock contention, trace latency, and the machine underneath: garbage collection, safepoints, JIT compilation, threads, native memory, the container and the JVM's configuration. Starts from the catalogue or from a .jfr file Jeffrey has not seen yet. Use whenever the user asks why something is slow, where the time goes, what is allocating, why GC pauses are long, what is pausing the JVM, what the JIT compiler or deoptimisation is doing, which threads are burning CPU, why memory grows outside the heap, whether the container is throttling, what a JFR recording or flamegraph shows, or mentions a Jeffrey profile, a .jfr file or async-profiler output. For a heap dump or .hprof file, analyze-heap applies instead." allowed-tools: mcp__plugin_microscope_jeffrey__* mcp__jeffrey__* --- # Analysing a Jeffrey profile Jeffrey holds *profiles*: one analysed JFR recording or heap dump each. Nearly every tool reads. The few that do not create a profile or a cache, start work on the connector or in your editor, or stop work that was started — `recordings_` turns a recording *file* into a profile, and `operations_cancel` stops an import that was a mistake — and none of them changes an analysed profile. Tool names below omit the prefix your client puts in front of them — `mcp__plugin_microscope_jeffrey__` in Claude Code with the `microscope` plugin, `mcp__jeffrey__` in Codex or wherever the server is registered by hand as `jeffrey`, `mcp_jeffrey_` in Gemini CLI; the rest of the name is exact camelCase: `jfr_listTables`, not `jfr_list_tables`. ## 1. Get a `profileId` Every tool takes one, except `profiles_list` and the `recordings_`, `connector_` and `operations_` families, which are how a `profileId` comes to exist. `profiles_list` pages: a hundred profiles per call, with `nextCursor` and `hasMore` in the answer, so a catalogue past that size needs a second call carrying the cursor before "it is not there" is true. **The user named a file** (`target/app.jfr`, anything with a recording extension) — it may not be in Jeffrey yet. Call `recordings_analyzeFile` with the **absolute** path; `recordings_analyzeFile` returns the existing profile for a file with the same name and size as one already imported; after re-recording to the same path, pass `force=true`. The Jeffrey process opens that path, so the file has to be on the machine Jeffrey runs on — a container or a remote Jeffrey cannot see your working directory. A small recording is analysed inside the call and comes back with its `profileId`. A large one comes back with a status of `RUNNING` and an `operationId` — and no `recordingId`, because the copy may not have finished — and `operations_status(operationId)` follows the copy and the analysis until the `profileId` appears. Poll that rather than calling `recordings_analyzeFile` a second time to see whether it is done. `operations_cancel(operationId)` stops an import started by mistake. Operation ids live for an hour in Jeffrey's memory and do not survive a restart. **The recording is on the connector** — the user asked about an environment rather than a file ("production", "staging", "what recorded in the last hour"), and nothing local matches. Switch to the **analyze-connector** skill: it finds the session on the connector, pulls it in and comes back here with a `profileId`. **Otherwise** — `profiles_list`, pick the profile, then `profiles_summary` to learn what it can answer before asking for it. That one call carries what `profiles_get`, `profiles_features` and `profiles_samplerHealth` each carry a part of: `disabledFeatures` names what the profile lacks (traces exist only if the app ran Jeffrey's instrumentation; `HEAP_DUMP` there means no dump), `eventTypes` lists every recorded event type with its totals, and `topFindings` carries the auto-analysis findings when they have been computed. A profile whose `event source` column reads `HEAP_DUMP` is a heap dump: switch to the `analyze-heap` skill, because flamegraphs and traces do not apply to it. ## Ask before you analyse — the user picks what to spend on Orientation is cheap; analysis is not. `profiles_summary` is one compact call, while one flamegraph or trace export runs to tens of thousands of characters and a sweep of every family costs a dozen calls whose findings mostly go unread. So the user decides what is worth that, not habit: - **The question names an area** — "why is `/orders` slow", "what is allocating", "are the GC pauses long" — go straight to that area through the routing table below. Do only that area, and do not show the menu. - **The question is open** — "analyse this", "what is wrong", "review this recording" — call `profiles_summary`, put its `investigationAreas` to the user as below, and **stop**. Wait for the user's choice before any other call. A fired rule in `topFindings` and a `capabilityGaps` entry the question needs are worth one line each above the menu, because they are already paid for; neither is a reason to start work unasked. ### The menu is `investigationAreas` Jeffrey builds the menu, so do not assemble one yourself. Each entry has an `area`, its `group` (`CODE`, `WAITING`, `JVM`, `OVERALL` for a recording), a `title`, the `question` it answers, and its `availability`: - **`AVAILABLE`** — offer it, with its `weight` and, when `suggested` is true, its `evidence` (the rule that fired, or the share of samples that points there). `nextTools` are the calls that open it, ready to send. - **`NEEDS_PREPARATION`** — its data is there but has to be built first; its `nextTools` are that build (`heap_prepare`, slow). Only a heap dump's areas are ever in this state. - **`NOT_RECORDED`** — do not offer it. Name these in one line beneath the menu, with their `remedy` (`event=cpu`, `wall=10ms`, `alloc=512k`, `lock=10ms`, …) so the next recording can capture them. A menu of the heap groups (`HEAP_CONTENTS`, `HEAP_RETENTION`, `HEAP_WASTE`) means a heap dump: switch to `analyze-heap`, which puts that menu the same way. An empty `investigationAreas` means a perf-counters snapshot, which no tool reads yet: give the user its `uiLink`. **Put it as a structured question when your client has one.** In Claude Code that is `AskUserQuestion`: one multi-select question per group that has an `AVAILABLE` area, in group order, `header` the group in a word (Code, Waiting, JVM, Overall), one option per area with the `title` as its label and the description `weight · question` — with `· suggested: ` when suggested, and `· covered` when the ledger already has it. A group with a single available area joins another group's question when that one has room, since a question needs two options. Everything else — the fired rules, the gaps the question needs, the not-recorded line — is text above the question. **Above the question, a Pages table** — the click-through view of the profile, before anything is analysed. One row per area with data (`AVAILABLE` or `NEEDS_PREPARATION`), in this order: 1. **Traces**, whenever the profile has them — above the suggested areas too: a request's own time, span by span, is the page a user most often wants and the one no flamegraph replaces, and a ★ says where the evidence points, not which page comes first; 2. the suggested ones, marked ★; 3. the rest. Each row has its `title`, what its page shows in a few words, and `[↗ open]()` — for a profile with traces and a suggested lock area, it starts: ``` | Page | Shows | | |---|---|---| | Traces | Slow requests, span by span | [↗ open]() | | ★ Lock contention | Which locks held threads back | [↗ open]() | ``` A profile without traces says so in the not-recorded line, Traces named first with its `remedy`. Then `moreFlamegraphs` in one line — "Also graphable:" and each as `[