ProtoLog

Hệ thống ghi nhật ký Android hướng đến mục tiêu đạt được khả năng tiếp cận phổ quát và dễ sử dụng, giả sử mọi dữ liệu nhật ký đều có thể được biểu thị dưới dạng một chuỗi ký tự. Giả định này phù hợp với hầu hết các trường hợp sử dụng, đặc biệt là khi khả năng đọc nhật ký là rất quan trọng mà không cần đến các công cụ chuyên dụng. Tuy nhiên, trong những môi trường đòi hỏi hiệu suất ghi nhật ký cao và kích thước nhật ký bị hạn chế, thì việc ghi nhật ký dựa trên văn bản không phải là cách tối ưu. Một trường hợp như vậy là WindowManager. Trường hợp này yêu cầu một hệ thống ghi nhật ký mạnh mẽ xử lý nhật ký chuyển đổi cửa sổ theo thời gian thực với tác động tối thiểu đến hệ thống.

ProtoLog là giải pháp thay thế để giải quyết nhu cầu ghi nhật ký của WindowManager và các dịch vụ tương tự. So với logcat, ProtoLog có những lợi ích sau:

  • Sử dụng ít tài nguyên hơn để ghi nhật ký.
  • Theo quan điểm của nhà phát triển, công cụ này hoạt động giống như khung ghi nhật ký Android mặc định.
  • Cho phép bạn bật hoặc tắt câu lệnh nhật ký trong thời gian chạy.
  • Cũng có thể ghi nhật ký vào logcat.

Để tối ưu hoá mức sử dụng bộ nhớ, ProtoLog sử dụng cơ chế nội suy chuỗi. Cơ chế này tính toán và lưu băm đã biên dịch của thông báo. Để cải thiện hiệu suất, ProtoLog thực hiện nội suy chuỗi trong quá trình biên dịch cho các dịch vụ hệ thống. Công cụ này chỉ ghi lại giá trị nhận dạng thông báo và đối số trong thời gian chạy. Khi bạn tạo dấu vết ProtoLog hoặc lấy báo cáo lỗi, ProtoLog sẽ tự động kết hợp từ điển thông báo được tạo trong thời gian biên dịch. Điều này cho phép giải mã thông báo từ mọi bản dựng.

ProtoLog lưu trữ thông báo ở định dạng nhị phân (proto) trong dấu vết Perfetto. Quá trình giải mã thông báo diễn ra trong trace_processor của Perfetto. Quá trình này giải mã thông báo proto nhị phân, dịch giá trị nhận dạng thông báo thành chuỗi bằng từ điển thông báo được nhúng và định dạng chuỗi bằng đối số động.

ProtoLog hỗ trợ các cấp độ nhật ký giống như android.utils.Log, đó là d, v, i, w, e, và wtf.

ProtoLog phía máy khách

Ban đầu, ProtoLog chỉ dành cho phía máy chủ của WindowManager, hoạt động trong một quy trình và thành phần duy nhất. Sau đó, công cụ này được mở rộng để bao gồm mã shell WindowManager trong quy trình Giao diện người dùng hệ thống. Tuy nhiên, việc sử dụng ProtoLog đòi hỏi mã thiết lập boilerplate phức tạp. Ngoài ra, việc ghi nhật ký Proto bị hạn chế đối với máy chủ hệ thống và các quy trình Giao diện người dùng hệ thống. Điều này khiến việc kết hợp vào các quy trình khác trở nên khó khăn và yêu cầu thiết lập bộ nhớ đệm riêng cho từng quy trình. ProtoLog hiện đã có cho mã phía máy khách, giúp loại bỏ nhu cầu về mã nguyên mẫu bổ sung.

Không giống như mã dịch vụ hệ thống, mã phía máy khách thường bỏ qua quá trình nội suy chuỗi trong thời gian biên dịch. Thay vào đó, quá trình nội suy chuỗi diễn ra một cách linh hoạt trong một luồng nền. Do đó, mặc dù ProtoLog phía máy khách mang lại những lợi ích về việc sử dụng bộ nhớ tương đương với ProtoLog trên các dịch vụ hệ thống, nhưng công cụ này sẽ phát sinh chi phí hiệu suất cao hơn một chút và không có lợi thế về việc giảm bộ nhớ của bộ nhớ được ghim mà đối tác phía máy chủ của công cụ này mang lại.

Nhóm ProtoLog

Thông báo ProtoLog được sắp xếp thành các nhóm có tên là ProtoLogGroups, tương tự như cách Logcat thông báo được sắp xếp theo TAG. Các ProtoLogGroups này đóng vai trò là cụm thông báo mà bạn có thể bật hoặc tắt trong thời gian chạy. Chúng cũng kiểm soát việc thông báo có bị loại bỏ trong quá trình biên dịch hay không và vị trí ghi nhật ký (proto, logcat hoặc cả hai). Mỗi ProtoLogGroup bao gồm các thuộc tính sau:

  • enabled: Khi được đặt thành false, các thông báo trong nhóm này sẽ bị loại trừ trong quá trình biên dịch và không có trong thời gian chạy.
  • logToProto: Xác định xem nhóm này có ghi nhật ký ở định dạng nhị phân hay không.
  • logToLogcat: Xác định xem nhóm này có ghi nhật ký vào logcat hay không.
  • tag: Tên của nguồn thông báo đã ghi nhật ký.

Mỗi quy trình sử dụng ProtoLog phải được định cấu hình một thực thể ProtoLogGroup.

Loại đối số được hỗ trợ

ProtoLog định dạng chuỗi nội bộ bằng android.text.TextUtils#formatSimple(String, Object...), vì vậy cú pháp của công cụ này cũng giống nhau.

