sqlprocstats-review — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited sqlprocstats-review (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Analyze runtime statistics collected from sys.dm_exec_procedure_stats, sys.dm_exec_trigger_stats, and sys.dm_exec_function_stats into the collect.proc_stats table. Applies 25 checks (R1–R25) across five categories:
burning the most CPU, reads, or elapsed time in the collection interval
of how often they run — high average CPU, high reads, parameter sniffing signals, spills
workload concentration, infrequent-but-heavy outliers
plan changes, new high-cost entries (requires ≥ 3 snapshots from Q5)
trigger-dominated elapsed time, parallel-to-serial regression, Query Store plan instability
Accept any of:
04_report_queries.sql Query 1collect.query_stats using the report query inscripts/collection/12_report_all_collections.sql Section 2; all R-checks apply equally to statement-level data — just note the object_name may be NULL for ad-hoc SQL
For trend checks (R16–R20), Q5 output with ≥ 3 rows per object is required. State "Cannot evaluate R16–R20 — trend data not provided" if Q5 is absent.
| Column | Source | Notes |
|---|---|---|
execs_in_interval | execution_count_delta | Executions since last snapshot |
cpu_ms_per_sec | worker_time_per_sec | CPU ms consumed per second of sample |
avg_cpu_ms | avg_worker_time_ms | Avg CPU per execution (ms) |
avg_elapsed_ms | avg_elapsed_time_ms | Avg wall-clock per execution (ms) |
max_cpu_ms | max_worker_time / 1000 | Single worst execution CPU (ms) |
avg_logical_reads | computed | Avg 8-KB page reads per execution |
reads_per_sec | computed | Logical reads per second |
avg_spills | computed | Avg TempDb spill pages per execution |
physical_reads_delta | delta | Total physical reads in interval |
physical_pct | computed | Physical reads as % of logical (cache miss rate) |
execs_per_sec | computed | Execution rate per second |
max_to_avg_cpu_ratio | computed | max_cpu_ms / avg_cpu_ms — parameter sniffing signal |
cpu_to_elapsed_ratio | computed | avg_cpu / avg_elapsed — > 1.5 = parallel; < 0.2 = blocking/IO |
sample_seconds | delta SP | Duration of the collection interval |
cache_age_minutes | computed | How long the current plan has been in cache |
| Metric | Info | Warning | Critical |
|---|---|---|---|
cpu_ms_per_sec (single proc) | — | ≥ 50 ms/s | ≥ 500 ms/s |
| Single proc share of total CPU delta | — | > 50% | > 80% |
avg_cpu_ms per execution | — | ≥ 1,000 ms | ≥ 10,000 ms |
avg_logical_reads per execution | — | ≥ 50,000 | ≥ 500,000 |
| Physical reads as % of logical | — | > 10% | > 50% |
execs_in_interval | — | ≥ 10,000 | — |
execs_per_sec (chatty) | — | ≥ 10/s | ≥ 100/s |
avg_spills per execution | — | ≥ 1 | ≥ 10 |
cpu_to_elapsed_ratio (parallel waste) | — | > 1.5 | > 3.0 |
cpu_to_elapsed_ratio (blocking/IO wait) | — | < 0.2 | < 0.05 |
max_to_avg_cpu_ratio (sniffing signal) | ≥ 3 | ≥ 10 | ≥ 100 |
| Top 3 procs share of total CPU delta | ≥ 70% | ≥ 90% | — |
avg_elapsed_ms per execution | — | ≥ 5,000 ms | ≥ 30,000 ms |
| Trend: execution rate spike | — | latest > 2× mean | > 5× mean |
| Trend: CPU worsening (monotonic) | 2 snapshots | 3+ snapshots | — |
Run these first. They identify which objects dominate the workload in the collection interval.
cpu_ms_per_sec ≥ 50 for a single object, OR that object's total_worker_time_delta represents > 50% of the sum across all objects in the result set/sqlplan-review on this procedure's cached plan to identify the expensive operator. Check for missing indexes, implicit conversions, or missing parallelism. Use OPTION (RECOMPILE) as immediate mitigation if parameter sniffing is suspected (see R9).reads_per_sec ≥ 5,000 for a single object, OR that object's total_logical_reads_delta represents > 50% of total reads in the result/sqlindex-advisor on its execution plan. High avg_logical_reads (see R7) indicates a per-execution index problem; high reads_per_sec with low avg_logical_reads indicates high frequency (see R4/R12).avg_elapsed_ms ≥ 5,000 ms AND execs_in_interval > 0avg_elapsed_ms >> avg_cpu_ms (see R8 blocking signal), investigate locking with /sqlwait-review or /sqldeadlock-review. If avg_elapsed_ms ≈ avg_cpu_ms, the work itself is expensive — run /sqlplan-review.execs_in_interval ≥ 10,000 in a single collection windowphysical_reads_delta > 0 AND physical_pct > 10% (more than 1 in 10 page reads goes to disk)physical_pct > 10%; Critical if > 50%sys.dm_os_memory_clerks; (2) scans reading cold data — add covering indexes; (3) first execution after cache flush — monitor over multiple windows to see if physical reads decline.Apply these regardless of execution count — an infrequent but expensive proc matters.
avg_cpu_ms ≥ 1,000 msSET STATISTICS TIME ON or SSMS actual plan) and run /sqlplan-review. Focus on the highest-cost operator. Common causes: full table scans from missing indexes, sort spills, hash join spills, large row sets.avg_logical_reads ≥ 50,000 per execution/sqlindex-advisor on its plan to generate covering index DDL. Check for table scans, key lookups, and missing WHERE clause indexes.cpu_to_elapsed_ratio > 1.5: CPU > elapsed → object uses parallelism, but threads may be poorly utilized (CXPACKET waits)cpu_to_elapsed_ratio < 0.2: elapsed >> CPU → most time is waiting, not executing (blocking, lock waits, I/O)/sqlplan-review and check N27 (thread skew) and S8 (ineffective parallelism). Consider reducing MAXDOP./sqlwait-review to identify the dominant wait type. If PAGEIOLATCH, investigate missing indexes. If LCK_M_*, investigate blocking chains or isolation level.max_to_avg_cpu_ratio ≥ 10 (worst single execution used 10× the average CPU)OPTION (RECOMPILE) as immediate mitigation. Long term: use OPTION (OPTIMIZE FOR), filtered statistics, or a plan guide. Run /sqlplan-review on the bad-parameter plan.avg_spills ≥ 1 (the object spills to TempDb on average at least once per execution)/sqlplan-review and look for N6 (sort spill risk) or N7 (hash spill risk). Update statistics and fix cardinality errors.execs_in_interval ≥ 1,000 AND avg_logical_reads < 100 AND avg_cpu_ms < 10IN (...) to batch the calls. Check cpu_ms_per_sec — even cheap procs at high frequency add up.execs_per_sec ≥ 10 (10 or more executions every second)database_name + object_name but have different plan_handle values, AND cache_age_minutes for at least one row is < 60WITH RECOMPILE on the procedure definition, OPTION (RECOMPILE) on inner statements, schema changes, statistics updates mid-execution, or SET option mismatches from different callers. Run DBCC FREEPROCCACHE monitoring or Extended Events to capture the recompile reason.total_worker_time_delta across all rows in the result set, OR the top 3 account for > 90%execs_in_interval ≤ 5 AND avg_logical_reads ≥ 100,000Skip these checks and state "Cannot evaluate R16–R20 — Q5 trend data not provided" if the input does not contain multiple rows per object across different collection_time values.
avg_cpu_ms or cpu_ms_per_sec increases monotonically across ≥ 3 consecutive snapshots for the same objectSET STATISTICS IO, TIME ON.execs_in_interval for the most recent snapshot > 2× the mean of prior snapshots for the same objectsys.dm_exec_sessions or Extended Events. Check for application loops triggered by errors (error → retry → error spiral). Reduce call frequency or add circuit-breaker logic.avg_logical_reads increases by > 50% between the oldest and newest snapshot for the same object/sqlplan-compare; (2) data growth making a seek read more rows; (3) index dropped or disabled. Compare plan_handle across snapshots (see R20 — if it changed, the plan regressed).cpu_ms_per_sec ≥ 50 OR avg_logical_reads ≥ 50,000/sqlplan-review.database_name + object_name shows different plan_handle values across consecutive snapshots in the trend outputavg_cpu_ms or avg_logical_reads worsened after the plan change (correlate with R16/R18). If so, this is a plan regression — use Query Store to force the prior plan while investigating the root cause.sys.sql_modules.uses_native_compilation = 1) shows avg_cpu_ms increasing by ≥ 100% across snapshots — SQL 2014+ (In-Memory OLTP). Note: per-execution stats for natively compiled procedures appear in sys.dm_exec_procedure_stats only when statistics collection is enabled (sys.sp_xtp_control_proc_exec_stats)EXEC sp_recompile N'schema.proc'. Check for schema changes to underlying memory-optimized tables. Verify the underlying tables' DURABILITY = SCHEMA_AND_DATA setting is intact and no new row-version contention has emerged. Note that STATISTICS IO reports 0 logical reads for memory-optimized tables — compare worker time instead.sys.dm_exec_procedure_stats.type = 'PC') or a statement with total_clr_time / total_elapsed_time ≥ 40% from statement-level stats (sys.dm_exec_query_stats.total_clr_time — CLR time is not exposed in sys.dm_exec_procedure_stats) — indicates CLR assembly code dominates execution timesys.dm_exec_trigger_stats with sys.dm_exec_procedure_stats reveals that a trigger's total_elapsed_ms is ≥ 50% of the DML procedure's total_elapsed_ms on the same tableSELECT * FROM sys.triggers WHERE parent_id = OBJECT_ID('schema.table'). Run the trigger body through /tsql-review and capture its execution plan. Consider whether trigger logic can be moved to the application layer or batched asynchronously (e.g., via Service Broker or a background job).avg_cpu_ms / avg_elapsed_ms ratio decreasing from ≥ 1.5 to ≤ 1.0 across consecutive snapshots — signals transition from a parallel to a serial planSELECT * FROM sys.query_store_plan WHERE query_id = (SELECT query_id FROM sys.query_store_query WHERE query_hash = 0x<hash>). If a MAXDOP change, statistics update, or schema change caused the plan regression, use Query Store to force the parallel plan. Run /sqlplan-review on both plans to confirm the DOP change (check S7, N30).sys.query_store_plan with ≥ 3 distinct plans AND avg_cpu_ms variance between proc_stats snapshots is ≥ 50% — SQL 2016+ (Query Store)EXEC sys.sp_query_store_force_plan @query_id = <id>, @plan_id = <id>. Investigate why plans are changing: statistics updates, ad-hoc parameter values, or RECOMPILE hints. Use /sqlquerystore-review (Q7–Q12) for full plan instability analysis.If the SQL Server version is stated by the user, read VERSION_COMPATIBILITY.md (~/.claude/skills/VERSION_COMPATIBILITY.md if installed, or skills/VERSION_COMPATIBILITY.md from the repo). If unavailable, skip silently. For checks whose minimum version exceeds the instance version: verbose mode → log as SKIP (version: requires SQL 20XX+, instance is SQL 20YY); standard report → omit entirely. Do not suppress NOT ASSESSED rows from missing input — only suppress version-inapplicable checks.
Structure your report as follows. Follow every formatting rule below exactly — the reference output in skills/sqlprocstats-review/examples/proc_stats_output-analysis.md demonstrates the expected quality level.
## Procedure Stats Analysis
### Input Summary
- Source: collect.proc_stats — [Q1 / Q2 / Q3 / Q4 / Q5] output
- Sample window: N minutes (sample_seconds = X)
- Collection time: [collection_time from data]
- Objects in result: N (PROCEDURE: N, TRIGGER: N, FUNCTION: N)
- Trend snapshots: N [or "N/A — single snapshot"]Always include this table, populated from whatever input was provided:
### Top Resource Consumers
| Rank | Object | Type | DB | Execs | CPU ms/s | Avg CPU ms | Avg Reads | Reads/s |
|------|--------|------|----|-------|----------|------------|-----------|---------|
| 1 | ... | PROC | .. | ... | ... | ... | ... | ... |Each finding must include the check ID that fired:
### [C1 — R6] High Average CPU — dbo.usp_GetOrderHistory (avg 12,400 ms)
- **Observed:** avg_cpu_ms = 12,400, execs_in_interval = 48, cpu_ms_per_sec = 99.2
- **Impact:** [why this matters at runtime]
- **Fix:** [concrete action]schema_name.object_nameformat when schema_name is present in the input.** Use bare object_name only when schema_name is NULL (ad-hoc SQL with no associated object). Correct: dbo.usp_GetSalesReport — Wrong: usp_GetSalesReport
max_to_avg_cpu_ratio ≥ 10 (Warning).Always end the findings with this table:
### Prioritized Action Order
| Priority | Action | Resolves | Effort |
|----------|--------|----------|--------|
| 1 — Immediately | Run /sqlplan-review on dbo.usp_GetOrderHistory | C1, W2 | 15 min |
| 2 — Today | Add covering index on Orders(CustomerId) | W2 | 30 min |Format as a two-column table. Include every check explicitly evaluated and not triggered.
### Passed Checks
| Check | Result |
|-------|--------|
| R1 — CPU Hotspot | PASS — no object > 50% of total CPU delta (top proc = 34%) |
| R5 — Physical I/O | PASS — physical_reads_delta = 0 across all objects |
---
*Analyzed by: [state the AI model and version you are running as, e.g. "Claude Sonnet 4.6", "DeepSeek R1", "GPT-4o"] · [current date and time in the user's local timezone, or UTC if timezone is unknown, e.g. "2026-05-16 20:15 NZST"]*averages together may not be fully evaluable — state the limitation explicitly.
total_worker_time is cumulative microseconds in the DMV; avg_worker_time_ms is alreadyconverted to milliseconds in the report queries — do not double-convert.
avg_spills = NULL means the SQL Server version does not support total_spills (versions prior to SQL Server 2016 SP2 or SQL Server 2017 CU3; total_spills was added in SQL Server 2016 SP2 and SQL Server 2017 CU3; SQL Server 2017 RTM through CU2 also return NULL) — skip R10 and note the version limitation.`--brief` — Omit the Passed Checks table and attribution footer. Output the Summary, Findings, and Prioritized Fix Sequence sections only. Use when a quick scan of what fired is all that's needed.
`--critical-only` — Suppress Warning and Info findings. Show only Critical findings. The Passed Checks table is also omitted. Use when triaging an incident and only actionable blockers matter.
Both flags can be combined: --brief --critical-only produces the Summary section plus Critical findings only.
When neither flag is present, produce the full report as documented above.
When the user's request includes --verbose, --trace, or the word verbose:
1. Append a `## Check Evaluation Log` section after the Passed Checks table.
Include one row for every check in this skill's ruleset, in check-ID order:
| Check | Evidence | Threshold | Result |
|---|---|---|---|
| [ID — Name] | [key attribute(s) and value found, or "absent"] | [threshold or condition] | PASS / FIRE → [severity] / NOT ASSESSED |
Result conventions:
PASS — attribute present, threshold not met**FIRE → Critical/Warning/Info** — threshold met; bold to distinguish from passesNOT ASSESSED — required attribute absent from input2. Save both files to the current working directory using the Write tool:
output/<skill-name>/<YYYY-MM-DD-HHmmss>-<input-prefix>/analysis.md ← full report output/<skill-name>/<YYYY-MM-DD-HHmmss>-<input-prefix>/trace.md ← Check Evaluation Log
Derive <input-prefix>:
horrible.sqlplan → horrible)runSanitize: alphanumeric + hyphens/underscores only, max 32 chars.
File headers: analysis.md → # Analysis — <skill-name> / # Input: <first 80 chars> / # Generated: <UTC timestamp> trace.md → # Check Evaluation Log — <skill-name> / # Input: <first 80 chars> / # Generated: <UTC timestamp>
Create directories as needed. When --verbose is not present, write nothing to disk.
by R1/R2/R6/R7. The plan reveals which operator inside the procedure is expensive.
execution). Feed the procedure's cached plan XML to the advisor.
analysis to identify whether the bottleneck is LCK_M_*, PAGEIOLATCH, or RESOURCE_SEMAPHORE.
to identify which statement inside the procedure is the bottleneck.
use Query Store analysis to identify the regressed plan and its first-seen date.
plans, diff them to understand exactly what changed in the query plan.
(implicit conversions, non-sargable predicates, cursor loops) before capturing a plan.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.