ProtoLog

מערכת הרישום ביומן של Android נועדה להיות נגישה לכולם וקלה לשימוש, בהנחה שאפשר לייצג את כל נתוני היומן כרצף של תווים. ההנחה הזו תואמת לרוב התרחישים לדוגמה, במיוחד כשחשוב שיומן יהיה קריא בלי כלים מיוחדים. עם זאת, בסביבות שבהן נדרשים ביצועים גבוהים של רישום ביומן ומגבלות על גודל היומנים, יכול להיות שרישום ביומן מבוסס-טקסט לא יהיה אופטימלי. תרחיש אחד כזה הוא WindowManager, שדורש מערכת רישום חזקה שיכולה לטפל ביומני מעבר של חלונות בזמן אמת עם השפעה מינימלית על המערכת.

ProtoLog הוא פתרון חלופי לצורכי הרישום ביומן של WindowManager ושירותים דומים. היתרונות העיקריים של ProtoLog על פני logcat הם:

  • כמות המשאבים שמשמשים לרישום ביומן קטנה יותר.
  • מנקודת המבט של המפתחים, זה זהה לשימוש במסגרת ברירת המחדל של Android לתיעוד ביומן.
  • תמיכה בהצהרות ביומן שניתן להפעיל או להשבית אותן במהלך זמן הריצה.
  • עדיין אפשר להיכנס ליומן ב-logcat אם צריך.

כדי לבצע אופטימיזציה של השימוש בזיכרון, ProtoLog משתמש במנגנון של הטמעת מחרוזות (interning) שכולל חישוב ושמירה של גיבוב (hash) של ההודעה. כדי לשפר את הביצועים, ProtoLog מבצע הטמעה של מחרוזות במהלך הידור (לשירותי מערכת), ומתעדה רק את מזהה ההודעה ואת הארגומנטים בזמן הריצה. בנוסף, כשיוצרים מעקב ProtoLog או מקבלים דוח באג, ProtoLog משלב באופן אוטומטי את מילון ההודעות שנוצר בזמן הידור, ומאפשר לפענח הודעות מכל גרסה.

באמצעות ProtoLog, ההודעה מאוחסנת בפורמט בינארי (proto) בתוך מעקב Perfetto. פענוח ההודעה מתבצע ב-trace_processor של Perfetto. התהליך כולל פענוח של הודעות ה-proto הבינאריות, תרגום של מזהי ההודעות למחרוזות באמצעות מילון ההודעות המוטמע ועיצוב המחרוזת באמצעות ארגומנטים דינמיים.

ProtoLog תומך באותן רמות יומן כמו android.utils.Log, כלומר: d, v, i, w, e, wtf.

ProtoLog בצד הלקוח

בהתחלה, ProtoLog נועד רק לצד השרת של WindowManager, ופעל בתהליך וברכיב יחידים. לאחר מכן, הוא הורחב כך שיכלול את קוד המעטפת של WindowManager בתהליך של ממשק המשתמש של המערכת, אבל השימוש ב-ProtoLog נדרש קוד הגדרה מורכב. בנוסף, הרישום ביומן של Proto היה מוגבל לשרת המערכת ולתהליכים של ממשק המשתמש של המערכת, ולכן קשה לשלב אותו בתהליכים אחרים, ונדרש להגדיר מאגר זיכרון נפרד לכל אחד מהם. עם זאת, ProtoLog זמין עכשיו לקוד בצד הלקוח, כך שאין צורך בקוד נוסף של תבנית בסיסית.

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

קבוצות של ProtoLog

