ProtoLog

يهدف نظام التسجيل في Android إلى تحقيق إمكانية الوصول الشامل وسهولة الاستخدام، على افتراض أنّه يمكن تمثيل جميع بيانات السجلّ كسلسلة من الأحرف. يتوافق هذا الافتراض مع معظم حالات الاستخدام، لا سيما عندما تكون إمكانية قراءة السجلّات أمرًا بالغ الأهمية بدون أدوات متخصّصة. ومع ذلك، في البيئات التي تتطلّب أداءً عاليًا في التسجيل وأحجامًا محدودة للسجلات، لا يكون التسجيل المستند إلى النصوص هو الخيار الأمثل. أحد هذه السيناريوهات هو WindowManager، الذي يتطلّب نظام تسجيل قويًا يمكنه التعامل مع سجلّات نقل النوافذ في الوقت الفعلي بأقل تأثير على النظام.

‫ProtoLog هو البديل لتلبية احتياجات التسجيل في WindowManager والخدمات المشابهة. توفّر ProtoLog المزايا التالية مقارنةً بأداة logcat:

  • يستخدم موارد أقل لتسجيل البيانات.
  • من منظور المطوّر، يعمل هذا الإطار بالطريقة نفسها التي يعمل بها إطار تسجيل البيانات التلقائي في Android.
  • تتيح لك تفعيل عبارات السجلّ أو إيقافها في وقت التشغيل.
  • يمكن أيضًا تسجيلها في logcat.

لتحسين استخدام الذاكرة، تستخدم ProtoLog آلية تخزين السلاسل. تحسب هذه الآلية قيمة التجزئة المجمَّعة للرسالة وتحفظها. لتحسين الأداء، تنفّذ ProtoLog عملية تخزين السلاسل أثناء التجميع لخدمات النظام. ولا يسجّل سوى معرّف الرسالة ووسيطاتها في وقت التشغيل. عند إنشاء تتبُّع ProtoLog أو الحصول على تقرير خطأ، يدمج ProtoLog تلقائيًا قاموس الرسائل الذي تم إنشاؤه في وقت الترجمة. يؤدي ذلك إلى تفعيل فك ترميز الرسائل من أي إصدار.

يخزّن ProtoLog الرسائل بتنسيق ثنائي (proto) ضمن تتبُّع Perfetto. يتم فك ترميز الرسالة ضمن trace_processor في Perfetto. تؤدي العملية إلى فك ترميز رسائل البروتوكول الثنائي، وترجمة معرّفات الرسائل إلى سلاسل باستخدام قاموس الرسائل المضمّن، وتنسيق السلسلة باستخدام وسيطات ديناميكية.

يتوافق 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 - نوع عدد صحيح (قصير أو عادي أو طويل)
  • %f - نوع النقطة العائمة (float أو double)
  • %s - string
  • %%: حرف النسبة المئوية الحرفي

تتوفّر معدِّلات العرض والدقة، مثل %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 في وقت التجميع في إحدى العمليات، يجب تغيير قواعد الإنشاء الخاصة بها واستدعاء الثنائي 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 هي طريقة التسجيل.

يتم إجراء عملية التحويل على مستوى المصدر. يتم إنشاء قيمة تجزئة من سلسلة التنسيق ومستوى السجلّ واسم مجموعة السجلّ، ويتم إدراجها بعد الوسيطة 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.

عارض Winscope

تعرض علامة التبويب "عارض ProtoLog" في Winscope عمليات تتبُّع ProtoLog منظَّمة في تنسيق جدولي. يمكنك فلترة عمليات التتبُّع حسب مستوى السجلّ والعلامة والملف المصدر (الذي يتضمّن عبارة ProtoLog) ومحتوى الرسالة. يمكن فلترة جميع الأعمدة. يؤدي النقر على الطابع الزمني في العمود الأول إلى نقل المخطط الزمني إلى الطابع الزمني للرسالة. بالإضافة إلى ذلك، يؤدي النقر على الانتقال إلى الوقت الحالي إلى إعادة الجدول ProtoLog إلى الطابع الزمني المحدّد في المخطط الزمني:

أداة عرض ProtoLog

الشكل 1. أداة عرض ProtoLog