Debugovanje i profilisanje

Sledeca lekcija
Video lesson

Zlatno pravilo u programiranju jeste da kod ne radi ono što očekujete od njega, već ono što mu kažete da uradi. Premostiti taj problem ponekad može biti veoma krupan korak. U ovoj lekciji ćemo preći korisne tehnike za nošenje sa kodom koji ima bagove i kojem fale resursi: uklanjanje grešaka i profilisanje.

Uklanjanje grešaka

Printf uklanjanje grešaka i logovanje

"Najefikasnije sredstvo za uklanjanje grešaka je i dalje pažljivo razmišljanje, zajedno sa dobro postavljenim izjavama o ispisu" - Brian Kernighan, Unix for Beginners.

Prvi pristup da bi se uklonile greške iz programa je dodavanje print izjave okolo dijela u kojem imate problem, i ponavljanje toga dok ne dobijete dovoljno informacija da shvatite ko je krivac za dati problem.

Drugi pristup je upotreba logovanja u vašem programu, umjesto ad hoc print izjava. Logovanje je bolje u odnosu na print izjave iz više razloga:

  • Možete logovati fajlove, sockets-e, ili čak i udaljene servere umjesto standardnog output-a.
  • Logovanje podržava ozbiljne nivoe (kao što su INFO, DEBUG, WARN, ERROR, i sl.), koji vam omogućuju da prema njima filtrirate output.
  • Za nove probleme, postoji dobra šansa da će vaši logovi sadržati dovoljno informacija da shvatite šta nije u redu.

Ovdje je primjer koda koji loguje poruku:

1 2 3 4 5 6 7 8 $ python logger.py # Raw output as with just prints $ python logger.py log # Log formatted output $ python logger.py log ERROR # Print only ERROR levels and above $ python logger.py color # Color formatted output

Jedan od mojih omiljenih savjeta da bi poboljšali čitljivost logova jeste da obilježite kod bojom. Do sada ste vjerovatno shvatili da vaš terminal koristi boje da učini stvari čitljivijim. Ali, kako to radi? Programi kao što su

1 ls
ili
1 grep
koriste ANSI escape codes, koji su specijalizovani dijelovi karaktera koji pokazuju vašem shell-u da promijeni boju output-a. Na primer, izvršavanje
1 echo -e "\e[38;2;255;0;0mThis is red\e[0m"
ispisuje poruku
1 This is red
crvenom bojom na vašem terminalu, dokle god podržava true color. Ako vaš terminal ne podržava ovo (npr. macOS Terminal.app), možete koristiti univerzalnije podržani escape code sa izborom od 16 boja, na primer
1 echo -e "\e[31;1mThis is red\e[0m"
.

Sledeća skripta pokazuje kako ispisati mnogo RGB boja u vašem terminalu (opet, sve dok podržavaju true color).

1 2 3 4 5 6 7 8 #!/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

Logovi trećih strana

Kako počinjete da pravite šire softverske sisteme vjerovatno ćete naići na zavisnosti koje se pokreću kao poseban program. Veb serveri, baze podataka ili brokeri poruka su česti primjeri ovakvih vrsta zavisnosti. Kada imate interakciju sa ovim sistemima često je neophodno pročitati njihove logove, jer error poruka na klijent strani možda neće biti dovoljna.

Srećom, većina programa piše svoje logove negdje u vašem sistemu. U UNIX sistemima, uobičajeno je da programi pišu svoje logove u

1 var/log
. Na primer, NGINX webserveri ih čuvaju u
1 /var/log/nginx
. U skorije vrijeme, sistemi su počeli da koriste system log, što je sve više mjesto gdje vaše log poruke idu. Većina (ali ne i svi) Linux sistemi koriste
1 systemd
, sistem daemon koji kontroliše mnoge stvari u vašem sistemu kao npr. koje usluge su omogućene i pokrenute.
1 systemd
stavlja logove u
1 /var/log/journal
u posebni format i možete koristiti journalctl komandu da prikažete poruke. Slično, na macOS takođe postoji
1 /var/log/system.log
ali povećan broj alata koristi system log, koji može biti prikazan sa log show. Na većini UNIX sistema takođe možete koristiti dmesg komandu da pristupite logu kernela.

