Ana içeriğe geç

Bölüm 14:Sistemi Gözlemlemek

6 dakikalık okumaGüncelleme:

Şimdiye kadar okuduğun her şey — syscall’lar, sayfa hataları, context switch’ler, dinamik bağlama — soyut kavramlar değil. Hepsi şu anda makinende olup bitiyor ve hepsini doğrudan ölçebilirsin.

Bu bölüm yeni bir kavram öğretmiyor. Öğrendiklerini görünür kılan araçları tanıtıyor; çünkü bir mekanizmayı gerçekten anladığını, onu kendi sisteminde gösterebildiğinde anlarsın.

Bu bölümde neyi çözüyoruz?

  • Bir programın yaptığı syscall’ları strace ile canlı izleyeceğiz.
  • /proc üzerinden çalışan bir process’in bellek haritasını ve durumunu okuyacağız.
  • perf ile cache miss ve branch miss sayaçlarını ölçüp 3. ve 5. bölümdeki iddiaları doğrulayacağız.

strace: Kernel’a Yapılan Her Çağrı#

2. bölümde syscall’ın user mode ile kernel mode arasındaki resmi kapı olduğunu görmüştük. strace bu kapıdan geçen her şeyi yazdırır.

En basit programla başlayalım:

strace -f echo merhaba

Çıktı uzun görünür ama tanıdıktır. Başta execve vardır — 6. bölümde gördüğümüz, programı belleğe yükleyen çağrı. Ardından dinamik bağlayıcının kütüphane arayışı gelir: openat, mmap, fstat. Sonunda tek bir write ve exit_group.

Gürültüyü azaltıp yalnızca ilgilendiğin çağrılara bakabilirsin:

strace -e trace=openat,read,write -f cat /etc/hostname

Hangi çağrıların ne kadar sürdüğünü özetlemek için:

strace -c ls /usr/bin

Bu tablo, “program neden yavaş” sorusuna çoğu zaman ilk cevabı verir. Binlerce stat çağrısı görüyorsan sorun disk erişim desenindedir; futex çağrıları tepedeyse thread’ler kilit için bekliyordur.

Bir yana: strace neden programı yavaşlatır?

strace, ptrace syscall’ı üzerinden çalışır ve izlenen her çağrıda process’i durdurup izleyiciye kontrolü verir. Yani her syscall iki ek context switch demektir. Yoğun I/O yapan bir programı strace altında çalıştırmak onu kat kat yavaşlatabilir; ölçüm yaparken bunu hesaba kat. Üretimdeki bir process’e bağlanacaksan -p PID ile bağlanabilirsin, ama aynı yavaşlama orada da geçerlidir.

ltrace ise aynı işi kütüphane çağrıları için yapar: malloc, strcpy, printf gibi libc fonksiyonlarını gösterir. 8. bölümde gördüğümüz PLT mekanizmasını çalışırken izlemenin en kolay yoludur.

/proc: Kernel’ın Açık Defteri#

Bir önceki bölümde /proc’un diskte karşılığı olmayan sanal bir dosya sistemi olduğunu gördük. Çalışan her process için orada bir dizin vardır ve içi, o process hakkında kernel’ın bildiği her şeydir.

Kendi kabuğunun bellek haritasına bakalım:

cat /proc/self/maps

Her satır bir bellek bölgesidir: adres aralığı, izinler, dosya eşlemesi. 9. bölümde anlatılan her şey burada somut hâlde durur — r-xp kod bölgeleri, rw-p veri bölgeleri, [heap], [stack] ve eşlenmiş kütüphaneler.

Bellek kullanımının ayrıntılı dökümü için:

grep -E 'VmRSS|VmSize|Threads' /proc/self/status

VmSize process’in sanal adres alanı, VmRSS ise gerçekten RAM’de duran kısımdır. İkisi arasındaki fark genelde büyüktür ve bu tuhaf değildir: demand paging sayesinde eşlenmiş ama hiç dokunulmamış sayfalar fiziksel bellek tüketmez. Bir programın “8 GB bellek kullanıyor” görünüp aslında 200 MB tutmasının açıklaması budur.

Sayfa hatası sayaçlarını da doğrudan okuyabilirsin:

ps -o min_flt,maj_flt,cmd -p $$

min_flt (minor fault) sayfanın zaten bellekte olduğu, yalnızca eşlemenin kurulması gereken durumdur — ucuzdur. maj_flt (major fault) ise diske gidilmesi gerektiği anlamına gelir ve pahalıdır. Sağlıklı bir sistemde major fault sayısı düşük kalır; sürekli artıyorsa makine bellek baskısı altındadır.

perf: Donanım Sayaçlarını Oku#

strace kernel’a yapılan çağrıları gösterir, ama CPU’nun içinde ne olduğunu söylemez. perf tam olarak orayı ölçer; modern işlemcilerdeki performans sayaçlarını okur.

3. bölümde satır bazlı ve sütun bazlı matris erişiminin cache davranışı yüzünden farklı hızlarda çalıştığını söylemiştik. Bunu artık iddia olarak bırakmak zorunda değiliz:

cache-testi.c
#include <stdlib.h>
#define N 4096

int main(int argc, char **argv) {
  static int m[N][N];
  long sum = 0;

  if (argc > 1) {                    // sütun bazlı: her erişim yeni cache line
    for (int j = 0; j < N; j++)
      for (int i = 0; i < N; i++)
        sum += m[i][j];
  } else {                           // satır bazlı: ardışık erişim
    for (int i = 0; i < N; i++)
      for (int j = 0; j < N; j++)
        sum += m[i][j];
  }
  return sum == 0;
}

Derleyip iki varyantı ölç:

gcc -O1 -o cache-testi cache-testi.c
perf stat -e cache-misses,cache-references,instructions ./cache-testi
perf stat -e cache-misses,cache-references,instructions ./cache-testi sutun

İki çalıştırma neredeyse aynı sayıda talimat yürütür, ama sütun bazlı sürümün cache miss sayısı kat kat yüksektir ve geçen süre buna paralel artar. Aynı işi yapan iki döngü arasındaki farkın tamamı bellek erişim deseninden gelir.

Aynı araçla 5. bölümdeki branch prediction iddiasını da sınayabilirsin:

perf stat -e branches,branch-misses ./program

Sıralanmış bir dizide dallanma tahmin edilebilir olduğu için branch-miss oranı düşer; aynı veri karıştırıldığında oran yükselir ve program yavaşlar.

Zamanın nerede harcandığını fonksiyon düzeyinde görmek için:

perf record -g ./program
perf report

İzin gerekebilir. perf çekirdek sayaçlarına eriştiği için çoğu dağıtımda /proc/sys/kernel/perf_event_paranoid değeri erişimi kısıtlar. Kendi makinende geçici olarak gevşetebilirsin, ama paylaşılan bir sunucuda bu ayarı değiştirmeden önce iki kez düşün: sayaçlar yan kanal ölçümü için de kullanılabilir.

ftrace ve eBPF: Kernel’ın İçine Bakmak#

strace kernel’ın kapısındaki trafiği, perf CPU sayaçlarını gösterir. Peki kernel’ın içinde hangi fonksiyonun çağrıldığını görmek istersen?

ftrace, kernel’a gömülü izleme altyapısıdır ve /sys/kernel/debug/tracing altından yönetilir. Ham kullanımı zahmetlidir; trace-cmd gibi sarmalayıcılar işi kolaylaştırır.

eBPF ise bugünün cevabı. Kernel içinde çalışan küçük, doğrulanmış programlar yazmanı sağlar: bir kernel fonksiyonuna bağlanır, olay gerçekleştiğinde veri toplar. Kernel’ı yeniden derlemeye veya modül yüklemeye gerek yoktur ve doğrulayıcı, programın sonsuz döngüye girmesini veya geçersiz belleğe erişmesini baştan engeller.

bcc ve bpftrace araç setleri bunu kullanılabilir kılar:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @[comm] = count(); }'

Bu tek satır, sistemdeki hangi programın kaç kez dosya açtığını canlı sayar. Ctrl-C ile durdurduğunda özet tabloyu verir. Aynı yaklaşımla disk gecikmelerini, TCP yeniden iletimlerini veya scheduler kuyruk sürelerini ölçebilirsin — üstelik üretim yükü altında, strace’in getirdiği ağır bedel olmadan.

Pratik: Yavaş Bir Programı Teşhis Etmek#

Araçları tek tek bilmek yetmiyor; hangi soruyu hangi araca soracağını bilmek gerekiyor. Kaba bir karar sırası:

  1. Program CPU’da mı bekliyor, yoksa bir şey mi bekliyor? top ya da time. Gerçek süre (real) CPU süresinden (user + sys) çok büyükse program bekliyordur.
  2. Bekliyorsa neyi bekliyor? strace -c ile hangi syscall’da zaman geçtiğine bak. read/futex ağırlıklıysa I/O ya da kilit sorunu vardır.
  3. CPU’da yanıyorsa nerede? perf record ile profil çıkar, perf report ile sıcak fonksiyonu bul.
  4. Sıcak fonksiyon beklenenden yavaşsa neden? perf stat ile cache-miss ve branch-miss oranlarına bak; bellek erişim deseni mi, dallanma mı sorun?
  5. Bellek büyüyor mu? /proc/PID/status içindeki VmRSS değerini zaman içinde izle.

Bu sıralama, tahmin etmek yerine ölçmeye zorlar. Performans işinde en pahalı hata, yanlış yeri optimize etmektir.

Özet#

Bu bölümle birlikte teoriyi ölçmeye çevirdik. Son bölümde geriye dönüp bakacağız: parçaları birleştirdiğimizde bilgisayarın bir programı çalıştırma hikâyesi baştan sona nasıl görünüyor?

15. bölüme devam et: Son Söz