Windows’ta Performans Sayacı (Performance Counter) Oluşturma

Monitoring altyapısı kuruyorsunuz, her şey hazır, ama tam da ihtiyacınız olan metriği hiçbir yerde bulamıyorsunuz. Uygulama geliştirme ekibi “biz zaten log yazıyoruz” diyor, siz ise o logları Grafana’ya taşımakla uğraşmak yerine doğrudan Performance Monitor’da görmek istiyorsunuz. İşte tam bu noktada custom performance counter oluşturma devreye giriyor.

Windows’un built-in performans sayaç altyapısı aslında son derece güçlü bir mekanizma. .NET uygulamalarından PowerShell scriptlerine, C++ servislerinden Python uygulamalarına kadar her şey bu altyapıya veri yazabilir. Sonrasında bu verileri Performance Monitor, Nagios, Zabbix, Prometheus (windows_exporter ile) veya herhangi bir izleme aracıyla okuyabilirsiniz.

Performans Sayacı Nedir ve Nasıl Çalışır

Windows Performance Counter altyapısı, işletim sisteminin derinlerine işlenmiş bir veri toplama mekanizmasıdır. Üç temel kavram üzerine kuruludur:

  • Category (Kategori): Sayaçları mantıksal olarak gruplar. Örneğin “Processor”, “Memory”, “Network Interface” birer kategoridir.
  • Counter: Kategorinin altında ölçülen belirli bir metrik. “% Processor Time” veya “Available Bytes” gibi.
  • Instance: Aynı sayacın birden fazla örneği olduğunda kullanılır. Birden fazla CPU çekirdeği veya birden fazla network arayüzü gibi.

Veri okuma tarafında ise iki mod var: SingleInstance modunda kategorinin tek bir instance’ı olur, MultiInstance modunda ise birden fazla nesne ayrı ayrı izlenebilir. Bunu doğru seçmek önemli, çünkü sonradan değiştirmek için kategoriyi silip yeniden oluşturmanız gerekiyor.

PowerShell ile Hızlı Counter Oluşturma

Hızlı prototipleme veya basit izleme ihtiyaçları için PowerShell yeterli. Aşağıdaki örnek, bir web uygulamasının aktif kullanıcı sayısını izlemek için kullanılabilecek bir kategori ve sayaç oluşturuyor.

# Yönetici olarak çalıştırın
$categoryName = "WebApp Metrics"
$counterName = "Active Users"

# Önce kategorinin var olup olmadığını kontrol et
if ([System.Diagnostics.PerformanceCounterCategory]::Exists($categoryName)) {
    [System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName)
    Write-Host "Mevcut kategori silindi, yeniden oluşturuluyor..."
}

# Counter data collection oluştur
$counterDataCollection = New-Object System.Diagnostics.CounterCreationDataCollection

# İlk sayaç: Aktif kullanıcılar
$activeUsers = New-Object System.Diagnostics.CounterCreationData
$activeUsers.CounterName = "Active Users"
$activeUsers.CounterHelp = "Sistemde aktif olan kullanıcı sayisi"
$activeUsers.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems32

$counterDataCollection.Add($activeUsers) | Out-Null

# Kategoriyi oluştur
[System.Diagnostics.PerformanceCounterCategory]::Create(
    $categoryName,
    "Web uygulamasi metrikleri",
    [System.Diagnostics.PerformanceCounterCategoryType]::SingleInstance,
    $counterDataCollection
)

Write-Host "Kategori ve sayac basariyla olusturuldu."

Bu scripti çalıştırdıktan sonra Performance Monitor’ı açıp “Add Counters” dediğinizde “WebApp Metrics” kategorisini görebilirsiniz. Güzel, ama sayacın içi hâlâ boş. Şimdi bu sayaca veri yazmayı görelim.

Sayaca Değer Yazma

# Sayaca değer yazan PowerShell script örneği
$counter = New-Object System.Diagnostics.PerformanceCounter(
    "WebApp Metrics",
    "Active Users",
    $false  # readOnly = false
)

# Değeri sıfırla
$counter.RawValue = 0

# Değeri artır
$counter.Increment()
$counter.Increment()
$counter.Increment()

Write-Host "Mevcut değer: $($counter.RawValue)"

# Belirli bir değere set et
$counter.RawValue = 47

