PowerShell ile Windows Performans Verisi Toplama

Üretim ortamında bir Windows sunucusu aniden yavaşladığında, elinizin altında doğru araçlar yoksa sorunun kaynağını bulmak gerçek bir kabuса dönüşebilir. Görev Yöneticisi’ni açıp “bir şeyler yüksek” demek yetmez, bu bilgiyi kayıt altına almanız, geçmişle karşılaştırmanız ve anlamlı raporlar üretmeniz gerekir. İşte tam bu noktada PowerShell, Windows sunucu yöneticilerinin en güçlü silahı haline gelir.

Bu yazıda sizi teorik bir PowerShell dersinden geçirmeyeceğim. Gerçek ortamlarda karşılaştığım senaryolara, üretimde kullandığım scriptlere ve birkaç acı tecrübeden çıkardığım derslere odaklanacağım. Hazırsanız başlayalım.

Temel Performans Verisi Toplama: Get-Counter ile Başlamak

PowerShell’in yerleşik Get-Counter cmdlet’i, Windows Performance Monitor’ün sunduğu her şeye komut satırından erişmenizi sağlar. Ancak çoğu kişinin bilmediği şey, bu cmdlet’in ne kadar esnek olduğudur.

Önce basit bir CPU kullanım sorgusuyla başlayalım:

# Anlık CPU kullanımını al
Get-Counter 'Processor(_Total)% Processor Time'

# Belirli aralıklarla sürekli örnekleme
Get-Counter 'Processor(_Total)% Processor Time' -SampleInterval 5 -MaxSamples 12

Bu komut 5 saniye aralıklarla 12 örnek alır, yani 1 dakikalık bir CPU profili çıkarır. Peki ya birden fazla sayacı aynı anda izlemek istiyorsanız?

# CPU, RAM ve Disk I/O'yu birlikte izle
$sayaclar = @(
    'Processor(_Total)% Processor Time',
    'MemoryAvailable MBytes',
    'PhysicalDisk(_Total)Disk Reads/sec',
    'PhysicalDisk(_Total)Disk Writes/sec',
    'Network Interface(*)Bytes Total/sec'
)

Get-Counter -Counter $sayaclar -SampleInterval 10 -MaxSamples 6 |
    ForEach-Object {
        $_.CounterSamples | ForEach-Object {
            [PSCustomObject]@{
                Zaman     = $_.Timestamp
                Sayac     = $_.Path
                Deger     = [Math]::Round($_.CookedValue, 2)
            }
        }
    } | Format-Table -AutoSize

Bunu çalıştırdığınızda anlık olarak güzel bir tablo göreceksiniz. Ama verileri ekrana değil bir dosyaya kaydetmek istiyorsanız, Export-Csv ile kolayca yapabilirsiniz.

WMI ve CIM: Daha Derin Sistem Bilgisi

Get-Counter anlık performans sayaçları için mükemmeldir, ancak donanım bilgisi, kurulu servisler veya proses detayları için WMI/CIM sorguları çok daha güçlüdür. Modern PowerShell’de Get-CimInstance tercih edilmeli, eski Get-WmiObject yerine.

# CPU detayları ve mevcut yük
$cpu = Get-CimInstance -ClassName Win32_Processor
$os  = Get-CimInstance -ClassName Win32_OperatingSystem

