Obsah
1. Krátká lekce z historie: příkazy TRON a TROFF
3. Zjištění počtu průchodů jednotlivými řádky skriptu či skriptů
4. Detekce řádků s „mrtvým“ kódem
5. Tisk seznamu funkcí, které byly zavolány
6. Seznam se vzájemnými vztahy mezi funkcemi (které funkce jsou volány)
7. Úplné trasování, informace o relativních časech při trasování
8. Pomocný nástroj coverage.py
9. Spuštění aplikace přes nástroj coverage.py
12. Export výsledků do formátů JSON a XML
13. Zobrazení výsledků ve formě vygenerovaných HTML stránek
14. Interní struktura s informacemi o trasování (SQLite databáze)
16. Instalace nástroje pycrunch-pytrace
17. Úprava trasovaného skriptu a spuštění trasování
18. Prohlédnutí výsledků trasování v GUI
19. Repositář s demonstračními příklady
1. Krátká lekce z historie: příkazy TRON a TROFF
Již starodávný interpret BASICu pojmenovaný GW-BASIC, který pochází z roku 1983, obsahoval základní prostředky použitelné pro ladění aplikací. Zatímco v současných vývojových prostředích je běžné, že uživatel může postupně krokovat jednotlivými příkazy laděné aplikace a přitom si vypisovat obsah proměnných (popř. obsah proměnných měnit či modifikovat vlastní programový kód), na počátku osmdesátých let minulého století nebyly dostupné vývojové prostředky na mikropočítačích zdaleka tak dokonalé. V GW-BASICu bylo samozřejmě možné, podobně jako v prakticky všech interpretrech BASICu, vypisovat hodnoty proměnných či celých výrazů, spouštět program od místa kde byl zastaven, provést skok na libovolný programový řádek atd. Navíc však GW-BASIC obsahoval i velmi užitečný příkaz TRON (trace on), který prováděl přesně to, co jeho název napovídá – po spuštění programu vypisoval čísla řádků, kterými interpret při běhu programu prošel, takže programátor mohl po ukončení (pádu :-) programu alespoň částečně zrekonstruovat, co jeho aplikace ve skutečnosti prováděla.
Obrázek 1: Program spuštěný (interpretovaný) běžných způsobem, na obrazovku vypisuje pouze svůj výstup – sekvenci čísel 1 až 10.
Na druhém obrázku je ukázáno, jak se bude běh (tj. ve skutečnosti interpretace) programu lišit po zadání příkazu TRON. Pokud je tento příkaz aktivní, vypisují se při interpretaci programu do hranatých závorek čísla programových řádků, kterými program při svém běhu prochází. V demonstračním příkladu ukázaném na druhém obrázku jsou čísla řádků prokládána samotným výstupem programu, konkrétně výsledkem příkazu PRINT A. Pokud by program na výstup neposílal žádný text, byla by výsledkem jeho běhu pouze řada čísel umístěná do hranatých závorek. Příkaz TRON je možné deaktivovat příkazem TROFF (trace off).
Obrázek 2: Běh programu po zadání příkazu TRON.
2. Standardní modul trace
Vývojáři používající jazyk Python mají k dispozici hned několik nástrojů určených pro sledování (trasování) běhu programu. Jeden z těchto nástrojů se jmenuje příznačně trace a jeho nespornou předností je fakt, že je již součástí standardní knihovny Pythonu (tedy patří do skupiny nástrojů shrnutých pod heslem „batteries included“). Příklad použití tohoto nástroje si ukážeme na velmi jednoduchém skriptu, jenž obsahuje implementaci rekurzivního algoritmu pro výpočet faktoriálu. Jedná se tedy o klasický „školní“ algoritmus, který pravděpodobně není nutné podrobněji popisovat:
"""Výpočet faktoriálu.""" def factorial(n): """Rekurzivní výpočet faktoriálu.""" assert isinstance(n, int), "Integer expected" if n < 0: return None if n == 0: return 1 result = n * factorial(n-1) assert isinstance(result, int), "Internal error in factorial computation" return result def main(): for n in range(0, 11): print(n, factorial(n)) if __name__ == "__main__": main()
3. Zjištění počtu průchodů jednotlivými řádky skriptu či skriptů
První informací, kterou s využitím základního modulu trace můžeme zjistit, je počet průchodů jednotlivými řádky spouštěného skriptu nebo skriptů (pochopitelně psaných v Pythonu). Pokud například budeme chtít zjistit, kolikrát jsou provedeny (vykonány) řádky ve skriptu pro výpočet faktoriálu, postačuje použít tento příkaz:
$ python3 -m trace --count -C . factorial.py
Výsledkem by měl být soubor nazvaný factorial.cover s tímto obsahem:
1: """Výpočet faktoriálu.""" 1: def factorial(n): """Rekurzivní výpočet faktoriálu.""" 66: assert isinstance(n, int), "Integer expected" 66: if n < 0: return None 66: if n == 0: 11: return 1 55: result = n * factorial(n-1) 55: assert isinstance(result, int), "Internal error in factorial computation" 55: return result 1: def main(): 12: for n in range(0, 11): 11: print(n, factorial(n)) 1: if __name__ == "__main__": 1: main()
Povšimněte si, že i dokumentační řetězce jsou spuštěny a vyhodnocovány (jde totiž o výrazy, jejichž výsledek je zahozen). To si ostatně můžeme snadno ověřit:
"""Test.""" # test "Test." # test
Výsledek trasování tohoto skriptu:
1: """Test.""" # test 1: "Test." # test
Vidíme, že dokumentační i běžné řetězce jsou skutečně považovány za příkazy, kdežto komentáře nikoli.
4. Detekce řádků s „mrtvým“ kódem
Ve výpisu z předchozí kapitoly se nachází i jeden řádek, který nebyl spuštěn ani jednou:
1: """Výpočet faktoriálu.""" 1: def factorial(n): """Rekurzivní výpočet faktoriálu.""" 66: assert isinstance(n, int), "Integer expected" 66: if n < 0: return None 66: if n == 0: 11: return 1 55: result = n * factorial(n-1) 55: assert isinstance(result, int), "Internal error in factorial computation" 55: return result 1: def main(): 12: for n in range(0, 11): 11: print(n, factorial(n)) 1: if __name__ == "__main__": 1: main()
V delším skriptu je mnohdy obtížné takové řádky najít; proto modul trace podporuje volbu -m, která takové „mrtvé“ příkazy zvýrazní:
$ python3 -m trace --count -m -C . factorial.py
Nyní vypadá výsledek následovně – řádek s „mrtvým“ kódem je zvýrazněn pomocí znaků >>>>>>:
1: """Výpočet faktoriálu.""" 1: def factorial(n): """Rekurzivní výpočet faktoriálu.""" 66: assert isinstance(n, int), "Integer expected" 66: if n < 0: >>>>>> return None 66: if n == 0: 11: return 1 55: result = n * factorial(n-1) 55: assert isinstance(result, int), "Internal error in factorial computation" 55: return result 1: def main(): 12: for n in range(0, 11): 11: print(n, factorial(n)) 1: if __name__ == "__main__": 1: main()
Zobrazit je možné i souhrnné informace o skriptech, jejichž řádky byly při spuštění vyhodnoceny:
$ python3 -m trace --count --summary -C . factorial.py
Po spuštění předchozího příkazu se nejdříve zobrazí výstup produkovaný samotným skriptem a posléze i souhrnné informace:
0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800 lines cov% module (path) 14 100% factorial (factorial.py) 1 100% trace (/usr/lib64/python3.6/trace.py)
5. Tisk seznamu funkcí, které byly zavolány
Mnohdy nám postačuje znát pouze informace o funkcích (a pochopitelně i o metodách), které byly při běhu skriptu zavolány. Jedná se o odlišný režim nástroje trace, který se vybírá volbou –listfuncs tak, jak je to ukázáno v dalším příkladu:
$ python3 -m trace --listfuncs -C . factorial.py
Nejprve se opět vypíšou výsledky produkované přímo při výpočtu faktoriálu:
0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Nakonec získáme seznam volaných funkcí, včetně pseudofunkce volané při vstupu do modulu factorial:
functions called: filename: /usr/lib64/python3.6/trace.py, modulename: trace, funcname: _unsettrace filename: factorial.py, modulename: factorial, funcname: <module> filename: factorial.py, modulename: factorial, funcname: factorial filename: factorial.py, modulename: factorial, funcname: main
6. Seznam se vzájemnými vztahy mezi funkcemi (které funkce jsou volány)
Další potenciálně velmi užitečnou informací jsou vzájemné vztahy mezi funkcemi, přesněji řečeno informace o tom, z jaké funkce došlo k volání jiné či stejné (přímá rekurze) funkce. I tyto informace lze standardním nástrojem trace získat, a to konkrétně při použití přepínače –trackcalls. Opět se podívejme na demonstrační příklad:
$ python3 -m trace --trackcalls -C . factorial.py
Po již obligátním výpisu tabulky s faktoriály…
0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
… se zobrazí následující řádky nabízející informace o dynamických vztazích mezi funkcemi, tedy o vztazích, které mnohdy nelze získat pouhou statickou analýzou zdrojových kódů:
calling relationships: *** /usr/lib64/python3.6/trace.py *** trace.Trace.runctx -> trace._unsettrace --> factorial.py trace.Trace.runctx -> factorial.<module> *** factorial.py *** factorial.<module> -> factorial.main factorial.factorial -> factorial.factorial factorial.main -> factorial.factorial
7. Úplné trasování, informace o relativních časech při trasování
Poslední vlastnost modulu trace, s níž se dnes seznámíme, je možnost přímo při běhu skriptu současně vypisovat programové řádky, které se právě provádí. Jedná se vlastně o vylepšenou variantu příkazu TRON, která v mnoha BASICech vypisovala pouze čísla řádků. V případě Pythonu a modulu trace se vypisují celé řádky, nikoli jejich číslo (které má mimo BASIC jen omezený význam). Podívejme se nyní na základní příklad použití:
$ python3 -m trace --trace -C . factorial.py > factorial.trace
Po spuštění takto zapsaného příkazu se postupně začnou jednotlivé spouštěné řádky vypisovat ve formátu jméno souboru(číslo řádku): programový řádek:
--- modulename: factorial, funcname: <module> factorial.py(1): """Výpočet faktoriálu.""" factorial.py(4): def factorial(n): factorial.py(18): def main(): factorial.py(23): if __name__ == "__main__": factorial.py(24): main() --- modulename: factorial, funcname: main factorial.py(19): for n in range(0, 11): factorial.py(20): print(n, factorial(n)) --- modulename: factorial, funcname: factorial factorial.py(6): assert isinstance(n, int), "Integer expected" factorial.py(8): if n < 0: factorial.py(10): if n == 0: factorial.py(11): return 1 ... ... ...
Navíc je možné ke každému vypsanému řádku přidat i relativní čas (v sekundách) počítaný od doby spuštění programu:
$ python3 -m trace --trace -g -C . factorial.py > factorial.timing
S výsledkem:
--- modulename: factorial, funcname: 0.00 factorial.py(1): """Výpočet faktoriálu.""" 0.00 factorial.py(4): def factorial(n): 0.00 factorial.py(18): def main(): 0.00 factorial.py(23): if __name__ == "__main__": 0.00 factorial.py(24): main() --- modulename: factorial, funcname: main 0.00 factorial.py(19): for n in range(0, 11): 0.00 factorial.py(20): print(n, factorial(n)) --- modulename: factorial, funcname: factorial 0.00 factorial.py(6): assert isinstance(n, int), "Integer expected" 0.00 factorial.py(8): if n < 0: 0.00 factorial.py(10): if n == 0: 0.00 factorial.py(11): return 1 ... ... ...
8. Pomocný nástroj coverage.py
V seriálu o testování v Pythonu jsme se mj. seznámili i s užitečným nástrojem pytest-cov, který dokáže zjistit, které části kódu jsou pokryté jednotkovými testy (tedy které programové řádky jsou skutečně zavolány). Podobnou informaci je ovšem možné zjistit i pro běžně spouštěné skripty – tedy jde nám o výpis informací o těch řádcích kódu, které byly skutečně spuštěny a které naopak nebyly, a to buď z toho důvodu, že nebyly splněny nějaké podmínky, nedošlo k chybě, popř. jsou dané řádky zbytečné z důvodu logické chyby v přepisovaném algoritmu. Tyto informace lze v přehledné podobě získat s využitím nástroje coverage.py, který existuje i ve výše zmíněné variantě pytest-cov.
Instalace nástroje coverage.py je přímočará, protože se jedná o balíček dostupný na PyPi:
$ pip3 install --user coverage
Průběh instalace:
Collecting coverage Downloading https://files.pythonhosted.org/packages/5a/0d/a1cb46ee9b9f6369e2bcf72b8277654a806bdf3f17f724be24a3a72afdc3/coverage-5.4-cp38-cp38-manylinux2010_x86_64.whl (245kB) |████████████████████████████████| 245kB 1.9MB/s Installing collected packages: coverage Successfully installed coverage-5.4
Po instalaci se přesvědčíme, že je nástroj coverage.py volatelný přímo z příkazové řádky:
$ coverage help Coverage.py, version 5.4 with C extension Measure, collect, and report on code coverage in Python programs. usage: coverage <command> [options] [args] Commands: annotate Annotate source files with execution information. combine Combine a number of data files. debug Display information about the internals of coverage.py erase Erase previously collected coverage data. help Get help on using coverage.py. html Create an HTML report. json Create a JSON report of coverage results. report Report coverage stats on modules. run Run a Python program and measure code execution. xml Create an XML report of coverage results. Use "coverage help <command>" for detailed help on any command. Full documentation is at https://coverage.readthedocs.io
9. Spuštění aplikace přes nástroj coverage.py
Libovolný skript psaný v Pythonu by nyní mělo být možné spustit nepřímo příkazem coverage, tedy následujícím způsobem:
$ coverage run factorial.py
Skript se zdánlivě spustí naprosto normálním způsobem:
0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
Ve skutečnosti se ovšem průběžně vytváří soubor .coverage, jehož obsah je dále zpracovatelný, což si ostatně ukážeme v navazujících kapitolách.
10. Zobrazení výsledků
Výsledky získané nástrojem coverage je možné zobrazit v několika podobách. Základem je tabulka obsahující jména jednotlivých souborů se zdrojovými kódy, počet příkazů (statements) v každém souboru, počet příkazů, které nebyly vykonány a konečně poměr vykonaných příkazů ke všem příkazům.
Pro náš jednoduchý příklad s jediným skriptem bude výsledek vypadat následovně:
$ coverage report Name Stmts Miss Cover ---------------------------------- factorial.py 14 1 93% ---------------------------------- TOTAL 14 1 93%
Z výsledků můžeme vyčíst informaci, kterou již známe – jeden z příkazů nebyl použit.
Pro složitější aplikaci skládající se z několika souborů se zdrojovými texty bude výsledek vypadat takto:
$ coverage report Name Stmts Miss Cover ------------------------------------------------------------ src/gui/__init__.py 0 0 100% src/gui/canvas.py 7 4 43% src/gui/dialogs/__init__.py 0 0 100% src/gui/dialogs/about_dialog.py 4 1 75% src/gui/dialogs/fractal_type_dialog.py 28 20 29% src/gui/dialogs/help_dialog.py 34 28 18% src/gui/icons.py 19 8 58% src/gui/main_window.py 23 13 43% src/gui/menubar.py 36 29 19% src/icons/__init__.py 0 0 100% src/icons/application_exit.py 1 0 100% src/icons/edit.py 1 0 100% src/icons/file_open.py 1 0 100% src/icons/file_save.py 1 0 100% src/icons/file_save_as.py 1 0 100% src/icons/fractal_new.py 1 0 100% src/icons/help_about.py 1 0 100% src/icons/help_faq.py 1 0 100% src/svitava-gui.py 5 1 80% ------------------------------------------------------------ TOTAL 164 104 37%
S využitím přepínače –skip-covered je navíc možné vynechat ty soubory, jejichž všechny příkazy jsou alespoň jedenkrát vykonány:
$ coverage report --skip-covered Name Stmts Miss Cover ------------------------------------------------------------ src/gui/canvas.py 7 4 43% src/gui/dialogs/about_dialog.py 4 1 75% src/gui/dialogs/fractal_type_dialog.py 28 20 29% src/gui/dialogs/help_dialog.py 34 28 18% src/gui/icons.py 19 8 58% src/gui/main_window.py 23 13 43% src/gui/menubar.py 36 29 19% src/svitava-gui.py 5 1 80% ------------------------------------------------------------ TOTAL 164 104 37% 11 files skipped due to complete coverage.
11. Anotace zdrojového kódu
Příkazem:
$ coverage annotate
získáme výpis zdrojových kódů, ke kterým jsou přidány informace o tom, který z příkazů byl alespoň jedenkrát vykonán a který naopak vůbec vykonán nebyl. Výsledek pro náš demonstrační příklad s faktoriálem vypadá takto:
> """Výpočet faktoriálu.""" > def factorial(n): > """Rekurzivní výpočet faktoriálu.""" > assert isinstance(n, int), "Integer expected" > if n < 0: ! return None > if n == 0: > return 1 > result = n * factorial(n-1) > assert isinstance(result, int), "Internal error in factorial computation" > return result > def main(): > for n in range(0, 11): > print(n, factorial(n)) > if __name__ == "__main__": > main()
12. Export výsledků do formátů JSON a XML
Nástroj coverage.py podporuje export naměřených výsledků, tedy přesněji řečeno informací o tom, které programové řádky byly vykonány a které nikoli, do formátů JSON a XML, což umožňuje relativně snadnou integraci tohoto nástroje například do integrovaných vývojových prostředí, nástrojů pro dynamickou analýzu zdrojových kódů atd.
Export do formátu JSON zajišťuje příkaz:
$ coverage json
Výsledkem bude (pro demonstrační příklad obsahující výpočet faktoriálu) tento soubor:
{"meta": {"version": "5.4", "timestamp": "2021-02-05T09:37:22.563357", "branch_coverage": false, "show_contexts": false}, "files": {"factorial.py": {"executed_lines": [1, 3, 5, 7, 9, 10, 11, 13, 14, 17, 18, 19, 22, 23], "summary": {"covered_lines": 13, "num_statements": 14, "percent_covered": 92.85714285714286, "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [8], "excluded_lines": []}}, "totals": {"covered_lines": 13, "num_statements": 14, "percent_covered": 92.85714285714286, "missing_lines": 1, "excluded_lines": 0}}
Který můžeme s využitím nástroje jq převést do čitelnější podoby, z níž je patrné jakým způsobem jsou informace prezentovány:
{ "meta": { "version": "5.4", "timestamp": "2021-02-05T09:37:22.563357", "branch_coverage": false, "show_contexts": false }, "files": { "factorial.py": { "executed_lines": [ 1, 3, 5, 7, 9, 10, 11, 13, 14, 17, 18, 19, 22, 23 ], "summary": { "covered_lines": 13, "num_statements": 14, "percent_covered": 92.85714285714286, "missing_lines": 1, "excluded_lines": 0 }, "missing_lines": [ 8 ], "excluded_lines": [] } }, "totals": { "covered_lines": 13, "num_statements": 14, "percent_covered": 92.85714285714286, "missing_lines": 1, "excluded_lines": 0 } }
Podobný příkaz zajišťuje export do formátu XML:
$ coverge xml
Tentokrát je výsledný soubor větší, i když ve skutečnosti obsahuje podobné informace, pouze jinak prezentované:
<?xml version="1.0" ?> <coverage version="5.4" timestamp="1612601186209" lines-valid="14" lines-covered="13" line-rate="0.9286" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0"> <!-- Generated by coverage.py: https://coverage.readthedocs.io --> <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd --> <sources> <source>/home/tester</source> </sources> <packages> <package name="." line-rate="0.9286" branch-rate="0" complexity="0"> <classes> <class name="factorial.py" filename="factorial.py" complexity="0" line-rate="0.9286" branch-rate="0"> <methods/> <lines> <line number="3" hits="1"/> <line number="5" hits="1"/> <line number="7" hits="1"/> <line number="8" hits="0"/> <line number="9" hits="1"/> <line number="10" hits="1"/> <line number="11" hits="1"/> <line number="13" hits="1"/> <line number="14" hits="1"/> <line number="17" hits="1"/> <line number="18" hits="1"/> <line number="19" hits="1"/> <line number="22" hits="1"/> <line number="23" hits="1"/> </lines> </class> </classes> </package> </packages> </coverage>
13. Zobrazení výsledků ve formě vygenerovaných HTML stránek
Posledním způsobem zobrazení výsledků získaných nástrojem coverage.py je vygenerování HTML stránek s informacemi o tom, které části zdrojových kódů byly vykonány a které nikoli. Tento výstup získáme příkazem:
$ coverage html
Výsledkem je několik HTML stránek, CSS souborů a souborů s podpůrnými funkcemi JavaScriptu, které obsahují jak souhrnné informace o příkazech, které byly provedeny, tak i podrobnější informace vztažené k jednotlivým programovým řádků z každého zdrojového souboru.
Obrázek 3: Souhrnné informace o celém programu, který byl sledován.
Obrázek 4: Podrobné informace o příkazech provedených ve vybraném skriptu.
Obrázek 5: Souhrnné informace pro složitější projekt s více zdrojovými soubory.
14. Interní struktura s informacemi o trasování (SQLite databáze)
Pro úplnost se ještě podívejme na to, jaká data a v jakém formátu vlastně obsahuje soubor .coverage, který je vytvořen ve chvíli, kdy spustíme sledovaný program pomocí příkazu coverage run. Jedná se o binární soubor:
$ od -t x1 -N 1800 .coverage
Interní struktura nám mnoho neprozradí:
0000000 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00 0000020 10 00 01 01 00 40 20 20 00 00 00 0a 00 00 00 0d 0000040 00 00 00 00 00 00 00 00 00 00 00 07 00 00 00 04 0000060 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 0000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0a 0000140 00 2e 4b 90 0d 0f 56 00 0c 06 96 00 0f 5e 0d 58 0000160 0f 2d 0c a5 0d 2f 0b da 0c 76 09 93 0b a7 07 6a 0000200 09 6c 06 96 00 00 00 00 00 00 00 00 00 00 00 00 0000220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 0003220 00 00 00 00 00 00 81 51 0c 07 17 19 19 01 82 7d 0003240 74 61 62 6c 65 74 72 61 63 65 72 74 72 61 63 65 0003260 72 0d 43 52 45 41 54 45 20 54 41 42 4c 45 20 74 0003300 72 61 63 65 72 20 28 0a 20 20 20 20 2d 2d 20 41 0003320 20 72 6f 77 20 70 65 72 20 66 69 6c 65 20 69 6e 0003340 64 69 63 61 74 69 6e 67 20 74 68 65 20 74 72 61 0003360 63 65 72 20 75 73 65 64 20 66 6f 72 20 74 68 61 0003400 74 20 66 69 6c 65 2e 0a 0003410
Ve skutečnosti lze poměrně snadno zjistit, že se jedná o soubor používající formát souborové databáze SQLite:
$ file .coverage .coverage: SQLite 3.x database, last written using SQLite version 3034000
Můžeme tedy obsah tohoto souboru prozkoumat (ovšem pochopitelně s tím dodatkem, že se jeho formát může v dalších verzích změnit):
$ sqlite3 .coverage SQLite version 3.34.0 2020-12-01 16:14:00 Enter ".help" for usage hints.
V databázi nalezneme celkem sedm tabulek:
sqlite> .tables arc coverage_schema line_bits tracer context file meta
Důležité jsou především tabulky file, tracer a line_bits, mezi nimiž existují relační vazby:
sqlite> .schema file CREATE TABLE file ( -- A row per file measured. id integer primary key, path text, unique (path) ); sqlite> .schema tracer CREATE TABLE tracer ( -- A row per file indicating the tracer used for that file. file_id integer primary key, tracer text, foreign key (file_id) references file (id) ); sqlite> .schema line_bits CREATE TABLE line_bits ( -- If recording lines, a row per context per file executed. -- All of the line numbers for that file/context are in one numbits. file_id integer, -- foreign key to `file`. context_id integer, -- foreign key to `context`. numbits blob, -- see the numbits functions in coverage.numbits foreign key (file_id) references file (id), foreign key (context_id) references context (id), unique (file_id, context_id) );
Samozřejmě si můžeme obsah jednotlivých tabulek zobrazit:
sqlite> select * from file; 1|/home/tester/factorial.py
Ovšem v případě tabulky line_bits je nutné upozornit na to, že informace o řádcích s vykonanými příkazy jsou uloženy ve formě bitových množin popsaných na stránce https://coverage.readthedocs.io/en/coverage-5.3.1/dbschema.html#numbits (a to z důvodu větší efektivity uložení). Získání těchto informací pouze s využitím SQL příkazů je tedy dosti problematické.
15. Nástroj pycrunch-pytrace
Třetím nástrojem, se kterým se v dnešním článku setkáme, je nástroj nazvaný pycrunch-pytrace, popř. zkráceně pouze pytrace (což je ovšem nepřesné, protože existuje ještě jeden balíček s tímto jménem, který ovšem pracuje jinak). Tento potenciálně velmi užitečný nástroj dokáže sledovat a zaznamenávat činnosti prováděné sledovanou aplikací a následně zobrazit stav aplikace v libovolném čase do minulosti (a to až na úroveň volání jednotlivých funkcí a jejích parametrů). Jedná se tedy o jakousi obdobu debuggeru, v němž se ale můžeme pohybovat nikoli pouze dopředu (typicky příkazy step a run), ale i pozpátku. Toto zpětné „přetáčení“ minulosti aplikace je prováděno v grafickém uživatelském rozhraní běžícím ve webovém prohlížeči:
Obrázek 4: Grafické uživatelské rozhraní programu, kterým je možné zpětně sledovat chování aplikace.
16. Instalace nástroje pycrunch-pytrace
Instalace samotného nástroje pycrunch-pytrace (resp. přesněji řečeno jeho „trasovací“ části) je velmi jednoduchá, neboť se opět jedná o nástroj dostupný na PyPi. Instalaci provedeme následujícím způsobem:
$ pip install --user pycrunch-trace
Povšimněte si, že jednou ze závislostí tohoto nástroje je i Cython, což je technologie, kterou jsme se zabývali v článku RPython vs Cython aneb dvojí přístup k překladu Pythonu do nativního kódu:
Collecting pycrunch-trace Downloading https://files.pythonhosted.org/packages/ef/30/96a3666b1a88399183f8fad6fc930f325fdd16afad837063814005573c80/pycrunch-trace-0.1.5.tar.gz (44kB) |████████████████████████████████| 51kB 184kB/s Collecting Cython Downloading https://files.pythonhosted.org/packages/9f/05/959e78f2aeade1c9e85a7adc4c376f454ecaeb4cb6b079ca7a85684b69c1/Cython-0.29.21-cp38-cp38-manylinux1_x86_64.whl (1.9MB) |████████████████████████████████| 1.9MB 339kB/s Collecting jsonpickle Downloading https://files.pythonhosted.org/packages/77/a7/c2f527ddce3155ae9e008385963c2325cbfd52969f8b38efa2723e2af4af/jsonpickle-1.5.1-py2.py3-none-any.whl Collecting PyYAML Downloading https://files.pythonhosted.org/packages/70/96/c7245e551b1cb496bfb95840ace55ca60f20d3d8e33d70faf8c78a976899/PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl (662kB) |████████████████████████████████| 665kB 4.5MB/s Collecting protobuf==3.11.3 Downloading https://files.pythonhosted.org/packages/9a/71/5cdb5ed762a537eac39097ae6ecf8785e276b5044efe99b8e53cb3addd7f/protobuf-3.11.3-cp38-cp38-manylinux1_x86_64.whl (1.3MB) |████████████████████████████████| 1.3MB 4.6MB/s Requirement already satisfied: setuptools in /usr/lib/python3.8/site-packages (from protobuf==3.11.3->pycrunch-trace) (41.6.0) Requirement already satisfied: six>=1.9 in /usr/lib/python3.8/site-packages (from protobuf==3.11.3->pycrunch-trace) (1.14.0) Installing collected packages: Cython, jsonpickle, PyYAML, protobuf, pycrunch-trace Running setup.py install for pycrunch-trace ... done Successfully installed Cython-0.29.21 PyYAML-5.4.1 jsonpickle-1.5.1 protobuf-3.11.3 pycrunch-trace-0.1.5
Základní otestování instalace můžeme provést interaktivně z REPLu jazyka Python:
$ python Python 3.8.7 (default, Dec 22 2020, 00:00:00) [GCC 10.2.1 20201125 (Red Hat 10.2.1-9)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import pycrunch_trace >>> help(pycrunch_trace) Help on package pycrunch_trace: NAME pycrunch_trace PACKAGE CONTENTS client (package) config demo (package) events (package) ... ... ...
17. Úprava trasovaného skriptu a spuštění trasování
Na rozdíl od obou předchozích nástrojů vyžaduje pycrunch-trace úpravu zdrojových kódů sledované aplikace. Ve skutečnosti je ovšem tato úprava triviální, protože je zapotřebí přidat jeden příkaz import:
from pycrunch_trace.client.api import trace
… a následně použít anotaci @trace u té funkce, u níž má trasování začít. Může se jednat například o funkci main:
from pycrunch_trace.client.api import trace """Výpočet faktoriálu.""" def factorial(n): """Rekurzivní výpočet faktoriálu.""" assert isinstance(n, int), "Integer expected" if n < 0: return None if n == 0: return 1 result = n * factorial(n-1) assert isinstance(result, int), "Internal error in factorial computation" return result @trace def main(): for n in range(0, 11): print(n, factorial(n)) if __name__ == "__main__": main()
Ovšem tuto anotaci můžeme přidat k libovolné funkci, která se interně volá:
from pycrunch_trace.client.api import trace """Výpočet faktoriálu.""" @trace def factorial(n): """Rekurzivní výpočet faktoriálu.""" assert isinstance(n, int), "Integer expected" if n < 0: return None if n == 0: return 1 result = n * factorial(n-1) assert isinstance(result, int), "Internal error in factorial computation" return result def main(): for n in range(0, 11): print(n, factorial(n)) if __name__ == "__main__": main()
Následně je již možné sledovanou aplikaci spustit, a to naprosto běžným způsobem (což je další rozdíl oproti nástrojům trace a coverage):
$ python factorial.py
Sledovaný program by se měl spustit kromě dalších informací vypsat i zprávy o tom, že bylo zahájeno sledování:
/home/tester/.local/lib/python3.8/site-packages/Cython/Compiler/Main.py:369: FutureWarning: Cython directive 'language_level' not set, using 2 for now (Py2). This will change in a later release! File: /home/tester/.local/lib/python3.8/site-packages/pycrunch_trace/client/networking/strategies/native_write_strategy.pyx tree = Parsing.p_module(s, pxd, full_module_name) ----print_timings---- tracing complete, saving results total_samples - 538 total overhead time - 6 ms 6.324903004497173 0.01176 ms avg call time overhead total events C: 31 main - put_events: so far: 531 finalizing native tracer queue length 0 got evt EventsSlice put_file_slice tracing_did_complete queue length 0 got evt FileContentSlice skip_to_free_header_chunk pos = 12 after pos = 28 queue length 0 got evt StopCommand skip_to_free_header_chunk pos = 12 after pos = 52 metadata saved to /home/tester/pycrunch-recordings/main/pycrunch-trace.meta.json Timeout while waiting for new msg... Thread will stop for now Thread stopped
Výsledkem by měl být soubor pycrunch-trace.meta.json a především pak binární soubor session.chunked.pycrunch-trace, který použijeme v navazující kapitole.
18. Prohlédnutí výsledků trasování v GUI
Soubor session.chunked.pycrunch-trace je možné nahrát do webové aplikace dostupné na adrese https://pytrace.com/ (popř. si tuto aplikaci spustit lokálně – existuje v repositáři https://github.com/gleb-sevruk/pycrunch-tracing-webui). Měla by se objevit především časová osa, po které je možné se pohybovat a zobrazit tak stav programu ve vybraném okamžiku. V dolní části je navíc možné zapnout pseudografické zobrazení volaných funkcí:
Obrázek 7: Načtení souboru s informacemi o sledované aplikaci se zobrazením právě prováděného příkazu.
Obrázek 8: V pravé části jsou zobrazeny informace o parametrech i o lokálních proměnných.
Obrázek 9: Grafické znázornění rekurzivního výpočtu faktoriálu.
Obrázek 10: Krokování volanými funkcemi (opět se zobrazují i parametry a lokální proměnné).
Obrázek 11: Odlišná aplikace s GUI.
Obrázek 12: Vzorová aplikace určená pro otestování možností pycrunch-trace.
19. Repositář s demonstračními příklady
Zdrojové kódy všech tří dnes použitých demonstračních příkladů určených pro Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs (jedná se o několik variant implementace funkce pro výpočet faktoriálu). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
# | Demonstrační příklad | Stručný popis příkladu | Cesta |
---|---|---|---|
1 | factorial.py | původní varianta skriptu pro výpočet faktoriálu | https://github.com/tisnik/most-popular-python-libs/blob/master/tracing/factorial.py |
2 | factorial_pycrunch1.py | úprava pro trasování s využitím pycrunch-trace | https://github.com/tisnik/most-popular-python-libs/blob/master/tracing/factorial_pycrunch1.py |
3 | factorial_pycrunch2.py | alternativní úprava pro trasování s využitím pycrunch-trace | https://github.com/tisnik/most-popular-python-libs/blob/master/tracing/factorial_pycrunch2.py |
K dispozici jsou i datové soubory vytvořené nástrojem coverage.py i standardním nástrojem trace:
# | Demonstrační příklad | Stručný popis příkladu | Cesta |
---|---|---|---|
1 | factorial.cover | počet přístupů k jednotlivým řádkům kódu | https://github.com/tisnik/most-popular-python-libs/blob/master/tracing/factorial.cover |
2 | factorial.trace | výsledek trasování | https://github.com/tisnik/most-popular-python-libs/blob/master/tracing/factorial.trace |
3 | factorial.timing | výsledek trasování + informace o relativním času běhu aplikace | https://github.com/tisnik/most-popular-python-libs/blob/master/tracing/factorial.timing |
20. Odkazy na Internetu
- Stránka projektu PyTrace
https://pytrace.com/ - Nástroj pycrunch-trace na PyPi
https://pypi.org/project/pycrunch-trace/ - Repositář nástroje pycrunch-trace na GitHubu
https://github.com/gleb-sevruk/pycrunch-trace - Repositář serveru pycrunch-tracing-webui na GitHubu
https://github.com/gleb-sevruk/pycrunch-tracing-webui - Server s GUI pro nástroj pycrunch-trace
https://app.pytrace.com/ - Dokumentace k projektu Coverage.py
https://coverage.readthedocs.io/en/coverage-5.3.1/index.html - Projekt coveragepy na GitHubu
https://github.com/nedbat/coveragepy - Projekt coverage 5.4 na PyPi
https://pypi.org/project/coverage/ - Coverage.py database schema
https://coverage.readthedocs.io/en/coverage-5.3.1/dbschema.html - Numbits
https://coverage.readthedocs.io/en/coverage-5.3.1/dbschema.html#numbits - SQLite Show Tables
https://www.sqlitetutorial.net/sqlite-tutorial/sqlite-show-tables/ - SQLite Describe Table
https://www.sqlitetutorial.net/sqlite-tutorial/sqlite-describe-table/ - Trasování a ladění nativních aplikací v Linuxu
https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu/ - Trasování a ladění nativních aplikací v Linuxu: použití GDB a jeho nadstaveb
https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu-pouziti-gdb-a-jeho-nadstaveb/ - Trasování a ladění nativních aplikací v Linuxu: pokročilejší možnosti nabízené GNU Debuggerem
https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu-pokrocilejsi-moznosti-nabizene-gnu-debuggerem/ - Trasování a ladění nativních aplikací v Linuxu: nástroj SystemTap
https://www.root.cz/clanky/trasovani-a-ladeni-nativnich-aplikaci-v-linuxu-pouziti-nastroje-systemtap/ - Trasování a ladění v Linuxu: jazyk používaný SystemTapem
https://www.root.cz/clanky/trasovani-a-ladeni-v-linuxu-jazyk-pouzivany-systemtapem/ - Grafická nadstavba nad GNU Debuggerem gdbgui a její alternativy
https://www.root.cz/clanky/graficka-nadstavba-nad-gnu-debuggerem-gdbgui-a-jeji-alternativy/ - Debugging C128 BASIC with TRON, TRAP, and More
https://www.youtube.com/watch?v=D11AuAl5T-s - TRON command
https://en.wikipedia.org/wiki/TRON_command