sqmSQLTool · Performance & Diagnostics · 2026
Find bottlenecks in minutes instead of hours — straight from PowerShell, no external tools.
Uwe Janke · Senior SQL Server DBA · dtcSoftware
Why sqmSQLTool Performance & Diagnostics?
Without structured diagnostic tools, root-cause work stays slow and tedious.
Slow queries exist - but nobody knows if they were always slow, or got worse after a change.
sys.dm_os_wait_stats returns raw data. Without normalizing and filtering it, bottlenecks aren't visible at a glance.
In many environments Query Store is disabled or unconfigured - even though it's the single most effective tool against plan regressions.
DMVs like sys.dm_db_missing_index_details deliver valuable hints, but they're rarely reviewed systematically.
Function overview
Every function is self-contained and can be used individually or combined into monitoring scripts.
Wait analysis
Reads sys.dm_os_wait_stats, filters out irrelevant system waits, and calculates the percentage share of each wait type.
# Top waits for an instance
Get-sqmWaitStatistics -SqlInstance "SQL-PROD-01" -Top 10
# Critical wait types only (threshold > 5%)
Get-sqmWaitStatistics -SqlInstance "SQL-PROD-01" `
-MinPercent 5 -ExcludeKnownIdle
# Multiple instances in parallel
$instances = "SQL-01", "SQL-02", "SQL-03"
$instances | ForEach-Object -Parallel {
Get-sqmWaitStatistics -SqlInstance $_
} | Sort-Object WaitPercent -Descending
Output mockup — top wait types (SQL-PROD-01)
| WaitType | WaitMs | Pct | Category |
|---|---|---|---|
| CXPACKET | 1,284,320 | 38.4 | Parallelism |
| PAGEIOLATCH_SH | 724,810 | 21.7 | I/O |
| LCK_M_X | 474,500 | 14.2 | Locking |
| SOS_SCHEDULER_YIELD | 297,140 | 8.9 | CPU |
| ASYNC_NETWORK_IO | 170,330 | 5.1 | Network |
| WRITELOG | 126,900 | 3.8 | I/O |
Query diagnostics
Reads sys.dm_exec_requests combined with sys.dm_exec_sql_text and sys.dm_exec_query_plan — an instant snapshot of every active query above the threshold.
sys.dm_exec_query_plan# Every query longer than 5 seconds
Get-sqmLongRunningQueries `
-SqlInstance "SQL-PROD-01" `
-ThresholdMs 5000
# With a blocking filter and export
Get-sqmLongRunningQueries `
-SqlInstance "SQL-PROD-01" `
-ThresholdMs 1000 `
-IncludeBlocking |
Export-Csv "C:\Reports\LongQueries.csv" `
-NoTypeInformation
Index optimization
Reads sys.dm_db_missing_index_details + sys.dm_db_missing_index_group_stats. Returns an impact score, table, columns, and a ready-to-run CREATE INDEX statement.
# Missing indexes with impact > 60
Get-sqmMissingIndexes `
-SqlInstance "SQL-PROD-01" `
-Database "DWH_Production" `
-MinImpact 60 |
Format-Table Table, ImpactScore, `
IndexStatement -AutoSize
Reads sys.dm_db_index_physical_stats and classifies: REBUILD (frag > 30%), REORGANIZE (10–30%), OK (below 10%).
# Fragmentation of every index
Get-sqmIndexFragmentation `
-SqlInstance "SQL-PROD-01" `
-Database "DWH_Production" `
-MinFragmentation 10 |
Where-Object { $_.Recommendation -eq 'REBUILD' } |
Export-Csv "Rebuild_List.csv"
Query diagnostics
Enables Query Store on one or more databases, configures the capture mode and retention, and reads the top regressed queries with a plan comparison.
# Enable Query Store with default configuration
Invoke-sqmQueryStore `
-SqlInstance "SQL-PROD-01" `
-Database "AppDB" `
-Action "Enable" `
-CaptureMode "Auto" `
-RetentionDays 30
# Top 5 plan-regressed queries in the last 7 days
Invoke-sqmQueryStore `
-SqlInstance "SQL-PROD-01" `
-Database "AppDB" `
-Action "GetTopRegressed" `
-Days 7 -Top 5
-Action ForcePlan -QueryID 142 forces a stable plan — an instant return to prior performance with no code change.
Tracing & diagnostics
Creates, starts, and stops Extended Events sessions for deadlock tracing and long-query monitoring — no Profiler, no trace flags.
# Create the deadlock XE session
Invoke-sqmExtendedEvents `
-SqlInstance "SQL-PROD-01" `
-Action "CreateDeadlockSession" `
-SessionName "sqm_deadlock_trace" `
-TargetPath "D:\XE\deadlocks"
# Long-query session (from 3 seconds)
Invoke-sqmExtendedEvents `
-SqlInstance "SQL-PROD-01" `
-Action "CreateLongQuerySession" `
-ThresholdMs 3000 `
-SessionName "sqm_longquery"
# Stop the session and read the events
Invoke-sqmExtendedEvents `
-SqlInstance "SQL-PROD-01" `
-Action "ReadAndStop" `
-SessionName "sqm_longquery"
An XE session for xml_deadlock_report — writes XEL files to disk. The basis for Get-sqmDeadlockReport.
Tracks sql_statement_completed, filtered on duration > threshold. Captures query text, plan handle, wait info.
Lifecycle management for the session, no manual T-SQL.
Reads events from the ring buffer or the XEL file, stops the session, returns a PSObject array.
-TargetPath automatically. If no path is given, the session falls back to the ring buffer.
Baseline & monitoring
Takes a snapshot of key performance metrics before a change and compares it after — an objective answer on whether things improved or got worse.
# Save a snapshot BEFORE the change
Invoke-sqmPerfBaseline `
-SqlInstance "SQL-PROD-01" `
-Action "Snapshot" `
-Label "before_index_change" `
-OutputPath "D:\Baseline"
# ... make the change ...
# Snapshot AFTER the change
Invoke-sqmPerfBaseline `
-SqlInstance "SQL-PROD-01" `
-Action "Snapshot" `
-Label "after_index_change" `
-OutputPath "D:\Baseline"
# Compare the two snapshots
Invoke-sqmPerfBaseline `
-Action "Compare" `
-BaselinePath "D:\Baseline\before_index_change.json" `
-ComparePath "D:\Baseline\after_index_change.json"
Comparison report (excerpt)
| Metric | Before | After | Delta |
|---|---|---|---|
| Avg Query Duration (ms) | 1,243 | 312 | -74.9% |
| Logical Reads / Batch | 4,821,332 | 287,440 | -94.0% |
| PAGEIOLATCH_SH Wait (ms) | 724,810 | 48,220 | -93.3% |
| CPU Time / Batch (ms) | 14,440 | 3,120 | -78.4% |
| LCK_M_X Wait (ms) | 474,500 | 471,900 | -0.5% |
Resource monitoring
Reads sys.dm_exec_sessions and sys.dm_exec_connections — shows active connections grouped by login, host, and status.
# Get connection statistics
Get-sqmConnectionStats `
-SqlInstance "SQL-PROD-01" `
-GroupBy "LoginName"
# Sleeping sessions older than 10 minutes
Get-sqmConnectionStats `
-SqlInstance "SQL-PROD-01" `
-Status "sleeping" `
-MinIdleMinutes 10
Reads Windows PerfMon counters via sys.dm_os_performance_counters or WMI — no remote connection to the OS needed.
# Key SQL PerfMon counters
Get-sqmPerfCounters `
-SqlInstance "SQL-PROD-01" `
-Category "SQLServer:Buffer Manager", `
"SQLServer:SQL Statistics", `
"SQLServer:Memory Manager"
# As continuous sampling (10s interval)
Get-sqmPerfCounters `
-SqlInstance "SQL-PROD-01" `
-SampleCount 6 -IntervalSec 10
Deadlock analysis
Parses deadlock XML from the System Health XE target, or from your own XEL files, and outputs a readable, structured summary per deadlock event.
# Deadlocks from System Health (last 7 days)
Get-sqmDeadlockReport `
-SqlInstance "SQL-PROD-01" `
-Source "SystemHealth" `
-Days 7
# Read from your own XEL file
Get-sqmDeadlockReport `
-SqlInstance "SQL-PROD-01" `
-Source "XelFile" `
-XelPath "D:\XE\deadlocks\sqm_deadlock*.xel"
# Export as an HTML report
Get-sqmDeadlockReport `
-SqlInstance "SQL-PROD-01" `
-Source "SystemHealth" `
-Days 30 |
ConvertTo-Html -Title "Deadlock Report" |
Out-File "DeadlockReport.html"
Quick reference
| Function | Category | Main parameters | Returns | DMV / source |
|---|---|---|---|---|
| Get-sqmWaitStatistics | Wait | -Top, -MinPercent, -ExcludeKnownIdle | PSObject[] | dm_os_wait_stats |
| Get-sqmLongRunningQueries | Query | -ThresholdMs, -IncludeBlocking | PSObject[] | dm_exec_requests |
| Get-sqmMissingIndexes | Index | -Database, -MinImpact | PSObject[] | dm_db_missing_index_* |
| Get-sqmIndexFragmentation | Index | -Database, -MinFragmentation | PSObject[] | dm_db_index_physical_stats |
| Invoke-sqmQueryStore | Query | -Action, -Days, -Top | PSObject[] / void | query_store_* |
| Invoke-sqmExtendedEvents | Tracing | -Action, -SessionName, -ThresholdMs | PSObject[] / void | XE Engine |
| Invoke-sqmPerfBaseline | Baseline | -Action, -Label, -OutputPath | PSObject[] / JSON | DMVs / JSON file |
| Get-sqmConnectionStats | Resource | -GroupBy, -Status, -MinIdleMinutes | PSObject[] | dm_exec_sessions |
| Get-sqmPerfCounters | Resource | -Category, -SampleCount, -IntervalSec | PSObject[] | dm_os_performance_counters |
| Get-sqmDeadlockReport | Deadlock | -Source, -Days, -XelPath | PSObject[] | System Health / XEL |
Conclusion
Script-based diagnostics are repeatable, versionable, and can be embedded in CI/CD pipelines or monitoring jobs. No more "I quickly did that in SSMS."
No schema changes, no extra database. The module only reads DMVs, system catalogs, and optional XEL files.
Uwe Janke · Senior SQL Server DBA · dtcSoftware · 2026
GitHub → sqmSQLTool