سیستم ثبت وقایع اندروید با فرض اینکه تمام دادههای ثبت وقایع میتوانند به صورت دنبالهای از کاراکترها نمایش داده شوند، با هدف دستیابی به دسترسی جهانی و سهولت استفاده طراحی شده است. این فرض با اکثر موارد استفاده، به ویژه هنگامی که خوانایی گزارش بدون ابزارهای تخصصی بسیار مهم است، همسو است. با این حال، در محیطهایی که نیاز به عملکرد ثبت وقایع بالا و اندازههای محدود گزارش دارند، ثبت وقایع مبتنی بر متن بهینه نیست. یکی از این سناریوها WindowManager است که به یک سیستم ثبت وقایع قوی نیاز دارد که گزارشهای انتقال پنجره را به صورت بلادرنگ با حداقل تأثیر بر سیستم مدیریت کند.
ProtoLog جایگزینی برای رفع نیازهای ثبت وقایع WindowManager و سرویسهای مشابه است. ProtoLog مزایای زیر را نسبت به logcat ارائه میدهد:
- از منابع کمتری برای ثبت وقایع استفاده میکند.
- از دیدگاه یک توسعهدهنده، این فریمورک همانند چارچوب پیشفرض ثبت وقایع اندروید عمل میکند.
- به شما امکان میدهد دستورات لاگ را در زمان اجرا فعال یا غیرفعال کنید.
- همچنین میتواند در logcat ثبت شود.
برای بهینهسازی استفاده از حافظه، ProtoLog از یک مکانیزم اینترونینگ رشتهای استفاده میکند. این مکانیزم یک هش کامپایلشده از پیام را محاسبه و ذخیره میکند. برای بهبود عملکرد، ProtoLog در طول کامپایل برای سرویسهای سیستم اینترونینگ رشتهای را انجام میدهد. این مکانیزم فقط شناسه پیام و آرگومانها را در زمان اجرا ثبت میکند. هنگامی که شما یک ردیابی ProtoLog ایجاد میکنید یا یک گزارش اشکال دریافت میکنید، ProtoLog به طور خودکار فرهنگ لغت پیام ایجاد شده در زمان کامپایل را در خود جای میدهد. این امر رمزگشایی پیام را از هر ساختاری امکانپذیر میکند.
ProtoLog پیامها را در قالب دودویی (proto) در یک ردیابی Perfetto ذخیره میکند. رمزگشایی پیام در trace_processor
Perfetto رخ میدهد. این فرآیند پیامهای دودویی proto را رمزگشایی میکند، شناسههای پیام را با استفاده از فرهنگ لغت پیام تعبیه شده به رشتهها ترجمه میکند و رشته را با استفاده از آرگومانهای پویا قالببندی میکند.
ProtoLog از همان سطوح گزارش android.utils.Log
پشتیبانی میکند که عبارتند از d
، v
، i
، w
، e
و wtf
.
پروتو لاگ سمت کلاینت
در ابتدا، ProtoLog فقط برای سمت سرور WindowManager در نظر گرفته شده بود که در یک فرآیند و کامپوننت واحد عمل میکرد. بعداً، گسترش یافت تا کد پوسته WindowManager را در فرآیند رابط کاربری سیستم در بر بگیرد. با این حال، استفاده از ProtoLog نیاز به کد تنظیم قالبی پیچیدهای داشت. علاوه بر این، ثبت وقایع Proto به فرآیندهای سرور سیستم و رابط کاربری سیستم محدود میشد. این امر ادغام آن را در سایر فرآیندها دشوار میکرد و برای هر کدام به یک تنظیم بافر حافظه جداگانه نیاز داشت. 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
- نوع صحیح (کوتاه، صحیح یا بلند) -
%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);
به:
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
متد ثبت وقایع است.
تبدیل در سطح منبع انجام میشود. یک هش از رشته فرمت، سطح لاگ و نام گروه لاگ تولید شده و پس از آرگومان 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 این است که میتوانید آن را در زمان اجرا فعال یا غیرفعال کنید. به عنوان مثال، میتوانید در یک ساخت، گزارشگیری مفصلتری داشته باشید که به طور پیشفرض غیرفعال شده و در طول توسعه محلی برای اشکالزدایی یک مشکل خاص فعال شود. این الگو، به عنوان مثال، در 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 از ادغام رشتهای هم برای پیام و هم برای آرگومانهای رشتهای ارسالی استفاده میکند. این بدان معناست که برای بهرهمندی بیشتر از 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 درصدی فضای اشغال شده توسط حافظه میشود.
نمایشگر وینسکو
تب نمایشگر ProtoLog در Winscope، ردپاهای ProtoLog را که به صورت جدولی سازماندهی شدهاند، نشان میدهد. میتوانید ردپاها را بر اساس سطح گزارش، برچسب، فایل منبع (جایی که عبارت ProtoLog وجود دارد) و محتوای پیام فیلتر کنید. همه ستونها قابل فیلتر هستند. کلیک بر روی مهر زمانی در ستون اول، جدول زمانی را به مهر زمانی پیام منتقل میکند. علاوه بر این، کلیک بر روی Go to Current Time ، جدول ProtoLog را به مهر زمانی انتخاب شده در جدول زمانی برمیگرداند:
شکل ۱. نمایشگر ProtoLog