הודעות ProtoLog מאורגנות בקבוצות שנקראות ProtoLogGroups, בדומה לאופן שבו הודעות Logcat מאורגנות לפי TAG. הערכים של ProtoLogGroups משמשים כאשכולות של הודעות שאפשר להפעיל או להשבית אותם ביחד במהלך זמן הריצה. בנוסף, הם קובעים אם צריך להסיר הודעות במהלך הידור ואיפה צריך לתעד אותן ביומן (proto,‏ logcat או שניהם). כל ProtoLogGroup מורכב מהמאפיינים הבאים:

  • enabled: כשהערך מוגדר ל-false, ההודעות בקבוצה הזו לא נכללות במהלך הידור ולא זמינות בסביבת זמן הריצה.
  • logToProto: מגדיר אם הקבוצה הזו מתעדת ביומן בפורמט בינארי.
  • logToLogcat: מגדיר אם הקבוצה הזו תירשם ביומן logcat.
  • tag: שם המקור של ההודעה ביומן.

לכל תהליך שמשתמש ב-ProtoLog צריכה להיות מוגדרת מכונה של ProtoLogGroup.

סוגי הארגומנטים הנתמכים

באופן פנימי, מחרוזות בפורמט ProtoLog משתמשות ב-android.text.TextUtils#formatSimple(String, Object...), כך שהתחביר שלהן זהה.

ב-ProtoLog יש תמיכה בסוגי הארגומנטים הבאים:

  • %b – בוליאני
  • %d,‏ %x – סוג שלם (short,‏ integer או long)
  • %f – טיפוס של נקודה צפה (float או double)
  • %s – מחרוזת
  • %% – תו אחוזים מילולי

אפשר להשתמש במודיפיקרים של רוחב ודיוק, כמו %04d ו-%10b, אבל אי אפשר להשתמש ב-argument_index וב-flags.

שימוש ב-ProtoLog בשירות חדש

כדי להשתמש ב-ProtoLog בתהליך חדש:

  1. יוצרים הגדרה של ProtoLogGroup לשירות הזה.

  2. מפעילים את ההגדרה לפני השימוש הראשון בה (לדוגמה, בזמן יצירת התהליך):

    Protolog.init(ProtologGroup.values());

  3. משתמשים ב-Protolog באותו אופן שבו משתמשים ב-android.util.Log:

    ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);

הפעלת אופטימיזציה בזמן הידור

כדי להפעיל את ProtoLog בזמן הידור בתהליך, צריך לשנות את כללי ה-build שלו ולהפעיל את קובץ ה-binary protologtool.

ProtoLogTool הוא קובץ בינארי להמרת קוד שמבצע הטמעה של מחרוזות ומעדכן את ההפעלה של ProtoLog. הקובץ הבינארי הזה ממיר כל קריאה לתיעוד ביומן ProtoLog, כפי שמוצג בדוגמה הבאה:

ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);

לתוך:

if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
    int protoLogParam0 = value1;
    String protoLogParam1 = String.valueOf(value2);
    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}

בדוגמה הזו, ProtoLog,‏ ProtoLogImpl ו-ProtoLogGroup הן הכיתות שסופקו כארגומנטים (אפשר לייבא אותן, לייבא אותן באופן סטטי או לציין את הנתיב המלא שלהן, אבל אסור לייבא אותן באמצעות תווים כלליים). ‏x היא שיטת הרישום ביומן.

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

דוגמה:

genrule {
    name: "wm_shell_protolog_src",
    srcs: [
        ":protolog-impl", // protolog lib
        ":wm_shell_protolog-groups", // protolog groups declaration
        ":wm_shell-sources", // source code
    ],
    tools: ["protologtool"],
    cmd: "$(location protologtool) transform-protolog-calls " +
        "--protolog-class com.android.internal.protolog.ProtoLog " +
        "--loggroups-class com.android.wm.shell.protolog.ShellProtoLogGroup " +
        "--loggroups-jar $(location :wm_shell_protolog-groups) " +
        "--viewer-config-file-path /system_ext/etc/wmshell.protolog.pb " +
        "--legacy-viewer-config-file-path /system_ext/etc/wmshell.protolog.json.gz " +
        "--legacy-output-file-path /data/misc/wmtrace/shell_log.winscope " +
        "--output-srcjar $(out) " +
        "$(locations :wm_shell-sources)",
    out: ["wm_shell_protolog.srcjar"],
}

אפשרויות של שורת הפקודה

