ProtoLog

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

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

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

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

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

يتوافق ProtoLog مع مستويات السجلّ نفسها التي يتوافق معها android.utils.Log، وهي: d وv وi وw وe وwtf.

ProtoLog من جهة العميل

في البداية، كان الهدف من ProtoLog هو استخدامه فقط في جهة الخادم من WindowManager، حيث يعمل ضمن عملية ومكوّن واحد. بعد ذلك، تم توسيعه ليشمل رمز shell الخاص بـ 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