Již známe profiler Devel::NYTProf. Nutno říct, že existuje řada jednodušších. Ale přesto leckoho napadne otázka: Na jakém principu vlastně taková věc funguje?
30.8.2011 00:00 | Jiří Václavík | přečteno 9438×
Překvapivě není vůbec těžké napsat si vlastní profiler nebo debugger. Rozhodně je dobré si to zkusit, protože potom člověk snáze pochopí, jak vlastně debugger (potažmo všechny profilery) fungují.
K tomu je třeba napsat modul a v něm zadefinovat podprogram DB::DB, který je automaticky volán během zpracovávání každého řádku. Výsledky pak můžeme zobrazit v bloku END. Použití se neliší od profileru Devel::NYTProf.
Nejjednodušší (avšak velmi limitovaný) způsob, jak napsat vlastní debugger, je pomocí proměnné prostředí PERL5DB. V ní můžeme nastavit pomocí podprogramu DB::DB chování. Například následující nastavení vždy vyhodnotí řádek a počká si před dalším pokračováním na ENTER.
PERL5DB="sub DB::DB {scalar <STDIN>}"
Takto tedy můžeme spustit libovolný program.
PERL5DB="sub DB::DB {scalar <STDIN>}" perl -d program.pl
Jakkoliv je proměnné PERL5DB zajímavá, je vhodnější si pro nějaké smysluplné profilery vytvořit přimo balík, ve kterém se o všechny požadované operace postaráme.
Ukážeme si zde jednoduchý příklad. Napíšeme si takovou hodně odlehčenou verzi modulu Devel::Cover, o kterém jsme se letmo zmínili minule. Budeme pro libovolný program počítat, kolikkrát jsme vyhodnocovali který řádek.
Jediné, o co se tedy budeme starat, je udržování nějakého pole, které nám bude uchovávat pro každý řádek počet jeho volání.
Vytvořme tedy soubor Devel/MujProfiler.pm, který se nachází v cestě z @INC. V něm bude celý náš profiler.
package Devel::MujProfiler;
Naším hlavním úkolem je přetížit podprogram DB::DB. Změňme tedy balík a zadefinujme v něm podprogram DB::DB.
package DB;
sub DB {
...tady budeme počítat volání...
}
Budeme používat nějakou globální proměnnou na počítání řádků. Informace do ní získáme pomocí funkce caller, která vrací jméno balíku, programu a aktuální řádek. Uvědomme si, že DB::DB je volán na každém řádku, takže nyní již pouze stačí přičíst 1 k aktuálnímu řádku.
my($balik, $soubor, $radek) = caller;
if($soubor eq $0){
$radky[$radek]++;
}
Data máme posbíraná, nyní je potřebujeme nějak zobrazit nebo někam vyexportovat. Zde je nejvhodnějším způsobem umístění do bloku END, který se provádí těsně před koncem programu.
Proměnná @radky je globální a tak není problém k ní přistupovat. Otevřeme tedy sama sebe (tj. soubor z proměnné $0) a budeme po řádcích číst. Každý řádek vytiskneme a z proměnné @radky ještě dodáme číslo, kolikkrát byl právě tento řádek volán (nebo nulu, pokud volán nebyl).
END {
my $radek = 0;
open KOD, $0;
while(my $kod = <KOD>){
printf("%2d | %s", $radky[++$radek] || 0, $kod);
}
}
Udělejme ještě pár drobných úprav a potom již celý profiler můžeme uložit do Devel/MujProfiler.pm.
package Devel::MujProfiler;
use strict;
use warnings;
package DB;
our @radky;
print "Spoustim program. Vystup:\n";
sub DB {
my($balik, $soubor, $radek) = caller;
if($soubor eq $0){
$radky[$radek]++;
}
return;
}
END {
my $radek = 0;
print "\nProgram skoncil.\n\nVysledek profileru:\n\n";
open KOD, $0;
while(my $kod = <KOD>){
printf("%2d | %s", $radky[++$radek] || 0, $kod);
}
print "\n";
}
1;
Vyberme si nyní nějaký program, který budeme chtít profilovat. Zde je výstup programu, který obsahuje některé řídící struktury. Povšimněme si například, že uvnitř cyklu jsme napočítali 10 vyhodnocení.
$ perl -d:MujProfiler program.pl
Spoustim program. Vystup:
Zkusme si cyklus:123456789109 != 10
Program skoncil.
Vysledek profileru:
0 | #!/usr/bin/env perl
0 | use strict;
0 | use warnings;
0 |
1 | print "Zkusme si cyklus:";
0 |
1 | for (1 .. 10){
10 | print;
0 | }
0 |
1 | if(9 == 10){
0 | print '9 == 10';
0 | }else{
1 | print '9 != 10';
0 | }
$
Dalším zajímavým podprogramem pro nás bude DB::sub. Ten je volán pokaždé, když je volán nějaký podprogram. Jako parametry přitom dostane stejné parametry, jako onen volaný podprogram.
V proměnné $DB::sub máme vždy dostupné jméno aktuálního podprogramu. Díky tomu můžeme pracovat s celými podprogramy úplně stejně jako se řádky. Již bez dalšího komentáře si tedy uveďme, jak by mohl vypadat profiler, který by počítal volání jednotlivých podprogramů.
package Devel::SpocitejVolaniPodprogramu;
use strict;
use warnings;
package DB;
our %subs;
print "Spoustim program. Vystup:\n";
sub DB {}
sub sub {
$subs{$DB::sub}++;
}
END {
print "\nVolane podprogramy:\n";
for (keys %subs){
sub printf("%2d | %s\n", $subs{$_} || 0, $_);
}
print "\n";
}
1;
Použijme tento profiler na libovolný program. Na výstupu dostaneme seznam a počet použití jednotlivých podprogramů.
$ perl -d:SpocitejVolaniPodprogramu sub.pl
Spoustim program. Vystup:
Volane podprogramy:
19 | main::new
5 | main::union
1 | main::init
$