The Art of Debugging
stas00/the-art-of-debugging
Condensed debugging method and tool recipes for Unix, Python and PyTorch programs: crashes, hangs, segfaults, wrong output, CUDA OOM, NaN values and slowness.
Profile JMH benchmark regressions using async-profiler on a Hetzner CCX33 server.
The automated check flagged lines worth reading first. See the safety section below.
$ npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install JetBrains/youtrackdb profile-jmh-regressions --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/JetBrains/youtrackdb.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/profile-jmh-regressions .claude/skills/profile-jmh-regressions && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "profile-jmh-regressions" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressions into .claude/skills/profile-jmh-regressions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-jmh-regressions", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressionsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install JetBrains/youtrackdb profile-jmh-regressions --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JetBrains/youtrackdb.git skills-src && mkdir -p .agents/skills && cp -r skills-src/.claude/skills/profile-jmh-regressions .agents/skills/profile-jmh-regressions && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "profile-jmh-regressions" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressions into .agents/skills/profile-jmh-regressions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-jmh-regressions", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install JetBrains/youtrackdb profile-jmh-regressions --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JetBrains/youtrackdb.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/.claude/skills/profile-jmh-regressions .cursor/skills/profile-jmh-regressions && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "profile-jmh-regressions" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressions into .cursor/skills/profile-jmh-regressions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-jmh-regressions", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/JetBrains/youtrackdb.git --path .claude/skills/profile-jmh-regressions--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install JetBrains/youtrackdb profile-jmh-regressions --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JetBrains/youtrackdb.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/.claude/skills/profile-jmh-regressions .gemini/skills/profile-jmh-regressions && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "profile-jmh-regressions" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressions into .gemini/skills/profile-jmh-regressions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-jmh-regressions", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install JetBrains/youtrackdb profile-jmh-regressionsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/JetBrains/youtrackdb.git skills-src && mkdir -p .github/skills && cp -r skills-src/.claude/skills/profile-jmh-regressions .github/skills/profile-jmh-regressions && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "profile-jmh-regressions" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressions into .github/skills/profile-jmh-regressions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-jmh-regressions", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install JetBrains/youtrackdb profile-jmh-regressions --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/JetBrains/youtrackdb.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/.claude/skills/profile-jmh-regressions .opencode/skills/profile-jmh-regressions && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "profile-jmh-regressions" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-jmh-regressions into .opencode/skills/profile-jmh-regressions/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-jmh-regressions", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
profile-jmh-regressionsProfile JMH benchmark regressions using async-profiler on a Hetzner CCX33 server.
Profile Jmh Regressions is an agent skill from JetBrains/youtrackdb, published by the product's own GitHub organization. Profile JMH benchmark regressions using async-profiler on a Hetzner CCX33 server. Reads regressions from a PR benchmark comment, profiles both HEAD and BASE with collapsed-stack output, compares self-time and inclusive-time per method, and identifies root causes. Use when the user asks to profile regressions after a benchmark comparison run.
Its SKILL.md is about 6.4k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development, covering Performance optimization and Root cause analysis. The repository describes itself as: YouTrackDB is a general-use object-oriented graph database with storage format native to handle graph relations. YouTrackDB supports Gremlin queries and ACID transactions. YTDB… The licence is Apache-2.0.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 78d0f15. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
gitsshcurljavahcloudghrsyncapt-getpython3From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
HETZNER_S3_ACCESS_KEYHETZNER_S3_SECRET_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Profile Jmh Regressions loads about 6.4k tokens when it runs. Until then it costs about 92 tokens; SKILL.md has 1,968 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found patterns that need a careful read before installing.
- SSH key pair at `~/.ssh/id_ed25519`name "$KEY_NAME" --public-key-from-file ~/.ssh/id_ed25519.pubssh-keygen -f ~/.ssh/known_hosts -R <IP>Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from JetBrains/youtrackdb at commit 78d0f15, republished under its Apache-2.0 licence (© JetBrains). 1,968 words, ~6,432 tokens.
.claude/skills/profile-jmh-regressions/SKILL.md (or your agent's skills folder).Profile benchmark regressions found in a PR's JMH comparison comment. Provisions a Hetzner CCX33 server, deploys both HEAD and BASE code, runs async-profiler on the regressing benchmarks, performs differential analysis of collapsed stacks, and reports root causes.
hcloud CLI installed and authenticatedboto3 Python library installed locally~/.ssh/id_ed25519ldbc-jmh-compare.yml or manually)HETZNER_S3_ACCESS_KEY, HETZNER_S3_SECRET_KEY, HETZNER_S3_ENDPOINTRead the benchmark comparison comment from the PR on the current branch:
# Find the PR
gh pr list --head $(git rev-parse --abbrev-ref HEAD) --json number,url
# Read the latest benchmark comment (filtering by content to avoid noise)
gh pr view <NUMBER> --json comments --jq '.comments | map(select(.body | contains("## JMH LDBC Benchmark Comparison"))) | last | .body'Parse the markdown table to extract all benchmarks marked with :red_circle: (regression). Record for each:
ic4_newTopics)LdbcSingleThread*) or Multi-thread (LdbcMultiThread*)Also record the base commit (fork-point with develop):
git merge-base HEAD origin/developEach benchmark belongs to a tier with different profiling settings. Benchmarks are assigned to tiers by query name (see jmh-ldbc/README.md), not by dynamic ops/s lookup. To determine the tier, check which base class the benchmark extends.
Note: The profiling args below are intentionally reduced from the production annotations (fewer forks, shorter warmup/measurement). Profiling needs only 1 fork — we're analyzing CPU distribution, not statistical throughput. Warmup is shortened but kept long enough for JIT to stabilize.
| Tier | Base Class | Queries | Production annotations | Profiling args |
|---|---|---|---|---|
| IS-ultra-fast | LdbcISUltraFastBenchmarkBase | IS1, IS3-IS6, IC13 | 5f, 1×5s wi, 3×10s | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 (ST) |
| IS-noisy | LdbcISBenchmarkBase | IS2, IS7, IC8 | 10f, 3×5s wi, 3×10s | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 (ST) |
| IC | LdbcICBenchmarkBase | IC2, IC7, IC11 | 3f, 1×10s wi, 5×20s | -f 1 -wi 1 -w 10s -i 3 -r 20s -t 1 (ST) |
| IC-slow | LdbcICSlowBenchmarkBase | IC1, IC4, IC6, IC9, IC12 | 3f, 1×30s wi, 5×30s | -f 1 -wi 1 -w 30s -i 3 -r 30s -t 1 (ST) |
| IC-ultra-slow | LdbcICUltraSlowBenchmarkBase | IC3, IC5, IC10 | 5f, 1×60s wi, 3×120s | -f 1 -wi 1 -w 60s -i 3 -r 60s -t 1 (ST) |
IC4 exception: ic4_newTopics has a method-level override @Warmup(iterations = 3, time = 30). For profiling, use -wi 2 -w 30s instead of the IC-slow default.
For multi-thread regressions, use the same warmup/measurement but omit -t 1 (uses @Threads(Threads.MAX) default).
Use the same naming convention as run-jmh-benchmarks-hetzner:
BRANCH=$(git rev-parse --abbrev-ref HEAD | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | cut -c1-40)
SERVER_NAME="jmh-prof-${BRANCH}"
KEY_NAME="jmh-prof-key-${BRANCH}"
hcloud ssh-key create --name "$KEY_NAME" --public-key-from-file ~/.ssh/id_ed25519.pub
hcloud server create --name "$SERVER_NAME" --type ccx33 --image ubuntu-24.04 --location fsn1 --ssh-key "$KEY_NAME"Record the IPv4. Wait ~15s for boot. Remove stale host key if needed:
ssh-keygen -f ~/.ssh/known_hosts -R <IP>ssh -o StrictHostKeyChecking=no root@<IP> \
'apt-get update -qq && apt-get install -y -qq openjdk-21-jdk-headless git tmux > /dev/null 2>&1 && java -version'Install async-profiler:
ssh root@<IP> 'cd /tmp && \
curl -sLO https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz && \
tar xzf async-profiler-3.0-linux-x64.tar.gz && \
echo 1 > /proc/sys/kernel/perf_event_paranoid && \
echo 0 > /proc/sys/kernel/kptr_restrict'The async-profiler library path is: /tmp/async-profiler-3.0-linux-x64/lib/libasyncProfiler.so
HEAD (current worktree):
rsync -az --exclude='.git' --exclude='target' --exclude='.idea' "$(git rev-parse --show-toplevel)/" root@<IP>:/root/ytdb/
ssh root@<IP> 'git config --global --add safe.directory /root/ytdb && \
git config --global user.email "bench@test" && \
git config --global user.name "bench" && \
cd /root/ytdb && git init && git add -A && git commit -m "head" --quiet'BASE (fork-point commit): Create a local worktree, rsync to a separate directory:
BASE_COMMIT=$(git merge-base HEAD origin/develop)
WORKTREE_DIR="/tmp/ytdb-base-profiling-$$"
rm -rf "$WORKTREE_DIR" && git worktree prune
git worktree add "$WORKTREE_DIR" "$BASE_COMMIT"
rsync -az --exclude='.git' --exclude='target' --exclude='.idea' "$WORKTREE_DIR/" root@<IP>:/root/ytdb-base/
ssh root@<IP> 'cd /root/ytdb-base && git init && git add -A && git commit -m "base" --quiet'Compile both in parallel using isolated local repositories to avoid ~/.m2/repository corruption from concurrent writes:
ssh root@<IP> '
(cd /root/ytdb && chmod +x mvnw && ./mvnw -pl jmh-ldbc -am package -DskipTests -Dspotless.check.skip=true -q -Dmaven.repo.local=/root/.m2-head) &
(cd /root/ytdb-base && chmod +x mvnw && ./mvnw -pl jmh-ldbc -am package -DskipTests -Dspotless.check.skip=true -q -Dmaven.repo.local=/root/.m2-base) &
wait'Download the LDBC SF 1 CSV dataset and canonical curated parameters from Hetzner S3. Generate presigned URLs locally (see run-jmh-benchmarks-hetzner skill Step 3b for the boto3 presigned URL generation):
# Generate presigned URLs locally (boto3 required)
# IMPORTANT: Force HTTPS — the HETZNER_S3_ENDPOINT env var may contain http://
# but Hetzner servers cannot reach the S3 endpoint over plain HTTP (connection timeout).
python3 -c "
import boto3, os
endpoint = os.environ['HETZNER_S3_ENDPOINT']
if endpoint.startswith('http://'):
endpoint = 'https://' + endpoint[len('http://'):]
s3 = boto3.client('s3',
endpoint_url=endpoint,
aws_access_key_id=os.environ['HETZNER_S3_ACCESS_KEY'],
aws_secret_access_key=os.environ['HETZNER_S3_SECRET_KEY'])
for key in ['ldbc/ldbc-sf1-composite-merged-fk.tar.zst', 'ldbc/curated-params-v3.json', 'ldbc/factor-tables.json']:
url = s3.generate_presigned_url('get_object',
Params={'Bucket': 'bench-cache', 'Key': key},
ExpiresIn=7200)
print(f'{key}: {url}')
"Download and extract CSV dataset:
# Download and extract CSV dataset to HEAD
# Note: the tar archive contains a top-level sf1/ directory, so extract into ldbc-dataset/
# to get the expected ldbc-dataset/sf1/static/ and ldbc-dataset/sf1/dynamic/ layout.
ssh root@<IP> 'apt-get install -y -qq zstd > /dev/null 2>&1 && \
mkdir -p /root/ytdb/jmh-ldbc/target/ldbc-dataset && \
cd /root/ytdb/jmh-ldbc/target/ldbc-dataset && \
curl -sS "<CSV_PRESIGNED_URL>" | zstd -dc | tar xf - && \
echo "Dataset ready" && ls sf1/static/ sf1/dynamic/'
# Copy CSV dataset to BASE
ssh root@<IP> 'cp -r /root/ytdb/jmh-ldbc/target/ldbc-dataset /root/ytdb-base/jmh-ldbc/target/'Download canonical curated parameters for both HEAD and BASE:
# Install canonical curated params into HEAD DB directory
ssh root@<IP> 'mkdir -p /root/ytdb/jmh-ldbc/target/ldbc-bench-db && \
curl -sS -o /root/ytdb/jmh-ldbc/target/ldbc-bench-db/factor-tables.json "<FACTOR_TABLES_URL>" && \
curl -sS -o /root/ytdb/jmh-ldbc/target/ldbc-bench-db/curated-params-v3.json "<CURATED_PARAMS_URL>" && \
echo "HEAD curated params installed"'
# Install canonical curated params into BASE DB directory
ssh root@<IP> 'mkdir -p /root/ytdb-base/jmh-ldbc/target/ldbc-bench-db && \
curl -sS -o /root/ytdb-base/jmh-ldbc/target/ldbc-bench-db/factor-tables.json "<FACTOR_TABLES_URL>" && \
curl -sS -o /root/ytdb-base/jmh-ldbc/target/ldbc-bench-db/curated-params-v3.json "<CURATED_PARAMS_URL>" && \
echo "BASE curated params installed"'Critical: Both HEAD and BASE must use the same canonical curated parameters downloaded from S3. Never let either version regenerate params independently — internal data structure changes can alter iteration order and produce incomparable parameter sets (see IC4 desync incident in jmh-ldbc/README.md).
Run a throwaway fork on each version to trigger DB creation from CSV files (~21 min for SF 1) and load canonical curated parameters. Any benchmark name works here — the goal is just to trigger @Setup(Level.Trial) which creates the DB from CSV and loads the pre-downloaded curated parameters from the JSON cache files installed in Step 5.
Important: Use -f 1 (not -f 0). With -f 0 the benchmark runs in-process and JMH exits 0 even when all benchmarks fail — silent failures are hard to diagnose on a remote server.
Important: Run HEAD and BASE pre-loads sequentially, not in parallel. JMH uses a global lock file (/tmp/jmh*.lock) regardless of the working directory, so concurrent runs will fail with "Another JMH instance might be running". If a prior run left a stale lock, delete it with rm -f /tmp/jmh*.lock before starting.
# HEAD (run first)
ssh root@<IP> 'rm -f /tmp/jmh*.lock && cd /root/ytdb/jmh-ldbc && java \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens java.base/java.lang.invoke=ALL-UNNAMED \
--add-opens java.base/java.io=ALL-UNNAMED \
--add-opens java.base/java.nio=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED \
--add-opens java.base/java.net=ALL-UNNAMED \
--add-opens java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens java.base/sun.nio.cs=ALL-UNNAMED \
--add-opens java.base/sun.security.x509=ALL-UNNAMED \
--add-opens jdk.unsupported/sun.misc=ALL-UNNAMED \
-Xms4096m -Xmx4096m \
-jar target/youtrackdb-jmh-ldbc-*.jar \
"LdbcSingleThread.*ic5_newGroups" -f 1 -wi 0 -i 1 -r 1s -t 1'
# BASE (run after HEAD completes — JMH global lock prevents parallel runs)
ssh root@<IP> 'rm -f /tmp/jmh*.lock && cd /root/ytdb-base/jmh-ldbc && java \
--add-opens java.base/java.lang=ALL-UNNAMED \
--add-opens java.base/java.lang.reflect=ALL-UNNAMED \
--add-opens java.base/java.lang.invoke=ALL-UNNAMED \
--add-opens java.base/java.io=ALL-UNNAMED \
--add-opens java.base/java.nio=ALL-UNNAMED \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent=ALL-UNNAMED \
--add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED \
--add-opens java.base/java.net=ALL-UNNAMED \
--add-opens java.base/sun.nio.ch=ALL-UNNAMED \
--add-opens java.base/sun.nio.cs=ALL-UNNAMED \
--add-opens java.base/sun.security.x509=ALL-UNNAMED \
--add-opens jdk.unsupported/sun.misc=ALL-UNNAMED \
-Xms4096m -Xmx4096m \
-jar target/youtrackdb-jmh-ldbc-*.jar \
"LdbcSingleThread.*ic5_newGroups" -f 1 -wi 0 -i 1 -r 1s -t 1'Before spending time on profiling, run each regressing benchmark without async-profiler on both HEAD and BASE to confirm the regression reproduces. Use the same tier-based JMH parameters from Step 1.
Use the wrapper script (without -prof):
ssh root@<IP> 'cat > /root/run-bench.sh << '\''SCRIPT'\''
#!/bin/bash
DIR=$1 # /root/ytdb or /root/ytdb-base
BENCH=$2 # benchmark regex
shift 2
ARGS="$@" # JMH args (shift+$@ preserves spaces in multi-word args)
JVM_ARGS="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.x509=ALL-UNNAMED --add-opens jdk.unsupported/sun.misc=ALL-UNNAMED -Xms4096m -Xmx4096m"
cd $DIR/jmh-ldbc && java $JVM_ARGS \
-jar target/youtrackdb-jmh-ldbc-*.jar \
"$BENCH" $ARGS
SCRIPT
chmod +x /root/run-bench.sh'For each regression, run HEAD then BASE sequentially:
ssh root@<IP> '/root/run-bench.sh /root/ytdb "<benchmark-regex>" "<jmh-args>"'
ssh root@<IP> '/root/run-bench.sh /root/ytdb-base "<benchmark-regex>" "<jmh-args>"'Decision rule: Compare HEAD vs BASE ops/s from the triage run. Classify as measurement noise and skip profiling if ANY of these hold:
CI (99.9%): [low, high]Only proceed to Step 8 for benchmarks that reproduce a ≥5% regression with non-overlapping confidence intervals in the triage run.
Record the triage results in the final report alongside profiling throughput for transparency.
Run each confirmed regressing benchmark with async-profiler collapsed-stack output. Use the uber-jar directly to avoid shell escaping issues with Maven's -Djmh.args.
Important: Run benchmarks sequentially — never concurrently on the same server. HEAD and BASE can interleave (HEAD-ic4, BASE-ic4, HEAD-is3, BASE-is3...) or run all HEAD first then all BASE. Sequential within a version is easier to manage.
SSH escaping: The -prof async:...;...;... argument contains semicolons that are interpreted by the remote shell when passed through SSH, causing the profiler to silently not attach. To avoid this, create a wrapper script on the server:
ssh root@<IP> 'cat > /root/run-profile.sh << '\''SCRIPT'\''
#!/bin/bash
VERSION=$1 # head or base
DIR=$2 # /root/ytdb or /root/ytdb-base
BENCH=$3 # benchmark regex
shift 3
ARGS="$@" # JMH args (shift+$@ preserves spaces in multi-word args)
JVM_ARGS="--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.lang.invoke=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/sun.nio.cs=ALL-UNNAMED --add-opens java.base/sun.security.x509=ALL-UNNAMED --add-opens jdk.unsupported/sun.misc=ALL-UNNAMED -Xms4096m -Xmx4096m"
mkdir -p /root/profiles/$VERSION
cd $DIR/jmh-ldbc && java $JVM_ARGS \
-jar target/youtrackdb-jmh-ldbc-*.jar \
"$BENCH" $ARGS \
-prof "async:libPath=/tmp/async-profiler-3.0-linux-x64/lib/libasyncProfiler.so;output=collapsed;dir=/root/profiles/$VERSION;event=cpu"
SCRIPT
chmod +x /root/run-profile.sh'For each regression, run:
ssh root@<IP> '/root/run-profile.sh <version> /root/ytdb<-base> "<benchmark-regex>" "<jmh-args>"'Benchmark regex format: LdbcSingleThread.*<benchmark_name> or LdbcMultiThread.*<benchmark_name>
Output files: Collapsed stacks are written as .csv files under /root/profiles/<version>/<fully-qualified-benchmark-name>-Throughput/collapsed-cpu.csv
All analysis commands in this step run on the remote server via SSH (the profile .csv files are in /root/profiles/ on the server). Either wrap each command in ssh root@<IP> '...' or open an interactive SSH session.
For each confirmed regression, perform five levels of analysis:
Async-profiler captures ALL JVM threads across the entire fork lifetime — including @TearDown, WAL vacuum, GC, and JVM service threads. These inflate HEAD/BASE sample counts unevenly and obscure the real benchmark-thread signal. Always filter before computing leaf self-time.
# Filter out non-measurement stacks
grep -vE 'tearDown|WALVacuum|G1Conc|G1ParScan|GCThread|GangWorker|VMThread|CompilerThread|ServiceThread|SafepointSynchronize|SafepointCleanup|MonitorDeflation' <file.csv> > <file-filtered.csv>Compare total samples before and after filtering for both HEAD and BASE. Large deltas indicate:
sched_yield/__schedule_[k] samples; does not steal CPU on multi-core servers for single-threaded benchmarksUse the filtered files for all subsequent analysis steps.
Extract the method with the most self-time (CPU samples where this method is the leaf frame):
awk -F";" '{split($NF, a, " "); method=a[1]; samples=a[2]; if(method != "") leaf[method]+=samples} END {for(m in leaf) print leaf[m], m}' <file-filtered.csv> | sort -rn | head -30Compare HEAD vs BASE top-30 leaf methods. Look for:
Sum the sample counts across all stacks containing a given method (measures total time including children):
grep -E "(^|;)<method-name>(;| )" <file-filtered.csv> | awk '{sum += $NF} END {print sum}'
# Note: escape dots in method names for exact matching, e.g. EntityImpl\.hasPropertyFocus on methods from the current branch's changed code. To identify them, diff HEAD vs BASE:
git diff --name-only $(git merge-base HEAD origin/develop) HEAD -- '*.java' | head -30Then grep the profiles for class/method names from those changed files. Common hot-path methods worth checking in any regression: executeReadRecord, EntityImpl.deserializeProperties, LockFreeReadCache, ConcurrentHashMap.
Extract what a specific method calls (its direct children in the profile):
grep -E "(^|;)<parent-method>;" <file-filtered.csv> | sed 's/^[^;]*<parent-method>;//' | awk -F"[ ;]" '{sum[$1]+=$NF} END {for (c in sum) print sum[c], c}' | sort -rn | head -15Compare HEAD vs BASE children. Changes in child method distribution indicate:
# Compare method bytecode sizes by checking the last instruction offset
# The last offset in javap output is the actual bytecode size — counting lines is NOT a reliable proxy
# Search in core/target/classes (not jmh-ldbc/target/classes — the shade uber-jar doesn't unpack dependency classes there)
javap -c $(find /root/ytdb/core/target/classes -name "SQLBinaryCondition.class" | head -n 1) | awk '/evaluate/,/^$/' | tail -5
javap -c $(find /root/ytdb-base/core/target/classes -name "SQLBinaryCondition.class" | head -n 1) | awk '/evaluate/,/^$/' | tail -5The last instruction's offset (e.g., 324: in javap output) indicates the bytecode size. HotSpot default inlining threshold is ~325 bytecodes. Methods exceeding this won't be inlined at call sites, causing cascading de-inlining effects.
For each regression, report:
getCollate called in guard + fallthrough)@TearDown (e.g., O(n) cache eviction) that inflates raw sample counts but does not affect throughput measurement — flag as a production concern, not a benchmark regression# Remove local worktree (use the same $WORKTREE_DIR from Step 4)
git worktree remove --force "$WORKTREE_DIR"
# Destroy server
hcloud server delete "$SERVER_NAME"
hcloud ssh-key delete "$KEY_NAME"Always destroy the server — CCX33 costs ~0.09 EUR/hour.
After completing the analysis, review the entire session for desynchronizations and improvements. This step is mandatory — do not skip it.
Compare what actually happened during execution against what this skill document describes. Flag any discrepancies:
.csv vs .collapsed), jar name, directory layoutapt-get lock on fresh servers, JMH lock conflicts between HEAD/BASE runsawk field separator assumptions, stack frame format changesReflect on the profiling session and identify improvements to the workflow:
jfr, tree) have been more useful? Would differential flamegraphs help?Important: All proposed improvements must be generally applicable — they should benefit any future profiling session, not just the specific benchmarks or regressions analyzed in this session. Do not propose narrow fixes that only apply to one query, one benchmark tier, or one particular code path.
If any desynchronizations or improvements were found, present them to the user as a numbered list of proposed skill edits. Include:
Apply changes only after user approval. If nothing needs updating, explicitly state: "Skill is in sync — no updates needed."
| Problem | Solution |
|---|---|
Another JMH instance might be running | A prior run left a stale lock file. Delete /tmp/jmh-*.lock in the benchmark directory, or add -Djmh.ignoreLock=true to JVM_ARGS |
No matching benchmarks | List benchmarks with -l flag; use LdbcSingleThread* / LdbcMultiThread* prefix |
async-profiler perf_event_open failed | Run echo 1 > /proc/sys/kernel/perf_event_paranoid |
Collapsed output is .csv not .collapsed | This is normal for async-profiler 3.0 — the format is the same (semicolon-separated stacks, space, count) |
| Profiling throughput doesn't match benchmark | Expected — profiling adds ~5-15% overhead uniformly. Compare relative differences, not absolutes |
Profiler produces no output files (empty /root/profiles/) | Semicolons in -prof async:...;...;... are eaten by the remote shell. Use the wrapper script approach documented in Step 8 |
awk/grep/sort.© JetBrains, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in .claude/skills/profile-jmh-regressions of JetBrains/youtrackdb.
Open the folder on GitHubat commit 78d0f15
Profile Jmh Regressions next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Profile Jmh Regressions this skillJetBrains/youtrackdb | 439 | — | ~6.4k | Automated safety check: Warn | Apache-2.0 | |
| The Art of Debuggingstas00/the-art-of-debugging | 1.7k | — | ~6.1k | Automated safety check: Notes | CC-BY-SA-4.0 | |
| Ascendcascend-ai-coding/awesome-ascend-skills | 174 | — | ~3.5k | Automated safety check: Pass | None | |
| Code Review ChecklistshareAI-lab/learn-claude-code | 78k | 4 repos | ~1.1k | Automated safety check: Pass | MIT | |
| LLM Torch Profiler Analysissgl-project/sglang | 37k | 2 repos | ~6.4k | Automated safety check: Pass | Apache-2.0 | |
| OpenLogi macOS Permissions TriageAprilNEA/OpenLogi | 23k | — | ~2.5k | Automated safety check: Notes | Apache-2.0 |
stas00/the-art-of-debugging
Condensed debugging method and tool recipes for Unix, Python and PyTorch programs: crashes, hangs, segfaults, wrong output, CUDA OOM, NaN values and slowness.
ascend-ai-coding/awesome-ascend-skills
End-to-end AscendC custom operator development for Ascend NPU in an ascend-kernel (csrc/ops + build.sh + torchnpu PyTorch custom op) project.
shareAI-lab/learn-claude-code
Reviews code against a five-part checklist covering security, correctness, performance, maintainability and testing, and reports findings in a fixed format.
sgl-project/sglang
Unified LLM torch-profiler triage skill for sglang, vllm, TensorRT-LLM, and TokenSpeed.
AprilNEA/OpenLogi
Decides whether an OpenLogi device problem on macOS is a privacy-permission (TCC) problem, using agent log lines, and says which identity needs which grant.
saadeghi/daisyui
Investigates suspected bugs in the daisyUI monorepo through read-only analysis, then writes a decision-ready fix plan in tmp/bugs without changing any product code.
JetBrains/youtrackdb
Audit a finished design document for hard-to-read or hard-to-understand paragraphs, then harden the house-style rules so future design docs avoid them.
JetBrains/youtrackdb
Review documentation files for grammar, factual accuracy, and query correctness.
JetBrains/youtrackdb
Apply an edit to design.md or design-mechanics.md through the mutation discipline: apply → auto-review → iterate → present.
JetBrains/youtrackdb
Migrate a branch's docs/adr/<dir/workflow/ artifacts by replaying workflow-format commits from the per-artifact stamp base through HEAD.
JetBrains/youtrackdb
Provision a Hetzner CCX33 server, deploy the project, run JMH benchmarks, collect results, and destroy the server.
JetBrains/youtrackdb
Review a workflow-style PR's design, plan, and track files in research-mode Q&A; auto-records observations and submits a line-anchored review via gh api.
Categories
Profile JMH benchmark regressions using async-profiler on a Hetzner CCX33 server. Profile Jmh Regressions is an agent skill from JetBrains/youtrackdb, published by the product's own GitHub organization. Profile JMH benchmark regressions using async-profiler on a Hetzner CCX33 server.
Profile Jmh Regressions fits situations like: the user asks to profile regressions after a benchmark comparison run; tasks that involve Performance optimization; tasks that involve Root cause analysis.
Run `npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a claude-code`. Or copy the skill folder (.claude/skills/profile-jmh-regressions in JetBrains/youtrackdb) into .claude/skills/profile-jmh-regressions in your project. Claude Code loads it when a task matches its description.
Run `npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a codex`. Or copy the skill folder (.claude/skills/profile-jmh-regressions in JetBrains/youtrackdb) into .agents/skills/profile-jmh-regressions in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add JetBrains/youtrackdb --skill profile-jmh-regressions -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/profile-jmh-regressions, .gemini/skills/profile-jmh-regressions, .github/skills/profile-jmh-regressions and .opencode/skills/profile-jmh-regressions in your project.
Going by SKILL.md and its folder, Profile Jmh Regressions needs the command-line tools its instructions call (git, ssh, curl, java, hcloud and gh) and credentials named HETZNER_S3_ACCESS_KEY and HETZNER_S3_SECRET_KEY. Our summary lists: Python 3; A credential in HETZNER_S3_ACCESS_KEY; A credential in HETZNER_S3_SECRET_KEY.
SKILL.md names 1 domain. In commands or code: github.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md flagged 3 warning(s): mentions a credentials file (ssh keys, cloud or package-manager tokens). Read the flagged lines before installing; the check is not a guarantee either way.
Profile Jmh Regressions is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.4k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Profile Jmh Regressions: The Art of Debugging (stas00/the-art-of-debugging, 1.7k stars), Ascendc (ascend-ai-coding/awesome-ascend-skills, 174 stars), Code Review Checklist (shareAI-lab/learn-claude-code, 78k stars) and LLM Torch Profiler Analysis (sgl-project/sglang, 37k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
JetBrains (a GitHub organization, an official publisher) maintains it in JetBrains/youtrackdb, which has 439 GitHub stars. The repository holds 16 skills in this directory. The repository was last updated on October 10, 2026.
Source: JetBrains/youtrackdb on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.