Za logovanje u sistem logs možete koristiti logger shell program. Evo primjera korišćenja

1 logger
-a i kako da provjerite da li je unos stigao do sistem logova. Štaviše, većina programskih jezika ima vezivanje logginga za sistem log.

1 2 3 4 5 logger "Hello Logs" # On macOS log show --last 1m | grep Hello # On Linux journalctl --since "1m ago" | grep Hello

Kao što smo vidjeli u lekciji o upravljanju podacima, logs mogu biti veoma opširni i oni zahtijevaju određeni nivo filtriranja i procesiranja da bi dobili informacije koje želite. Ukoliko upadnete u teško filtriranje kroz

1 journalctl
i
1 log show
razmislite da koristite njihove flagove, koji mogu izršiti prvi korak u filtriranju njihovog outputa. Postoje još neki alati kao što je lnav, koji omogućuju poboljšanu prezentaciju i navigaciju kroz log fajlove.

Debageri

Kada printf ispravljanje grešaka nije dovoljno trebali bi da koristite debager. Debageri su programi koji vam omogućuju interakciju sa izvršenjem programa, dozvoljavajući sledeće:

  • Zaustavljanje izvršenja programa kada dođe do određene linije.
  • Prolazak kroz program sa jednom instrukcijom u isto vrijeme.
  • Ispitivanje vrijednosti varijable nakon greške programa.
  • Uslovno zaustavljanje izvršenja kada je određeni uslov zadovoljen.
  • I još mnogo naprednih funkcija.

Mnogi programski jezici dolaze sa nekom formom debagera. U Pythonu to je Python Debugger pdb.

Evo kratkog opisa nekih od komandi koje

1 pdb
podržava:

  • l(ist) - Prikazuje 11 linija oko trenutne linije ili nastavlja prethodni listing.
  • s(tep) - Izvršava trenutnu liniju, zaustavlja se prve moguće prilike.
  • n(ext) - Nastavlja izvršavanje sve dok se ne dostigne sledeća linija u trenutnoj funkciji ili dok ne vrati nešto.
  • b(reak) - Postavlja tačku prekida (zavisno od pruženih argumenata).
  • p(rint) - Procijenjuje izraz u trenutnom kontekstu i ispisuje njegovu vrijednost. Takođe postoji
    1 pp
    da se prikaže koristeći pprint.
  • r(eturn) - Nastavlja izvršavanje sve dok sledeća funkcija ne vrati.
  • q(uit) - Izlazi iz debagera.

Hajde da prođemo kroz primjer koristeći

1 pdb
da popravimo sledeći Python kod koji ima bagove. (Pogledajte video lekciju).

1 2 3 4 5 6 7 8 9 10 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]))

Imajte u vidu da pošto je Python interpretiran jezik mi možemo koristiti

1 pdb
shell da izvršimo komande i izvršimo instrukcije. ipdb je unapređeni
1 pdb
koji koristi IPython REPL omogućujući dovršavanje tabova, isticanje sintakse, bolje tragove, i bolju introspekciju dok zadržava isti interfejs kao i
1 pdb
modul.

Za još niži nivo programiranja vjerovatno ćete željeti da pogledate gdb (i kvalitet njegovih izmjena pwndbg) i lldb. Oni su optimizovani za debaging za jezike kao što je C, ali će vam dozvoliti isptivanje bilo kojeg procesa i dobićete trenutno stanje mašine: registre, stack, brojač programa i sl.

Specijalizovani alati

Čak iako želite da ispravite greške u crnom binarnom okviru, postoje alati koji vam mogu pomoći sa tim. Kada god program mora da izvrši akciju koju samo kernel može, oni koriste System Calls. Postoje komande koje vam dopuštaju da pratite syscalls koje vaš program napravi. U Linuxu postoji strace, a macOS i BSD imaju dtrace.

