מערכת הרישום ביומן של Android נועדה להשיג נגישות אוניברסלית ונוחות שימוש, בהנחה שכל נתוני היומן יכולים להיות מיוצגים כרצף של תווים. ההנחה הזו תואמת לרוב תרחישי השימוש, במיוחד כשחשוב שהיומנים יהיו קריאים בלי כלים ייעודיים. עם זאת, בסביבות שבהן נדרשים ביצועים גבוהים של רישום ביומן וגודלי יומן מוגבלים, רישום ביומן מבוסס-טקסט הוא לא אופטימלי. אחד מהתרחישים האלה הוא WindowManager, שנדרשת לו מערכת רישום חזקה שמטפלת ביומני מעברים בין חלונות בזמן אמת עם השפעה מינימלית על המערכת.
ProtoLog הוא חלופה לטיפול בצרכי הרישום ביומן של WindowManager ושירותים דומים. ל-ProtoLog יש את היתרונות הבאים בהשוואה ל-logcat:
- משתמש בפחות משאבים לרישום ביומן.
- מנקודת המבט של מפתח, הוא פועל כמו מסגרת ברירת המחדל של רישום ביומן ב-Android.
- מאפשרת להפעיל או להשבית הצהרות יומן בזמן הריצה.
- אפשר גם לרשום ב-logcat.
כדי לבצע אופטימיזציה של השימוש בזיכרון, ProtoLog משתמש במנגנון של שיתוף מחרוזות. המנגנון הזה מחשב ושומר גיבוב מהודר של ההודעה. כדי לשפר את הביצועים, ProtoLog מבצע אינטרנינג של מחרוזות במהלך הקומפילציה של שירותי המערכת. הוא מתעד רק את מזהה ההודעה ואת הארגומנטים בזמן הריצה. כשיוצרים מעקב ProtoLog או מקבלים דוח על באג, ProtoLog משלב באופן אוטומטי את מילון ההודעות שנוצר בזמן ההידור. כך אפשר לפענח הודעות מכל גרסת build.
ProtoLog מאחסן הודעות בפורמט בינארי (proto) בתוך עקבות Perfetto.
פענוח ההודעות מתבצע ב-trace_processor
של Perfetto. במהלך התהליך, ההודעות הבינאריות של פרוטו מפוענחות, מזהי ההודעות מתורגמים למחרוזות באמצעות מילון ההודעות המוטמע, והמחרוזת מעוצבת באמצעות ארגומנטים דינמיים.
ProtoLog תומך באותן רמות ביומן כמו android.utils.Log
, שהן d
, v
, i
, w
, e
ו-wtf
.
Client-side ProtoLog
בתחילה, ProtoLog נועד רק לצד השרת של WindowManager, והוא פועל בתהליך וברכיב יחידים. בהמשך, הוא הורחב כך שיכלול את קוד המעטפת של WindowManager בתהליך של ממשק המשתמש של המערכת. עם זאת, השימוש ב-ProtoLog דרש קוד הגדרה מורכב. בנוסף, רישום ביומן של Proto הוגבל לתהליכים של שרת המערכת וממשק המשתמש של המערכת. בגלל זה היה קשה לשלב אותם בתהליכים אחרים, והיה צריך להגדיר לכל אחד מהם מאגר זיכרון נפרד. ProtoLog זמין עכשיו לקוד מצד הלקוח, כך שלא צריך להשתמש בקוד boilerplate נוסף.
בניגוד לקוד של שירותי מערכת, קוד בצד הלקוח בדרך כלל מדלג על אינטרנינג של מחרוזות בזמן ההידור. במקום זאת, מתבצעת הקצאת מחרוזות באופן דינמי בשרשור ברקע. כתוצאה מכך, למרות ש-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
– boolean -
%d
,%x
– סוג מספר שלם (short, integer או long) -
%f
– סוג נקודה צפה (float או double) -
%s
– מחרוזת -
%%
– סימן אחוזים מילולי
יש תמיכה במגדירי רוחב ודיוק, כמו %04d
ו-%10b
.
עם זאת, אין תמיכה ב-argument_index
וב-flags
.
שימוש ב-ProtoLog בשירות חדש
כדי להשתמש ב-ProtoLog בשירות חדש, פועלים לפי השלבים הבאים:
- יוצרים
ProtoLogGroup
הגדרה לשירות הזה. צריך לאתחל את ההגדרה לפני השימוש הראשון בה. לדוגמה, מאתחלים אותו במהלך יצירת התהליך:
ProtoLog.init(ProtoLogGroup.values());
משתמשים ב-
ProtoLog
באותו אופן שבו משתמשים ב-android.util.Log
:ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);
הפעלת אופטימיזציה בזמן הידור
כדי להפעיל את ProtoLog בזמן הקומפילציה בתהליך, צריך לשנות את כללי הבנייה שלו ולהפעיל את קובץ ה-protologtool
הבינארי.
ProtoLogTool
הוא קובץ בינארי של טרנספורמציה של קוד שמבצע אינטרנינג של מחרוזות ומעדכן את ההפעלה של ProtoLog. הקובץ הבינארי הזה משנה כל קריאה לרישום ביומן ProtoLog
, כמו בדוגמה הזו:
ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);
into:
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 משתמש ב-string 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 viewer
בכרטיסייה ProtoLog viewer של Winscope מוצגים עקבות של ProtoLog בפורמט טבלאי. אפשר לסנן את העקבות לפי רמת יומן, תג, קובץ מקור (שבו מופיעה ההצהרה ProtoLog) ותוכן ההודעה. אפשר לסנן את כל העמודות. לחיצה על חותמת הזמן בעמודה הראשונה מעבירה את ציר הזמן לחותמת הזמן של ההודעה. בנוסף, אם לוחצים על מעבר לזמן הנוכחי, הטבלה של ProtoLog חוזרת לסימן הזמן שנבחר בציר הזמן:
איור 1. כלי להצגת יומני ProtoLog