Chapter 22 · Observability: DMVs, Extended Events, Wait Stats, Perf Counters, and Incident Analysis

Performance Counters, OS Metrics, Storage Latency, Memory Pressure, and CPU Scheduling

Correlate SQL performance counters, file I/O, process/host memory, schedulers, and host metrics without relying on universal thresholds or mistaking cumulative counters for incident latency.

Advanced170–230 minutesSQL/OS resource correlation labSQL Server 2025 CU7 · 17.0.4065.4Compatibility 170 · single-instance labSSMS 22.8.2 · Last reviewed August 2026

Learning outcomes

A ServiceHub report says “SQL Server is using too much CPU.” The host chart shows 85% CPU, one database file has high cumulative I/O stall, and Page Life Expectancy is lower than yesterday. None of those facts independently identifies a bottleneck. SQL Server runs inside an operating system, VM or container with its own CPU scheduling, memory pressure and storage path. Observability becomes useful when engine counters and host metrics describe the same interval and you understand whether each counter is instantaneous, cumulative, sampled or derived.

01

Interpret SQL Server performance counters according to cntr_type rather than treating every cntr_value as an instantaneous number.

02

Compute coarse file-level I/O stall averages while preserving the cumulative/reset context.

03

Distinguish process memory, host memory, buffer-cache symptoms, and explicit low-memory signals.

04

Use scheduler runnable/work queues as evidence of SQL CPU/worker pressure instead of equating high host CPU with SQL CPU starvation.

05

Correlate Windows/Linux/container infrastructure metrics with SQL evidence without adopting universal threshold folklore.

Lab baseline. SQL Server 2025 (17.x), CU7 build 17.0.4065.4; compatibility level 170 unless explicitly changed. SSMS 22.8.2 is the checked Windows administration tool; current VS Code + MSSQL and current sqlcmd are valid free alternatives. Azure Data Studio is retired. The mandatory labs are single-instance and use a disposable database named ServiceHubObservabilityLab. SQL Server 2022+ performance DMVs commonly require VIEW SERVER PERFORMANCE STATE; use the least privilege that satisfies the collector. Host and SQL counters must share timestamps and scope. sys.dm_io_virtual_file_stats and many OS DMVs require VIEW SERVER PERFORMANCE STATE on SQL Server 2022+.

1. SQL performance counters need their counter type

sys.dm_os_performance_counters exposes the raw values SQL Server provides to the Windows Performance Counter engine. Some counters are instantaneous; others are cumulative bases from which a rate or ratio must be calculated. For a “per second” counter, one raw cntr_value is not the per-second rate. Sample twice and divide the delta by elapsed time. The cntr_type tells you how the value must be interpreted.

sql · inspect selected SQL counters with counter types intact
SELECT    object_name,    counter_name,    instance_name,    cntr_value,    cntr_typeFROM sys.dm_os_performance_countersWHERE counter_name IN(    N'Batch Requests/sec',    N'Page life expectancy',    N'Page reads/sec',    N'Page writes/sec',    N'User Connections',    N'Log Flushes/sec')ORDER BY object_name, counter_name, instance_name;

Do not turn Page Life Expectancy into a universal magic threshold. Memory size, workload, columnstore/In-Memory OLTP usage and engine version change its meaning. A downward shift that aligns with query regressions, physical reads and memory-pressure signals is more useful than one isolated number.

2. File I/O DMVs expose cumulative stall, not a storage latency percentile

sys.dm_io_virtual_file_stats tracks read/write counts and stall time per database file since engine start (or the documented reset condition). Dividing cumulative stall by cumulative I/O count yields a coarse average over that entire observation lifetime. It is useful for comparing files and spotting changes, but it is not a p95/p99 latency measurement and cannot distinguish every storage-layer queue.

sql · compute coarse cumulative file-I/O averages with reset context
SELECT    DB_NAME(vfs.database_id) AS database_name,    mf.name AS logical_file_name,    mf.type_desc,    vfs.num_of_reads,    CAST(vfs.io_stall_read_ms * 1.0 / NULLIF(vfs.num_of_reads,0) AS decimal(18,2)) AS avg_read_ms,    vfs.num_of_writes,    CAST(vfs.io_stall_write_ms * 1.0 / NULLIF(vfs.num_of_writes,0) AS decimal(18,2)) AS avg_write_ms,    vfs.num_of_bytes_read,    vfs.num_of_bytes_written,    vfs.sample_msFROM sys.dm_io_virtual_file_stats(NULL,NULL) AS vfsJOIN sys.master_files AS mf  ON mf.database_id = vfs.database_id AND mf.file_id = vfs.file_idORDER BY avg_read_ms DESC;

Corroborate with wait types such as PAGEIOLATCH_..., query plans, physical read counts, storage metrics and workload changes. Slow average file I/O can be cause, consequence, or simply historical residue from a past event.

3. Memory pressure is a system state, not “SQL uses lots of RAM”

SQL Server deliberately caches data and plans, so a large working set is expected. sys.dm_os_process_memory reports process-level memory and explicit low-memory signals. sys.dm_os_sys_memory reports host-level physical/commit state. In a container or VM, also inspect the effective guest/cgroup limits because host physical memory may not equal what SQL Server can actually consume.

