Android Profiler
arindamxd/camerax-android
Manages Android performance profiling and debugging. An agent skill from arindamxd/camerax-android.
Profile and diagnose YouTrackDB SQL/MATCH query bottlenecks.
The automated check flagged lines worth reading first. See the safety section below.
$ npx skills add JetBrains/youtrackdb --skill profile-query-bottleneck -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install JetBrains/youtrackdb profile-query-bottleneck --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-query-bottleneck .claude/skills/profile-query-bottleneck && 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-query-bottleneck" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-query-bottleneck into .claude/skills/profile-query-bottleneck/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-query-bottleneck", 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-query-bottleneckType 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-query-bottleneck -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install JetBrains/youtrackdb profile-query-bottleneck --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-query-bottleneck .agents/skills/profile-query-bottleneck && 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-query-bottleneck" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-query-bottleneck into .agents/skills/profile-query-bottleneck/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-query-bottleneck", 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-query-bottleneck -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install JetBrains/youtrackdb profile-query-bottleneck --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-query-bottleneck .cursor/skills/profile-query-bottleneck && 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-query-bottleneck" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-query-bottleneck into .cursor/skills/profile-query-bottleneck/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-query-bottleneck", 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-query-bottleneck--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-query-bottleneck -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install JetBrains/youtrackdb profile-query-bottleneck --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-query-bottleneck .gemini/skills/profile-query-bottleneck && 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-query-bottleneck" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-query-bottleneck into .gemini/skills/profile-query-bottleneck/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-query-bottleneck", 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-query-bottleneckInstalls 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-query-bottleneck -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-query-bottleneck .github/skills/profile-query-bottleneck && 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-query-bottleneck" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-query-bottleneck into .github/skills/profile-query-bottleneck/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-query-bottleneck", 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-query-bottleneck -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-query-bottleneck --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-query-bottleneck .opencode/skills/profile-query-bottleneck && 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-query-bottleneck" agent skill from https://github.com/JetBrains/youtrackdb/tree/develop/.claude/skills/profile-query-bottleneck into .opencode/skills/profile-query-bottleneck/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "profile-query-bottleneck", 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-query-bottleneckProfile and diagnose YouTrackDB SQL/MATCH query bottlenecks.
Profile Query Bottleneck is an agent skill from JetBrains/youtrackdb, published by the product's own GitHub organization. Profile and diagnose YouTrackDB SQL/MATCH query bottlenecks. Accepts one or more LDBC query names (e.g., IC5, IS7, IC1,IC10). Combines async-profiler flame graphs, step-by-step selectivity measurement, and fan-out analysis on a Hetzner CCX33 server against the LDBC SF 1 dataset.
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. It works with SQL. 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.
7 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 5cd02fb. 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:
sshscpjavahcloudcurljavacgitFrom 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 Query Bottleneck loads about 6.4k tokens when it runs. Until then it costs about 76 tokens; SKILL.md has 2,322 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`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 5cd02fb, republished under its Apache-2.0 licence (© JetBrains). 2,322 words, ~6,388 tokens.
.claude/skills/profile-query-bottleneck/SKILL.md (or your agent's skills folder).Diagnose performance bottlenecks in YouTrackDB SQL and MATCH queries by combining CPU profiling (async-profiler), step-by-step selectivity analysis, and fan-out measurement on a dedicated Hetzner CCX33 server with the LDBC SF 1 dataset.
Accepts one or more LDBC query names as arguments. Format examples:
/profile-query-bottleneck IC5 — single query/profile-query-bottleneck IC5,IC10,IS7 — comma-separated list/profile-query-bottleneck IC5 IC10 IS7 — space-separated listQuery names are case-insensitive (ic5, IC5, Ic5 all work). Valid names:
IS1–IS7, IC1–IC13.
If no query names are provided, ask the user which queries to profile.
hcloud CLI installed and authenticatedboto3 Python library installed locally~/.ssh/id_ed25519jmh-ldbc module compiles locallyHETZNER_S3_ACCESS_KEY, HETZNER_S3_SECRET_KEY, HETZNER_S3_ENDPOINTFor each query name from the input, look up the benchmark method, benchmark class prefix, and tier-appropriate profiling JMH arguments from the table below.
| Query | Benchmark method | ST class prefix | Tier | Profiling args (ST) |
|---|---|---|---|---|
| IS1 | is1_personProfile | LdbcSingleThreadISUltraFast | IS-ultra-fast | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IS2 | is2_personPosts | LdbcSingleThreadIS | IS-noisy | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IS3 | is3_personFriends | LdbcSingleThreadISUltraFast | IS-ultra-fast | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IS4 | is4_messageContent | LdbcSingleThreadISUltraFast | IS-ultra-fast | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IS5 | is5_messageCreator | LdbcSingleThreadISUltraFast | IS-ultra-fast | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IS6 | is6_messageForum | LdbcSingleThreadISUltraFast | IS-ultra-fast | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IS7 | is7_messageReplies | LdbcSingleThreadIS | IS-noisy | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IC1 | ic1_transitiveFriends | LdbcSingleThreadICSlow | IC-slow | -f 1 -wi 1 -w 30s -i 3 -r 30s -t 1 |
| IC2 | ic2_recentFriendMessages | LdbcSingleThreadIC | IC | -f 1 -wi 1 -w 10s -i 3 -r 20s -t 1 |
| IC3 | ic3_friendsInCountries | LdbcSingleThreadICUltraSlow | IC-ultra-slow | -f 1 -wi 1 -w 60s -i 3 -r 60s -t 1 |
| IC4 | ic4_newTopics | LdbcSingleThreadICSlow | IC-slow | -f 1 -wi 2 -w 30s -i 3 -r 30s -t 1 |
| IC5 | ic5_newGroups | LdbcSingleThreadICUltraSlow | IC-ultra-slow | -f 1 -wi 1 -w 60s -i 3 -r 60s -t 1 |
| IC6 | ic6_tagCoOccurrence | LdbcSingleThreadICSlow | IC-slow | -f 1 -wi 1 -w 30s -i 3 -r 30s -t 1 |
| IC7 | ic7_recentLikers | LdbcSingleThreadIC | IC | -f 1 -wi 1 -w 10s -i 3 -r 20s -t 1 |
| IC8 | ic8_recentReplies | LdbcSingleThreadIS | IS-noisy | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
| IC9 | ic9_recentFofMessages | LdbcSingleThreadICSlow | IC-slow | -f 1 -wi 1 -w 30s -i 3 -r 30s -t 1 |
| IC10 | ic10_friendRecommendation | LdbcSingleThreadICUltraSlow | IC-ultra-slow | -f 1 -wi 1 -w 60s -i 3 -r 60s -t 1 |
| IC11 | ic11_jobReferral | LdbcSingleThreadIC | IC | -f 1 -wi 1 -w 10s -i 3 -r 20s -t 1 |
| IC12 | ic12_expertSearch | LdbcSingleThreadICSlow | IC-slow | -f 1 -wi 1 -w 30s -i 3 -r 30s -t 1 |
| IC13 | ic13_shortestPath | LdbcSingleThreadISUltraFast | IS-ultra-fast | -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1 |
IC4 exception: ic4_newTopics has a method-level @Warmup(iterations = 3, time = 30) override.
Profiling uses -wi 2 -w 30s instead of the IC-slow default -wi 1 -w 30s.
Multi-thread class prefix: Replace LdbcSingleThread with LdbcMultiThread and omit -t 1.
Single-thread profiling is recommended — use MT only if the bottleneck is contention-related.
The JMH benchmark regex for profiling combines the class prefix and method:
<ST class prefix>Benchmark.<method>Examples:
LdbcSingleThreadICUltraSlowBenchmark.ic5_newGroupsLdbcSingleThreadISBenchmark.is7_messageRepliesLdbcSingleThreadISUltraFastBenchmark.ic13_shortestPathFor multiple queries in the same tier, combine with |:
"LdbcSingleThreadICSlow.*(ic1_transitiveFriends|ic6_tagCoOccurrence)"For queries in different tiers, run them as separate profiling invocations — each needs its own tier-appropriate warmup/measurement parameters.
The actual SQL for each LDBC query lives in jmh-ldbc/src/main/resources/ldbc-queries/<QUERY>.sql
(e.g., IC5.sql, IS7.sql). Read these to understand the MATCH chain before writing
the diagnostic program in Phase 3.
Follow the run-jmh-benchmarks-hetzner skill's Steps 1–4 to:
jmh-ldbcAdditionally 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 && \
ln -sf /tmp/async-profiler-3.0-linux-x64 /opt/async-profiler && \
echo 1 > /proc/sys/kernel/perf_event_paranoid && \
echo 0 > /proc/sys/kernel/kptr_restrict && \
echo "async-profiler ready"'The -prof async:...;...;... argument contains semicolons that are interpreted by the
remote shell when passed through SSH, causing the profiler to silently not attach. Use
the uber-jar directly (not Maven's -Djmh.args) with a wrapper script to avoid all
shell escaping issues:
ssh root@<IP> 'cat > /root/run-profile.sh << '\''SCRIPT'\''
#!/bin/bash
BENCH=$1 # benchmark regex
OUTPUT=$2 # flamegraph or collapsed
shift 2
ARGS="$@" # JMH 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
cd /root/ytdb/jmh-ldbc && java $JVM_ARGS \
-jar target/youtrackdb-jmh-ldbc-*.jar \
"$BENCH" $ARGS \
-prof "async:libPath=/opt/async-profiler/lib/libasyncProfiler.so;output=$OUTPUT;dir=/root/profiles;event=cpu"
SCRIPT
chmod +x /root/run-profile.sh'For each query resolved in Phase 0, run the benchmark with async-profiler attached using the benchmark regex and tier-appropriate profiling args:
# Example: IC5 (IC-ultra-slow tier)
ssh root@<IP> '/root/run-profile.sh "LdbcSingleThreadICUltraSlowBenchmark.ic5_newGroups" flamegraph -f 1 -wi 1 -w 60s -i 3 -r 60s -t 1'
# Example: IS7 (IS-noisy tier)
ssh root@<IP> '/root/run-profile.sh "LdbcSingleThreadISBenchmark.is7_messageReplies" flamegraph -f 1 -wi 1 -w 5s -i 3 -r 10s -t 1'Important: Use -t 1 (single thread) for profiling — multi-threaded profiles are
harder to interpret and the contention patterns differ from the actual bottleneck.
Important: Do NOT run any other CPU-intensive process (builds, other benchmarks, diagnostic programs) while profiling. Concurrent processes compete for CPU and memory, causing OOM kills (exit 137), corrupted profiles, and skewed results. Finish all other work first, then run the profiler on a quiet machine.
Important: When profiling multiple queries, run them sequentially — one at a time. Never run concurrent JMH processes on the same server.
Download the flame graphs:
scp root@<IP>:/root/profiles/<benchmark-name>-Throughput/flame-cpu-reverse.html /tmp/flame-reverse-$$.html
scp root@<IP>:/root/profiles/<benchmark-name>-Throughput/flame-cpu-forward.html /tmp/flame-forward-$$.htmlRun with collapsed output to get machine-parseable stacks (same regex and args):
# Example: IC5
ssh root@<IP> '/root/run-profile.sh "LdbcSingleThreadICUltraSlowBenchmark.ic5_newGroups" collapsed -f 1 -wi 1 -w 60s -i 3 -r 60s -t 1'Download:
scp root@<IP>:/root/profiles/<benchmark-name>-Throughput/collapsed-cpu.csv /tmp/collapsed-$$.csvAsync-profiler captures ALL JVM threads across the entire fork lifetime — including
@TearDown, WAL vacuum, GC, and JVM service threads. These inflate sample counts
and obscure the real benchmark-thread signal. Always filter before analysis.
grep -vE 'tearDown|WALVacuum|G1Conc|G1ParScan|GCThread|GangWorker|VMThread|CompilerThread|ServiceThread|SafepointSynchronize|SafepointCleanup|MonitorDeflation' /tmp/collapsed-$$.csv > collapsed-filtered.csvCompare total samples before and after filtering. Large deltas indicate TearDown overhead or background thread contention — production concerns but not measurement bottlenecks.
Use the filtered file for all subsequent analysis steps.
The collapsed stack format is: frame1;frame2;...;leafFrame count
Find hottest leaf methods (actual CPU consumers):
cat collapsed-filtered.csv | awk '{
n = split($0, parts, ";")
last = parts[n]
idx = match(last, / [0-9]+$/)
if (idx > 0) { count = substr(last, idx+1)+0; frame = substr(last, 1, idx-1) }
else next
leaves[frame] += count
} END { for (f in leaves) print leaves[f], f }' | sort -rn | head -30Aggregate by YouTrackDB/Gremlin method (inclusive samples):
cat collapsed-filtered.csv | awk -F';' '{
n = split($0, parts, ";")
last = parts[n]; split(last, lp, " "); count = lp[length(lp)]
for (i=1; i<=n; i++) {
frame = parts[i]; if (i == n) { split(frame, fp, " "); frame = fp[1] }
if (frame ~ /youtrackdb|tinkerpop|gremlin/) {
gsub(/ $/, "", frame); frames[frame] += count
}
}
} END { for (f in frames) print frames[f], f }' | sort -rn | head -40Trace call chains from a specific method (e.g., what calls loadEntity):
cat collapsed-filtered.csv | grep 'loadEntity' | awk '{
n = split($0, parts, ";"); last = parts[n]
idx = match(last, / [0-9]+$/); count = substr(last, idx+1)+0
for (i=1; i<=n; i++) {
if (parts[i] ~ /loadEntity/) {
key = ""
for (j=i-3; j<=i; j++) {
if (j >= 1) { frame = parts[j]; gsub(/ [0-9]+$/, "", frame)
gsub(/.*\//, "", frame); key = key (key=="" ? "" : " -> ") frame }
}
paths[key] += count; break
}
}
} END { for (p in paths) print paths[p], p }' | sort -rn | head -20| Method | What it means |
|---|---|
EdgeFromLinkBagIterator.loadEdge | Loading edge records — proportional to edges traversed |
VertexFromLinkBagIterator.loadVertex | Loading vertex records from link bags |
EdgeEntityImpl.getTo/getFrom | Resolving edge→vertex (each triggers a record load) |
SQLFunctionMove.execute | Graph traversal step (.out/.in/.outE/.inE) |
SQLFunctionInV/OutV.move | .inV()/.outV() resolution |
SQLAndBlock.evaluate | WHERE clause filter evaluation |
MatchEdgeTraverser.executeTraversal | MATCH edge step execution |
MatchEdgeTraverser.applyPreFilter | Pre-filter (RID intersection) application |
AbstractLinkBag$MergingSpliterator | Link bag iteration (proportional to adjacency list size) |
RecordCacheWeakRefs.get | Record cache lookups |
EntityImpl.deserializeProperties | Deserialization cost |
FrontendTransactionImpl.loadEntity | Full entity load (cache miss → disk read) |
DatabaseSessionEmbedded.executeReadRecord | Lowest-level record read |
High loadEdge/loadVertex samples = too many records being loaded.
High SQLAndBlock.evaluate = filter evaluation is expensive (complex WHERE clauses).
High deserializeProperties = loading properties that aren't needed.
This is the most valuable diagnostic. For a multi-step MATCH query, measure the row count and execution time at each intermediate step to find the combinatorial explosion point.
Create a Java file that opens the LDBC database and runs progressively longer prefixes of the MATCH query, measuring row count and time at each step.
Template (adapt the MATCH chain to your query):
import com.jetbrains.youtrackdb.api.YourTracks;
import com.jetbrains.youtrackdb.api.gremlin.YTDBGraphTraversalSourceDSL;
import com.jetbrains.youtrackdb.api.gremlin.YTDBGraphTraversalSource;
import java.util.*;
public class QueryDiag {
static YTDBGraphTraversalSourceDSL g;
@SuppressWarnings("unchecked")
static List<Map<String, Object>> sql(String q, Object... kv) {
return g.computeInTx(tx -> {
var dsl = (YTDBGraphTraversalSource) tx;
var results = new ArrayList<Map<String, Object>>();
for (Object r : dsl.yql(q, kv).toList()) results.add((Map<String, Object>) r);
return results;
});
}
static int count(String q, Object... kv) { return sql(q, kv).size(); }
public static void main(String[] args) throws Exception {
var db = YourTracks.instance("./jmh-ldbc/target/ldbc-bench-db");
g = (YTDBGraphTraversalSourceDSL) db.openTraversal("ldbc_benchmark", "admin", "admin");
// Get sample parameter values
var persons = sql("SELECT id FROM Person ORDER BY id LIMIT 3");
// ... extract IDs ...
for (long pid : pids) {
// Step 1: first edge only
long t0 = System.nanoTime();
int step1 = count("MATCH {class: Person, where: (id = :pid)}"
+ ".out('KNOWS'){as: friend} RETURN friend", "pid", pid);
System.out.printf("Step 1: %d rows [%d ms]%n", step1, (System.nanoTime()-t0)/1_000_000);
// Step 2: first + second edge
// ... progressively add edges ...
// Step N: full query
// ... measure full query ...
}
g.close();
db.close();
}
}yql() takes key/value pairs: "paramName", value, "param2", value2
NOT positional ? placeholders.new Date(epochMillis), NOT raw Long. The histogram
selectivity estimator will throw ClassCastException: Long cannot be cast to Date
if you pass Long for a Date-typed indexed property.yql().toList() returns List<Object> where each object is a
Map<String, Object> (not a Result instance). Cast directly to Map.find /root/ytdb/jmh-ldbc/target/ldbc-bench-db -name "*.lock" -exec rm -f {} \;/tmp/jmh.lock.
Remove it before re-running: rm -f /tmp/jmh.lockout('KNOWS').size() in ORDER BY. Always alias computed expressions first:
SELECT out('KNOWS').size() as cnt ... ORDER BY cnt DESC (not ORDER BY out('KNOWS').size() DESC)Important: Run from the project root (/root/ytdb), not from jmh-ldbc/.
The diagnostic program uses ./jmh-ldbc/target/ldbc-bench-db as the DB path.
The benchmark itself (JMH via Maven) runs from jmh-ldbc/ and uses ./target/ldbc-bench-db.
# From /root/ytdb — compile against the uber-jar (has all dependencies)
javac -proc:none -cp "jmh-ldbc/target/youtrackdb-jmh-ldbc-0.5.0-SNAPSHOT.jar" QueryDiag.java
# Run with required --add-opens flags
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 \
-cp ".:jmh-ldbc/target/youtrackdb-jmh-ldbc-0.5.0-SNAPSHOT.jar" -Xmx4g QueryDiagNote: the jdk.unsupported/sun.misc=ALL-UNNAMED flag is required for the storage engine.
Without it, the DB opens but fails with InaccessibleObjectException on first record load.
For each step in the MATCH chain, record:
| Metric | Why |
|---|---|
| Row count | Identifies the fan-out explosion point |
| Distinct row count | Reveals duplicate amplification |
| Execution time | Shows per-step cost |
| Time delta vs previous step | Isolates the expensive step |
Also compute selectivity ratios:
rows[N] / rows[N-1] = fan-out at step Nfinal_rows / intermediate_rows = overall selectivity (how much work is wasted)distinct / total = duplicate ratioFor key edges, measure the degree distribution:
-- Average out-degree for an edge class from a specific vertex set
SELECT min(cnt) as minD, max(cnt) as maxD, avg(cnt) as avgD FROM (
SELECT out('EDGE_CLASS').size() as cnt FROM (
MATCH {class: StartClass, where: (id = :id)}
.out('PREV_EDGE'){as: v} RETURN v))This reveals whether fan-out is uniform or skewed (a few vertices with huge adjacency lists).
After collecting profile + selectivity data, classify the bottleneck:
1. Combinatorial explosion in intermediate rows
2. Expensive per-row operation
loadEdge, deserializeProperties)3. Large adjacency list iteration
AbstractLinkBag$MergingSpliterator, LinkBag.iteratorEdgeFromLinkBagIterator.hasNext dominates4. Filter evaluation overhead
SQLAndBlock.evaluate, SQLOrBlock.evaluate| Pattern | Cost Model | Notes |
|---|---|---|
.out('E'){class: V} | O(adjacency list size) | Filtered by collection ID |
.outE('E').inV() | O(adjacency list) + O(edge load per match) | Each edge must be loaded to resolve target vertex |
while: ($depth < N) | O(fan-out^N) | Exponential — the most expensive pattern |
where: (@rid = $matched.X.@rid) | O(1) with pre-filter, O(adjacency list) without | Back-reference check |
where: (prop >= :val) on indexed edge | O(log N) with index pre-filter | Requires index on edge class property |
A common bottleneck in LDBC queries: while: ($depth < 2) on KNOWS produces
~3-5K friends, each with ~100-200 posts = 300K-1M intermediate rows. Even with
O(1) per-row filtering downstream, the sheer volume dominates.
Mitigations:
Always destroy the Hetzner server when done. Use the same branch-based names
from run-jmh-benchmarks-hetzner Step 1:
BRANCH=$(git rev-parse --abbrev-ref HEAD | tr '[:upper:]/' '[:lower:]-' | cut -c1-40)
SERVER_NAME="jmh-bench-${BRANCH}"
KEY_NAME="jmh-bench-key-${BRANCH}"
hcloud server delete "$SERVER_NAME"
hcloud ssh-key delete "$KEY_NAME"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, DB lock files after crashesyql() return type changed, new parameter passing conventions, class renamesawk 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 query or bottleneck analyzed in this session. Do not propose narrow fixes that only apply to one query 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."
| Entity | Count |
|---|---|
| Person | 10,620 |
| Post | 1,192,942 |
| Comment | ~2,000,000 |
| Forum | 106,594 |
| Company | ~1,575 |
| Country | ~111 |
| KNOWS edges | ~360,000 |
| HAS_CREATOR edges | ~3,200,000 |
| HAS_MEMBER edges | 3,260,692 |
| WORK_AT edges | 22,766 |
| STUDY_AT edges | ~17,000 |
| IS_LOCATED_IN edges | ~4,400,000 |
Average degrees:
WORK_AT.workFrom distribution (year → edge count): 1998: 117, 1999: 411, 2000: 887, 2001: 1227, 2002: 1667, 2003: 1999, 2004: 2155, 2005: 2127, 2006: 2168, 2007: 2332, 2008: 2252, 2009: 1918, 2010: 1484, 2011: 1105, 2012: 825, 2013: 92 → 85% of WORK_AT edges have workFrom < 2010
These numbers are essential for estimating query cost. A while: ($depth < 2) KNOWS
traversal from a typical person produces 34 + 34*34 ≈ 1,190 paths (with duplicates),
~800 distinct friends. For high-degree persons (top 5 have 936-977 KNOWS), this
explodes to ~51K paths (~8.4K distinct) with 6.1x duplication.
© 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-query-bottleneck of JetBrains/youtrackdb.
Open the folder on GitHubat commit 5cd02fb
Profile Query Bottleneck 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 Query Bottleneck this skillJetBrains/youtrackdb | 439 | — | ~6.4k | Automated safety check: Warn | Apache-2.0 | |
| Android Profilerarindamxd/camerax-android | 132 | 2 repos | ~493 | Automated safety check: Pass | Apache-2.0 | |
| External Mindstudio Ascend Profiler DB Explorerascend-ai-coding/awesome-ascend-skills | 174 | — | ~1.4k | Automated safety check: Pass | None | |
| Sap Sqlscriptsecondsky/sap-skills | 462 | — | ~4k | Automated safety check: Pass | GPL-3.0 | |
| Django Filter Benchmarksaleor/saleor | 23k | — | ~2.3k | Automated safety check: Pass | BSD-3-Clause | |
| Cpu ProfileClickHouse/ClickHouse | 50k | — | ~1.9k | Automated safety check: Notes | Apache-2.0 |
arindamxd/camerax-android
Manages Android performance profiling and debugging. An agent skill from arindamxd/camerax-android.
ascend-ai-coding/awesome-ascend-skills
面向 Ascend PyTorch Profiler / msprof DB(如 ascendpytorchprofiler.db、msprof.db)的 SQL 分析技能。将自然语言问题(算子耗时、通信、下发、调度、schema/table 查询)转为安全可执行 SQL,并按需从官方文档提取表结构详情。
secondsky/sap-skills
This skill should be used when the user asks to "write a SQLScript procedure", "create HANA stored procedure", "implement AMDP method", "optimize SQLScript performance", "handle SQLScript…
saleor/saleor
Benchmarks Django ORM filters in Saleor by generating bulk data, extracting the SQL and running EXPLAIN ANALYZE to check index usage.
ClickHouse/ClickHouse
Profile a ClickHouse query using the sampling query profiler and system.tracelog.
Jeffallan/claude-skills
Tunes PostgreSQL and MySQL performance by analyzing slow queries and execution plans, designing indexes, rewriting queries and adjusting configuration, one validated change at a time.
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.
Works with
Categories
Profile and diagnose YouTrackDB SQL/MATCH query bottlenecks. Profile Query Bottleneck is an agent skill from JetBrains/youtrackdb, published by the product's own GitHub organization. Profile and diagnose YouTrackDB SQL/MATCH query bottlenecks.
Profile Query Bottleneck fits situations like: tasks that involve Performance optimization.
Run `npx skills add JetBrains/youtrackdb --skill profile-query-bottleneck -a claude-code`. Or copy the skill folder (.claude/skills/profile-query-bottleneck in JetBrains/youtrackdb) into .claude/skills/profile-query-bottleneck in your project. Claude Code loads it when a task matches its description.
Run `npx skills add JetBrains/youtrackdb --skill profile-query-bottleneck -a codex`. Or copy the skill folder (.claude/skills/profile-query-bottleneck in JetBrains/youtrackdb) into .agents/skills/profile-query-bottleneck 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-query-bottleneck -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-query-bottleneck, .gemini/skills/profile-query-bottleneck, .github/skills/profile-query-bottleneck and .opencode/skills/profile-query-bottleneck in your project.
Going by SKILL.md and its folder, Profile Query Bottleneck needs the command-line tools its instructions call (ssh, scp, java, hcloud, curl and javac) 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 1 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 Query Bottleneck 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 Query Bottleneck: Android Profiler (arindamxd/camerax-android, 132 stars), External Mindstudio Ascend Profiler DB Explorer (ascend-ai-coding/awesome-ascend-skills, 174 stars), Sap Sqlscript (secondsky/sap-skills, 462 stars) and Django Filter Benchmark (saleor/saleor, 23k 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 8, 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.