$toplamRam   = [Math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$kullanilanRam = [Math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1MB, 2)
$bosRam      = [Math]::Round($os.FreePhysicalMemory / 1MB, 2)
$ramYuzdesi  = [Math]::Round(($kullanilanRam / $toplamRam) * 100, 1)

Write-Host "=== SISTEM OZETI ===" -ForegroundColor Cyan
Write-Host "Sunucu  : $($env:COMPUTERNAME)"
Write-Host "CPU     : $($cpu.Name)"
Write-Host "Cekirdek: $($cpu.NumberOfCores) fiziksel / $($cpu.NumberOfLogicalProcessors) mantiksal"
Write-Host "CPU Yuk : $($cpu.LoadPercentage)%"
Write-Host "RAM     : $kullanilanRam GB / $toplamRam GB ($ramYuzdesi%)" -ForegroundColor $(if ($ramYuzdesi -gt 85) {'Red'} else {'Green'})
Write-Host "Bos RAM : $bosRam GB"

Bu script özellikle sabah kontrol rutinlerinde işe yarar. RAM kullanımı yüzde 85’i geçtiğinde otomatik olarak kırmızı renkte uyarı verir.

Disk Performansı: Sadece Doluluk Değil, I/O da Önemli

Sistem yöneticilerinin yaptığı en yaygın hatalardan biri disk izlemeyi sadece doluluk yüzdesiyle sınırlamak. Bir disk yüzde 40 dolu olsa bile yüksek I/O latency nedeniyle sistemin canını yakabilir. İşte kapsamlı bir disk analiz scripti:

# Disk doluluk ve I/O analizi
function Get-DiskPerformans {
    param(
        [string]$Sunucu = $env:COMPUTERNAME,
        [int]    $EsikYuzdesi = 80
    )

    # Disk doluluk bilgisi
    $diskler = Get-CimInstance -ComputerName $Sunucu -ClassName Win32_LogicalDisk -Filter "DriveType=3"

    foreach ($disk in $diskler) {
        $toplamGB   = [Math]::Round($disk.Size / 1GB, 1)
        $bosGB      = [Math]::Round($disk.FreeSpace / 1GB, 1)
        $kullanilanGB = $toplamGB - $bosGB
        $kullanim   = [Math]::Round(($kullanilanGB / $toplamGB) * 100, 1)

        $renk = switch ($true) {
            ($kullanim -ge 90) { 'Red' }
            ($kullanim -ge $EsikYuzdesi) { 'Yellow' }
            default { 'Green' }
        }

        Write-Host ("[{0}] Toplam: {1}GB | Kullanilan: {2}GB | Bos: {3}GB | Doluluk: {4}%" -f
            $disk.DeviceID, $toplamGB, $kullanilanGB, $bosGB, $kullanim) -ForegroundColor $renk
    }

    # Disk I/O sayaclari
    Write-Host "`n--- DISK I/O (5 saniye ortalama) ---" -ForegroundColor Cyan
    $ioSayaclar = @(
        'PhysicalDisk(*)Avg. Disk sec/Read',
        'PhysicalDisk(*)Avg. Disk sec/Write',
        'PhysicalDisk(*)Disk Transfers/sec'
    )

    Get-Counter -Counter $ioSayaclar -SampleInterval 5 -MaxSamples 1 |
        Select-Object -ExpandProperty CounterSamples |
        Where-Object { $_.InstanceName -ne '_total' } |
        ForEach-Object {
            Write-Host ("  {0}: {1}" -f $_.Path.Split('')[-1], [Math]::Round($_.CookedValue, 4))
        }
}

Get-DiskPerformans -EsikYuzdesi 75

Proses Bazlı Kaynak Tüketimi: Suçluyu Bulmak

Sunucu yavaşladığında ilk yapmanız gereken şey hangi prosesin kaynakları tükettiğini tespit etmek. Windows’un Get-Process cmdlet’i oldukça yetersiz kalır bu konuda. Aşağıdaki yaklaşım çok daha bilgi verici:

# CPU ve RAM'i en cok tuketenleri bul
function Get-TopProsesler {
    param(
        [int]$TopN = 10,
        [ValidateSet('CPU','RAM','Ikisi')]
        [string]$SiralayaGore = 'CPU'
    )

    $prosesler = Get-Process | Select-Object `
        Name,
        Id,
        @{N='CPU_Saniye'; E={[Math]::Round($_.CPU, 1)}},
        @{N='RAM_MB';     E={[Math]::Round($_.WorkingSet64 / 1MB, 1)}},
        @{N='Handle';     E={$_.HandleCount}},
        @{N='Thread';     E={$_.Threads.Count}},
        @{N='BaslangicZamani'; E={
            try { $_.StartTime.ToString('dd.MM.yyyy HH:mm') }
            catch { 'N/A' }
        }}

    switch ($SiralayaGore) {
        'CPU'  { $prosesler | Sort-Object CPU_Saniye -Descending | Select-Object -First $TopN }
        'RAM'  { $prosesler | Sort-Object RAM_MB -Descending | Select-Object -First $TopN }
        'Ikisi'{
            Write-Host "=== CPU Bazli ===" -ForegroundColor Yellow
            $prosesler | Sort-Object CPU_Saniye -Descending | Select-Object -First $TopN | Format-Table
            Write-Host "=== RAM Bazli ===" -ForegroundColor Yellow
            $prosesler | Sort-Object RAM_MB -Descending | Select-Object -First $TopN | Format-Table
        }
    }
}

Get-TopProsesler -TopN 10 -SiralayaGore 'Ikisi'

Geçen yıl bir müşterinin IIS sunucusunda haftalık memory leak sorunu yaşıyorduk. Bu script sayesinde w3wp.exe prosesinin her Salı gecesi 4 GB’ı aşan RAM tüketimine ulaştığını belgeledik. Pattern netleşince sorun kaynağı bir uygulama havuzu yapılandırmasıydı.

Ağ Performansı İzleme

Ağ sorunları genellikle en zor tespit edilenlerdir çünkü anlık bir ekran görüntüsü yeterli olmaz, trend verisi gerekir.

# Network adapter istatistikleri
function Get-AgPerformans {
    $adaptorler = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration |
        Where-Object { $_.IPEnabled -eq $true }

    $agSayaclar = Get-Counter 'Network Interface(*)*' -SampleInterval 3 -MaxSamples 3 |
        Select-Object -ExpandProperty CounterSamples |
        Group-Object InstanceName

    foreach ($adaptor in $adaptorler) {
        Write-Host "`nAdaptor: $($adaptor.Description)" -ForegroundColor Cyan
        Write-Host "IP     : $($adaptor.IPAddress -join ', ')"

        # Ilgili sayaclari goster
        $agSayaclar | Where-Object {
            $_.Name -like "*$($adaptor.Description.Substring(0, [Math]::Min(20, $adaptor.Description.Length)))*"
        } | ForEach-Object {
            $ortalama = ($_.Group | Measure-Object -Property CookedValue -Average).Average
            $sayacAdi = $_.Group[0].Path.Split('')[-1]
            if ($ortalama -gt 0) {
                Write-Host ("  {0,-40}: {1:N2}" -f $sayacAdi, $ortalama)
            }
        }
    }
}