1 dtrace
može biti nezgodan za korišćenje zato što koristi svoj
1 d
jezik, ali postoji omotač koji se naziva
1 dtruss
koji omogućuje interfejs koji je sličniji
1 strace
(više detalja ovdje.

Ispod su neki primjeri korišćenja

1 strace
ili
1 dtruss
da pokažu stat syscall tragove za izvršenje
1 ls
. Za dublji pregled
1 strace
, ovo je dobro za čitanje.

1 2 3 4 5 # On Linux sudo strace -e lstat ls -l > /dev/null 4 # On macOS sudo dtruss -t lstat64_extended ls -l > /dev/null

Pod nekim okolnostima, možda ćete morati da pogledate network pakete da bi shvatili koji je problem u vašem programu. Alati kao što su tcpdump i Wireshark su analizatori network paketa koji vam dopuštaju da čitate sadržaj network paketa i filtrirate ih na osnovu različitog kriterijuma.

Za web development, Chrome/Firefox alatke za programere su veoma pogodne. One imaju veliki broj alata, uključujući:

  • Izvorni kod - Pregledajte HTML/CSS/JS izvorni kod bilo kojeg sajta.
  • Uživo modifikacije HTML/CSS/JS - Mijenjate sadržaj sajtova, stilova i ponašanja da bi testirali (možete i sami da vidite da snimci sajtova nisu validni dokazi).
  • Javascript shell - Izvršite komande u JS REPL.
  • Network - Analizirajte vremensku liniju zahtjeva.
  • Skladište - Pregledajte kolačiće i skladište lokalne aplikacije.

Statička analiza

Za neke probleme ne morate da izvršavate bilo kakav kod. Na primer, samo pažljivim gledanjem u dio vašeg koda možete shvatiti da varijabla petlje zasjenjuje već postojeću varijablu ili naziv funkcije; ili da program čita varijablu prije njenog definisanja. Ovo je gdje alati statičke analize dolaze do izražaja. Programi statičke analize uzimaju izvorni kod kao input i analiziraju ga koristeći pravila kodiranja da bi procijenili njegovu ispravnost.

U sledećem Python isječku postoji nekoliko grešaka. Prvo, naša varijabla petlje

1 foo
zasjenjuje prethodnu definiciju funkcije
1 foo
. Takođe smo napisali
1 baz
umjesto
1 bar
u poslednjoj liniji, tako da će u programu doći do greške nakon izvršavanja
1 sleep
poziva (što će trajati oko minut).

1 2 3 4 5 6 7 8 9 10 11 import time def foo(): return 42 for foo in range(5): print(foo) bar = 1 bar *= 0.2 time.sleep(60) print(baz)

Statička analiza može identifikovati ovakvu vrstu problema. Kada pokrenemo pyflakes u kodu mi dobijamo grešku koja se odnosi na oba baga. mypy je još jedan alat koji može otkriti problem sa provjerom tipa. Ovdje,

1 mypy
nas upozorava da je bar inicijalno
1 int
a da se kasnije pretvara u
1 float
. Opet, imajte u vidu da su svi ovi problemi otkriveni bez potrebe da se izvršava kod.

U lekciji o shell alatima pokrili smo shellcheck, koji je sličan alat za shell skripte.

1 2 3 4 5 6 7 8 9 $ 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)

Većina editora i radnih okruženja podržavaju prikazivanje ovog outputa u samom editoru, ističući lokacije upozorenja i grešaka. Ovo se često naziva code linting i takođe može biti korišćeno da se prikažu različiti tipovi problema kao što su stilska kršenja ili nesigurni konstrukti.

U vim-u, plugini ale ili syntastic će odraditi taj posao. Za Python, pylint i pep8 su primjeri stilskih lintera i bandit je alat koji je dizajniran da pronađe poznate bezbjednosne probleme. Za druge jezike ljudi su kompajlirali sveobuhvatne liste korisnih alata za statičku analizu, kao što je Awesome Static Analysis (možda ćete željeti da pogledate Writing sekciju) a za lintere tu je Awesome Linters.

Dodatni alati za linting stila su kod formateri kao što je black za Python,

1 gofmt
za Go,
1 rustfmt
za Rust ili prettier za JavaScript, HTML i CSS. Ovi alati autoformatuju vaš kod tako da bude dosledan sa uobičajenim stilskim obrascima za dati programski jezik. Iako možda niste voljni da prepustite stilsku kontrolu nad vašim kodom, standardizacija formata koda će pomoći drugim ljudima u čitanju vašeg koda i pomoći će vama u čitanju koda (stilski standardizovanog) drugih ljudi.

Profilisanje

Čak iako se vaš kod funkcionalno ponaša kao što i očekujete, to možda neće biti dovoljno dobro ukoliko zauzima čitav CPU ili memoriju u tom procesu. Na časovima algoritama se često uči big O notation ali ne i kako pronaći hot spots u vašim programima. Kako je prerana optimizacija korijen svakog zla, trebali bi da naučite o profilisanju i alatima za praćenje. Oni će vam pomoći da shvatite koji dijelovi vašeg programa zauzimaju najviše vremena i/ili resursa tako da se možete fokusirati na optimizaciju tih dijelova.

Tajming

Slično kao u slučaju uklanjanja grešaka, u mnogim scenarijima može biti sasvim dovoljno da ispišete vrijeme koje je vašem kodu bilo potrebno između dvije tačke. Evo primjera u Pythonu koristeći time modul.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import time, random n = random.randint(1, 10) * 100 # Get current time start = time.time() # Do some work print("Sleeping for {} ms".format(n)) time.sleep(n/1000) # Compute time between start and now print(time.time() - start) # Output # Sleeping for 500 ms # 0.5713930130004883

Ipak, vrijeme sata može biti pogrešno uzimajući u obzir da vaš računar možda obavlja i druge procese u isto vrijeme ili čeka na to da se određeni događaj dogodi. Uobičajeno je za alate da prave razliku između Real, User i Sys vremena. Generalno, User + Sys vam govore koliko je vremena vaš proces zapravo proveo u CPU (detaljnije objašnjenje je ovdje).

  • Real - Sat pokazuje proteklo vrijeme od početka do kraja programa, uključujući vrijeme koje su zauzeli drugi procesi i vrijeme dok je bio blokiran(npr. čekajući na I/O ili network)
  • User - Količinu vremena provedenog u CPU izvršavajući kod korisnika
  • Sys Količinu vremena provedenog u CPU izvršavajući kernel kod

Na primer, pokušajte da izvršite komandu koja obavlja HTTP zahtjev i dodajte prefix time. Ukoliko vam je konekcija spora možda ćete dobiti output kao što je navedeno ispod. Ovdje je trebalo preko 2 sekunde da se zahtjev obradi ali je proces trajao 15ms CPU korisničkog vremena i 12ms kernel CPU vremena.

1 2 3 4 $ time curl https://missing.csail.mit.edu &> /dev/null` real 0m2.561s user 0m0.015s sys 0m0.012s

Profajleri

CPU

Većinu vremena kada ljudi ukazuju na profajlere oni zapravo misle na CPU profajlere, koji su i najčešći. Postoji dva glavna tipa CPU profajlera: tracing i sampling profajleri. Tracing profajleri vode evidenciju svakog poziva funkcije koji vaš program napravi dok sampling profajleri periodično probaju vaš program (često svake milisekunde) i prave zabilješku stack programa. Oni koriste ove zabilješke da predstave zbirne statistike u vezi sa tim na šta je vaš program proveo najviše vremena. Ovdje je dobar uvodni članak ukoliko želite više detalja na ovu temu.

Većina programskih jezika ima neku vrstu profajlera komandne linije koje možete koristiti da bi analizirali vaš kod. Oni su često integrisani sa punopravnim radnim okruženjem ali za ovu lekciju mi ćemo se fokusirati na same alate komandne linije.

U Pythonu možemo koristiti

1 cProfile
modul da profilišemo vrijeme za poziv funkcije. Evo jednostavnog primjera koji implementira osnovni grep u Pythonu:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #!/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)

Možemo profilsati ovaj kod koristeći sledeću komandu. Analiziranjem outputa možemo vidjeti da IO zauzima najviše vremena i da kompajliranje tog regexa takođe troši srednju količinu vremena. Budući da se regex kompajlira samo jednom, možemo ga faktorisati izvan for-a.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ 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]

Napomena za Pythonov

1 cProfile
profajler (i mnoge drugi profajlere koji se toga tiču) jeste da oni prikazuju vrijeme za pojedinačan poziv funkcije. To može postati neintuitivno veoma brzo, posebno ako koristite biblioteke treće strane u vašem kodu budući da se unutrašnji pozivi funkcija takođe računaju. Intuitivniji način prikazivanja informacija o profilisanju jeste da uključe vrijeme koje je potrošeno za pojedinačnu liniju koda, što je ono što line profilers rade.

Na primer, sledeći dio Python koda izvršava zahtjev na sajtu i parsira odgovor da bi dobio sve URL-ove na stranici:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/usr/bin/env python import requests from bs4 import BeautifulSoup # This is a decorator that tells line_profiler # that we want to analyze this function @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()

Ako bi koristili Pythonov

1 cProfile
profajler dobili bi preko 2500 linija outputa, čak i sa sortiranjem bi bilo teško da shvatimo gdje se zapravo troši vrijeme. Brzo izvršavanje sa line profiler pokazuje vrijeme potrošeno po liniji.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ 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'])

Memorija

U jezicima kao što su C ili C++ curenje memorije može prouzrokovati problem da se nikada ne oslobodi memorija koja se više ne koristi. Za pomoć u procesu ispravljanja grešaka u memoriji možete koristiti alate kao što je Valgrind koji će vam pomoći da identifikujete curenje memorije.

U jezicima sa sakupljačima smeća kao što je Python i dalje je korisno koristiti profajler memorije jer dokle god imate pokazivače na objekte u memoriji njih čistač neće očistiti. Evo primjera programa i njegovog povezanog outputa kada ga pokrećete sa memory-profiler (primjetite dekorator kao u

1 line-profiler
).

1 2 3 4 5 6 7 8 9 @profile def my_func(): a = [1] * (10 ** 6) b = [2] * (2 * 10 ** 7) del b return a if __name__ == '__main__': my_func()
1 2 3 4 5 6 7 8 9 $ 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

Profilisanje događaja

Kao što je bio slučaj sa

1 strace
za ispravljanje grešaka, možda ćete željeti da ignorišete specifičnosti koda koji izvršavate i tretirate ga kao crnu kutiju u toku profilisanja. perf komanda sažima CPU razlike i ne izvještava o vremenu ili memoriji, umjesto toga izvještava sistemske događaje koji su vezani za vaš program. Na primer
1 perf
može lako izvjestiti o lošem keš lokalitetu, velikoj količini grešaka ili nepostojećih stranica. Evo pregleda komandi:

  • 1 perf list
    - Navodi događaje koji mogu biti praćeni sa perf-om
  • 1 perf stat COMMAND ARG1 ARG2
    - Uzima broj različitih događaja koji su povezani sa procesom ili komandom
  • 1 perf record COMMAND ARG1 ARG2
    - Zapisuje izvršavanje komande i čuva statističke podatke u datoteci koja se zove
    1 perf.data
  • 1 perf report
    - Formatira i ispisuje podatke sačuvane u
    1 perf.data

Vizualizacija

Output profajlera za stvarne programe će sadržati veliku količinu informacija zbog kompleksnosti softverskog projekta. Ljudi su vizuelna bića i prilično su loši u čitanju velike količine brojeva koji bi trebali da imaju smisao. Dodatno, postoji mnogo alata za prikazivanje outputa profajlera na lakši način.

Jedan čest način da bi se prikazalo profilisanje CPU informacija za uzeti profajler jeste korišćenje Flame Graph koji će prikazati hijerarhiju poziva funkcije preko Y ose i korišćeno vrijeme proporcionalno X osi. Takođe su interaktivni, dopuštaju vam da zumirate specifične dijelove programa i da dobijete njihove stack tragove (pokušajte da kliknete na sliku ispod).

