Tracing: צפייה בזרימת הביצוע#

פרק זה מכסה traces שמתעדים מה התוכנית עשתה מבלי לעצור אותה: פקודת ה־t של מנפה השגיאות ואפשרויות trace, משפחת המודולים Devel::Trace, ו־tracer של מנוע ה־regex. השתמשו ב־tracing כאשר הבאג הוא ״איזה מסלול קוד באמת רץ?״ ולא ״איזה ערך יש ל־X בשורה N?״.

קוראים שמגיעים יודעים כיצד לקבוע breakpoints ולבחון מצב. trace הוא ההשלמה - ״עקוב אחר כל צעד וספר לי עליו״ במקום ״עצור במקום מעניין אחד״.

t: trace בתוך מנפה השגיאות#

t מחליף את מצב ה־tracing לכל הוראה. כל שורה שניתנת לעצירה מדפיסה סמן מיקום לפני הביצוע:

  DB<1> t
Trace = on

  DB<2> c
main::(app.pl:5):          my $key = 'welcome';
main::(app.pl:6):          my %data = (
main::handle_request(app.pl:42):   my $user = load_user($req);
main::load_user(app.pl:87):        my ($req) = @_;
main::load_user(app.pl:88):        return unless $req;
...

הגבלת העומק:

  DB<3> t 2           # toggle with maximum sub-depth 2
  DB<4> t 0 my_work() # trace one expression only

האפשרות frame שולטת במה ש־trace מדפיס בקריאות sub וב־returns:

ביט

דגל

השפעה

1

enter

הדפסה בכניסה ל־sub.

2

exit

הדפסה ביציאה מ־sub.

4

ctx+args

כולל את הקשר הקריאה ואת הארגומנטים.

8

tied/ref

סימון קריאות sub של משתנים קשורים והפניות.

16

return

הדפסת ערך ההחזרה.

קבעו את frame באמצעות o:

  DB<5> o frame=15       # enter + exit + ctx/args + tied/ref

שילובים נפוצים:

  • frame=2: רק traces של כניסה/יציאה, שורה אחת לכל קריאה - תצוגת call-tree.

  • frame=6: מוסיף הקשר קורא ו־dump של ארגומנטים - trace מלא.

  • frame=30: כמו frame=6 בתוספת ערכי החזרה.

פלט ה־trace הולך לזרם הפלט של מנפה השגיאות. הפנו אותו לקובץ כדי שלא יטביע את הסשן האינטראקטיבי:

  DB<6> o LineInfo=trace.out

קראו את הקובץ בטרמינל אחר:

tail -f trace.out

Tracing לא־אינטראקטיבי: NonStop + AutoTrace#

עבור ״הקלט trace ביצוע מלא ללא אינטראקציה״, שלבו NonStop=1 (ללא prompts) ו־AutoTrace=1 (tracing מופעל מההתחלה):

PERLDB_OPTS="NonStop=1 AutoTrace=1 frame=2 LineInfo=trace.out" \
  perl -d script.pl

התוכנית רצה ישר; trace.out לוכד כל שורה הניתנת לעצירה וכל כניסה ל־sub. אידאלי ללכידת כשלים במצב CI, שרתים headless, או כל סביבה שבה prompt של TTY אינו זמין.

הוסיפו inhibit_exit=0 כדי לתת לתוכנית ליפול מהקצה ללא ה־prompt ״Debugged program terminated״.

Devel::Trace: כל שורה, ללא מנפה השגיאות#

Devel::Trace עוקב אחר כל שורה בכל קובץ ל־STDERR. ללא אינטראקציית שורת פקודה. ללא breakpoints. פשוט ורועש:

perl -d:Trace script.pl 2> trace.out
>> app.pl:4: my $key = 'welcome';
>> app.pl:5: my %data = (
>> app.pl:11: print "$data{$key}\n";

הריצו פעם אחת, בצעו grep על הפלט לחידה. גודל הפלט פרופורציונלי לאורך הריצה של התוכנית - השתמשו על reproducer מינימלי, לא על כל חבילת המבחנים.

וריאנטים:

  • Devel::DumpTrace - אותו דבר בתוספת ערכי משתנים לכל שורה.

  • Devel::TraceUse - ״אילו מודולים נטענו, באיזה סדר, על־ידי מי״:

    perl -d:TraceUse script.pl
    
  • Devel::TraceINC - צר יותר: רק שרשרת ה־require עם תזמון.

  • Devel::Trace::Subs - מחולל call-flow ו־stack-trace עבור ״אילו subs באמת רצו״.

Tracing של אזור ספציפי#

המשתנה $DB::trace מחליף את מצב ה־tracing מתוך קוד Perl:

$DB::trace = 1;
interesting_region();
$DB::trace = 0;

השמה ל־$DB::trace כאשר אינכם תחת -d מבצעת autovivify של המשתנה ללא נזק - הקוד בטוח לקבע ב־commit ורץ כ־no-op מחוץ למנפה השגיאות.

אותה תבנית עבור single-stepping של אזור:

$DB::single = 1;        # break here when next statement runs under -d

Tracing של regex: use re 'debug'#

למנוע ה־regex יש tracer משלו. אפשרו אותו לקסיקלית בתוך אזור, או בשורת הפקודה:

{
    use re 'debug';
    $str =~ /(\w+)\s+(\w+)/;
}
perl -Mre=debug -e '"hello world" =~ /(\w+)\s+(\w+)/'

פלט שלב ההידור מראה מה ה־optimiser עשה:

  • Compiling REx 'PATTERN' - תבנית המקור.

  • size N - גודל טבלת הצמתים הפנימית.

  • anchored 'SUB' at K / floating 'SUB' at K..LIMIT - איזו תת־מחרוזת המנוע יחפש כסינון מקדים.

  • stclass TYPE - מחלקת תו ההתחלה.

  • minlen N - לא תיתכן התאמה על קלט קצר מ־N.

  • רישום הצמתים - התוכנית המהודרת.

פלט זמן הריצה מראה כל צעד:

  POS <PRE-MATCH> <POST-MATCH>   |  NODE_NUM: OPNAME

ההזחה משקפת את עומק החזרה לאחור; failed... ו־failed, try continuation... מסמנים backtracks; Match successful! / Match failed. סוגרים את ה־trace.

אם לא מופיע כלל פלט זמן ריצה, ה־optimiser הכריע את ההתאמה מבלי להיכנס למנוע - או ש־minlen סינן מראש או שהתבנית קופלה לבדיקה קבועה.

מנוע ה־regex של pperl הוא מימוש מחדש מקומי, לא port של זה של perl5. ה־trace שווה־ערך פונקציונלית אך שמות הצמתים המדויקים ופורמטי השדות אינם זהים בית־לבית ל־upstream. ניתוח ה־trace באופן תכנותי (מעט כלים עושים) יזדקק להתאמה.

מתגים חלקיים מצמצמים את הפלט:

use re 'debugcolor';                  # ANSI colour
use re debug => 'EXECUTE';            # runtime only, no compile output

Devel::TraceUse לבאגי טעינת מודולים#

תוכנית הרצה לאט באתחול, או ״מדוע המודול הזה נטען בכלל?״, היא כמעט תמיד שאלת module-graph. Devel::TraceUse מדפיס את מלוא עץ ה־use/require:

perl -d:TraceUse script.pl
Modules used from script.pl:
   1. strict 1.12, script.pl line 2 [main]
   2. warnings 1.58, script.pl line 2 [main]
   3. DBI 1.643, script.pl line 5 [main]
      4. Carp 1.52, DBI line 13 [DBI]
      5. DynaLoader 1.47, DBI line 14 [DBI]
   ...

ההזחה מראה את שרשרת התלויות. הצליבו עם מספר השורה בקובץ ההורה כדי לראות היכן כל טעינה מתרחשת.

שילוב traces עם מנפה השגיאות#

Trace לקובץ, צעידה אינטראקטיבית:

PERLDB_OPTS="frame=6 LineInfo=trace.out" perl -d app.pl

ה־prompt של מנפה השגיאות מופיע כרגיל; trace.out צובר את ה־call-tree בזמן שאתם צועדים וממשיכים. שימושי כאשר שרשרת קריאות עמוקה מדי כדי לעקוב באמצעות צעידה אך עדיין רוצים לעצור בנקודות ספציפיות לבחינה.

למידע נוסף#

  • breakpoints - עצירה בנקודה ספציפית במקום הקלטת כל צעד.

  • inspecting-state - הפקודות העונות על ״מה הערך הזה?״.