awk ile İstatistiksel Hesaplamalar: Ortalama, Toplam ve Standart Sapma

Üretim ortamında bir şeyler ters gidince, genellikle elimizin altındaki en temel araçlarla sorunu çözmek zorunda kalırız. Kibana çökmüş, Grafana datasource’u yanıt vermiyor, ama log dosyaları orada duruyor. İşte tam bu noktada awk gerçek değerini gösteriyor. Sadece metin işleme değil, ciddi istatistiksel hesaplamalar yapabilen, her Linux sistemde hazır bekleyen bu araç hakkında bildiklerimi aktarmak istiyorum.

awk Neden İstatistik İçin Uygun Bir Araç?

awk temelde bir satır işleme motorudur. Her satırı alanlarına ayırır, koşul kontrolü yapabilir ve matematiksel işlemler gerçekleştirebilir. Bu yapısı onu log analizi için biçilmiş kaftan yapar. Özellikle şu durumlar için idealdir:

  • Nginx, Apache, HAProxy gibi web sunucu loglarından yanıt süresi istatistikleri çıkarmak
  • Sistem metrik dosyalarını (/proc altındaki dosyalar dahil) analiz etmek
  • CSV ya da boşlukla ayrılmış veri dosyalarını işlemek
  • Anlık müdahale gerektiren durumlarda pipeline içinde hızlı hesaplama yapmak

Şimdi adım adım, gerçekten kullandığım senaryolar üzerinden gidelim.

Temel Yapı: Toplam ve Ortalama

En basit istatistiksel işlemden başlayalım. Diyelim ki bir web servisinizin yanıt sürelerini içeren bir log dosyanız var. Her satırda yanıt süresi milisaniye cinsinden yazıyor.

# response_times.txt içeriği:
# 120
# 340
# 89
# 512
# 203

awk '{sum += $1; count++} END {print "Toplam:", sum, "nSayi:", count, "nOrtalama:", sum/count}' response_times.txt

Bu üç satırlık awk kodu toplam, kayıt sayısı ve ortalamayı verir. END bloğu tüm satırlar işlendikten sonra çalışır, bunu unutmayın.

Gerçek dünya senaryosuna geçelim. Nginx log dosyasından yanıt sürelerini çekelim. Nginx’in default combined log formatında yanıt süresi yoktur ama biz production’da genellikle $request_time veya $upstream_response_time ekleriz. Şöyle bir format varsayalım:

192.168.1.1 - - [12/Nov/2024:10:23:45 +0300] "GET /api/health HTTP/1.1" 200 512 0.043

Son alan yanıt süresi (saniye cinsinden). Şimdi bu veriden ortalama çıkaralım:

awk '{sum += $NF; count++} END {
    if (count > 0)
        printf "Istek sayisi: %dnOrtalama yanit suresi: %.4f snn", count, sum/count
    else
        print "Veri bulunamadi"
}' /var/log/nginx/access.log

$NF son alanı temsil eder, alan sayısından bağımsız. Bu küçük detay, farklı log formatlarında kodu tekrar yazmaktan kurtarır.

Standart Sapma Hesabı

Standart sapma, ortalamanın ne kadar güvenilir olduğunu söyler. Ortalama yanıt süreniz 200ms ama standart sapmanız 800ms ise, aslında son derece tutarsız bir servisiniz var demektir. Bunu tek geçişte hesaplamak için Welford’un online algoritmasını ya da iki geçişli klasik yöntemi kullanabiliriz.

İki geçişli yöntem daha anlaşılır olduğu için onunla başlayalım:

# İki geçişli standart sapma hesabı
awk '{data[NR] = $NF; sum += $NF} 
END {
    mean = sum / NR
    for (i = 1; i <= NR; i++) {
        diff = data[i] - mean
        variance += diff * diff
    }
    variance = variance / NR
    stddev = sqrt(variance)
    printf "Ortalama: %.4fnVaryans: %.4fnStandart Sapma: %.4fn", mean, variance, stddev
}' /var/log/nginx/access.log

