Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Jfr Profiling

ASecurity

Profile Java/Kotlin processes using JFR (Java Flight Recorder). Covers Gradle task setup, CI integration, collapsed-stacks output for LLM analysis, flamegraph visualization, and bottleneck identification patterns.

8 stars
0 votes
0 copies
0 views
Added 9/20/2026
devopspythongojavakotlinbashspringdebugginggitperformance

Works with

cli

Security Analysis

A96/100
mediumUses curl or wget to download content

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add tstapler/dotfiles --skill jfr-profiling --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Jfr Profiling?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Jfr Profiling
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tstapler-jfr-profiling/badge)](https://www.skillsdirectory.com/skills/tstapler-jfr-profiling)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: jfr-profiling
description: Profile Java/Kotlin processes using JFR (Java Flight Recorder). Covers Gradle task setup, CI integration, collapsed-stacks output for LLM analysis, flamegraph visualization, and bottleneck identification patterns.
paths: "**/*.java,**/*.kt"
---

# JFR Profiling for Java/Kotlin Projects

End-to-end workflow: Gradle task → JFR → collapsed stacks (primary analysis format) → flamegraph (visualization).

## Format Strategy

| Format | Size vs JFR | Best for | Command |
|--------|-------------|----------|---------|
| **Collapsed stacks** | ~1-2% | LLM analysis, CI diffs, awk parsing | `jfrconv -o collapsed` |
| HTML flamegraph | ~5-10% | Interactive human exploration | `jfrconv` (default) |
| Differential flamegraph | ~5-10% | A/B comparison | `jfrconv --diff` |
| Raw JFR | 100% | Archive, future re-analysis | — |

**Use collapsed stacks for LLM analysis.** Format: `frame1;frame2;leaf N` — one line per unique call stack, count at end. No decoder needed.

---

## Step 1 — Gradle Task Setup

```kotlin
// build.gradle.kts
tasks.register<Test>("jvmTestProfile") {
    description = "Run benchmark under JFR"
    group = "verification"
    testClassesDirs = sourceSets["jvmTest"].output.classesDirs
    classpath = sourceSets["jvmTest"].runtimeClasspath
    filter { includeTestsMatching("*.GraphLoadTimingTest") }

    val jfrFile = layout.buildDirectory.file("reports/graph-load.jfr").get().asFile
    jvmArgs(
        "-XX:+FlightRecorder",
        "-XX:StartFlightRecording=filename=${jfrFile.absolutePath},settings=profile,dumponexit=true",
    )
    outputs.upToDateWhen { false }

    doLast {
        val collapsedFile = jfrFile.resolveSibling("graph-load.collapsed")
        val htmlFile = jfrFile.resolveSibling("flamegraph.html")
        // Auto-convert if jfrconv is on PATH
        val jfrconvPath = listOf("jfrconv", "/opt/homebrew/bin/jfrconv")
            .firstOrNull { cmd -> runCatching { ProcessBuilder("which", cmd).start().waitFor() == 0 }.getOrDefault(false) }
        if (jfrconvPath != null) {
            exec { commandLine(jfrconvPath, "--cpu", "-o", "collapsed", "$jfrFile", "$collapsedFile"); isIgnoreExitValue = true }
            exec { commandLine(jfrconvPath, "--cpu", "$jfrFile", "$htmlFile"); isIgnoreExitValue = true }
            println("Collapsed stacks: $collapsedFile")
        } else {
            println("Install async-profiler: brew install async-profiler")
        }
    }
}
```

Key flags:
- `settings=profile` — enables CPU sampling (vs `settings=default` which is minimal)
- `dumponexit=true` — writes the JFR on normal JVM exit, including test runner exit
- `outputs.upToDateWhen { false }` — Gradle must always re-run profiling tasks

---

## Step 2 — Install jfrconv

```bash
# macOS
brew install async-profiler

# Linux — download from https://github.com/async-profiler/async-profiler/releases
# then: export PATH="$HOME/async-profiler/bin:$PATH"
```

Manual conversion after a run:

```bash
# Collapsed stacks (primary — use this for analysis)
jfrconv --cpu -o collapsed graph-load.jfr graph-load.collapsed

# HTML flamegraph (visualization)
jfrconv --cpu graph-load.jfr flamegraph.html

# Allocation profile (heap pressure)
jfrconv --alloc -o collapsed graph-load.jfr alloc.collapsed

# Lock contention (thread starvation)
jfrconv --lock -o collapsed graph-load.jfr lock.collapsed
```

**Filtering frames:** `jfrconv` supports `--exclude REGEX` and `--include REGEX` at conversion time if you want to narrow the output. Avoid hardcoding library names (e.g. `kryo`) since those may be app dependencies you want to see. Use `--include "dev\.yourpackage"` to show only your code when you know exactly what you're looking for.

---

## Step 3 — Analyze Collapsed Stacks

### Read it directly

Collapsed stacks are plain text — read with the `Read` tool. Each line is:
```
pkg/ClassName.method;pkg/Caller.method;pkg/Root.method 1247
```
Count is the last token. Root of call tree is rightmost frame; leaf (hottest) is leftmost.

### Extract top frames (awk — no Python needed)

```bash
# Top leaf frames by self-sample count
awk '{n=$NF; sub(/ [0-9]+$/,"",n); split(n,a,";"); leaf=a[1]; count[leaf]+=$NF} END{for(f in count) print count[f],f}' \
  build/reports/graph-load.collapsed | sort -rn | head -20

# Top full stacks (raw, sorted by count)
sort -t' ' -k2 -rn build/reports/graph-load.collapsed | head -20

# Stacks containing a specific class (drill into a subsystem)
grep "GraphLoader" build/reports/graph-load.collapsed | sort -t' ' -k2 -rn | head -10
```

### Minimal Python alternative (when you need total-sample percentages)

```python
from collections import defaultdict
import sys

lines = open(sys.argv[1]).readlines()
total = sum(int(l.rsplit(" ", 1)[1]) for l in lines if l.strip())
by_leaf = defaultdict(int)
for line in lines:
    stack, _, count = line.strip().rpartition(" ")
    leaf = stack.split(";")[0]
    by_leaf[leaf] += int(count)

for count, frame in sorted((-v, k) for k, v in by_leaf.items())[:20]:
    print(f"{100*-count/total:5.1f}%  {-count:6d}  {frame}")
```

---

## Step 4 — CI Integration

```yaml
# .github/workflows/profile.yml
jobs:
  profile:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { distribution: temurin, java-version: 17 }

      - name: Install async-profiler
        run: |
          wget -q https://github.com/async-profiler/async-profiler/releases/latest/download/async-profiler-linux-x64.tar.gz
          tar xf async-profiler-linux-x64.tar.gz
          echo "$PWD/async-profiler/bin" >> $GITHUB_PATH

      - run: ./gradlew jvmTestProfile --rerun-tasks

      - uses: actions/upload-artifact@v4
        with:
          name: profiling-${{ github.sha }}
          path: |
            kmp/build/reports/graph-load.collapsed
            kmp/build/reports/flamegraph.html
          retention-days: 30
```

**Store collapsed stacks as the primary CI artifact** — they're text, ~10-50 KB, and diffable between commits.

---

## Step 5 — A/B Comparison

```bash
# Download artifacts for two commits
# (GitHub CLI: gh run download <run-id> --name profiling-<sha>)

# Text diff — shows which stacks gained/lost samples
diff baseline.collapsed current.collapsed

# Differential flamegraph (red=regression, blue=improvement)
jfrconv --diff baseline.jfr current.jfr diff-flamegraph.html
# Or from collapsed stacks via Brendan Gregg's tools:
# difffolded.pl baseline.collapsed current.collapsed | flamegraph.pl > diff.svg
```

---

> For applying fixes after identifying hotspots, apply the `jvm-performance` skill.

## Step 6 — Interpreting Results

### Hotspot patterns and fixes

| Pattern in collapsed stacks | Diagnosis | Fix |
|-----------------------------|-----------|-----|
| `JDBC.connect;DriverManager.getConnection` in hot path | New connection per transaction | Connection pool (HikariCP) or persistent connection |
| `transaction;newTransaction` dominant (>15%) | Too many small transactions | Batch N operations into one `transaction {}` |
| `GarbageCollector` or `G1Young` > 10% | Heap pressure / allocation churn | Object pooling; reduce per-loop allocations |
| `AbstractQueuedSynchronizer.acquire` | Lock contention | Actor pattern; reduce lock scope |
| `KotlinJsonDecoder` / `ObjectInputStream` | Serialization on hot path | Lazy parse; cache deserialized objects |
| `EPoll.wait` / `Kryo` | Gradle test infra (IPC/sockets) | Ignore — filter your package namespace |

### Filter to app-only frames

```bash
# Exclude JDK/Gradle infrastructure; show only your packages
grep "dev\.stapler\|dev\.logseq" build/reports/graph-load.collapsed | \
  sort -t' ' -k2 -rn | head -30
```

Sample count in infra threads (EPoll, Kryo, JUnit engine) is expected. If they're < 5% of total samples, ignore them entirely.

---

## Quick Reference

| Goal | Command |
|------|---------|
| Run benchmark | `./gradlew jvmTestProfile` |
| Collapsed stacks | `jfrconv --cpu -o collapsed graph-load.jfr graph-load.collapsed` |
| HTML flamegraph | `jfrconv --cpu graph-load.jfr flamegraph.html` |
| Alloc profile | `jfrconv --alloc -o collapsed graph-load.jfr alloc.collapsed` |
| Lock profile | `jfrconv --lock -o collapsed graph-load.jfr lock.collapsed` |
| Top leaf frames | `awk '...' graph-load.collapsed \| sort -rn \| head -20` (see Step 3) |
| A/B diff | `jfrconv --diff before.jfr after.jfr diff.html` |

---

## Related Skills

| Skill | When to apply |
|-------|--------------|
| `jvm-performance` | Fix allocation, GC, lock, or JIT bottlenecks found in the profile |
| `code-spring-boot` | Spring Boot-specific performance patterns and optimizations |
| `github-actions-debugging` | Debug CI failures in profiling or benchmark jobs |
| `mermaid-diagrams` | Visualize call graphs or bottleneck flows as diagrams |

## Common Pitfalls

- **`settings=default`** — CPU sampling is off by default; always use `settings=profile`
- **Forgetting `--rerun-tasks` in CI** — Gradle caches the task result; profiling is always stale
- **Reading EPoll/Kryo as app code** — these are Gradle test-infra threads; filter by your package
- **Using HTML for LLM analysis** — the cpool encoding requires a decoder; use collapsed stacks instead
- **p50 only** — always collect p50 + p95 + p99; tail latency reveals starvation

Attribution

tstaplertstapler
View sourceMore from tstapler →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Terraform Module Library

Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

393431 votes

sematext-otel

Wire a service's OpenTelemetry output to Sematext Cloud. Walks through region, App-type, instrumentation flow (managed OTLP endpoint vs Sematext Agent), and signal selection (traces/metrics/logs), then produces the exact env-var block and points at a runnable reference example in this repo. Invoke when instrumenting a new app for Sematext.

01 votes

Deployment Patterns

Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications. Use when setting up deployment infrastructure or planning releases.

2459130 votes

Babysit

Watch a pull request or review cycle until it is ready to merge. Use when asked to babysit, monitor, or keep checking PR comments, reviews, and CI until all actionable issues are resolved.

929660 votes

V7 Roster

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

805540 votes
View all in devops →