Windows Server ships with a full performance-monitoring stack — Performance Monitor (PerfMon), Data Collector Sets, and the Get-Counter PowerShell cmdlet — that covers 95% of what a small team needs without buying an agent. The problem is rarely tooling; it is knowing which of the hundreds of counters actually predict trouble. This article lists the counters that matter on a typical IIS or SQL Server workload, gives realistic thresholds, and shows how to build a scheduled Data Collector Set with alerting so you hear about problems before users do. It assumes a standard Windows Server VPS; the Windows VPS hosting guides on this site cover the base setup these checks assume.
The counters that actually matter
Most counters are noise. The shortlist below has caught real incidents — runaway loops, memory leaks, disk saturation — on every server I have monitored. Thresholds are for a 2–4 vCPU / 4–8 GB box; scale the queue-length rules by core count.
| Counter | Warning threshold | What it really tells you |
|---|---|---|
Processor\% Processor Time (_Total) | > 85% sustained | CPU saturation; check System\Processor Queue Length to confirm |
System\Processor Queue Length | > 2 per core | Threads waiting for CPU — classic oversubscription sign |
Memory\Available MBytes | < 10% of total RAM | Memory pressure; pages/sec spikes usually follow |
Memory\Pages/sec | > 1,000 sustained | Heavy paging — disk is now your RAM |
PhysicalDisk\Avg. Disk sec/Read | > 20 ms | Slow reads; on NVMe anything above ~5 ms is suspicious |
PhysicalDisk\Avg. Disk sec/Write | > 20 ms | Write latency; check for backup jobs overlapping traffic |
PhysicalDisk\Current Disk Queue Length | > 2 per disk | Requests stacking up; correlate with latency before acting |
Network Interface\Bytes Total/sec | > 80% of NIC speed | Bandwidth ceiling; relevant on 1 Gbps VPS NICs |
Process\Working Set (per w3wp/sqlservr) | Growth over 24 h | Leak detection — trend, not threshold |
One caution: % Processor Time above 85% is only actionable when the processor queue is also high. A single core pegged at 100% on a four-core box shows 25% total — check the per-instance counters (Processor(1)\% Processor Time) to find a stuck thread.
Quick checks with Get-Counter
Before building anything persistent, run these one-liners to get a baseline:
Get-Counter '\Processor(_Total)\% Processor Time','\Memory\Available MBytes','\PhysicalDisk(_Total)\Avg. Disk sec/Read','\PhysicalDisk(_Total)\Avg. Disk sec/Write' -SampleInterval 1 -MaxSamples 5
And the leak check — working set of the top five processes by memory:
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 5 Name, @{n='WorkingSetMB';e={[math]::Round($_.WorkingSet64/1MB)}}
Run these three times a day for a week and note the normal range. Thresholds are only meaningful relative to your baseline — a server that always idles at 60% CPU is different from one spiking from 10% to 60% at the same hour every day.
Build a Data Collector Set
Data Collector Sets are PerfMon’s way of logging counters to disk on a schedule. To create one for the shortlist above from an elevated prompt:
logman create counter ServerHealth -f bincirc -max 1024 -si 00:00:30 `
-c "\Processor(_Total)\% Processor Time" "\System\Processor Queue Length" `
"\Memory\Available MBytes" "\Memory\Pages/sec" `
"\PhysicalDisk(_Total)\Avg. Disk sec/Read" "\PhysicalDisk(_Total)\Avg. Disk sec/Write" `
"\PhysicalDisk(_Total)\Current Disk Queue Length" "\Network Interface(*)\Bytes Total/sec"
logman start ServerHealth
This logs every 30 seconds into a circular 1 GB file. To make it survive reboots, register it as a scheduled task: logman create ... then schtasks /create /tn "ServerHealth" /tr "logman start ServerHealth" /sc onstart. The bincirc format is compact and PerfMon reads it natively; convert to CSV later with relog if you want it in Excel.
Turn the log into an alert
A log nobody reads is just a bigger disk problem. The built-in alerting path is: PerfMon alerts that fire a scheduled task. First create an alert Data Collector Set:
logman create alert CPUAlert -th "\Processor(_Total)\% Processor Time>85" -si 00:01:00
logman start CPUAlert
Then attach an action. The cleanest no-code option: in the alert’s properties (Performance Monitor → Data Collector Sets → User Defined → CPUAlert → right-click → Properties → Alerts tab → Task), point it at a scheduled task that sends email or writes to the Application log. With no mail server handy, have the task run PowerShell that writes an event:
logman create alert CPUAlert -th "\Processor(_Total)\% Processor Time>85" -si 00:01:00 -tn "CPUAlertAction"
And the action task:
schtasks /create /tn CPUAlertAction /tr "powershell -Command Write-EventLog -LogName Application -Source PerfMon -EventId 9001 -EntryType Warning -Message 'CPU above 85%% for 1 minute'" /sc once /st 00:00 /ru SYSTEM /f
Whatever action you choose, alert on the sustained condition, not a single sample. A 5-second compile spike on a build server will page you into oblivion if you alert on any reading above 85%; the 1-minute sample interval above is a reasonable compromise.
Reading the results
After a few days, convert the binary log and look for patterns rather than peaks:
relog ServerHealth_000001.blg -f csv -o health.csv
Import-Csv health.csv | Group-Object { ([datetime]$_.Timestamp).Hour } |
ForEach-Object { [pscustomobject]@{ Hour=$_.Name; AvgCPU=($_.Group.'\\machine\processor(_total)\% processor time' | Measure-Object -Average).Average } }
Hourly averages instantly reveal the daily shape: the 02:00 backup spike, the 09:00 login rush, the 17:00 batch job. Compare the same hour across days — that is where slow leaks and creeping baselines show up. If Avg. Disk sec/Read climbs by 2 ms every day for a week while the workload is flat, the disk is degrading; that is the signal to move the database file or open a ticket with your provider before it fails outright.
When to graduate to real monitoring
PerfMon covers capacity and health, but it will not tell you that a specific HTTP endpoint is returning 500s or that TLS certificates are expiring. Once your infrastructure outgrows a single box — multiple servers, a load balancer, a team that needs a shared dashboard — a proper monitoring stack (Prometheus/Grafana or a SaaS agent) earns its keep. Until then, the counters above, a scheduled Data Collector Set, and one alert catch the incidents that actually hurt. If you are standing up a new server to monitor, our Windows VPS plans include enough CPU and RAM headroom for PerfMon logging without disturbing your application.