Bu kodu kullanırken dikkat etmeniz gereken bir nokta var: tüm veriyi bellekte tutuyoruz (data[NR]). Milyonlarca satırlık dosyalarda bellek sorun yaratabilir. Bu durumda tek geçişli Welford algoritması daha güvenli:

# Welford online algoritması - büyük dosyalar için
awk '{
    count++
    delta = $NF - mean
    mean += delta / count
    delta2 = $NF - mean
    M2 += delta * delta2
}
END {
    if (count > 1) {
        variance = M2 / count
        stddev = sqrt(variance)
        printf "Kayit sayisi: %dn", count
        printf "Ortalama: %.4fn", mean
        printf "Standart sapma: %.4fn", stddev
    }
}' /var/log/nginx/access.log

Welford algoritması numerik açıdan daha kararlıdır ve tüm veriyi bellekte tutmaz. Büyük log dosyalarında bunu tercih edin.

HTTP Status Kodlarına Göre Gruplu İstatistik

Sadece genel ortalama değil, HTTP durum kodlarına göre ayrı istatistikler de isteyebilirsiniz. 200 isteklerinin ortalama yanıt süresi 50ms iken 500 hatalarının 2000ms olması, sorunu doğrudan gösterir.

awk '{
    status = $9      # HTTP status kodu 9. alanda
    resp_time = $NF  # yanıt süresi son alanda
    sum[status] += resp_time
    count[status]++
    
    # Varyans için sum of squares
    sumsq[status] += resp_time * resp_time
}
END {
    print "StatustSayitOrtalama(sn)tStdDev"
    for (s in count) {
        mean = sum[s] / count[s]
        variance = (sumsq[s] / count[s]) - (mean * mean)
        if (variance < 0) variance = 0  # floating point hatası önlemi
        stddev = sqrt(variance)
        printf "%st%dt%.4ftt%.4fn", s, count[s], mean, stddev
    }
}' /var/log/nginx/access.log | sort -k1

Bu scriptte sumsq ile alternatif varyans formülü kullandım: Var(X) = E[X²] - (E[X])². Tek geçişte varyans hesaplamak için pratik bir yöntem ama çok büyük sayılarla çalışırken floating point hassasiyeti sorun çıkarabilir. O durumlarda Welford’a dönün.

Yüzdelik Dilimler: Percentile Hesabı

Ortalama ve standart sapma bazen yanıltıcı olabilir. SLA hesaplamalarında genellikle P95 veya P99 değerleri daha anlamlıdır. “Kullanıcıların yüzde 99’u isteğini 500ms içinde tamamlıyor” gibi.

awk ile percentile hesabı için veriyi sıralamak gerekir. sort ile birlikte çalışalım:

# P50, P90, P95, P99 hesabı
sort -n response_times.txt | awk '
BEGIN {
    p[1] = 50; p[2] = 90; p[3] = 95; p[4] = 99
}
{
    data[NR] = $1
}
END {
    n = NR
    for (i in p) {
        idx = int(n * p[i] / 100)
        if (idx < 1) idx = 1
        printf "P%d: %.4fn", p[i], data[idx]
    }
    printf "Min: %.4fn", data[1]
    printf "Max: %.4fn", data[n]
}
'

Nginx loglarında $NF alanını ayırıp pipeline ile kullanalım:

awk '{print $NF}' /var/log/nginx/access.log | 
grep -E '^[0-9]+.?[0-9]*$' | 
sort -n | 
awk '
{data[NR] = $1; sum += $1}
END {
    n = NR
    mean = sum / n
    
    # Standart sapma
    for (i = 1; i <= n; i++) {
        diff = data[i] - mean
        variance += diff * diff
    }
    stddev = sqrt(variance / n)
    
    # Percentile indexleri
    p50_idx = int(n * 0.50)
    p90_idx = int(n * 0.90)
    p95_idx = int(n * 0.95)
    p99_idx = int(n * 0.99)
    
    printf "=== Yanit Suresi Istatistikleri ===n"
    printf "Toplam istek: %dn", n
    printf "Min:          %.3f snn", data[1]
    printf "Max:          %.3f snn", data[n]
    printf "Ortalama:     %.3f snn", mean
    printf "Std Sapma:    %.3f snn", stddev
    printf "P50 (medyan): %.3f snn", data[p50_idx]
    printf "P90:          %.3f snn", data[p90_idx]
    printf "P95:          %.3f snn", data[p95_idx]
    printf "P99:          %.3f snn", data[p99_idx]
}
'

grep -E '^[0-9]+.?[0-9]*$' satırı önemli. Log dosyalarında zaman zaman yanıt süresinin “-” olarak yazıldığı durumlar olur (örneğin bağlantı kesilmesi). Bu satır olmadan awk hata vermez ama hesaplamalar bozulur.

Zaman Dilimine Göre İstatistik: Saatlik Analiz

Günün hangi saatlerinde servisiniz zorlanıyor? Bu soruyu yanıtlamak için saatlik gruplu istatistik çok işe yarar. Özellikle kapasiteli planlama yaparken ya da bir anomali araştırırken.

awk '{
    # Log formatından saati çek: [12/Nov/2024:10:23:45
    match($4, /[0-9]+:[0-9]+:[0-9]+/)
    time_str = substr($4, RSTART, RLENGTH)
    split(time_str, t, ":")
    hour = t[1]
    
    resp = $NF + 0  # sayıya çevir
    
    hour_sum[hour] += resp
    hour_count[hour]++
    hour_sumsq[hour] += resp * resp
}
END {
    print "SaattIstektOrtalamatStdDev"
    for (h = 0; h <= 23; h++) {
        if (hour_count[h] > 0) {
            mean = hour_sum[h] / hour_count[h]
            variance = (hour_sumsq[h] / hour_count[h]) - (mean * mean)
            if (variance < 0) variance = 0
            stddev = sqrt(variance)
            printf "%02d:00t%dt%.3ftt%.3fn", h, hour_count[h], mean, stddev
        }
    }
}' /var/log/nginx/access.log

Bu çıktı size gün içindeki yük dağılımını verir. Peak saatlerde hem istek sayısı hem de standart sapma artıyorsa, sisteminiz basınç altında tutarsız davranıyor demektir.

CPU ve Bellek Metrikleri İçin awk

sar veya vmstat çıktılarını analiz etmek için de awk güçlü bir seçenek. Diyelim ki sar -u 1 60 çıktısını bir dosyaya kaydettiniz:

# sar çıktısından CPU kullanım istatistikleri
sar -u 1 60 2>/dev/null | 
awk '/^[0-9]/{
    cpu_user = $3
    cpu_system = $5
    cpu_idle = $8
    
    user_sum += cpu_user
    sys_sum += cpu_system
    idle_sum += cpu_idle
    
    # Varyans için
    user_sumsq += cpu_user * cpu_user
    
    count++
}
END {
    if (count > 0) {
        user_mean = user_sum / count
        user_variance = (user_sumsq / count) - (user_mean * user_mean)
        if (user_variance < 0) user_variance = 0
        
        printf "Ornek sayisi: %dn", count
        printf "Ort. CPU User: %.2f%%n", user_mean
        printf "Ort. CPU Sys:  %.2f%%n", sys_sum / count
        printf "Ort. CPU Idle: %.2f%%n", idle_sum / count
        printf "CPU User StdDev: %.2f%%n", sqrt(user_variance)
    }
}'

Anomali Tespiti: Z-Score ile Aykırı Değer Bulma

İstatistiksel hesaplamaları bir adım öteye taşıyalım. Z-score, bir değerin ortalamadan kaç standart sapma uzakta olduğunu söyler. |z| > 3 olan değerler genellikle anormal kabul edilir.

