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ı (
/procaltı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 > 0kontrolünü ihmal etmeyin. Boş dosyalarda hata alırsınız - Floating point:
awkdouble 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:
awkC ile yazılmış ve son derece hızlıdır, ama gereksiz regex kullanımı yavaşlatır - gawk vs mawk:
gawkdaha fazla özellik sunar,mawkdaha hızlıdır. Ubuntu’damawkvarsayı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.
