Windows ortamlarında performans sorunlarını tespit etmek, kapasite planlaması yapmak veya uzun süreli sistem davranışlarını izlemek istediğinizde, Performance Counter’lar tam olarak ihtiyacınız olan şeydir. Grafik arayüzden Perfmon.exe’yi açıp tıklamak kolay olsa da, onlarca sunucuyu yönetirken veya otomatik uyarı mekanizmaları kurarken PowerShell’in gücünden faydalanmak çok daha verimli bir yaklaşım sunar. Bu yazıda, PowerShell ile performance counter’ları nasıl okuyacağınızı, izleyeceğinizi ve anlamlı raporlar haline getireceğinizi gerçek dünya senaryolarıyla ele alacağız.
Performance Counter Nedir?
Windows işletim sistemi, sistem kaynaklarını ve uygulama davranışlarını sürekli olarak sayaçlar aracılığıyla ölçer. Bu sayaçlar; işlemci kullanımından disk I/O’ya, ağ trafiğinden IIS request sayısına kadar yüzlerce farklı metriği kapsar. Her sayaç üç ana bileşenden oluşur:
- Category (Kategori): Sayacın ait olduğu grup. Örneğin
Processor,Memory,PhysicalDisk - Counter Name: Ölçülen spesifik metrik. Örneğin
% Processor Time,Available MBytes - Instance: Birden fazla örnek varsa hangi örneğin ölçüleceği. Örneğin çok çekirdekli sistemlerde her çekirdek ayrı bir instance’tır
PowerShell’de bu sayaçlara erişmek için temel olarak Get-Counter cmdlet’ini kullanırız. Ancak bunun yanında [System.Diagnostics.PerformanceCounter] .NET sınıfını da doğrudan kullanabilirsiniz, bu da daha ileri düzey senaryolarda daha fazla esneklik sağlar.
Get-Counter ile Temel Kullanım
Get-Counter cmdlet’i, Windows Server 2008 R2 ve üzeri sistemlerde varsayılan olarak gelir. Herhangi bir ek modül yüklemenize gerek yoktur.
En basit kullanımıyla işlemci kullanımını anlık olarak okuyalım:
# Anlık CPU kullanımını okuma
Get-Counter 'Processor(_Total)% Processor Time'
Bu komut size bir anlık değer döndürür. Ancak tek bir okuma çoğu zaman yanıltıcı olabilir. Belirli aralıklarla birden fazla okuma yapmak için -SampleInterval ve -MaxSamples parametrelerini kullanın:
# Her 5 saniyede bir, 10 örnek toplama
Get-Counter 'Processor(_Total)% Processor Time' `
-SampleInterval 5 `
-MaxSamples 10
Birden fazla sayacı aynı anda okumak da mümkündür. Bu, ilişkili metrikleri aynı zaman damgasıyla karşılaştırmanızı sağlar:
# Birden fazla sayacı aynı anda okuma
$counters = @(
'Processor(_Total)% Processor Time',
'MemoryAvailable MBytes',
'PhysicalDisk(_Total)Disk Reads/sec',
'PhysicalDisk(_Total)Disk Writes/sec'
)
Get-Counter -Counter $counters -SampleInterval 2 -MaxSamples 5
Çıktıyı daha okunabilir hale getirmek için Select-Object ve Format-Table kullanabilirsiniz:
# Çıktıyı düzenli biçimde gösterme
Get-Counter 'Processor(_Total)% Processor Time' -MaxSamples 5 |
Select-Object -ExpandProperty CounterSamples |
Select-Object TimeStamp, CookedValue |
Format-List
Mevcut Sayaçları Keşfetme
Binlerce performance counter mevcuttur ve hangi sayacın ne işe yaradığını bilmek başlı başına bir beceridir. PowerShell’de mevcut kategorileri ve sayaçları listelemek için şu yaklaşımı kullanabilirsiniz:
# Tüm mevcut performance counter kategorilerini listeleme
$categories = [System.Diagnostics.PerformanceCounterCategory]::GetCategories()
$categories | Select-Object CategoryName | Sort-Object CategoryName
# Belirli bir kategorideki sayaçları listeleme
$category = [System.Diagnostics.PerformanceCounterCategory]::new('Processor')
$category.GetCounters('_Total') | Select-Object CounterName
# Instance listesini alma
$category.GetInstanceNames()
Bu yöntem özellikle belirli bir uygulama veya servis için özel sayaçlar aradığınızda çok işe yarar. Örneğin SQL Server’ın yüklü olduğu bir sunucuda MSSQL ile başlayan kategorileri filtreleyerek SQL’e özgü tüm sayaçları görebilirsiniz.
Gerçek Dünya Senaryosu 1: Otomatik Kaynak İzleme Scripti
Bir üretim ortamında belirli eşik değerleri aşıldığında e-posta veya Event Log kaydı oluşturması gereken bir monitoring scripti yazalım. Bu senaryo özellikle kurumsal ortamlarda SCOM veya başka bir monitoring aracı olmayan durumlarda son derece değerlidir:
# ResourceMonitor.ps1
# Sistem kaynaklarını izler, eşik aşımında uyarı verir
param(
[int]$CpuThreshold = 85,
[int]$MemoryThresholdMB = 512,
[int]$DiskThresholdPercent = 90,
[int]$MonitoringDurationMinutes = 60,
[string]$LogPath = "C:LogsResourceMonitor.log"
)
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logEntry = "[$timestamp] [$Level] $Message"
Add-Content -Path $LogPath -Value $logEntry
Write-Host $logEntry
}
function Send-Alert {
param([string]$Subject, [string]$Body)
# Event Log'a yaz
$eventParams = @{
LogName = 'Application'
Source = 'ResourceMonitor'
EntryType = 'Warning'
EventId = 9001
Message = "$Subject`n$Body"
}
# Kaynak yoksa önce oluştur
if (-not [System.Diagnostics.EventLog]::SourceExists('ResourceMonitor')) {
New-EventLog -LogName Application -Source ResourceMonitor
}
Write-EventLog @eventParams
Write-Log "ALERT: $Subject" -Level "WARN"
}
$endTime = (Get-Date).AddMinutes($MonitoringDurationMinutes)
$alertCooldown = @{}
Write-Log "Kaynak izleme başladı. Süre: $MonitoringDurationMinutes dakika"
while ((Get-Date) -lt $endTime) {
$samples = Get-Counter -Counter @(
'Processor(_Total)% Processor Time',
'MemoryAvailable MBytes',
'LogicalDisk(C:)% Free Space'
) -SampleInterval 30 -MaxSamples 1
foreach ($sample in $samples.CounterSamples) {
$value = [math]::Round($sample.CookedValue, 2)
$counterPath = $sample.Path
# CPU kontrolü
if ($counterPath -match 'Processor' -and $value -gt $CpuThreshold) {
$alertKey = "CPU_HIGH"
$lastAlert = $alertCooldown[$alertKey]
if (-not $lastAlert -or ((Get-Date) - $lastAlert).TotalMinutes -gt 15) {
Send-Alert "Yüksek CPU Kullanımı" "CPU kullanımı: $value% (Eşik: $CpuThreshold%)"
$alertCooldown[$alertKey] = Get-Date
}
}
# Bellek kontrolü
if ($counterPath -match 'Memory' -and $value -lt $MemoryThresholdMB) {
$alertKey = "MEM_LOW"
$lastAlert = $alertCooldown[$alertKey]
if (-not $lastAlert -or ((Get-Date) - $lastAlert).TotalMinutes -gt 15) {
Send-Alert "Düşük Bellek" "Kullanılabilir bellek: ${value}MB (Eşik: ${MemoryThresholdMB}MB)"
$alertCooldown[$alertKey] = Get-Date
}
}
Write-Log "Sayaç: $counterPath | Değer: $value"
}
}
Write-Log "Kaynak izleme tamamlandı."
Bu scripti Görev Zamanlayıcısı (Task Scheduler) ile başlangıçta çalıştırabilir veya bir Windows Servisi olarak yapılandırabilirsiniz.
Gerçek Dünya Senaryosu 2: Uzak Sunucularda Performance Counter Okuma
Büyük ortamlarda onlarca sunucunun performansını tek bir konsoldan izlemek gerekir. Get-Counter‘ın -ComputerName parametresi bu iş için biçilmiş kaftandır:
# Uzak sunucularda performans verisi toplama
$servers = @('WebServer01', 'WebServer02', 'AppServer01', 'DBServer01')
$counters = @(
'Processor(_Total)% Processor Time',
'MemoryAvailable MBytes',
'Network Interface(*)Bytes Total/sec'
)
$results = foreach ($server in $servers) {
try {
$data = Get-Counter -ComputerName $server `
-Counter $counters `
-MaxSamples 3 `
-SampleInterval 10 `
-ErrorAction Stop
foreach ($sample in $data.CounterSamples) {
[PSCustomObject]@{
Sunucu = $server
Sayac = $sample.Path.Split('')[-1]
Deger = [math]::Round($sample.CookedValue, 2)
Zaman = $sample.TimeStamp
Durum = 'OK'
}
}
}
catch {
[PSCustomObject]@{
Sunucu = $server
Sayac = 'N/A'
Deger = 0
Zaman = Get-Date
Durum = "HATA: $($_.Exception.Message)"
}
}
}
# Sonuçları göster
$results | Sort-Object Sunucu, Sayac | Format-List
# CSV'ye aktar
$results | Export-Csv -Path "C:ReportsPerformanceReport_$(Get-Date -Format 'yyyyMMdd').csv" `
-NoTypeInformation `
-Encoding UTF8
Dikkat: Uzak sunuculardan veri toplarken güvenlik duvarının Remote Registry ve WMI portlarının açık olduğundan emin olun. Ayrıca çalıştıran hesabın uzak sunucuda Performance Monitor Users grubunda olması gerekir.
Gerçek Dünya Senaryosu 3: Uzun Süreli Veri Toplama ve Raporlama
Bir müşterinin sunucusunda aralıklı yavaşlama şikayeti var ve sorun tam sırada yakalanmıyor. Bu tür “phantom performance” sorunları için uzun süreli veri toplama çok değerlidir:
# LongTermCollector.ps1
# Saatlik ortalamalar alarak uzun süre veri toplar
param(
[string]$OutputPath = "C:PerfData",
[int]$CollectionHours = 24,
[int]$SampleIntervalSeconds = 60
)
if (-not (Test-Path $OutputPath)) {
New-Item -ItemType Directory -Path $OutputPath | Out-Null
}
$counters = @(
'Processor(_Total)% Processor Time',
'Processor(_Total)% Privileged Time',
'MemoryAvailable MBytes',
'MemoryPages/sec',
'PhysicalDisk(_Total)Avg. Disk Queue Length',
'PhysicalDisk(_Total)Avg. Disk sec/Read',
'PhysicalDisk(_Total)Avg. Disk sec/Write',
'SystemProcessor Queue Length',
'Network Interface(*)Bytes Total/sec'
)
$outputFile = Join-Path $OutputPath "PerfData_$(Get-Date -Format 'yyyyMMdd_HHmm').csv"
$totalSamples = ($CollectionHours * 3600) / $SampleIntervalSeconds
$collected = 0
Write-Host "Veri toplama başladı. Toplam örnek sayısı: $totalSamples" -ForegroundColor Green
Write-Host "Çıktı dosyası: $outputFile" -ForegroundColor Cyan
do {
$sample = Get-Counter -Counter $counters -MaxSamples 1 -ErrorAction SilentlyContinue
if ($sample) {
$row = [ordered]@{
Zaman = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
foreach ($cs in $sample.CounterSamples) {
# Sayaç adını temizle
$counterName = $cs.Path -replace '.*\\[^\]+\', '' -replace '\', '_'
$row[$counterName] = [math]::Round($cs.CookedValue, 4)
}
[PSCustomObject]$row | Export-Csv -Path $outputFile `
-NoTypeInformation `
-Append `
-Encoding UTF8
$collected++
if ($collected % 60 -eq 0) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - $collected örnek toplandı..." -ForegroundColor Yellow
}
}
Start-Sleep -Seconds $SampleIntervalSeconds
} while ($collected -lt $totalSamples)
Write-Host "Veri toplama tamamlandı. Toplam: $collected örnek" -ForegroundColor Green
Bu script 24 saat boyunca dakikada bir veri toplayarak 1440 satırlık bir CSV üretir. Bu CSV’yi Excel’de açıp grafik oluşturduğunuzda performans sorunlarının hangi saatlerde yaşandığını net olarak görebilirsiniz.
.NET PerformanceCounter Sınıfı ile İleri Düzey Kullanım
Get-Counter cmdlet’i çoğu senaryo için yeterlidir. Ancak daha hassas kontrol, özel sayaç oluşturma veya çok yüksek frekanslı örnekleme ihtiyacı durumunda .NET’in System.Diagnostics.PerformanceCounter sınıfını doğrudan kullanmak gerekir:
# .NET PerformanceCounter sınıfı ile anlık okuma
# CPU sayacı oluştur
$cpuCounter = New-Object System.Diagnostics.PerformanceCounter(
'Processor', # Kategori
'% Processor Time', # Sayaç adı
'_Total' # Instance
)
# Bellek sayacı
$memCounter = New-Object System.Diagnostics.PerformanceCounter(
'Memory',
'Available MBytes',
'' # Tek instance
)
# İlk okuma her zaman 0 döner, warmup için bekle
$null = $cpuCounter.NextValue()
Start-Sleep -Seconds 1
# Gerçek değerleri oku
for ($i = 1; $i -le 10; $i++) {
$cpu = [math]::Round($cpuCounter.NextValue(), 1)
$mem = [math]::Round($memCounter.NextValue(), 0)
Write-Host "[$i] CPU: $cpu% | Bellek: ${mem}MB" -ForegroundColor $(
if ($cpu -gt 80) { 'Red' } elseif ($cpu -gt 60) { 'Yellow' } else { 'Green' }
)
Start-Sleep -Seconds 2
}
# Kaynakları serbest bırak
$cpuCounter.Dispose()
$memCounter.Dispose()
Önemli not: .NET PerformanceCounter sınıfını kullandığınızda, NextValue() metodunun ilk çağrısı her zaman 0 döner. Bu özellikle % Processor Time gibi oran bazlı sayaçlarda geçerlidir. Her zaman bir warmup okuması yapın ve gerçek okuma için kısa bir bekleme ekleyin.
Özel Performance Counter Oluşturma
Kendi uygulamanız veya servisiniz için özel sayaçlar oluşturmak, monitoring altyapınızı uygulama düzeyine taşımanızı sağlar. Bu özellikle PowerShell ile yazdığınız servislerde veya scriptlerde işlem sayısını, hata oranını veya işlem sürelerini takip etmek istediğinizde kullanışlıdır:
# Özel performance counter kategorisi ve sayaçları oluşturma
$categoryName = "MyApp Monitoring"
$categoryHelp = "Özel uygulama izleme sayaçları"
# Önceden var olan kategoriyi kaldır
if ([System.Diagnostics.PerformanceCounterCategory]::Exists($categoryName)) {
[System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName)
Write-Host "Mevcut kategori silindi." -ForegroundColor Yellow
}
# Sayaç tanımlarını oluştur
$counters = New-Object System.Diagnostics.CounterCreationDataCollection
$processedRequests = New-Object System.Diagnostics.CounterCreationData
$processedRequests.CounterName = "Processed Requests/sec"
$processedRequests.CounterHelp = "Saniyede işlenen istek sayısı"
$processedRequests.CounterType = [System.Diagnostics.PerformanceCounterType]::RateOfCountsPerSecond32
$failedRequests = New-Object System.Diagnostics.CounterCreationData
$failedRequests.CounterName = "Failed Requests Total"
$failedRequests.CounterHelp = "Toplam başarısız istek sayısı"
$failedRequests.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems32
$avgProcessingTime = New-Object System.Diagnostics.CounterCreationData
$avgProcessingTime.CounterName = "Avg Processing Time (ms)"
$avgProcessingTime.CounterHelp = "Ortalama işlem süresi milisaniye"
$avgProcessingTime.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems32
$counters.Add($processedRequests)
$counters.Add($failedRequests)
$counters.Add($avgProcessingTime)
# Kategoriyi oluştur
[System.Diagnostics.PerformanceCounterCategory]::Create(
$categoryName,
$categoryHelp,
[System.Diagnostics.PerformanceCounterCategoryType]::SingleInstance,
$counters
)
Write-Host "Özel sayaç kategorisi oluşturuldu: $categoryName" -ForegroundColor Green
Write-Host "Perfmon.exe'de '$categoryName' altında görüntüleyebilirsiniz." -ForegroundColor Cyan
Özel sayaçları Perfmon.exe’de görüntülemek için uygulamanın çalışır durumda olması gerekir. Bu sayaçları Windows Görev Yöneticisi’nden göremeseniz de Perfmon ve PowerShell üzerinden okuyabilirsiniz.
Performans Verilerini Görselleştirme
Toplanan verileri grafiksel olarak göstermek için basit bir HTML raporu oluşturmak, özellikle müşterilere veya üst yönetime sunum yapmanız gerektiğinde çok işe yarar:
# Anlık performans özeti HTML raporu
function Get-PerformanceSnapshot {
param([string]$ComputerName = $env:COMPUTERNAME)
$counters = @(
'Processor(_Total)% Processor Time',
'MemoryAvailable MBytes',
'Memory% Committed Bytes In Use',
'PhysicalDisk(_Total)Avg. Disk Queue Length',
'SystemProcessor Queue Length'
)
$snapshot = Get-Counter -ComputerName $ComputerName `
-Counter $counters `
-MaxSamples 3 `
-SampleInterval 5
$avgValues = $snapshot.CounterSamples | Group-Object Path | ForEach-Object {
[PSCustomObject]@{
Sayac = $_.Name.Split('')[-1]
Ortalama = [math]::Round(($_.Group.CookedValue | Measure-Object -Average).Average, 2)
Min = [math]::Round(($_.Group.CookedValue | Measure-Object -Minimum).Minimum, 2)
Max = [math]::Round(($_.Group.CookedValue | Measure-Object -Maximum).Maximum, 2)
}
}
# HTML raporu oluştur
$htmlBody = $avgValues | ConvertTo-Html -Property Sayac, Ortalama, Min, Max `
-Title "Performans Raporu - $ComputerName" `
-PreContent "<h2>Sunucu: $ComputerName | Tarih: $(Get-Date)</h2>"
$reportPath = "C:ReportsPerfReport_$(Get-Date -Format 'yyyyMMdd_HHmm').html"
$htmlBody | Out-File -FilePath $reportPath -Encoding UTF8
Write-Host "Rapor oluşturuldu: $reportPath" -ForegroundColor Green
return $avgValues
}
# Kullanım
Get-PerformanceSnapshot -ComputerName 'localhost'
Sık Kullanılan Sayaçlar ve Ne Anlama Geldikleri
Günlük sysadmin hayatında en sık karşılaşacağınız sayaçlar ve kritik eşik değerleri şunlardır:
İşlemci (CPU) Sayaçları:
Processor(_Total)% Processor Time: Toplam CPU kullanımı. Sürekli %85 üzeri sorun işaretiProcessor(_Total)% Privileged Time: Kernel modu CPU kullanımı. %25 üzeri driver veya kernel sorununa işaret edebilirSystemProcessor Queue Length: İşlemci kuyruğu. Çekirdek başına 2’den fazla bekleme varsa darboğaz var demektir
Bellek Sayaçları:
MemoryAvailable MBytes: Kullanılabilir fiziksel bellek. 100MB altı kritikMemoryPages/sec: Sayfa hataları. Sürekli 1000 üzeri bellek baskısına işaret ederMemory% Committed Bytes In Use: Taahhüt edilen bellek yüzdesi. %90 üzeri tehlike bölgesi
Disk Sayaçları:
PhysicalDisk(_Total)Avg. Disk Queue Length: Disk kuyruğu. Disk başına 2’den fazla bekleme sorunludurPhysicalDisk(_Total)Avg. Disk sec/Read: Ortalama okuma gecikmesi. 25ms üzeri yavaş diskPhysicalDisk(_Total)Avg. Disk sec/Write: Ortalama yazma gecikmesi. 25ms üzeri yavaş disk
Ağ Sayaçları:
Network Interface(*)Bytes Total/sec: Toplam ağ trafiği. Interface kapasitesinin %80’ine yaklaşılması sorunNetwork Interface(*)Output Queue Length: Çıkış kuyruğu. 2’den fazla bekleme ağ tıkanıklığı demektir
Scheduled Task ile Otomatik Veri Toplama
Script’i manuel çalıştırmak yerine Windows Görev Zamanlayıcısı’nı PowerShell üzerinden yapılandırabilirsiniz:
# Performance monitoring görevini kaydet
$taskAction = New-ScheduledTaskAction `
-Execute 'PowerShell.exe' `
-Argument '-NonInteractive -WindowStyle Hidden -File "C:ScriptsLongTermCollector.ps1" -CollectionHours 8'
$taskTrigger = New-ScheduledTaskTrigger `
-Daily `
-At '08:00'
$taskSettings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit (New-TimeSpan -Hours 10) `
-MultipleInstances IgnoreNew `
-RunOnlyIfNetworkAvailable
$taskPrincipal = New-ScheduledTaskPrincipal `
-UserId 'DOMAINsvc-monitoring' `
-LogonType Password `
-RunLevel Highest
Register-ScheduledTask `
-TaskName 'PerformanceDataCollector' `
-TaskPath 'SysAdmin' `
-Action $taskAction `
-Trigger $taskTrigger `
-Settings $taskSettings `
-Principal $taskPrincipal `
-Description 'Günlük performans verisi toplama görevi' `
-Force
Write-Host "Görev başarıyla kaydedildi." -ForegroundColor Green
Sonuç
PowerShell ile performance counter kullanımı, Windows ortamlarında proaktif sistem yönetiminin temel taşlarından birini oluşturur. Get-Counter cmdlet’i ile başlayarak, ihtiyaçlarınız büyüdükçe .NET PerformanceCounter sınıfına geçiş yapabilir, hatta kendi özel sayaçlarınızı oluşturabilirsiniz.
Bu yazıda ele aldığımız yaklaşımları kendi ortamınıza uyarlayarak başlamak için şu yolu izlemenizi öneririm: önce mevcut sayaçları keşfedin ve ortamınızdaki kritik metrikleri belirleyin. Ardından basit bir izleme scripti yazın ve bunu Görev Zamanlayıcısı ile otomatize edin. Zamanla veri biriktikçe, normal davranışın ne olduğunu anlayacak ve anormallikler çok daha hızlı dikkatinizi çekecektir. Reaktif sysadmin olmak yerine, veriye dayalı proaktif bir yönetim anlayışı geliştirmek; hem gece uyarılmanızı azaltır hem de performans sorunlarını kullanıcılar şikayet etmeden önce tespit etmenizi sağlar.