Get-AgPerformans

Scheduled Task ile Otomatik Performans Kayıt Sistemi

Anlık sorgular güzel, ama asıl güç düzenli veri toplamaktan gelir. Aşağıdaki script, belirli aralıklarla performans verisini CSV dosyasına kaydeden tam otomatik bir sistem kurar:

# Performans veri toplama ve kayit scripti
# C:ScriptsPerfCollector.ps1 olarak kaydedin

param(
    [string]$CiktiDizini  = "C:PerfLogs",
    [int]   $OrneklemeSuresi = 60,    # Dakika
    [int]   $OrneklemAraligi = 30    # Saniye
)

# Cikti dizini yoksa olustur
if (-not (Test-Path $CiktiDizini)) {
    New-Item -ItemType Directory -Path $CiktiDizini -Force | Out-Null
}

$dosyaAdi   = Join-Path $CiktiDizini ("Perf_{0}_{1}.csv" -f $env:COMPUTERNAME, (Get-Date -Format 'yyyyMMdd_HHmm'))
$maxOrnek   = [Math]::Ceiling($OrneklemeSuresi * 60 / $OrneklemAraligi)

$sayaclar = @(
    'Processor(_Total)% Processor Time',
    'MemoryAvailable MBytes',
    'MemoryPages/sec',
    'PhysicalDisk(_Total)Avg. Disk sec/Read',
    'PhysicalDisk(_Total)Avg. Disk sec/Write',
    'PhysicalDisk(_Total)Disk Transfers/sec',
    'Network Interface(*)Bytes Total/sec',
    'SystemProcessor Queue Length'
)

Write-Host "Veri toplama basliyor: $dosyaAdi" -ForegroundColor Green
Write-Host "Sure: $OrneklemeSuresi dk | Aralik: $OrneklemAraligi sn | Toplam ornek: $maxOrnek"

$baslik = "Zaman,CPU_Yuzde,RAM_BOS_MB,Sayfa_Hatalari,Disk_Okuma_Latency,Disk_Yazma_Latency,Disk_IOPS,Ag_Bytes,IslemciKuyrugu"
$baslik | Out-File $dosyaAdi -Encoding UTF8

Get-Counter -Counter $sayaclar -SampleInterval $OrneklemAraligi -MaxSamples $maxOrnek |
    ForEach-Object {
        $ornekler  = $_.CounterSamples
        $zaman     = $ornekler[0].Timestamp.ToString('yyyy-MM-dd HH:mm:ss')

        $degerler = $ornekler | Group-Object {
            $_.Path -replace '\\[^\]+', ''
        } | ForEach-Object { ($_.Group | Measure-Object -Property CookedValue -Average).Average }

        $satir = $zaman + ',' + ($degerler | ForEach-Object { [Math]::Round($_, 4) } | Join-String -Separator ',')
        $satir | Out-File $dosyaAdi -Append -Encoding UTF8
        Write-Host "[$zaman] Kaydedildi" -ForegroundColor Gray
    }

Write-Host "Tamamlandi: $dosyaAdi" -ForegroundColor Green

Bu scripti Scheduled Task olarak kaydetmek için:

# Her sabah 08:00'de calistir, 1 saatlik veri topla
$eylem = New-ScheduledTaskAction -Execute 'PowerShell.exe' `
    -Argument '-NonInteractive -File "C:ScriptsPerfCollector.ps1" -OrneklemeSuresi 60'

$tetikleyici = New-ScheduledTaskTrigger -Daily -At '08:00'

$ayarlar = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 2) `
    -RestartCount 2 -RestartInterval (New-TimeSpan -Minutes 5)

Register-ScheduledTask -TaskName 'PerfCollector_Sabah' `
    -Action $eylem -Trigger $tetikleyici -Settings $ayarlar `
    -RunLevel Highest -User 'SYSTEM'

Write-Host "Zamanlanmis gorev olusturuldu." -ForegroundColor Green

Çoklu Sunucu İzleme: Merkezi Yaklaşım

Tek sunucu güzel, ama ortamınızda 20-30 sunucu varsa ne yapacaksınız? PowerShell Remoting bu işi kolaylaştırır:

# Birden fazla sunucudan anlık ozet al
function Get-CokluSunucuOzet {
    param(
        [string[]]$Sunucular,
        [PSCredential]$Kimlik
    )

    $sonuclar = Invoke-Command -ComputerName $Sunucular -Credential $Kimlik -ScriptBlock {
        $os  = Get-CimInstance Win32_OperatingSystem
        $cpu = Get-CimInstance Win32_Processor

        $ramToplam    = [Math]::Round($os.TotalVisibleMemorySize / 1MB, 1)
        $ramBos       = [Math]::Round($os.FreePhysicalMemory / 1MB, 1)
        $ramKullanim  = [Math]::Round((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100, 1)

        [PSCustomObject]@{
            Sunucu      = $env:COMPUTERNAME
            CPU_Yuzdesi = $cpu.LoadPercentage
            RAM_Toplam  = $ramToplam
            RAM_Bos     = $ramBos
            RAM_Yuzde   = $ramKullanim
            Son_Boot    = $os.LastBootUpTime.ToString('dd.MM.yyyy HH:mm')
            Uptime_Gun  = ([DateTime]::Now - $os.LastBootUpTime).Days
            Durum       = if ($cpu.LoadPercentage -gt 90 -or $ramKullanim -gt 90) {'KRITIK'} elseif ($cpu.LoadPercentage -gt 70 -or $ramKullanim -gt 75) {'UYARI'} else {'NORMAL'}
        }
    } -ErrorAction SilentlyContinue

    $sonuclar | Sort-Object CPU_Yuzdesi -Descending | ForEach-Object {
        $renk = switch ($_.Durum) {
            'KRITIK' { 'Red' }
            'UYARI'  { 'Yellow' }
            default  { 'Green' }
        }
        Write-Host ("[{0,-15}] CPU: {1,3}% | RAM: {2,4}GB/{3,4}GB ({4,5}%) | Uptime: {5} gun | {6}" -f
            $_.Sunucu, $_.CPU_Yuzdesi, ($_.RAM_Toplam - $_.RAM_Bos), $_.RAM_Toplam, $_.RAM_Yuzde, $_.Uptime_Gun, $_.Durum) `
            -ForegroundColor $renk
    }
}

# Kullanim
$sunucuListesi = @('WebSrv01', 'WebSrv02', 'DbSrv01', 'AppSrv01')
$kimlik = Get-Credential -Message "Sunucu erisim bilgileri"
Get-CokluSunucuOzet -Sunucular $sunucuListesi -Kimlik $kimlik

Otomatik Uyarı ve Email Bildirimi

Veri toplamak yeterli değil, kritik eşikler aşıldığında haberdar olmanız gerekir:

# Esik tabanli uyari sistemi
function Test-PerformansEsikleri {
    param(
        [int]$CPU_Esik   = 90,
        [int]$RAM_Esik   = 85,
        [int]$Disk_Esik  = 80,
        [string]$EmailKimden = "[email protected]",
        [string]$EmailKime   = "[email protected]",
        [string]$SMTPSunucu  = "smtp.sirket.com"
    )

    $uyarilar  = @()
    $sunucu    = $env:COMPUTERNAME
    $zaman     = Get-Date -Format 'dd.MM.yyyy HH:mm:ss'

    # CPU kontrolu
    $cpuDeger = (Get-Counter 'Processor(_Total)% Processor Time' -SampleInterval 5 -MaxSamples 3 |
        Select-Object -ExpandProperty CounterSamples |
        Measure-Object -Property CookedValue -Average).Average
    $cpuDeger = [Math]::Round($cpuDeger, 1)

    if ($cpuDeger -gt $CPU_Esik) {
        $uyarilar += "[KRITIK] CPU kullanimi: $cpuDeger% (Esik: $CPU_Esik%)"
    }

    # RAM kontrolu
    $os        = Get-CimInstance Win32_OperatingSystem
    $ramYuzde  = [Math]::Round((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100, 1)
    if ($ramYuzde -gt $RAM_Esik) {
        $bosGB = [Math]::Round($os.FreePhysicalMemory / 1MB, 1)
        $uyarilar += "[KRITIK] RAM kullanimi: $ramYuzde% (Bos: $bosGB GB)"
    }

    # Disk doluluk kontrolu
    Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
        $doluluk = [Math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 1)
        if ($doluluk -gt $Disk_Esik) {
            $bosGB = [Math]::Round($_.FreeSpace / 1GB, 1)
            $uyarilar += "[UYARI] $($_.DeviceID) dolulugu: $doluluk% (Bos: $bosGB GB)"
        }
    }

    if ($uyarilar.Count -gt 0) {
        $govde = @"
Sunucu: $sunucu
Zaman : $zaman

Asagidaki esikler asildi:

$($uyarilar | ForEach-Object { "- $_" } | Out-String)

Bu mesaj otomatik olarak uretilmistir.
"@
        Send-MailMessage -From $EmailKimden -To $EmailKime `
            -Subject "[$sunucu] Performans Uyarisi - $($uyarilar.Count) sorun tespit edildi" `
            -Body $govde -SmtpServer $SMTPSunucu -Encoding UTF8

        Write-Host "Uyari emaili gonderildi: $($uyarilar.Count) sorun" -ForegroundColor Red
        $uyarilar | ForEach-Object { Write-Host "  $_" -ForegroundColor Yellow }
    } else {
        Write-Host "[$zaman] Tum metrikler normal sinirlar icinde." -ForegroundColor Green
    }
}

# Her 15 dakikada bir calistir
Test-PerformansEsikleri -CPU_Esik 90 -RAM_Esik 85 -Disk_Esik 80

Pratik Ipuçları ve Dikkat Edilmesi Gerekenler

Birkaç yıllık deneyimden damıttığım bazı notlar:

  • Get-Counter performance overhead’i: Çok sayıda sayacı çok sık sorgulamak sunucunuza yük bindirir. Üretim saatlerinde örnekleme aralığını 30 saniyenin altına düşürmeyin.
  • WinRM yapılandırması: Uzak sunucuları sorgulamak için Enable-PSRemoting ve uygun firewall kuralları şart. Bunu önceden test edin, kriz anında yapmaya çalışmayın.
  • Credential yönetimi: Scriptlerinizde asla plain-text parola kullanmayın. Get-Credential, Windows Credential Manager veya Secret Management modülünü tercih edin.
  • Log rotasyonu: CSV dosyalarınız zamanla şişer. Otomatik temizleme için Get-ChildItem | Where-Object {$_.CreationTime -lt (Get-Date).AddDays(-30)} | Remove-Item gibi bir temizlik görevi ekleyin.
  • PowerShell sürüm uyumluluğu: Join-String gibi cmdlet’ler PS 6.2+ gerektiriyor. Eski sunucularda $array -join ',' alternatifini kullanın.
  • Sayaç yolu yerelleştirmesi: Türkçe dil paketi yüklü Windows sunucularda sayaç yolları Türkçe gelebilir. Güvenlik için Get-Counter -ListSet * ile mevcut sayaçları doğrulayın.

Sonuç

PowerShell ile Windows performans izleme, pahalı lisanslara gerek kalmadan kurumsal düzeyde bir gözlemlenebilirlik altyapısı kurmanızı sağlar. Bu yazıda anlık veri toplamadan zamanlanmış kayıt sistemine, tek sunucudan çoklu ortam izlemeye kadar bir dizi pratik senaryo paylaştım.

Asıl mesaj şu: Veri toplamaya bugün başlayın. Bir sorun yaşamadan önce baseline oluşturmanız, o kriz anında size çok şey kazandırır. “Normalde CPU bu saatte yüzde kaçtaydı?” sorusunun cevabını verebilmek, sorun tespitini saatlerden dakikalara indirir.

Scriptleri doğrudan kopyalamak yerine kendi ortamınıza göre uyarlayın. Her sunucu farklıdır, her uygulama farklı bir profil çizer. PowerShell size ham gücü veriyor, bu gücü doğru yönlendirmek sizin işiniz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir