Hata Ayıklama ve Ayrıntılı İnceleme
Yazılım geliştirmenin altın kurallarından birisi yazdığınız programın sizin beklentinize göre değil programa yapacağı işi nasıl tarif ettiğinize göre çalışacağıdır. Bu dersimizde, hata barındıran ve beklediğimizden fazla kaynak tüketen programlarda hata ayıklama ve ayrıntılı inceleme için kullanacağımız faydalı yöntemleri inceleyeceğiz.
Hata ayıklama (Debugging)
Printf ile hata ayıklama ve loglama
“En etkili hata ayıklama aracı dikkatli düşünce ve akıllıca kullanılmış print cümlecikleridir.” — Brian Kernighan, Unix for Beginners.
Bir programda hata ayıklama için kullanılan ilk yöntem hatanın tespit edildiği kod bloklarının etrafına yerleştirilen print cümlecikleridir. Hata ile ilgili yeterince bilgi sahibi olana kadar print cümleciklerini ekleme işlemi yinelenir.
İkinci yöntem ise print cümlecikleri yerine programınızda loglama yaklaşımını kullanmaktır. Loglama, birkaç sebeple print cümleciklerinden daha iyi bir yöntemdir:
- print cümleciklerinde olduğu gibi sadece standard output’a değil dosyalara, soketlere ve hatta uzak sunuculara logları yazabilirsiniz.
- Loglama farklı önem derecelerinin (INFO, DEBUG, WARN, ERROR vb.) kullanılmasını destekler.Bu önem dereceleri sayesinde log mesajlarını filtreleyerek daha kolay inceleyebilirsiniz.
- İlk defa ortaya çıkan hatalar için log çıktısının hatayı daha kolay anlamanıza yetecek miktarda bilgi barındırması olasıdır.
Mesajları loglayan örnek bir program kodunu şu Python dosyasından inceleyebilirsiniz:
$ python logger.py
# Sadece print cümlecikleri kullanarak işlenmemiş çıktı
$ python logger.py log
# Formatlı log çıktısı
$ python logger.py log ERROR
# Sadece önem seviyesi ERROR veya üstü olan mesajları göster
$ python logger.py color
# Renkli ve formatlı log mesajları göster
Log mesajlarını daha okunabilir hale getirmek için favori yöntemlerinden birisi logları renkler kullanarak kodlamaktır. Şu ana kadar terminalinizin bazı şeyleri daha okunaklı hale getirmek için renkleri kullandığının farkına varmışsınızdır. Pekiyi, terminal bunu nasıl yapar? ls
veya grep
gibi programlarANSI escape codes adı verilen özel karakter dizilimlerini kullanır. Bu karakter dizilimleri komut satırınıza çıktıları nasıl renklendirmesi gerektiğini söyler. Örneğin, echo -e "\e[38;2;255;0;0mBu satır kırmızı renktedir\e[0m"
komutunu çalıştırdığınızda terminalinizde kırmızı renkli olarak Bu satır kırmızı renktedir
mesajını yazacaktır. Aşağıdaki örnek script çoğu RGB kodlu rengin nasıl yazdırılabileceğini gösterir.
#!/usr/bin/env bash
for R in $(seq 0 20 255); do
for G in $(seq 0 20 255); do
for B in $(seq 0 20 255); do
printf "\e[38;2;${R};${G};${B}m█\e[0m";
done
done
done
Çevirenin Notu: RGB, Red(Kırmızı)-Green(Yeşil)-Blue(Mavi) renk bileşenlerinden her birinin 8 bit ve 0-255 arasında bir değer ile ifade edildiği ve 17 miliyona yakın rengin temsil edilebildiği bir renklendirme şemasıdır.
3. parti program logları
Daha kapsamlı programlar geliştirmeye başladıkça bu programların farklı programlara bağımlılıkları oluşmaya başlayacaktır. Web sunucuları, veritabanları veya mesajlaşma programları bu tür bağımlılıklar için tipik örneklerdir. Bu tür programlar ile etkileşim halinde zaman zaman bu programların loglarını da incelemeniz gerekecektir, sadece kendi programınızın logları bazı durumlarda yetersiz kalacaktır.
Çoğu program kendi loglarını sistemlerinizde bir konuma yazarlar. UNIX ve benzeri sistemlerde bu logları /var/log
dizinine yazmak genel geçer bir yöntemdir. Örneğin, NGINX web sunucusu loglarını /var/log/nginx
dizinine yazar. Çoğu Linux sistem kurulu servisleri veya çalışan servisler gibi pek çok işlemi kontrol etmek için systemd
adı verilen özel bir hayalet program (daemon) kullanır. systemd
log çıktılarını /var/log/journal
dizini içine özel bir formatta yazar. systemd
loglarını incelemek için journalctl
komutunu kullanabilirsiniz. Benzer şekilde macOS’da da /var/log/system.log
dosyası yer alır ancak gün geçtikçe daha fazla sayıda program loglarını bu konuma değil sistemin kendi loguna yazar. macOS’da system.log içeriğini log show
komutu ile görüntüleyebilirsiniz. Çoğu UNIX sistemde dmesg
komutu ile işletim sisteminin kernel loglarını görüntüleyebilirsiniz.
Sistemin kendi loguna yazmak için logger
adı verilen komut satırı programını kullanabilirsiniz. Aşağıdaki örnekte logger
kullanılarak sistem loguna yazma ve sistem logundan görüntüleme işlemlerinin nasıl yapılabileceğini görebilirsiniz. Çoğu programlama dili bindingler aracılığı ile sistem loguna erişim ve kullanım imkanı sunar.
logger "Merhaba sistem logu"
# macOS üzerinde logu görüntülemek için
log show --last 1m | grep Merhaba
# Linux üzerinde logu görüntülemek için
journalctl --since "1m ago" | grep Merhaba
Veri işleme dersimizde de gördüğünüz üzere log mesajları büyük miktarda ve zengin içeriğe sahip oldukları için log mesajlarından faydalı bilgileri ayıklamak için bu mesajları işlemek ve filtrelemek gerekecektir. Eğer yoğun olarak journalctl
ve log show
ile log mesajlarını inceliyorsanız bu komutların flaglerini kullanarak ilk aşama filtreleme işlemlerini rahatlıkla yapabilirsiniz. Bununla birlikte gelişmiş log gösterim ve log dosyalarına konumlama özellikleri sunan lnav
gibi araçları da kullanabilirsiniz.
Hata ayıklayıcılar (Debuggers)
printf cümlecikleri ile yapacağınız hata ayıklama işlemleri ihtiyacınızı karşılamıyorsa hata ayıklayıcı (debugger) adı verilen özel programları kullanmalısınız. Hata ayıklayıcılar programınız ile çalışma anında etkileşime geçmenizi sağlarlar. Bu sayede:
- Programınızın çalışmasını belirttiğiniz herhangi bir satırda duraklatabilirsiniz
- Programınızı her seferinde bir komut olacak şekilde duraklatarak çalıştırabilirsiniz
- Programınız hata ile sonlandığında tüm program değişkenlerinin içeriğini inceleyebilirsiniz
- Belirli bir koşul oluştuğunda programınızı sonlandırabilirsiniz
- Daha bir çok gelişmiş özellik sayesinde hata ayıklama işlemlerini daha rahat yapabilirsiniz
Çoğu programlama dili ve ortamının kendi hata ayıklayıcısı vardır. Python için bu hata ayıklayıcı pdb
komutu ile kullanabileceğiniz Python Debugger’dır.
Aşağıda pdb
‘nin desteklediği bazı komutlar yer almaktadır:
- l(ist) - Aktif kod satırı etrafındaki 11 kod satırını gösterir veya önceki listeleme işlemini devam ettirir.
- s(tep) - Aktif kod satırını çalıştırıp, mümkün olan ilk anda da çalışmayı duraklatır.
- n(ext) - Aktif fonksiyonu sonraki satıra kadar veya fonksiyonun sonlandığı satıra kadar çalıştırır.
- b(reak) - breakpoint adı verilen duraklama noktası tanımlar (verilen argümana bağlı olarak).
- p(rint) - İfadeyi aktif bağlamda çalıştırır ve değerini yazdırır. Buna ilave olarak
pprint
kullanan pp alternatifi da vardır. - r(eturn) - Programın çalışmasını aktif fonksiyon sonuna kadar devam ettirir.
- q(uit) - Hata ayıklayıcıyı durdurur.
Gelin şimdi pdb
kullanarak aşağıdaki hatalı Python kodunu düzeltelim. (Ders videosuna bakınız).
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(n):
if arr[j] > arr[j+1]:
arr[j] = arr[j+1]
arr[j+1] = arr[j]
return arr
print(bubble_sort([4, 2, 1, 8, 7, 6]))
Python yorumlanan bir dil olduğu için komut satırından pdb
‘yi kullanarak komutları ve kod parçalarını çalıştırabiliriz. pdb
‘ye alternatif olarak IPython
REPL ortamını kullanan ipdb
‘yi de kullanabilirsiniz. ipdb
cümle tamamlama, kod renklendirme, daha iyi yığın izleme ve daha iyi gözlemleme gibi imkanları pdb
ile benzer bir ara yüz sağlayarak kullanabilmemizi sağlar.
Daha alt seviyede programlama işlemleri için gdb
( gdb kullanımını kolaylaştıran pwndbg
eklentisini de kullanabilirsiniz) ve lldb
gibi araçları kullanabilirsiniz. Bu araçlar C benzeri dillerdeki hata ayıklama işlemleri için optimize edilmişlerdir ve aşağı yukarı yukarıda ele aldığımız iş akışını kullanarak program değişkenlerini görüntülemenizi sağlarlar.
Çevirmenin Notu: REPL, Read-Evaluate-Print-Loop adı verilen iş akışının kısaltmasıdır. Çoğunlukla yorumlanan dillerde (Python, JavaScript vb.) etkileşimli geliştirme ortamında girdiğiniz kodun yorumlayıcı tarafından okunması (read), sonrasında değerlendirilmesi (evaluate) ve sonuçlarının da genelde terminalde gösterilmesini (print) ifade eden döngüyü (loop) ifade eder.
Özelleşmiş Araçlar
Kendi geliştirmediğiniz ve derlenmiş bir programda hata ayıklama yapmanız gerektiğinde bu işlem için de özelleşmiş araçlar vardır. Programlar sadece işletim sistemi kernelinin yapabileceği işlemleri yapabilmek için Sistem çağrıları (System Calls) adı verilen komutları kullanırlar. Bu sistem çağırılarını Linux üzerinde strace
, macOS ve BSD üzerinde isedtrace
gibi araçlar ile yakalayıp inceleyebilirsiniz. dtrace
, kendi dili olan D
dilini kullandığı için kullanımı biraz zor olabilir. dtrace
‘i kullanan ancak strace
‘e benzer ara yüz sağlayan dtruss
macOS ve BSD üzerinde size daha rahat bir kullanım sağlayacaktır (daha fazla ayrıntı için şu linkteki içeriği inceleyebilirsiniz).
Aşağıdaki örnekte ls
komutunun kullandığı stat
sistem çağrısının izini sürmek için strace
ve dtruss
araçlarının kullanımını görebilirsiniz. strace
hakkında daha ayrıntılı bilgi sahibi olmak için şu linkteki içeriği okumanızı tavsiye ediyorum.
# Linux
sudo strace -e lstat ls -l > /dev/null
4
# macOS
sudo dtruss -t lstat64_extended ls -l > /dev/null
Programınızdaki bazı sorunları tespit etmek için programın ağ üzerinden alıp verdiği paketleri incelemeniz gerekebilir. Bu durumda tcpdump
ve Wireshark gibi paket analizi araçlarını kullanarak ağ paketi içerikleri inceleyebilir ve çeşitili kriterlere göre filtreleyebilirsiniz.
Web geliştiricileri için Chrome ve Firefox tarayıcılarının geliştirici araçları oldukça kullanışlıdır. Bu araçlardan bazıları şunlardır:
- Kaynak kodu görüntüleme - herhangi bir web sitesinin HTML/CSS/JS kodlarını incelemek için kullanılabilir.
- Canlı HTML, CSS, JS düzenleme - test için web sitesinin kodunu ve stilini canlı olarak değiştirmek için kullanılabilir.
- Javascript komut satırı - Javascript komutlarını çalıştırmak için kullanılabilir.
- Ağ - web sitesinin yaptığı ağ isteklerinin zamanlamasını analiz etmek için kullanılabilir.
- Depolama - web sitesinin çerezlerini ve depoladığı diğer verileri incelemek için kullanılabilir
Statik Kod Analizi
Bazı hataları incelemek için herhangi bir kod çalıştırmanıza gerek yoktur. Örneğin, sadece yazılan kodu okuyarak bir döngünün sayaç değişkeninin dış bağlamdaki aynı isimli bir değişkeni gölgelediğini (döngünün dışındaki bağlamda yer alan değişkenin değerini bozma durumu) anlayabilirsiniz. Benzer şekilde sadece koda bakarak henüz tanımlanmamış bir değişkenin kullanılmaya çalışıldığını görebilirsiniz. Bu tür durumlarda statik kod analizi araçlarını kullanabilirsiniz. Statik kod analizi araçları kaynak kodunuzu girdi olarak alıp tanımlı kuralları kodunuza uygulayarak kodun doğruluğunu çıkarsamaya çalışırlar.
Aşağıdaki Python kod parçasında birkaç hata var. İlk olarak foo
isimli döngü sayaç değişkeni daha yukarıda tanımlanmış olan foo
fonksiyonunu maskelemektedir. Ayrıca son satırda değişken olarak bar
yerine baz
isimli değişken kullanılmaya çalışılmış. Örnek kod parçası sleep
çağrısı tamamlandıktan sonra (program çalıştırıldıktan aşağı yukarı 1 dakika sonra) hatalı bir şekilde sonlanır.
import time
def foo():
return 42
for foo in range(5):
print(foo)
bar = 1
bar *= 0.2
time.sleep(60)
print(baz)
Statik kod analizi araçları yukarıdakine benzer kod hatalarını tespit edebilirler. Yukarıdaki örnek kod parçasını pyflakes
‘e verdiğimizde her iki hatanın da tespit edileceğini görürsünüz. Aynı kod parçasını mypy
isimli tip kontrolü analizi yapan araca girdi olarak verdiğimizde bar
değişkenin ilk anda tipinin int
olduğunu ancak sonradan float
tipine dönüştürüldüğü tespit edecektir. Tekrar etmek gerekirse, bu sorunları kod çalıştırmadan statik kod analizi araçları kullanarak tespit ettik.
Komut satırı araçları dersimizde benzer statik analizi yapmamızı sağlayan shellcheck
aracını ele almıştık.
$ pyflakes foobar.py
foobar.py:6: redefinition of unused 'foo' from line 3
foobar.py:11: undefined name 'baz'
$ mypy foobar.py
foobar.py:6: error: Incompatible types in assignment (expression has type "int", variable has type "Callable[[], Any]")
foobar.py:9: error: Incompatible types in assignment (expression has type "float", variable has type "int")
foobar.py:11: error: Name 'baz' is not defined
Found 3 errors in 1 file (checked 1 source file)
Çoğu entegre yazılım geliştirme (IDE) ortamı bu araçların çıktılarını gösterme ve ilgili kod satırlarına konumlanma desteği sunar. Bu yönteme genelde code linting denir ve kod yazım stili ve güvenlik gibi denetimler için de kullanılan bir yöntemdir.
Vim kullanıyorsanız ale
veya syntastic
eklentileri ile benzer denetimlerin yapılmasını sağlayabilirsiniz. Python için pylint
ve pep8
araçlarını kod yazımı stili denetimi için, bandit
‘i ise yaygın güvenlik problemleri denetimi için kullanabilirsiniz. Diğer programlama dilleri için kullanabileceğiniz statik kod analizi araçlarının listesine Awesome Static Analysis sayfasından, linter araçları listesine de Awesome Linters sayfasından göz atabilirsiniz.
Kod stili denetlemesi yapan araçları bütünleyici araçlar olarak kod formatlama araçları da kullanılır. Python için black
, Go için gofmt
, Rust için rustfmt
ve JavaScript, HTML ve CSS için prettier
bu araçlardan bazılarıdır. Bu araçlar kodunuzu ilgili programlama dili için tanımlı genel geçer yazım stili kurallarını kullanarak otomatik olarak formatlarlar. Kod stiliniz ile ilgili denetimleri bu araçların kontrolüne bırakmak istemiyor olabilirsiniz. Ancak, diğer yazılımcıların sizin kodunuzu daha rahata okuması, aynı zamanda da sizin diğerlerinin kodunu daha rahat okuyabilmeniz için kod stili standartlarının kullanımı önemlidir.
Ayrıntılı İnceleme (Profiling)
Kodunuz beklediğiniz gibi çalışıyor olsa bile, eğer kodunuz gereğinden fazla CPU veya bellek kullanıyorsa programınız yeterince iyi kodlanmamış demektir. Algoritma derslerinde genelde big O notasyonu öğretilir, ancak bu derslerde kötü performansa neden olan kod parçalarını nasıl tespit edebileceğiniz öğretilmez. Yazılımcılar arasında “vakitsiz optimizasyon tüm kötülüklerin anasıdır” (premature optimization is the root of all evil) şeklinde bir söylem vardır. Bu söylem performans optimizasyonun doğru anda yapılmasının değerini ifade eder, bu nedele programlarınızın çalışma anındaki performans karakteristiklerini ayrıntılı inceleme için faydalanabileceğini araçların (profiling ve monitoring araçları) kullanımı hakkında fikriniz olmalı. Bu araçlar sayesinde programlarınızın hangi kısımlarının en çok zaman ve kaynak harcadığını tespit ederek doğru anda nokta atışı optimizasyon çalışmaları ile program davranışını ideale yaklaştırabilirsiniz.
Çevirmenin Notu: Big O notasyonu ( O(x) şeklinde gösterilir) algoritmaların çalışma anında harcadığı zaman veya diğer kaynakları kullanma ölçüsünü ifade eden bir algoritma performansı ölçme yöntemidir. Big O notasyonu Bachmann–Landau notasyonu veya asymptotic notation adı da verilen bir dizi notasyondan sadece bir tanesidir. Bu notasyonun arka planında ciddi matematiksel önermeler ve tesptiler yer alır. Kendi oluşturduğunuz algoritmalar için veya hazır verilen algoritmalar için O(1), O(n), O(n^2), O(logN), O(NlogN) ve O(n!) gibi performans ölçülerini ve algoritmaların hangi koşullarda ilgili ölçülere uygun performans sergilediğini incelemenizi öneriyorum.
Zamanlama
İki kod satırı arasında programınızın programınızın harcadığı zamanı, hata ayıklama işleminde olduğu gibi, sadece print cümlecikleri ve zamanlayıcıları kullanarak tespit edebilirsiniz. Aşağıdaki Python kod örneğinde time
modülü ile bu işlemin nasıl yapılabileceğini görebilirsiniz.
import time, random
n = random.randint(1, 10) * 100
# Şu anki zamanı bir değişkene ata
start = time.time()
# Herhangi bir işlem yap
print("{} ms boyunca bekleyeceğim".format(n))
time.sleep(n/1000)
# start ile şu anki zaman arasındaki farkı hesapla
print(time.time() - start)
# Çıktı
# 500 ms boyunca bekleyeceğim
# 0.5713930130004883
Ancak, duvar saati ile yapılan yukarıdakine benzer bir zamanlama ölçümü sizi yanıltabilir. Çünkü, bilgisayarınız sadece sizin programınızı değil diğer programları da aynı anda çalıştırmaktadır veya bir olayın olmasını bekliyor olabilir. Bu nedenle, zamanlama ölçmek için kullanılan araçlar Gerçek (Real), Kullanıcı (User) ve Sistem (Sys)zamanları şeklinde üç kırılıma sahiptirler. Genel anlamda, Kullanıcı + Sistem zamanlarının toplamı programınızın gerçekte kullandığı CPU zamanını verirler. Daha fazla bilgi için şu linki inceleyebilirsiniz.
- Gerçek Zaman - Programınızın başlaması ile bitmesi arasında geçen zamandır. Ölçülen bu zamana diğer process’ler için harcanan zaman veya bir olayı beklerken (I/O veya ağ) geçen zaman da dahildir.
- Kullanıcı Zamanı - Yazdığınız kodun harcadığı CPU zamanını verir.
- Sistem Zamanı - İşletim sistemi kernel kodu çalıştırmak için harcanan CPU zamanını verir.
Örneğin, time
modülü kullanarak ağ üzerinden HTTP ile veri alan bir kod parçasının zamanlamasını incelediğimizde yavaş bir ağ bağlantısına sahipesiniz aşağıdaki gibi bir durum ile karşılaşırsınız. Bu durumda HTTP üzerinden yapılan talebin 2 saniyede tamamlandığını ancak programın sadece 15 milisaniye seviyesinde CPU Kullanıcı zamanı ve 12 milisaniye seviyesinde CPU Sistem zamanı harcadığını görürüz.
$ time curl https://missing.csail.mit.edu &> /dev/null`
real 0m2.561s
user 0m0.015s
sys 0m0.012s
Çevirmenin Notu:
/dev/null
Unix benzeri işletim sistemlerinde özel bir dosya tanımlayıcısıdır. Bu dosyaya yapılan tüm yazma işlemleri yazılan tüm veriyi göz ardı ederek yazma işlemini için başarı kodu döndürür. Daha fazla ayrıntı için şu linke göz atabilirsiniz.
Ayrıntılı İnceleme Araçları (Profilers)
Merkezi İşlem Birimi (CPU)
Ayrıntılı inceleme araçlarından bahsedilirken genelde CPU kullanımı ile ilgili inceleme yapmamızı sağlayan araçlar kastedilir. CPU kullanımını incelememizi sağlayan araçlar en sık rastladığımız ayrıntılı inceleme araçlarıdır. İki tür CPU inceleme aracı vardır: takip (tracing) ve örneklem (sampling) inceleme araçları. Takip tipindeki araçlar kodunuzun içindeki tüm fonksiyon çağırılarını izlenmesini ve takip edilmesini sağlarken örneklem tipindeki araçlar belirli aralıklarla (genelde her milisaniyede bir defa) programınıza göz atarak programınızın yığınını (stack) kayıt altına alırlar. Örneklem tipindeki araçlar topladıkları örneklemler üzerinde bir takım istatistikler yöntemler uygulayarak programınızın en çok hangi işlemlerde zaman harcadığını size raporlarlar. Bu konu hakkında daha ayrıntılı bilgi sahibi olmak isterseniz şu linkten erişebileceğiniz giriş seviyesindeki kaynaktan faydalanabilirsiniz.
Çoğu programlama dili komut satırından kullanabileceğiniz CPU inceleme araçları ile birlikte gelir. Bu araçlar çoğu zaman entegre geliştirme ortamları (IDE) ile de çalışır, ancak bir bu dersimizde komut satırı araçlarını kullanacağız.
Python için fonksiyon çağırıları için harcanan zamanı cProfile
modülünü kullanarak ayrıntılı bir şekilde inceleyebiliriz. Aşağıda, bir dosya içinde grep benzeri bir yöntem ile arama yapan örnek bir Python kodu yer almaktadır.
#!/usr/bin/env python
import sys, re
def grep(pattern, file):
with open(file, 'r') as f:
print(file)
for i, line in enumerate(f.readlines()):
pattern = re.compile(pattern)
match = pattern.search(line)
if match is not None:
print("{}: {}".format(i, line), end="")
if __name__ == '__main__':
times = int(sys.argv[1])
pattern = sys.argv[2]
for i in range(times):
for file in sys.argv[3:]:
grep(pattern, file)
Yukarıdaki örnek programı aşağıda verilen komut ile ayrıntılı olarak inceleyebiliriz. İnceleme sonucuna baktığımızda en çok CPU zamanı harcayan işlemlerin I/O işlemleri ve regex işlemlerinin olduğunu görüyoruz. regex ifadelerini bir defa derleyip kod akışında derlenmiş ifadeyi kullanmak mümkün olduğu için re.compile()
satırını döngü bloğundan çıkararak kodun zaman performansı açısından daha verimli hale getirebiliriz.
$ python -m cProfile -s tottime grep.py 1000 '^(import|\s*def)[^,]*$' *.py
[omitted program output]
ncalls tottime percall cumtime percall filename:lineno(function)
8000 0.266 0.000 0.292 0.000 {built-in method io.open}
8000 0.153 0.000 0.894 0.000 grep.py:5(grep)
17000 0.101 0.000 0.101 0.000 {built-in method builtins.print}
8000 0.100 0.000 0.129 0.000 {method 'readlines' of '_io._IOBase' objects}
93000 0.097 0.000 0.111 0.000 re.py:286(_compile)
93000 0.069 0.000 0.069 0.000 {method 'search' of '_sre.SRE_Pattern' objects}
93000 0.030 0.000 0.141 0.000 re.py:231(compile)
17000 0.019 0.000 0.029 0.000 codecs.py:318(decode)
1 0.017 0.017 0.911 0.911 grep.py:3(<module>)
[omitted lines]
Python ile hazır gelen cProfile
modülünün ve diğer bir çok CPU incelemesi için kullanılan araçların temel sorunu zaman ölçümünü fonksiyon çağrısı bazında yapmalarıdır. Bu yöntem, özellikle 3. parti kütüphaneler kullanıyorsanız çok hızlı bir yöntemdir. Çünkü, bu yöntem ile bu kütüphanelerin içindeki fonksiyonlar da zaman ölçümüne dahil edilir.
Ayrıntılı inceleme verisini göstermek için satır bazında harcanan zamanın gösterilmesi daha uygun ve anlaşılır bir yöntemdir. Bu yöntemi kullanan inceleme araçların satır bazlı incelem araçları denir.
Örneğin, aşağıdaki Python kod parçası ders sitemizin içeriğini indirip, bu içerik içindeki tüm linkleri ayıklar.
#!/usr/bin/env python
import requests
from bs4 import BeautifulSoup
# @profile ibaresi satır bazlı inceleme aracına get_urls() fonksiyonunu
# incelemek istediğimizi ifade eden bir direktiftir
@profile
def get_urls():
response = requests.get('https://missing.csail.mit.edu')
s = BeautifulSoup(response.content, 'lxml')
urls = []
for url in s.find_all('a'):
urls.append(url['href'])
if __name__ == '__main__':
get_urls()
Eğer, Python ile hazır gelen cProfile
‘ı kullansaydık inceleme çıktısı olarak 2500 satırlık bir sonuç ile karşılaşacaktık. 2500 satırlık bu sonucu satır satır sıralayıp incelemek bir zahmetli olacaktır. Bunun yerine aynı kod örneğini line_profiler
aracını kullanarak çalıştırdığımızda fonksiyon içindeki her bir satır için aşağıdakine benzer bir çıktı elde ederiz.
$ kernprof -l -v a.py
Wrote profile results to urls.py.lprof
Timer unit: 1e-06 s
Total time: 0.636188 s
File: a.py
Function: get_urls at line 5
Line # Hits Time Per Hit % Time Line Contents
==============================================================
5 @profile
6 def get_urls():
7 1 613909.0 613909.0 96.5 response = requests.get('https://missing.csail.mit.edu')
8 1 21559.0 21559.0 3.4 s = BeautifulSoup(response.content, 'lxml')
9 1 2.0 2.0 0.0 urls = []
10 25 685.0 27.4 0.1 for url in s.find_all('a'):
11 24 33.0 1.4 0.0 urls.append(url['href'])
Bellek (Memory)
C ve C++ gibi dillerde programlarınızda bellek sızıntısı (memory leak) oluşabilir ve bu durumda programınız işletim sisteminden aldığı belleği ihtiyacı olmadığı halde çalıştığı süre boyunca kendinde tutabilir. Bu tip durumları ayrıntılı bir şekilde incelemek için Valgrind benzeri bellek sızıntısı tespitinde size yardımcı olabilecek araçları kullanabilirsiniz.
Python gibi ihtiyaç duyduğu belleği doğrudan işletim sisteminden değil de sanal makina benzeri bir yapı sunan kendi özgü çalışma ortamı olan diller için de bellek sızıntıları nadir de olsa karşılaşılan durumlardır. Bu tür dillere garbage collected
diller denir ve bu dillerde yazdığımız kodun kullandığı bellek tüm bellek referansları geçersiz hale gelince garbage collector
tarafından otomatik olarak geri alınır. Bu tür dillerde de nesnelere referans vermek için kullanılan işaretçiler nedeniyle bellek sızıntısının meydana gelebilir. Aşağıda, memory-profiler (@profile
direktifini bu araçla da kullandığımıza dikkat edin) kullanarak bellek incelemesi yapılan bir Python programının çıktısı yer almaktadır.
@profile
def my_func():
a = [1] * (10 ** 6)
b = [2] * (2 * 10 ** 7)
del b
return a
if __name__ == '__main__':
my_func()
$ python -m memory_profiler example.py
Line # Mem usage Increment Line Contents
==============================================
3 @profile
4 5.97 MB 0.00 MB def my_func():
5 13.61 MB 7.64 MB a = [1] * (10 ** 6)
6 166.20 MB 152.59 MB b = [2] * (2 * 10 ** 7)
7 13.61 MB -152.59 MB del b
8 13.61 MB 0.00 MB return a
Olay Ayrıntı İncelemesi (Event Profling)
Hata ayıklama için kullandığımız strace
de olduğu gibi ayrıntılı inceleme yapmak istediğiniz kodu bir kara kutu olarak görmek isteyebilirsiniz. Bu durumda perf
komutunu kullanabilirsiniz. perf
komutu CPU mimarileri arasındaki farklar soyutlama ile ortadan kaldırıp zaman ve bellek kullanımı ölçmek yerine sistem olaylarını ve ilişkili aktiviteleri bize raporlar. Örneğin, perf
komutu düşük cache yerelliği (cache locality) sorunlarını veya yüksek miktarda sayfa hatalarını (page fault) ve ortak kaynak kullanımını senkronize etmek için kullanılan kilit sorunlarını (livelock) kolayca raporlayabilir.
Aşağıda perf
komutunun kullanımı ile ilgili özet verilmiştir:
perf list
- perf ile takip edilip incelenebilecek olayları listeler.perf stat COMMAND ARG1 ARG2
- Bir komut veya process ile ilişkili olayların sayısını raporlarperf record COMMAND ARG1 ARG2
- Bir komutun çalışması sırasındaki olayları kayıt altında alıp komut ile ilgili istatistikleriperf.data
dosyasına kaydeder.perf report
-perf.data
dosyasında yer alan verileri formatlayıp raporlar
Çevirmenin Notu: Cache locality, ilişkili verilerin bir birine yakın bellek alanlarında olduğu varsayımı ile belirli bellek bölgelerinin ön belleğe alınması ve belirli kod parçalarının bellekten okuma hızında performans iyileştirmesi sağlayan bir yöntemdir.
Çevirmenin Notu: Page fault, bellekte yer alan ancak bellek donanımı tarafından henüz adreslenmemiş bellek alanlarına erişilmek istendiğinde oluşan donanım seviyesindeki bir hatadır. Bu hata bellekten okuma performansını olumsuz etkiler.
Çevirmenin Notu: Live lock, ortak bir kaynağı kullanmaya çalışan eş zamanlı işlemlerin dönüşümlü olarak çok kısa sürelerde kilit oluşturup sonra da bu kilidi serbest bırakmaları ve hiç birinin ilgili işlemi tamamlayacak kadar süre elde ederek ilgili ortak kaynağa erişememsi nedeni ile ortaya çıkan bir kaynak kullanım sorunudur. Bu sorun uygulamaların iş yapıyor gibi görünüp işlemlerini bitirememelerine neden olur.
Görselleştirme
Gerçek dünyada olay inceleme araçları programların çok karmaşık olması nedeni ile büyük miktarda bilgi üretecektir. İnsanlar görsel canlılardır ve büyük miktardaki veriyi ve sayıyı yorumlamakta, anlamakta ve bir sonuç çıkarmakta güçlük çekerler. Bu nedenle olay inceleme araçlarının çıktılarını daha kolay anlaşılabilir hale getiren bir çok yardımcı araç vardır.
Örneklem (sampling) tipindeki ayrıntı inceleme araçlarının çıktılarını incelemek için kullanılan görselleştirme yöntemlerinden bir tanesi Flame Graph adı verilen yöntemdir. Bu yöntemde fonksiyon çağırıları Y ekseninde yer alır ve her bir fonksiyon için harcanan süre de X ekseninde gösterilir. Bu grafikler aynı zamanda etkileşimlidir ve programınızın herhangi bir parçası ile ilgili kısmı yakınlaştırmanızı ve yığın bilgisini (stack trace) görmenizi sağlar (aşağıdaki görselde herhangi bir yere tıklamayı deneyin).
Çağırı grafları veya akış kontrol grafları programınızdaki fonksiyonlar arasındaki ilişkileri görselleştirir. Bu graflarda fonksiyonlar düğüm, fonksiyon çağırıları da kenar olarak görselleştirilir. Python için pycallgraph
kullanarak bu grafları üretebilirsiniz.
Çevirmenin Notu: Graf ve grafik birbiri yerine kullanılan ancak aralarında ufak da olsa fark olan kavramlardır. Graf matematiksel bir fonksiyonun görsel olarak ifade edilmiş halidir, grafik ise günlük kullanımda birden çok büyüklük arasındaki ilişkilerin görsel halini tarif eden bir terimdir.
Kaynak Kullanımı İzleme (Resource Monitoring)
Programınızın performansı hakkında fikir sahibi olmak için ilk adım olarak programınızın kaynak kullanımını analiz etmeniz gerekir. Programlar genelde kısıtlı kaynak durumunda yavaş çalışırlar, örneğin yavaş ağ bağlantısı veya yeterli miktarda bellek olmadığı durumlarda. CPU kullanımıi bellek kullanımı, ağ performansı, disk kullanımı gibi sistem kaynaklarının kullanımını ölçmek bir çok araç vardır.
- Genel İzleme - Büyük ihtimalle bu işlem için en popüler araçlardan birisi
htop
‘dır.htop
,top
aracının daha gelişmiş halidir.htop
bilgisayarımızda çalışan processler ile ilgili çeşitli istatistikleri raporlayabilir.htop
bir çok seçenek ve tuş kombinasyonuna sahiptir. Örneğin,<F6>
tuşu ile processleri sıralayabilirsiniz,t
seçeneği ile processleri ağaç hiyerarşisinde görüntüleyebilirsiniz,h
seçeneği ile threadlerin gösterilmesini veya gizlenmesini sağlayabilirsiniz. Benzer özelliklere ve güzel bir kullanıc ara yüzüne sahipglances
aracına da inceleyebilirsiniz. Tüm processler için özet ölçümleri görmek içindstat
aracını kullanabilirsiniz.dstat
I/O, ağ, CPU kullanımı ve diğer birçok sistem bileşeni için gerçek zamanlı istatistikleri ve ölçümleri hesaplayabilir. - I/O İşlemleri -
iotop
aracı gerçek zamanlı I/O kullanım bilgilerini gösteren ve yüksek I/O yapan processleri kontrol etmenizi sağlayan bir araçtır. - Disk Kullanımı -
df
disk alanı (partition) bazındaki kullanım verilerini,du
ise konumlanılan dizinde yer alan dosyaların disk kullanımını gösteren araçlardır. Bu araçların kabul ettiği-h
seçeneği ile gösterimin daha okunaklı raporlanması sağlanabilir.du
‘nun daha interaktif versiyonu olanncdu
dizinlere konumlanma, dosya ve dizinleri silme gibi işlemleri destekler. - Bellek Kullanımı -
free
aracı bilgisayarınızdaki toplam, kullanılmayan ve kullanılan bellek miktarı bilgisini gösteren bir araçtır. Bellek bilgisihtop
gibi araçlar tarafından da gösterilir. - Kullanımdaki Dosyalar -
lsof
aracı processler tarafından kullanılan dosyalara ilişkin bilgileri gösteren bir araçtır. Bu araç bir processing belirli bir dosyayı kullanıp kullanmadığını araştırmak için ideal bir araçtır. - Ağ Bağlantıları ve Konfigürasyonu -
ss
aracı gelen ve giden ağ paket istatistiklerini ve ağ bağdaştırıcısı istatistiklerini incelemenizi sağlayan bir araçtır.ss
aracı genel olarak bir portu hangi process’in kullandığını görmek için kullanılır. Yönlendirme, ağ cihazları ve bağdaştırıcılarına ilişkin bilgileri görmek içinip
komutunu kullanabilirsiniz. Unix benzeri sistemlerde kurulu gelennetstat
veifconfig
gibi araçlar kullanımdan kaldırılmak üzere belirlenmiş araçlardır. Bunların yerine yukarıda bahsettiğimiz araç ve komutları kullanabilirsiniz. - Ağ Kullanımı -
nethogs
veiftop
gibi etkileşimli araçlar ağ kullanımını incelemek için kullanılabilir.
Yukarıda bahsettiğimiz araçları ve komutları test etmek için stress
komutu ile bilgisayarınızda yapay yük oluşturabilirsiniz.
Özelleşmiş Araçlar
Bazı durumlarda ayrıntıları ile ilgilenmeden iki aracın performansını karşılaştırmak isteyebilirsiniz. hyperfine
gibi araçlar birbirinin alternatifi olan komut satırı araçlarının performanslarını karşılaştırmanızı sağlar. Örneğin, komut satırı ve scripting dersinde find
komutu yerine fd
komutunu kullanmanızı önermiştik. hyperfine
ile bu iki komutu karşılaştırabiliriz. Aşağıdaki örneğimizde karşılaştırma sonucunda fd
komutunun find
komutundan 20 kat daha hızlı olduğunu görebilirsiniz.
$ hyperfine --warmup 3 'fd -e jpg' 'find . -iname "*.jpg"'
Benchmark #1: fd -e jpg
Time (mean ± σ): 51.4 ms ± 2.9 ms [User: 121.0 ms, System: 160.5 ms]
Range (min … max): 44.2 ms … 60.1 ms 56 runs
Benchmark #2: find . -iname "*.jpg"
Time (mean ± σ): 1.126 s ± 0.101 s [User: 141.1 ms, System: 956.1 ms]
Range (min … max): 0.975 s … 1.287 s 10 runs
Summary
'fd -e jpg' ran
21.89 ± 2.33 times faster than 'find . -iname "*.jpg"'
Hata ayıklama araçlarında olduğu gibi ayrıntılı inceleme araçları noktasında da web tarayıcıları olağanüstü yardımcı araçlara sahiptirler. Bu araçları kullanarak web sayfalarının yüklenme sürelerini, yükleme sırasında hangi işlemlerde zaman harcandığını (yükleme, görselleştirme, scripting vb.) inceleyebilirsiniz. Tarayıcı ayrıntılı inceleme araçlarının ayrıntıları için şu linklere Firefox, Chrome göz atabilirsiniz: .
Alıştırmalar
Hata Ayıklama
-
Linux’da
journalctl
veya macOS’dalog show
komutlarını kullanarak superuser kullanıcısının son bir günde kullandığı komutları listeleyin. Eğer son bir gün içinde çalıştırılmış herhangi bir komut yoksasudo ls
gibi zararsız birkaç komut çalıştırarak işlemi tekrar deneyin. -
Şu linkteki uygulamalı
pdb
dersini adım adım yaparakpdb
komutlarını tanıyın. Daha ayrıntılı bilgi edinmek için şu sayfaya göz atabilirsiniz. -
shellcheck
aracını kurarak aşağıdaki scripti denetleyiniz. Aşağıdaki koda nasıl bir sorun var? Bu sorunu düzeltin. Kod editörünüze linter eklentisini kurarak yazım ile ilgili uyarıları otomatik olarak almaya başlayın.#!/bin/sh ## Example: a typical script with several problems for f in $(ls *.m3u) do grep -qi hq.*mp3 $f \ && echo -e 'Playlist $f contains a HQ file in mp3 format' done
-
(İleri Seviye) Tersine hata ayıklama (reversible debugging) konusu ile ilgili şu linkteki yazıyı okuyun ve
rr
veyaRevPDB
kullanarak bir örnek çalıştırın.
Ayrıntılı İnceleme
-
Şu Python dosyasında farklı birkaç sıralama algoritması yer alıyor.
cProfile
veline_profiler
kullanarak insertion sort ve quicksort algoritmalarının çalışma zamanlarını karşılaştırın. Bu algoritmaların darboğazları nelerdir? Algoritmaların bellek kullanımını görmek içinmemory_profiler
‘ı kullanın. Insertion sort algoritması diğerlerine göre neden daha iyi bir performansa sahip? Quicksort sıralama algoritmasının ilave bellek kullanmayan inplace versiyonunu inceleyin. Meydan okuma:perf
aracını kullanarak her bir algoritmanın döngü sayısını, ön bellek kullanımını inceleyin. -
Aşağıda, her bir argüman için Fibonacci sayısını hesaplayan örnek Python fonksiyon kodu verilmiştir.
#!/usr/bin/env python def fib0(): return 0 def fib1(): return 1 s = """def fib{}(): return fib{}() + fib{}()""" if __name__ == '__main__': for n in range(2, 10): exec(s.format(n, n-1, n-2)) # from functools import lru_cache # for n in range(10): # exec("fib{} = lru_cache(1)(fib{})".format(n, n)) print(eval("fib9()"))
Yukarıdaki kodu bir dosyaya taşıyarak çalıştırılabilir hale getirin.
pycallgraph
aracını kurun. Kodupycallgraph graphviz -- ./fib.py
komutu ile çalıştırın vepycallgraph.png
dosyasının açarak içeriğini kontrol edin.fib0
fonksiyonu kaç defa çağırılmış? Fonksiyonel programlama yöntemlerinden birimemoization
yöntemini kullanarak kodumuzun performansını iyileştirebiliriz. Yorum satırı olarak yukarıda yer alan kodu kullanılır hale getirerek kodupycallgraph
ile yeniden çalıştırın.fibN
fonksiyonunu bu düzenlemeler sonrasında kaç defa çağırıyoruz? -
Genel sorunlardan bir tanesi kullanmak istediğiniz bir portun başka bir process tarafında o anda kullanılıyor olmasıdır. Gelin isteğimiz portu kullanan process’in pid değerini öğrenelim. Önce
python -m http.server 4444
komutu ile 4444 portunu dinleyen minimal bir web suncusu çalıştırın. Farklı bir terminal penceresindelsof | grep LISTEN
komutunu çalıştırıp portları dinleyen tüm processleri listeleyin. 4444 portunu kullanan process’in pid’ini bulup bu process’ikill <PID>
komutu ile sonlandırın. -
Process’lerin kullanacağı kaynakları sınırlamak araç çantanızda bulunması gereken faydalı araçlardan biridir.
stress -c 3
komutunu çalıştırın vehtop
komutu ile CPU kullanımını görselleştirin. Şimdi detaskset --cpu-list 0,2 stress -c 3
komutunu çalıştırarak yinehtop
ile görselleştirin.stress
komutu 3 CPU mu kullanıyor?stress
neden 3 CPU kullanmıyor? Ayrıntılar içinman taskset
sayfasını inceleyin. Meydan Okuma: taskset ile tanımladığını kaynak kullanımı sınırlandırmasınıcgroups
kullanarak oluşturmayı deneyin. Bu seferstress -m
komutunun bellek kullanımını sınırlandırmayı deneyin. -
(İleri Seviye)
curl ipinfo.io
komutu HTTP isteği yaparak açık IP adresiniz ile ilgili bilgileri alır. Wireshark uygulamasını açıncurl
‘un gönderip aldığı paketleri izlemeyi deneyin. (İpucu: Wireshark’da sadece HTTP paketlerini izlemek içinhttp
filtersini kullanın).
CC BY-NC-SA lisansı ile lisanslanmıştır.