sql · inspect SQL process and host memory state
SELECT    physical_memory_in_use_kb,    memory_utilization_percentage,    available_commit_limit_kb,    process_physical_memory_low,    process_virtual_memory_lowFROM sys.dm_os_process_memory;SELECT    total_physical_memory_kb,    available_physical_memory_kb,    total_page_file_kb,    available_page_file_kb,    system_low_memory_signal_state,    system_memory_state_descFROM sys.dm_os_sys_memory;

If low-memory signals are not set, a full buffer pool is not evidence of memory starvation. Conversely, a memory grant wait can occur even when the OS has free memory because SQL Server’s query-workspace grant subsystem and workload concurrency impose their own constraints. Chapter 12 covered grants/spills; here the point is to align those internal symptoms with host memory evidence.

4. CPU: distinguish host utilization from SQL scheduler pressure

SQL Server’s SQLOS schedulers expose workers in runnable queues. runnable_tasks_count is the number of workers with tasks assigned that are waiting to get scheduled on a CPU. work_queue_count counts tasks waiting for a worker. Those describe different pressure modes: CPU runnable backlog versus worker-thread scarcity. Sample them repeatedly; a single momentary nonzero value can be normal.

sql · sample visible schedulers for CPU and worker pressure
SELECT    SYSDATETIMEOFFSET() AS observed_at,    scheduler_id,    cpu_id,    current_tasks_count,    runnable_tasks_count,    current_workers_count,    active_workers_count,    work_queue_count,    pending_disk_io_countFROM sys.dm_os_schedulersWHERE status = N'VISIBLE ONLINE'ORDER BY scheduler_id;

On Linux, SQL Server 2025 also exposes Linux-specific CPU/VM/disk/network DMVs. Microsoft’s current Linux CPU DMV can be correlated with SQL scheduler queues. On Windows, PerfMon/PowerShell counters provide host CPU/run-queue/storage context. The mandatory course path stops at the SQL DMVs because host tooling differs, but a production incident should include the platform-native layer.

powershell · optional Windows host snapshot with free built-in PowerShell
# Run in PowerShell on the SQL Server host when permitted.Get-Counter '\Processor(_Total)\% Processor Time',            '\System\Processor Queue Length',            '\Memory\Available MBytes' |  Select-Object -ExpandProperty CounterSamples |  Select-Object Path, CookedValue, Timestamp
Virtualization/container boundary. A guest can report CPU pressure because its vCPUs are constrained even when the physical host has spare capacity; a container can be throttled by cgroup quotas; a storage device can be healthy while a noisy neighbor or virtualization layer adds queueing. Record topology and limits before drawing hardware conclusions.

5. Build a correlated resource snapshot

A practical incident packet should timestamp SQL schedulers, process/host memory, file I/O deltas, key performance counters, wait deltas, active requests, Query Store intervals, and host metrics. Align clocks in UTC or with explicit offsets. Then state facts before hypotheses: “scheduler 3 had 12 runnable tasks for four consecutive 5-second samples” is a fact; “we need more CPU” is a hypothesis that still needs query/workload analysis.

No universal latency or CPU threshold is encoded here. Storage class, write-cache policy, synchronous replicas, backup/compression work, workload concurrency and service-level objectives all change what is acceptable. Build local percentiles/baselines, correlate deviations with query and wait evidence, and validate a remediation with the same measurements used to detect the problem.

Host metrics also need ownership context. On Windows, PerfMon counters may be collected by an infrastructure team; on Linux, tools such as vmstat, iostat, or the SQL Server Linux OS DMVs can provide complementary views. In Kubernetes or other container platforms, capture CPU/memory requests, limits and throttling alongside guest metrics. The diagnostic question is “what resource contract was SQL Server actually running under at this time?” rather than “what hardware model is the host?”

For rates, take paired samples with a monotonic timestamp. For example, a cumulative “Batch Requests/sec” raw value needs two readings; divide the counter delta by elapsed seconds. For I/O, keep read/write counts and stalls separately so a workload-volume change is not mistaken for a device-latency change. For schedulers, keep several consecutive samples because a runnable queue that appears for one instant can be ordinary burstiness.

Check your understanding

  1. Why can cntr_value be misleading by itself?
  2. What does avg_read_ms from sys.dm_io_virtual_file_stats represent?
  3. Does a large SQL Server working set prove memory pressure?
  4. What does runnable_tasks_count indicate?
  5. Why capture topology with host metrics?
Review the answers

1. Different performance counters have different cntr_type semantics; some raw values are cumulative inputs that require interval sampling or ratio calculations.

2. A coarse cumulative average stall per read across the counter lifetime, not a latency percentile for the incident window.

3. No. SQL Server intentionally caches memory; use explicit process/host low-memory signals plus workload evidence.

4. Workers already assigned tasks are waiting on a scheduler runnable queue for CPU time.

5. VM/container quotas, host scheduling and storage layers can change the meaning of guest SQL/OS measurements.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.