img1

Grafikoni poziva ili grafikoni kontrolnog toka prikazuju odnose između potprograma unutar programa uključujući funkcije kao nodove i pozive funkcije između njih kao direktne ivice. U kombinaciji sa podacima za profilisanje kao što su broj poziva i trajanje vremena, grafikoni poziva mogu biti veoma korisni za interpretiranje toka programa. U Pythonu možemo koristiti pycallgraph biblioteku da bi ih generisali.

img2

Praćenje resursa

Nekada, prvi korak ka analiziranju performansi vašeg programa je razumijevanje koja je njegova stvarna potrošnja resursa. Programi se često izvršavaju sporo kada su ograničeni resursi npr. bez dovoljno memorije ili na sporoj network konekciji. Postoji mnoštvo alata komandne linije za ispitivanje i prikazivanje različitih sistemskih resursa kao što je upotreba CPU, upotreba memorije, upotreba diska i tako dalje.

  • Generalno praćenje - Vjerovatno je najpopularniji htop, koji je poboljšana verzija top.
    1 htop
    predstavlja različite statistike za trenutni proces koji je pokrenut na sistemu.
    1 htop
    ima mnoštvo opcija i tasterskih prečica, a neke korisne su:
    1 <F6>
    za sortiranje procesa,
    1 t
    da bi se pokazala hijerarhija drveta i
    1 h
    za prebacivanje niti. Takođe pogledajte glances za sličnu implementaciju sa odličnim UI. Za dobijanje agregatnih mjera kroz sve procese, dstat je još jedan sjajan alat koji računa metričke resurse u realnom vremenu za više različitih podsistema kao što su I/O, networking, CPU upotreba, prebacivanje konteksta itd.
  • I/O operacije - iotop prikazuje uživo I/O upotrebu informacija i pogodan za provjeru da li proces radi teške I/O disk operacije
  • Korišćenje diska - df prikazuje metriku po particiji i du prikazuje korišćenje diska po fajlu za trenutni direktorijum. U ovim alatima
    1 -h
    flag govori programu da ispiše format koji je čitljiv za ljude. Interaktivnija verzija
    1 du
    -a je ncdu koji vam dopušta da se krećete kroz foldere i brišete fajlove i foldere u toku kretanja.
  • Korišćenje memorije - free prikazuje totalnu količinu slobodne i korišćene memorije u sistemu. Memorija se takođe prikazuje u alatima kao što je
    1 htop
    .
  • Otvoreni Fajlovi - lsof prikazuje informacije o fajlovima koji su otvoreni od strane procesa. Može biti veoma korisno za provjeru koji proces ima otvoreno specifičan fajl.
  • Network konekcija i konfiguracija - ss vam dopušta da pratite dolazeće i odlazeće network statističke pakete kao i interfejs statistike. Uobičajen način korišćenja
    1 ss
    jeste otkrivanje koji proces koristi dati port u mašini. Za prikazivanje routinga, network uređaja i interfejsa možete koristiti ip. Imajte u vidu da su
    1 netstat
    i
    1 ifconfig
    prevaziđeni u korist drugih alata.
  • Korišćenje networka - nethogs i iftop su dobri interaktivni CLI alati za praćenje korišćenja networka.

Ukolko želite da testirate ove alate možete vještački nametnuti opterećenja mašini koristeći stress komandu.

Specijalizovani alati

Ponekad, mjerenje crne kutije je sve što vam je potrebno da bi utvrdili koji softver da koristite. Alati kao što su hyperfine vam dopuštaju brzo mjerenje komandne linije programa. Na primer, u lekciji o shell alatima i skriptingu preporučivali smo

1 fd
u odnosu na
1 find
. Možemo koristiti
1 hyperfine
da bi ih uporedili u zadacima koje često izvršavamo. Npr. u primjeru ispod
1 fd
je bio 20 puta brži nego
1 find
na mojoj mašini.