Write-Host "Güncel değer: $($counter.RawValue)"

# Temizlik
$counter.Dispose()

Gerçek dünya senaryosunda bu kodu bir servise veya zamanlanmış göreve gömmek gerekiyor. Örneğin her dakika Active Directory’deki aktif oturumları sayıp bu sayaca yazan bir script çok işe yarayabilir.

Birden Fazla Sayaç ile Kapsamlı Kategori Oluşturma

Tek sayaçla iş bitmiyor genellikle. Bir e-ticaret uygulaması izliyorsanız sipariş sayısı, başarısız ödeme denemeleri, ortalama yanıt süresi gibi birden fazla metrik isteyebilirsiniz. Hepsini tek kategoride toplamak yönetimi kolaylaştırır.

$categoryName = "ECommerce Application"

if ([System.Diagnostics.PerformanceCounterCategory]::Exists($categoryName)) {
    [System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName)
}

$counters = New-Object System.Diagnostics.CounterCreationDataCollection

# Sipariş sayısı (kümülatif)
$orders = New-Object System.Diagnostics.CounterCreationData
$orders.CounterName = "Total Orders"
$orders.CounterHelp = "Uygulama basladigindan bu yana islenen toplam siparis sayisi"
$orders.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems64
$counters.Add($orders) | Out-Null

# Saniyedeki sipariş oranı
$ordersPerSec = New-Object System.Diagnostics.CounterCreationData
$ordersPerSec.CounterName = "Orders Per Second"
$ordersPerSec.CounterHelp = "Saniyede islenen siparis sayisi"
$ordersPerSec.CounterType = [System.Diagnostics.PerformanceCounterType]::RateOfCountsPerSecond32
$counters.Add($ordersPerSec) | Out-Null

# Başarısız ödeme girişimleri
$failedPayments = New-Object System.Diagnostics.CounterCreationData
$failedPayments.CounterName = "Failed Payment Attempts"
$failedPayments.CounterHelp = "Basarisiz odeme girisimi sayisi"
$failedPayments.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems32
$counters.Add($failedPayments) | Out-Null

# Aktif sepet sayısı
$activeCarts = New-Object System.Diagnostics.CounterCreationData
$activeCarts.CounterName = "Active Shopping Carts"
$activeCarts.CounterHelp = "Aktif alissveris sepeti sayisi"
$activeCarts.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems32
$counters.Add($activeCarts) | Out-Null

[System.Diagnostics.PerformanceCounterCategory]::Create(
    $categoryName,
    "E-ticaret uygulamasi performans metrikleri",
    [System.Diagnostics.PerformanceCounterCategoryType]::SingleInstance,
    $counters
)

Write-Host "$categoryName kategorisi $($counters.Count) sayac ile olusturuldu."

.NET Uygulamasından Sayaca Yazmak

Eğer izlemek istediğiniz uygulama bir .NET servisi ise, performans sayaçlarını doğrudan uygulama koduna entegre etmek en temiz yöntem. Uygulama geliştiricilere şu örneği göstermek genellikle işe yarıyor:

# Bu bir C# kod örneğidir, .csproj dosyanıza referans eklemeniz gerekir
# PowerShell'den bu kodu test etmek için Add-Type kullanabilirsiniz

$csharpCode = @"
using System;
using System.Diagnostics;

public class AppMetrics {
    private PerformanceCounter _requestCounter;
    private PerformanceCounter _errorCounter;
    private const string CategoryName = "MyDotNetApp";

    public AppMetrics() {
        // Sayaçlar zaten oluşturulmuş olmalı (setup script ile)
        _requestCounter = new PerformanceCounter(CategoryName, "Total Requests", false);
        _errorCounter = new PerformanceCounter(CategoryName, "Total Errors", false);
    }

    public void RecordRequest() {
        _requestCounter.Increment();
    }

    public void RecordError() {
        _errorCounter.Increment();
    }

    public long GetRequestCount() {
        return _requestCounter.RawValue;
    }
}
"@

Add-Type -TypeDefinition $csharpCode
$metrics = New-Object AppMetrics
$metrics.RecordRequest()
$metrics.RecordRequest()
$metrics.RecordError()
Write-Host "Toplam istek: $($metrics.GetRequestCount())"