ProtoLog hỗ trợ các loại đối số sau:

  • %b - boolean
  • %d, %x - kiểu số nguyên (ngắn, số nguyên hoặc dài)
  • %f - kiểu dấu phẩy động (float hoặc double)
  • %s - chuỗi
  • %% - ký tự phần trăm theo nghĩa đen

Các công cụ sửa đổi độ rộng và độ chính xác, chẳng hạn như %04d%10b, được hỗ trợ. Tuy nhiên, argument_indexflags không được hỗ trợ.

Sử dụng ProtoLog trong một dịch vụ mới

Để sử dụng ProtoLog trong một dịch vụ mới, hãy làm theo các bước sau:

  1. Tạo định nghĩa ProtoLogGroup cho dịch vụ này.
  2. Khởi chạy định nghĩa trước khi sử dụng lần đầu. Ví dụ: khởi chạy định nghĩa này trong quá trình tạo quy trình:

    ProtoLog.init(ProtoLogGroup.values());
    
  3. Sử dụng ProtoLog theo cách tương tự như cách bạn sử dụng android.util.Log:

    ProtoLog.v(WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId);
    

Bật tính năng tối ưu hoá trong thời gian biên dịch

Để bật ProtoLog trong thời gian biên dịch trong một quy trình, bạn phải thay đổi quy tắc xây dựng của quy trình đó và gọi tệp nhị phân protologtool.

ProtoLogTool là tệp nhị phân chuyển đổi mã thực hiện nội suy chuỗi và cập nhật lệnh gọi ProtoLog. Tệp nhị phân này chuyển đổi mọi lệnh gọi ghi nhật ký ProtoLog như trong ví dụ này:

ProtoLog.x(ProtoLogGroup.GROUP_NAME, "Format string %d %s", value1, value2);

thành:

if (ProtoLogImpl.isEnabled(GROUP_NAME)) {
    int protoLogParam0 = value1;
    String protoLogParam1 = String.valueOf(value2);
    ProtoLogImpl.x(ProtoLogGroup.GROUP_NAME, 1234560b0100, protoLogParam0, protoLogParam1);
}

Trong ví dụ này, ProtoLog, ProtoLogImplProtoLogGroup là các lớp được cung cấp dưới dạng đối số (có thể được nhập, nhập tĩnh hoặc đường dẫn đầy đủ, không được phép nhập ký tự đại diện) và x là phương thức ghi nhật ký.

Quá trình chuyển đổi được thực hiện ở cấp nguồn. Một băm được tạo từ chuỗi định dạng, cấp độ nhật ký và tên nhóm nhật ký, đồng thời được chèn sau đối số ProtoLogGroup. Mã được tạo thực tế được nội tuyến và một số ký tự dòng mới được thêm vào để giữ nguyên số dòng trong tệp.

Ví dụ:

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"],
}

Tuỳ chọn dòng lệnh

Một trong những ưu điểm chính của ProtoLog là bạn có thể bật hoặc tắt công cụ này trong thời gian chạy. Ví dụ: bạn có thể ghi nhật ký chi tiết hơn trong một bản dựng (tắt theo mặc định) và bật nhật ký đó trong quá trình phát triển cục bộ để gỡ lỗi một vấn đề cụ thể. Ví dụ: mẫu này được sử dụng trong WindowManager với các nhóm WM_DEBUG_WINDOW_TRANSITIONSWM_DEBUG_WINDOW_TRANSITIONS_MIN cho phép các loại ghi nhật ký chuyển đổi khác nhau, trong đó nhóm trước được bật theo mặc định.

Bạn có thể định cấu hình ProtoLog bằng Perfetto khi bắt đầu dấu vết. Bạn cũng có thể định cấu hình ProtoLog cục bộ bằng dòng lệnh adb.

Lệnh adb shell cmd protolog_configuration hỗ trợ các đối số sau:

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

Mẹo sử dụng hiệu quả

ProtoLog sử dụng nội suy chuỗi cho cả thông báo và mọi đối số chuỗi được truyền. Điều này có nghĩa là để tận dụng nhiều lợi ích hơn từ ProtoLog, các thông báo phải tách các giá trị lặp lại thành các biến.

Ví dụ: hãy xem xét câu lệnh sau:

Protolog.v(MY_GROUP, "%s", "The argument value is " + argument);

Khi được tối ưu hoá trong thời gian biên dịch, câu lệnh này sẽ chuyển thành:

ProtologImpl.v(MY_GROUP, 0x123, "The argument value is " + argument);

Nếu ProtoLog được sử dụng trong mã có đối số 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");

Kết quả là các thông báo sau trong bộ nhớ:

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)

Thay vào đó, nếu câu lệnh ProtoLog được viết như sau:

Protolog.v(MY_GROUP, "The argument value is %s", argument);

Bộ nhớ đệm trong bộ nhớ sẽ kết thúc như sau:

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)

Trình tự này dẫn đến mức sử dụng bộ nhớ nhỏ hơn 35%.

Trình xem Winscope

Thẻ trình xem ProtoLog của Winscope hiển thị các dấu vết ProtoLog được sắp xếp ở định dạng bảng. Bạn có thể lọc dấu vết theo cấp độ nhật ký, thẻ, tệp nguồn (nơi có câu lệnh ProtoLog) và nội dung thông báo. Bạn có thể lọc tất cả các cột. Khi nhấp vào dấu thời gian trong cột đầu tiên, dòng thời gian sẽ chuyển đến dấu thời gian thông báo. Ngoài ra, khi nhấp vào Chuyển đến thời gian hiện tại , bảng ProtoLog sẽ cuộn trở lại dấu thời gian được chọn trong dòng thời gian:

Trình xem ProtoLog

Hình 1. Trình xem ProtoLog