use-analysis
Perform a Brendan Gregg USE method analysis on perf-lab benchmark results. Use when analyzing benchmark output directories for performance bottlenecks.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Perform a Brendan Gregg USE method analysis on perf-lab benchmark results. Use when analyzing benchmark output directories for performance bottlenecks.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | use-analysis |
| description | Perform a Brendan Gregg USE method analysis on perf-lab benchmark results. Use when analyzing benchmark output directories for performance bottlenecks. |
| argument-hint | <results-dir> |
| allowed-tools | Read, Grep, Glob, Bash, Agent |
Perform a Brendan Gregg USE method (Utilization, Saturation, Errors) analysis on the benchmark results in the given directory.
Results directory: $ARGUMENTS
If no directory is provided, ask the user which results directory to analyze.
Glob for all files under <results-dir>/target-host/:
vmstat-*.log — system-wide memory, swap, CPU, I/Opidstat-*.log — per-process CPU (the app JVM)mpstat-*.log — per-CPU utilization breakdownwrk-*.log — load generator throughput & errors (measurement run)wrk-warmup-*.log — warmup run resultsload-test-*.log — application stdout/stderr (startup, errors, warnings)postgres-pidstat-*.log — database process CPUlgtm-pidstat-*.log — OTel LGTM stack CPUmetrics.json — run configuration (JVM args, CPU pinning, etc.)lscpu.txt — CPU/NUMA topologyIdentify all runtimes tested (e.g., quarkus3-jvm, spring4-jvm) from the filenames.
Read metrics.json to understand:
config.jvm.memory, config.jvm.args)config.resources.cpu.app, .db, .otel, .load_gen)Read lscpu.txt to understand NUMA topology — which CPUs map to which NUMA nodes.
The mpstat header is always:
HH:MM:SS AM/PM CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle
When parsing with awk (fields are 1-indexed, after time+AM/PM):
$1 = time, $2 = AM/PM, $3 = CPU number (or "all")$4 = %usr, $5 = %nice, $6 = %sys, $7 = %iowait, $8 = %irq, $9 = %soft$10 = %steal, $11 = %guest, $12 = %gnice, $13 = %idleWARNING: $7 is %iowait, NOT %sys. $6 is %sys. $NF (last field) is %idle. Always verify against the header line.
Example awk for per-CPU averages:
awk '$2=="AM" && ($3==CPU1 || $3==CPU2) && $1 >= "HH:MM:SS" && $1 <= "HH:MM:SS" {
cpu=$3; usr=$4; sys=$6; idle=$NF
sum_usr[cpu]+=usr; sum_sys[cpu]+=sys; sum_idle[cpu]+=idle; n[cpu]++
}
END {
for (c in sum_usr) printf "CPU %d: usr=%.1f%% sys=%.1f%% idle=%.1f%% (n=%d)\n",
c, sum_usr[c]/n[c], sum_sys[c]/n[c], sum_idle[c]/n[c], n[c]
}' mpstat-*.log
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
r b swpd free buff cache si so bi bo in cs us sy id wa st gu
Fields (1-indexed): $1=r, $2=b, $3=swpd, $4=free, $5=buff, $6=cache, $7=si, $8=so, $9=bi, $10=bo, $11=in, $12=cs, $13=us, $14=sy, $15=id, $16=wa, $17=st, $18=gu
Note: The first data line after the header is a historical average since boot — skip it. Real per-second samples start from the second data line.
Old format (pidstat -u -p PID 1):
HH:MM:SS UID PID %usr %system %guest %wait %CPU CPU Command
New format (pidstat -u -w -t -p PID 1) — produces TWO alternating blocks per second:
CPU block:
HH:MM:SS UID TGID TID %usr %system %guest %wait %CPU CPU Command
Context switch block:
HH:MM:SS UID TGID TID cswch/s nvcswch/s Command
The TGID line (TID="-") shows the process aggregate. TID lines (TGID="-") prefixed with |__ show individual threads.
The measurement window is the last ~30 seconds of active load before the application is killed. To find it:
Do NOT use the first seconds of mpstat data — those may be startup/warmup. Do NOT guess the window — always verify by finding the load-end transition in mpstat or vmstat.
Cross-validate with JFR: if JFR was enabled, the number of samples should be approximately duration_seconds × sample_rate_hz × num_cpus. For 10s at 100Hz on 4 CPUs: ~4000 samples = ~100% CPU utilization.
Source: pidstat-<runtime>-*.log and mpstat-<runtime>-*.log
r column, $1). Check if app CPUs show high %sys (kernel overhead). Context switch rate from vmstat (cs column, $12).Flag if: app CPUs >80% utilized, %sys > %usr (unusual kernel overhead), or run queue consistently > number of app CPUs.
Source: postgres-pidstat-<runtime>-*.log and mpstat-<runtime>-*.log (DB-pinned CPUs)
Flag if: DB CPUs are saturated (indicates DB is the bottleneck, not the app).
Source: lgtm-pidstat-<runtime>-*.log and mpstat-<runtime>-*.log (OTel-pinned CPUs)
load-test-*.log for OTel export failures ("Connection was closed", "Failed to export").Flag if: OTel CPUs saturated, or export errors present (telemetry backpressure affecting the app).
Source: mpstat-<runtime>-*.log (load-gen-pinned CPUs)
Flag if: load-gen CPUs >70% (wrk may be unable to generate enough load).
Source: vmstat-<runtime>-*.log
free and cache columns. Track free memory over time.si (swap-in) and so (swap-out) columns. swpd (swap used) over time. This is the most critical saturation signal.Flag if: swpd > 0 at any point, or si/so > 0 during the measurement window. Correlate with NUMA topology — if the system has multiple NUMA nodes, system-wide free memory can be misleading (one node may be exhausted while another has plenty).
Source: vmstat-<runtime>-*.log (bi/bo columns), wrk-*.log
b column in vmstat (processes blocked on I/O). wa (I/O wait) in CPU columns.Socket errors: line — connection errors, timeouts).Flag if: I/O wait >5%, blocked processes >0 sustained, or wrk reports socket errors.
From wrk-<runtime>-*.log, extract:
Present a comparison table across runtimes.
When comparing runtimes with different throughput levels, raw aggregate metrics (total context switches, total I/O, total interrupts) are misleading because the higher-throughput runtime naturally generates more total activity. In addition to raw values, present key metrics normalized per request (operation):
Present as a comparison table:
| Metric (per request) | Runtime A | Runtime B | Ratio |
|----------------------|-----------|-----------|-------|
| Total CPU | ... µs | ... µs | ...x |
| User CPU | ... µs | ... µs | ...x |
| Kernel CPU | ... µs | ... µs | ...x |
| Context switches | ... | ... | ...x |
| Interrupts | ... | ... | ...x |
| Disk write | ... KB | ... KB | ...x |
This per-operation view reveals whether a runtime is inherently more or less efficient, independent of the throughput it achieves.
Present findings in this structure:
## USE Analysis: <results-dir>
### Configuration
- JVM: ...
- CPU pinning: App=..., DB=..., OTel=..., LoadGen=...
- NUMA topology: ...
### Throughput
| Runtime | Req/sec | Avg Latency | Max Latency | Errors |
|---------|---------|-------------|-------------|--------|
### Per-Runtime Findings
#### <runtime-name>
| Resource | Utilization | Saturation | Errors | Verdict |
|----------|-------------|------------|--------|---------|
| App CPU | ... | ... | ... | OK/FLAG |
| DB CPU | ... | ... | ... | OK/FLAG |
| OTel CPU | ... | ... | ... | OK/FLAG |
| LoadGen | ... | ... | ... | OK/FLAG |
| Memory | ... | ... | ... | OK/FLAG |
| I/O | ... | ... | ... | OK/FLAG |
Key observations:
- ...
### Bottleneck Summary
- Primary bottleneck: ...
- Secondary concerns: ...
- Recommendations: ...
--drop-fs-caches was used (check metrics.json).$1 >= "09:45:40" && $2 == "AM").