Bu yaklaşımın avantajı, uygulama geliştirme ekibinin monitoring altyapısını kendi kodlarına natural bir şekilde entegre edebilmesi. Deploy pipeline’ınıza sayaç oluşturma scriptini setup adımı olarak eklersiniz, uygulama da bu sayaçlara yazar. Temiz ve bakımı kolay.

MultiInstance Sayaç: Birden Fazla Bileşeni İzleme

Mikro servis mimarisinde her servisin kendi instance’ını ayrı ayrı izlemek isteyebilirsiniz. Bunun için MultiInstance kategorisi oluşturmanız gerekiyor.

$categoryName = "Microservice Health"

if ([System.Diagnostics.PerformanceCounterCategory]::Exists($categoryName)) {
    [System.Diagnostics.PerformanceCounterCategory]::Delete($categoryName)
}

$counters = New-Object System.Diagnostics.CounterCreationDataCollection

$requestCount = New-Object System.Diagnostics.CounterCreationData
$requestCount.CounterName = "Request Count"
$requestCount.CounterHelp = "Servisin aldigi toplam istek sayisi"
$requestCount.CounterType = [System.Diagnostics.PerformanceCounterType]::NumberOfItems64
$counters.Add($requestCount) | Out-Null

$errorRate = New-Object System.Diagnostics.CounterCreationData
$errorRate.CounterName = "Error Rate"
$errorRate.CounterHelp = "Hata orani"
$errorRate.CounterType = [System.Diagnostics.PerformanceCounterType]::RateOfCountsPerSecond32
$counters.Add($errorRate) | Out-Null

# MultiInstance olarak oluştur
[System.Diagnostics.PerformanceCounterCategory]::Create(
    $categoryName,
    "Mikro servis saglik metrikleri",
    [System.Diagnostics.PerformanceCounterCategoryType]::MultiInstance,
    $counters
)

# Farklı servis instance'ları için sayaç yaz
$services = @("OrderService", "PaymentService", "InventoryService", "NotificationService")

foreach ($service in $services) {
    $counter = New-Object System.Diagnostics.PerformanceCounter(
        $categoryName, "Request Count", $service, $false
    )
    # Her servise farklı başlangıç değeri ver (simülasyon amaçlı)
    $counter.RawValue = Get-Random -Minimum 100 -Maximum 10000
    $counter.Dispose()
    Write-Host "$service icin sayac degeri yazildi."
}

Bu yapıyı kurduktan sonra Performance Monitor’da “Microservice Health” kategorisini seçip instance olarak hangi servisi izlemek istediğinizi seçebilirsiniz. Grafana’daki Prometheus data source ile kullanıyorsanız windows_exporter’ın custom collector özelliği bu instance’ları otomatik olarak keşfeder.

Sayaç Değerlerini Okuma ve Raporlama

Oluşturduğunuz sayaçları programatik olarak okumak da gerekebilir. Özellikle kendi alert mekanizmanızı kuruyorsanız veya periyodik rapor üretiyorsanız:

# Belirli bir kategorideki tüm sayaçları oku
function Get-PerformanceCounterValues {
    param(
        [string]$CategoryName,
        [string]$InstanceName = $null
    )
    
    $category = New-Object System.Diagnostics.PerformanceCounterCategory($CategoryName)
    $counterNames = $category.GetCounters() | Select-Object -ExpandProperty CounterName
    
    $results = @{}
    
    foreach ($counterName in $counterNames) {
        try {
            if ($InstanceName) {
                $counter = New-Object System.Diagnostics.PerformanceCounter(
                    $CategoryName, $counterName, $InstanceName
                )
            } else {
                $counter = New-Object System.Diagnostics.PerformanceCounter(
                    $CategoryName, $counterName
                )
            }
            
            # Rate counter'lar için iki okuma gerekir
            $counter.NextValue() | Out-Null
            Start-Sleep -Milliseconds 500
            $value = $counter.NextValue()
            
            $results[$counterName] = [Math]::Round($value, 2)
            $counter.Dispose()
        } catch {
            $results[$counterName] = "Okunamadi: $_"
        }
    }
    
    return $results
}

# Kullanım örneği
$values = Get-PerformanceCounterValues -CategoryName "ECommerce Application"
foreach ($key in $values.Keys) {
    Write-Host "$key : $($values[$key])"
}

