บันทึกโปรโตคอล

ระบบการบันทึกของ Android มีเป้าหมายเพื่อให้เข้าถึงได้แบบสากลและใช้งานง่าย โดยสมมติว่าข้อมูลบันทึกทั้งหมดสามารถแสดงเป็นลำดับอักขระได้ สมมติฐานนี้สอดคล้องกับ Use Case ส่วนใหญ่ โดยเฉพาะอย่างยิ่งเมื่อความอ่านง่ายของบันทึกเป็นสิ่งสําคัญโดยไม่มีเครื่องมือเฉพาะ อย่างไรก็ตาม ในสภาพแวดล้อมที่ต้องใช้ประสิทธิภาพการบันทึกที่สูงและขนาดบันทึกที่จำกัด การบันทึกแบบข้อความอาจไม่เหมาะ สถานการณ์หนึ่งๆ ดังกล่าวคือ WindowManager ซึ่งต้องใช้ระบบการบันทึกที่มีประสิทธิภาพซึ่งจัดการบันทึกการเปลี่ยนหน้าต่างแบบเรียลไทม์ได้โดยมีผลกระทบต่อระบบน้อยที่สุด

ProtoLog เป็นทางเลือกในการตอบสนองความต้องการในการบันทึกของ WindowManager และบริการที่คล้ายกัน ประโยชน์หลักของ ProtoLog เหนือกว่า logcat ได้แก่

  • ปริมาณทรัพยากรที่ใช้สำหรับการบันทึกจะน้อยลง
  • จากมุมมองของนักพัฒนาซอฟต์แวร์ การดำเนินการนี้จะเหมือนกับการใช้เฟรมเวิร์กการบันทึกเริ่มต้นของ Android
  • รองรับการเปิดหรือปิดใช้คำสั่งบันทึกเมื่อรันไทม์
  • ยังคงบันทึกลงใน Logcat ได้หากจำเป็น

ProtoLog ใช้กลไกการจัดเก็บสตริงชั่วคราวเพื่อเพิ่มประสิทธิภาพการใช้หน่วยความจํา ซึ่งเกี่ยวข้องกับการคํานวณและบันทึกแฮชที่คอมไพล์แล้วของข้อความ ProtoLog จะทำการรวมสตริงระหว่างการคอมไพล์ (สําหรับบริการของระบบ) เพื่อปรับปรุงประสิทธิภาพ โดยบันทึกเฉพาะตัวระบุข้อความและอาร์กิวเมนต์ที่รันไทม์ นอกจากนี้ เมื่อสร้างการติดตาม ProtoLog หรือรับรายงานข้อบกพร่อง ProtoLog จะรวมพจนานุกรมข้อความที่สร้างไว้ขณะคอมไพล์โดยอัตโนมัติ ซึ่งช่วยให้สามารถถอดรหัสข้อความจากบิลด์ใดก็ได้

เมื่อใช้ ProtoLog ระบบจะจัดเก็บข้อความในรูปแบบไบนารี (proto) ภายในการติดตามของ Perfetto การถอดรหัสข้อความจะเกิดขึ้นภายใน Perfetto's trace_processor กระบวนการนี้ประกอบด้วยการถอดรหัสข้อความโปรโตคอลแบบไบนารี การแปลตัวระบุข้อความเป็นสตริงโดยใช้พจนานุกรมข้อความที่ฝัง และการจัดรูปแบบสตริงโดยใช้อาร์กิวเมนต์แบบไดนามิก

ProtoLog รองรับระดับบันทึกเดียวกับ android.utils.Log ได้แก่ d, v, i, w, e, wtf

ProtoLog ฝั่งไคลเอ็นต์

