ProtoLog

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

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

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

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

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

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

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

في البداية، كان ProtoLog مخصّصًا فقط لخدمة ‎VMS على جانب الخادم، وكان يعمل ضمن عملية ومكوّن واحدَين. بعد ذلك، تم توسيعه ليشمل رمز Shell الخاص بـ WindowManager في عملية واجهة مستخدم النظام، ولكن استخدام ProtoLog يتطلب رمز إعدادات معقدًا. بالإضافة إلى ذلك، كانت تسجيلات Proto محدودة بعمليات System Server وSystem UI، ما يجعل من الصعب دمجها في العمليات الأخرى ويتطلّب إعداد ذاكرة تخزين مؤقتة مفصّلة لكل عملية. ومع ذلك، أصبح ProtoLog متاحًا الآن لرمز العميل ، ما يغني عن الحاجة إلى رمز نموذجي إضافي.

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

مجموعات ProtoLog

يتم تنظيم رسائل ProtoLog في مجموعات تُسمى ProtoLogGroups، على غرار طريقة تنظيم رسائل Logcat بواسطة TAG. تُستخدَم هذه ProtoLogGroups كمجموعات من الرسائل التي يمكن تفعيلها أو إيقافها بشكل جماعي أثناء التشغيل. بالإضافة إلى ذلك، تتحكّم هذه الإعدادات في ما إذا كان يجب إزالة الرسائل أثناء compilation (الترجمة) ومكان تسجيلها (proto أو logcat أو كليهما). تتضمّن كل ProtoLogGroup السمات التالية:

  • enabled: عند ضبط القيمة على false، يتم استبعاد الرسائل في هذه المجموعة أثناء compilation ولا تكون متاحة في وقت التشغيل.
  • logToProto: لتحديد ما إذا كانت هذه المجموعة تسجِّل باستخدام التنسيق الثنائي.
  • logToLogcat: لتحديد ما إذا كانت هذه المجموعة تسجِّل السجلّات في logcat.
  • tag: اسم مصدر الرسالة المسجّلة

يجب أن يكون لكل عملية تستخدم ProtoLog مثيل ProtoLogGroup تم إعداده.

أنواع الوسيطات المتوافقة

في ProtoLog، يتم استخدام التنسيقات سلاسل باستخدام android.text.TextUtils#formatSimple(String, Object...)، لذلك تكون بنية ProtoLog هي نفسها.

يتيح ProtoLog أنواع الوسيطات التالية:

  • %b - منطقي
  • %d أو %x: نوع عدد صحيح (short أو integer أو long)
  • %f - نوع النقطة العائمة (float أو double)
  • %s - سلسلة
  • %% - حرف رمزي للنسبة المئوية

يمكن استخدام معدِّلات العرض والدقة، مثل %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 logging call كما هو موضّح في هذا المثال:

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