אחד מהיתרונות העיקריים של ProtoLog הוא שאפשר להפעיל או להשבית אותו במהלך זמן הריצה. לדוגמה, אפשר להגדיר רישום מפורט יותר ביומן ב-build, שהוא מושבת כברירת מחדל, ולהפעיל אותו במהלך הפיתוח המקומי כדי לנפות באגים בבעיה ספציפית. לדוגמה, התבנית הזו משמשת ב-WindowManager עם הקבוצות WM_DEBUG_WINDOW_TRANSITIONS ו-WM_DEBUG_WINDOW_TRANSITIONS_MIN שמאפשרות סוגים שונים של רישום ביומן של מעברים, כאשר הקבוצה הראשונה מופעלת כברירת מחדל.

אפשר להגדיר את ProtoLog באמצעות Perfetto כשמתחילים מעקב. אפשר גם להגדיר את ProtoLog באופן מקומי באמצעות שורת הפקודה adb.

הפקודה adb shell cmd protolog_configuration תומכת בארגומנטים הבאים:

help
  Print this help text.

groups (list | status)
  list - lists all ProtoLog groups registered with ProtoLog service"
  status <group> - print the status of a ProtoLog group"

logcat (enable | disable) <group>"
  enable or disable ProtoLog to logcat

טיפים לשימוש יעיל

ב-ProtoLog נעשה שימוש בהטמעת מחרוזות (interning) גם להודעה וגם לכל ארגומנטים של מחרוזות שמועברים. המשמעות היא שכדי להפיק יותר תועלת מ-ProtoLog, צריך לבודד ערכים חוזרים במשתנים.

לדוגמה, נניח את ההצהרה הבאה:

Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);

כשהקוד עובר אופטימיזציה בזמן הידור, הוא מתורגם כך:

ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);

אם משתמשים ב-ProtoLog בקוד עם הארגומנטים A,B,C:

Protolog.v(MY_GROUP, "%s", "The argument value is A");
Protolog.v(MY_GROUP, "%s", "The argument value is B");
Protolog.v(MY_GROUP, "%s", "The argument value is C");
Protolog.v(MY_GROUP, "%s", "The argument value is A");

כתוצאה מכך, ההודעות הבאות מופיעות בזיכרון:

Dict:
  0x123: "%s"
  0x111: "The argument value is A"
  0x222: "The argument value is B"
  0x333: "The argument value is C"

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

אם במקום זאת, הצהרת ProtoLog נכתבה כך:

Protolog.v(MY_GROUP, "The argument value is %s", argument);

מאגר הנתונים הזמני בזיכרון ייראה כך:

Dict:
  0x123: "The argument value is %s" (24 b)
  0x111: "A" (1 b)
  0x222: "B" (1 b)
  0x333: "C" (1 b)

Message1 (Hash: 0x123, Arg1: 0x111)
Message2 (Hash: 0x123, Arg2: 0x222)
Message3 (Hash: 0x123, Arg3: 0x333)
Message4 (Hash: 0x123, Arg1: 0x111)

כתוצאה מהרצף הזה, נפח הזיכרון שנדרש קטן ב-35%.

חלון הצפייה ב-Winscope

בכרטיסייה של 'צפייה ב-ProtoLog' ב-Winscope מוצגים עקבות של ProtoLog שמאורגנים בפורמט טבלאי. אפשר לסנן את העקבות לפי רמת היומן, תג, קובץ מקור (שבו נמצאת ההצהרה ProtoLog) ותוכן ההודעה. אפשר לסנן את כל העמודות. לחיצה על חותמת הזמן בעמודה הראשונה מעבירה את ציר הזמן לחותמת הזמן של ההודעה. בנוסף, לחיצה על Go to Current Time (מעבר לשעה הנוכחית) גורמת לגלילה של טבלת ProtoLog חזרה לחותמת הזמן שנבחרה בציר הזמן:

כלי הצפייה ביומני ProtoLog

איור 1. כלי הצפייה ביומני ProtoLog