# Z-score ile aykırı yanıt sürelerini tespit et
awk '{print $NF, $0}' /var/log/nginx/access.log | 
grep -E '^[0-9]' | 
awk '
# İlk geçiş: ortalama ve standart sapma hesapla
NR == FNR {
    sum += $1
    sumsq += $1 * $1
    count++
    next
}
# İkinci geçiş: z-score hesapla ve aykırıları göster  
FNR == 1 {
    mean = sum / count
    variance = (sumsq / count) - (mean * mean)
    if (variance < 0) variance = 0
    stddev = sqrt(variance)
    threshold = 3
}
{
    z = ($1 - mean) / stddev
    if (z > threshold || z < -threshold) {
        printf "ANOMALI [z=%.2f]: %sn", z, $0
    }
}
' /dev/stdin /dev/stdin

Bu yaklaşım çalışmaz çünkü aynı stdin’i iki kez okuyamayız. Bunun yerine dosyaya kaydedip iki geçişli kullanalım ya da tek geçişte Welford ile halledelim ve aykırı satırları sonunda yazdıralım:

awk '{
    data[NR] = $NF
    line[NR] = $0
    count++
    delta = $NF - mean
    mean += delta / count
    delta2 = $NF - mean
    M2 += delta * delta2
}
END {
    if (count < 2) exit
    stddev = sqrt(M2 / count)
    
    print "=== Anomali Raporu (|z| > 3) ==="
    anomaly_count = 0
    for (i = 1; i <= count; i++) {
        if (stddev > 0) {
            z = (data[i] - mean) / stddev
            if (z > 3 || z < -3) {
                printf "[z=%.2f] %sn", z, line[i]
                anomaly_count++
            }
        }
    }
    printf "nToplam %d anomali tespit edildi (toplam: %d istek)n", anomaly_count, count
}' /var/log/nginx/access.log

Bu script tüm log dosyasını bellekte tutar. Büyük dosyalar için önce tail -n 10000 ile son 10 bin satırla çalışmak daha akıllıca.

Pratik İpuçları ve Dikkat Edilmesi Gerekenler

Üretimde bu scriptleri kullanırken öğrendiğim bazı şeyler var:

  • Alan ayırıcı: Varsayılan ayırıcı boşluktur. CSV için -F,, pipe için -F'|' kullanın
  • Sıfıra bölme: count > 0 kontrolünü ihmal etmeyin. Boş dosyalarda hata alırsınız
  • Floating point: awk double precision kullanır ama çok büyük sayılarda yine de hassasiyet kaybı olabilir
  • Bellek yönetimi: Array’e veri toplarken büyük dosyalarda bellek tüketimine dikkat edin
  • Performans: awk C ile yazılmış ve son derece hızlıdır, ama gereksiz regex kullanımı yavaşlatır
  • gawk vs mawk: gawk daha fazla özellik sunar, mawk daha hızlıdır. Ubuntu’da mawk varsayılandır, bazı regex özellikleri eksik olabilir

Bir de şunu söyleyeyim: bu hesaplamaları canlı sistemde büyük log dosyalarında yaparken nice ile çalıştırın:

nice -n 19 awk '{...}' /var/log/nginx/access.log

I/O baskısını azaltmak için ionice da eklenebilir:

ionice -c 3 nice -n 19 awk '{sum += $NF; count++} END {print sum/count}' access.log

Sonuç

awk salt bir metin işleme aracı değil, aynı zamanda küçük ama işlevsel bir istatistik motoru. Yukarıdaki örneklerle toplamı, ortalamayı, standart sapmayı, percentile değerlerini ve Z-score tabanlı anomali tespitini nasıl yapacağınızı gördünüz.

Bu araçların gerçek gücü, başka bir şeye ihtiyaç duymadan, kurulu her Linux sistemde çalışmasından geliyor. Monitoring stack’iniz çöktüğünde, acil bir analiz gerektiğinde ya da sadece hızlıca “şu son 1 saatin P95 yanıt süresi neydi?” sorusuna cevap bulmak istediğinizde, bu scriptler elinizin altında hazır olsun.

Welford algoritmasını standart sapma için aklınıza kazıyın. Tek geçişli, bellek dostu, numerik olarak kararlı. Production’da büyük dosyalarla çalışırken bu fark gerçekten hissediliyor. Geri kalanlar pratikle gelir.

Bir yanıt yazın

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