1 2 3 4 5 6 7 8 9 10 11 12 $ 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"'

Kao što je bio slučaj sa ispravljanjem grešaka, pretraživači takođe dolaze sa odličnim setom alata za profilisanje učitavanja stranice, dopuštajući vam da shvatite gdje se vrijeme troši (učitavanje, renderovanje, skripting itd.). Više informacija na Firefox i Chrome.

Vježbe

Ispravljanje grešaka

  1. Koristite
    1 journalctl
    na Linuxu ili
    1 log show
    na macOS da bi dobili komande od strane super korisnika u poslednjem danu. Ukoliko ih nema, možete izvršiti neke bezopasne komande kao što je
    1 sudo ls
    i provjeriti ponovo.
  2. Uradite ovaj
    1 pdb
    tutorijal da bi se upoznali sa komandama. Za dublji tutorijal pročitajte ovo.
  3. Instalirajte spellcheck i pokušajte da provjerite sledeću skriptu. Šta nije u redu sa ovim kodom? Popravite ga. Instalirajte linter plugin u vašem editoru tako da možete automatski da dobijete upozorenja.
1 2 3 4 5 6 7 #!/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
  1. (Napredno) Pročitajte o reversible debugging i uzmite jednostavan primjer kao što je rr ili RevPDB.

Profilisanje

  1. ovdje su neke sortirajuće implementacije algoritama. Koristite cProfile i line_profiler da bi uporedili vrijeme izvoženja sortiranja i brzog sortiranja. Šta je usko grlo svakog od algoritama? Zatim koristite

    1 memory_profiler
    da bi provjerili upotrebu memorije, zašto je umetnuto sortiranje bolje? Provjerite sada inplace verziju brzog sortiranja. Izazov: Koristite
    1 pref
    da bi pogledali brojanje ciklusa i keširanje udara i propusta svakog od algoritama.

  2. Evo jednog (iskrivljenog) Python koda za računanje Fibonačijevih brojeva korišćenjem funkcije za svaki broj.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/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()"))

Stavite kod u fajl i podesite da je izvršan. Instalirajte pycallgraph. Pokrenite kod kakav jeste sa

1 pycallgraph graphviz -- ./fib.py
i provjerite
1 pycallgraph.png
fajl. Koliko je puta
1 fib0
pozvan? Možemo uraditi bolje od toga optimizovanjem funkcija. Maknite iz komentara linije koje se nalaze u komentaru i regenerišite slike. Koliko puta mi pozivamo
1 fibN
funkciju sada?

  1. Čest problem je što je port koji želite da slušate već zauzet od strane drugog procesa. Hajde da naučimo kako da saznamo ID procesa. Prvo izvršite

    1 python -m http.server 4444
    da bi pokrenuli minimalistički web server slušajući na portu
    1 4444
    . Na odvojenim terminalima pokrenite
    1 lsof | grep LISTEN
    da bi ispisali sve procese i portove koji se slušaju. Pronađite ID tog procesa i prekinite ga komandom
    1 kill <PID>
    .

  2. Ograničavanje resursa za procese može biti još jedan zgodan alat koji posjedujete. Pokušajte pokretanje

    1 stress -c 3
    i vizualizujte potrošnju CPU-a sa
    1 htop
    . Sada izvršite
    1 taskset --cpu-list 0,2 stress -c 3
    i vizualizujte ga. Da li je
    1 stress
    zauzeo tri CPU-a? Zbog čega nije? Pročitajte man taskset. Izazov: ostvarite isto koristeći cpgroups. Pokušajte da ograničite korišćenje memorije
    1 stress -m
    .

  3. (Napredno) komanda

    1 curl ipinfo.io
    izvršava HTTP zahtjev i povlači informacije o vašoj javnoj IP adresi. Otvorite Wireshark pokušajte da prođete kroz request i reply pakete koje
    1 curl
    šalje i prima. (Nagovještaj: koristite
    1 http
    filter da bi samo posmatrali HTTP pakete).