เดิมที ProtoLog มีไว้สำหรับฝั่งเซิร์ฟเวอร์ของ WindowManager เท่านั้น ซึ่งทำงานภายในกระบวนการและคอมโพเนนต์เดียว ต่อมา เราได้ขยายการทำงานให้ครอบคลุมโค้ดเชลล์ WindowManager ในกระบวนการ UI ของระบบ แต่การใช้ ProtoLog ต้องใช้โค้ดการตั้งค่าที่ซ้ำกันซึ่งซับซ้อน นอกจากนี้ การบันทึก Proto นั้นถูกจำกัดไว้สำหรับเซิร์ฟเวอร์ของระบบและกระบวนการ UI ของระบบ ทำให้การรวมเข้ากับกระบวนการอื่นๆ เป็นเรื่องยากและจำเป็นต้องมีการตั้งค่าบัฟเฟอร์หน่วยความจำแยกต่างหากสำหรับแต่ละรายการ อย่างไรก็ตาม ตอนนี้ ProtoLog พร้อมใช้งานสำหรับโค้ดฝั่งไคลเอ็นต์แล้ว จึงไม่จำเป็นต้องใช้โค้ดที่ซ้ำกันอีก

โดยทั่วไปแล้ว โค้ดฝั่งไคลเอ็นต์จะข้ามการจัดเก็บสตริงชั่วคราวเมื่อคอมไพล์ ซึ่งต่างจากโค้ดบริการของระบบ แต่การรวมสตริงจะเกิดขึ้นแบบไดนามิกในเธรดเบื้องหลังแทน ด้วยเหตุนี้ แม้ว่า ProtoLog ฝั่งไคลเอ็นต์จะให้ข้อได้เปรียบในการใช้งานหน่วยความจำที่เทียบเท่ากับ ProtoLog ในบริการของระบบ แต่ก็มีค่าใช้จ่ายเพิ่มเติมด้านประสิทธิภาพสูงกว่าเล็กน้อยและไม่มีข้อได้เปรียบในการลดหน่วยความจำของหน่วยความจำที่ปักหมุดไว้ของคู่ค้าฝั่งเซิร์ฟเวอร์

กลุ่ม ProtoLog

ระบบจะจัดระเบียบข้อความ ProtoLog เป็นกลุ่มที่เรียกว่า ProtoLogGroups ซึ่งคล้ายกับการจัดระเบียบข้อความ Logcat เป็นกลุ่มโดย TAG ProtoLogGroups เหล่านี้ทำหน้าที่เป็นคลัสเตอร์ของข้อความที่เปิดหรือปิดใช้ร่วมกันได้เมื่อรันไทม์ นอกจากนี้ ยังควบคุมได้ว่าควรตัดข้อความออกระหว่างการคอมไพล์หรือไม่ และควรบันทึกข้อความไว้ที่ใด (proto, logcat หรือทั้ง 2 อย่าง) ProtoLogGroup แต่ละรายการประกอบด้วยพร็อพเพอร์ตี้ต่อไปนี้

  • enabled: เมื่อตั้งค่าเป็น false ระบบจะไม่รวมข้อความในกลุ่มนี้ระหว่างการคอมไพล์และข้อความจะไม่พร้อมใช้งานเมื่อรันไทม์
  • logToProto: กําหนดว่ากลุ่มนี้จะบันทึกด้วยรูปแบบไบนารีหรือไม่
  • logToLogcat: กำหนดว่ากลุ่มนี้จะบันทึกลงในบันทึกบันทึกหรือไม่
  • tag: ชื่อแหล่งที่มาของข้อความที่บันทึกไว้

แต่ละกระบวนการที่ใช้ ProtoLog ต้องได้รับการกําหนดค่าอินสแตนซ์ ProtoLogGroup

ประเภทอาร์กิวเมนต์ที่รองรับ

ภายใน ProtoLog จะจัดรูปแบบสตริงโดยใช้ android.text.TextUtils#formatSimple(String, Object...) ดังนั้นไวยากรณ์จึงเหมือนกัน

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 ทั้งหมดตามที่แสดงในตัวอย่างนี้

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 คือคลาสที่ระบุเป็นอาร์กิวเมนต์ (สามารถนําเข้า นําเข้าแบบคงที่ หรือนําเข้าแบบ Full Path แต่ไม่อนุญาตให้นําเข้าแบบไวลด์การ์ด) และ 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