מערכת הרישום ביומן של Android מיועדת לנגישות אוניברסלית ולשימוש קל, מתוך הנחה שכל נתוני היומן יכולים להיות מוצגים כרצף של תווים. ההנחה הזו תואמת לרוב התרחישים לדוגמה, במיוחד כשחשוב שהיומן יהיה קריא בלי כלים מיוחדים. עם זאת, בסביבות שבהן נדרשים ביצועי רישום גבוהים וגודלי יומן מוגבלים, רישום מבוסס-טקסט לא יהיה אופטימלי. אחד מהתרחישים האלה הוא WindowManager, שדורש מערכת רישום חזקה שיכולה לטפל ביומני מעבר חלונות בזמן אמת עם השפעה מינימלית על המערכת.
ProtoLog הוא חלופה לטיפול בצרכי הרישום ביומן של WindowManager ושירותים דומים. היתרונות העיקריים של ProtoLog על פני logcat הם:
- כמות המשאבים שמשמשת לרישום ביומן קטנה יותר.
- מנקודת המבט של מפתח, זה זהה לשימוש במסגרת ברירת המחדל של רישום ביומן ב-Android.
- תומך בהפעלת או בהשבתת הצהרות יומן בזמן ריצה.
- עדיין אפשר להתחבר ל-logcat אם צריך.
כדי לבצע אופטימיזציה של השימוש בזיכרון, ProtoLog משתמש במנגנון של שיתוף מחרוזות, שכולל חישוב ושמירה של גיבוב (hash) מהודר של ההודעה. כדי לשפר את הביצועים, ProtoLog מבצע אינטרנינג של מחרוזות במהלך הקומפילציה (עבור שירותי מערכת), ומתעד רק את מזהה ההודעה והארגומנטים בזמן הריצה. בנוסף, כשיוצרים מעקב ProtoLog או מקבלים דוח על באג, ProtoLog משלב באופן אוטומטי את מילון ההודעות שנוצר בזמן ההידור, וכך מאפשר פענוח של הודעות מכל בנייה.
באמצעות 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 מאורגנות בקבוצות שנקראות 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
הם המחלקות שסופקו כארגומנטים (אפשר לייבא אותם, לייבא אותם באופן סטטי או לציין את הנתיב המלא שלהם, אבל אי אפשר לייבא אותם באמצעות wildcard), ו-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