# JSON olarak kaydet (monitoring sistemine göndermek için)
$values | ConvertTo-Json | Out-File -FilePath "C:Monitoringmetrics_$(Get-Date -Format 'yyyyMMdd_HHmmss').json"

Bu fonksiyonu bir zamanlanmış görev içine koyup çıktıyı merkezi log sunucunuza veya Elasticsearch’e gönderebilirsiniz. Evet, windows_exporter bu işi daha temiz yapar ama bazen ortamınızda ajanı kurma imkânı olmuyor, o zaman bu tür çözümler kurtarıcı oluyor.

Sayaç Türleri Hakkında Pratik Rehber

Doğru sayaç türünü seçmek kritik öneme sahip. Yanlış türü seçerseniz veriler anlamsız görünür.

  • NumberOfItems32 / NumberOfItems64: En basit tür. Anlık değer depolar. Sıra derinliği, aktif bağlantı sayısı gibi “şu an kaç tane var” sorusunun cevabı için kullanın.
  • RateOfCountsPerSecond32 / RateOfCountsPerSecond64: İki okuma arasındaki farkı saniyeye normalize eder. Saniyedeki işlem sayısı, saniyedeki hata sayısı için idealdir.
  • CounterDelta32 / CounterDelta64: İki okuma arasındaki ham farkı verir. Normalizasyon yapmaz. Genellikle pek tercih edilmez.
  • AverageTimer32: Bir işlemin ortalama süresini hesaplar. Mutlaka bir base counter ile birlikte kullanılır.
  • PercentageActive: Yüzde değerler için. 0-100 arasında değer döndürür.
  • ElapsedTime: Bir olaydan bu yana geçen süreyi saniye cinsinden verir.

Pratikte en çok kullandığım türler NumberOfItems64 ve RateOfCountsPerSecond32. İkisi birlikte genellikle ihtiyacın yüzde seksenini karşılıyor.

Mevcut Sayaçları Listeleme ve Yönetme

Ortamda hangi custom sayaçların olduğunu görmek, eski ve kullanılmayan kategorileri temizlemek de bakım işinin parçası.

# Sistemdeki tüm performance counter kategorilerini listele
# Microsoft'un built-in kategorilerini filtrele, custom olanları bul
$builtInPrefixes = @("Processor", "Memory", "PhysicalDisk", "LogicalDisk", 
                      "Network", "System", ".NET", "ASP", "HTTP", "WMI",
                      "Hyper-V", "MSSQL", "W3SVC", "Windows")

$allCategories = [System.Diagnostics.PerformanceCounterCategory]::GetCategories()

Write-Host "=== Muhtemelen Custom Kategoriler ===" -ForegroundColor Green
foreach ($category in $allCategories) {
    $isBuiltIn = $false
    foreach ($prefix in $builtInPrefixes) {
        if ($category.CategoryName -like "$prefix*") {
            $isBuiltIn = $true
            break
        }
    }
    
    if (-not $isBuiltIn) {
        Write-Host "`nKategori: $($category.CategoryName)"
        Write-Host "Tip: $($category.CategoryType)"
        
        try {
            $counters = $category.GetCounters()
            foreach ($counter in $counters) {
                Write-Host "  - $($counter.CounterName) [$($counter.CounterType)]"
            }
        } catch {
            # MultiInstance kategorilerde GetCounters() farklı çalışır
            $instances = $category.GetInstanceNames()
            if ($instances.Count -gt 0) {
                $counters = $category.GetCounters($instances[0])
                foreach ($counter in $counters) {
                    Write-Host "  - $($counter.CounterName) [$($counter.CounterType)]"
                }
                Write-Host "  Instances: $($instances -join ', ')"
            }
        }
    }
}

# Kullanılmayan bir kategoriyi sil
# [System.Diagnostics.PerformanceCounterCategory]::Delete("Eski Kategori Adi")

Zabbix Entegrasyonu ile Custom Counter İzleme

Zabbix kullanan ortamlarda bu sayaçları otomatik olarak discovery edip izlemeye almak mümkün. Zabbix agent’ın Windows performans sayacı desteğini kullanarak şöyle bir item key tanımlayabilirsiniz:

# Zabbix item key formatı:
# perf_counter["ECommerce ApplicationTotal Orders"]
# perf_counter["Microservice Health(OrderService)Request Count"]

# Bunu test etmek için zabbix_get kullanın:
# zabbix_get -s 127.0.0.1 -k "perf_counter["ECommerce ApplicationTotal Orders"]"

# PowerShell ile Zabbix'e gönderilecek JSON formatında değer üret
$categoryName = "ECommerce Application"
$timestamp = [int][double]::Parse((Get-Date -UFormat %s))

$category = New-Object System.Diagnostics.PerformanceCounterCategory($categoryName)
$counterNames = $category.GetCounters() | Select-Object -ExpandProperty CounterName

$zabbixData = @()
foreach ($counterName in $counterNames) {
    $counter = New-Object System.Diagnostics.PerformanceCounter($categoryName, $counterName)
    $counter.NextValue() | Out-Null
    Start-Sleep -Milliseconds 200
    $value = $counter.NextValue()
    
    $zabbixData += @{
        host = $env:COMPUTERNAME
        key = "custom.perf[$counterName]"
        value = "$([Math]::Round($value, 0))"
        clock = $timestamp
    }
    $counter.Dispose()
}

# zabbix_sender ile gönderilebilecek format
$zabbixData | ConvertTo-Json -Depth 3

Yaygın Sorunlar ve Çözümleri

Birkaç yılda öğrendiğim sancılı noktaları paylaşayım:

“Access Denied” hatası: Sayaç oluşturmak için yönetici yetkisi gerekiyor. PowerShell’i “Run as Administrator” ile açmayı unutmayın. Servis hesaplarına sayaca yazma yetkisi vermek için “Performance Log Users” grubuna ekleyin.

Sayaç Performance Monitor’da görünmüyor: Kategoriyi oluşturduktan sonra bazen birkaç saniye beklemeniz gerekiyor. perfmon.exe’yi kapatıp tekrar açın. Hâlâ görünmüyorsa lodctr /r komutu ile performance counter registry’sini yeniden oluşturun.

RateOfCountsPerSecond değeri her zaman 0 gösteriyor: Bu tür sayaçlar için iki ardışık okuma yapmanız ve aralarında en az 1 saniye beklemeniz gerekiyor. Tek okumada her zaman 0 döner.

Kategori zaten var ama counter yok gibi davranıyor: Registry’de bozulma olmuş olabilir. Kategoriyi silip yeniden oluşturun. Silmeden önce mevcut yapıyı kaydedin.

# Performance counter registry'sini yeniden oluştur (son çare)
# Bu komut tüm built-in sayaçları sıfırlar, custom olanları değil
lodctr /r

# Belirli bir uygulama için counter manifest yeniden yükle
# lodctr "C:WindowsSystem32perfos.ini"

# Mevcut counter listesini dışa aktar (yedek amaçlı)
reg export HKLMSYSTEMCurrentControlSetServicesPerfProcPerformance perf_backup.reg

Sonuç

Windows performance counter altyapısı, çoğu sysadmin’in hak ettiği kadar dikkat göstermediği bir mekanizma. Uygulamalarınızın özel metriklerini bu sisteme entegre ettiğinizde, farklı izleme araçları arasında köprüler kurmak yerine hepsinin anlayacağı ortak bir dil elde ediyorsunuz. Zabbix, Nagios, SCOM, Prometheus, Performance Monitor; hepsi bu sayaçları okuyabiliyor.

Önerdiğim yaklaşım: Yeni bir uygulama deploy ederken geliştirme ekibiyle oturun ve “Bu uygulamanın sağlığını gösteren 5 metrik nedir?” sorusunu sorun. Bu metrikleri custom performance counter olarak tanımlayın, oluşturma scriptini deployment pipeline’ına ekleyin, izleme sistemine konfigürasyonu yapın. Altı ay sonra o uygulamada bir sorun çıktığında elinizde tarihsel veri olacak ve “hep böyle miydi bu böyle?” sorusuna veri ile cevap verebileceksiniz.

Monitoring bir kültür meselesi. Araç ne olursa olsun, önemli olan neyi izleyeceğinizi bilmek ve o veriyi tutarlı biçimde toplamak. Windows performance counter altyapısı bu yolda sağlam bir temel sağlıyor.

Bir yanıt yazın

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