Log Proto

Sistem logging Android ditujukan untuk aksesibilitas universal dan kemudahan penggunaan, dengan asumsi bahwa semua data log dapat direpresentasikan sebagai urutan karakter. Asumsi ini selaras dengan sebagian besar kasus penggunaan, terutama saat keterbacaan log sangat penting tanpa alat khusus. Namun, di lingkungan yang memerlukan performa logging tinggi dan ukuran log terbatas, logging berbasis teks mungkin tidak optimal. Salah satu skenario tersebut adalah WindowManager, yang memerlukan sistem logging yang andal dan mampu menangani log transisi jendela real-time dengan dampak sistem yang minimal.

ProtoLog adalah alternatif untuk memenuhi kebutuhan logging WindowManager dan layanan serupa. Manfaat utama ProtoLog dibandingkan logcat adalah:

  • Jumlah resource yang digunakan untuk logging lebih kecil.
  • Dari sudut pandang developer, hal ini sama dengan menggunakan framework logging Android default.
  • Mendukung pernyataan log untuk diaktifkan atau dinonaktifkan pada waktu proses.
  • Tetap dapat mencatat ke logcat jika diperlukan.

Untuk mengoptimalkan penggunaan memori, ProtoLog menggunakan mekanisme penggabungan string yang melibatkan penghitungan dan penyimpanan hash pesan yang dikompilasi. Untuk meningkatkan performa, ProtoLog melakukan penginterniran string selama kompilasi (untuk layanan sistem), hanya mencatat ID pesan dan argumen saat runtime. Selain itu, saat membuat rekaman aktivitas ProtoLog atau mendapatkan laporan bug, ProtoLog akan otomatis menyertakan kamus pesan yang dibuat pada waktu kompilasi, sehingga memungkinkan decoding pesan dari build apa pun.

Dengan ProtoLog, pesan disimpan dalam format biner (proto) dalam rekaman aktivitas Perfetto. Dekode pesan terjadi di dalam trace_processor Perfetto. Proses ini terdiri dari mendekode pesan proto biner, menerjemahkan ID pesan menjadi string menggunakan kamus pesan tersemat, dan memformat string menggunakan argumen dinamis.

ProtoLog mendukung level log yang sama dengan android.utils.Log, yaitu: d, v, i, w, e, wtf.

ProtoLog sisi klien

Awalnya, ProtoLog ditujukan hanya untuk sisi server WindowManager, yang beroperasi dalam satu proses dan komponen. Selanjutnya, cakupannya diperluas untuk mencakup kode shell WindowManager dalam proses UI Sistem, tetapi penggunaan ProtoLog memerlukan kode penyiapan boilerplate yang rumit. Selain itu, pencatatan aktivitas Proto dibatasi untuk proses server sistem dan UI Sistem, sehingga sulit untuk digabungkan ke dalam proses lain dan memerlukan penyiapan buffer memori terpisah untuk setiap proses. Namun, ProtoLog kini tersedia untuk kode sisi klien, sehingga tidak memerlukan kode boilerplate tambahan.

Tidak seperti kode layanan sistem, kode sisi klien biasanya melewati penginterniran string waktu kompilasi. Sebagai gantinya, string interning terjadi secara dinamis di thread latar belakang. Akibatnya, meskipun ProtoLog di sisi klien memberikan keuntungan penggunaan memori yang sebanding dengan ProtoLog di layanan sistem, ProtoLog di sisi klien menimbulkan overhead performa yang sedikit lebih tinggi dan tidak memiliki keuntungan pengurangan memori dari memori yang disematkan pada ProtoLog di sisi server.

Grup ProtoLog

Pesan ProtoLog diatur ke dalam grup yang disebut ProtoLogGroups, mirip dengan cara pesan Logcat diatur oleh TAG. ProtoLogGroups ini berfungsi sebagai kelompok pesan yang dapat diaktifkan atau dinonaktifkan secara bersamaan saat runtime. Selain itu, mereka mengontrol apakah pesan harus dihapus selama kompilasi dan tempat pesan harus dicatat (proto, logcat, atau keduanya). Setiap ProtoLogGroup terdiri dari properti berikut:

  • enabled: Jika disetel ke false, pesan dalam grup ini akan dikecualikan selama kompilasi dan tidak tersedia saat runtime.
  • logToProto: Menentukan apakah grup ini mencatat log dengan format biner.
  • logToLogcat: Menentukan apakah grup ini mencatat ke logcat.
  • tag: Nama sumber pesan yang dicatat.

Setiap proses yang menggunakan ProtoLog harus mengonfigurasi instance ProtoLogGroup.

Jenis argumen yang didukung

Secara internal, string format ProtoLog menggunakan android.text.TextUtils#formatSimple(String, Object...), sehingga sintaksisnya sama.

ProtoLog mendukung jenis argumen berikut:

  • %b - boolean
  • %d, %x - jenis integral (short, integer, atau long)
  • %f - jenis floating point (float atau double)
  • %s - string
  • %% - karakter persen literal

Pengubah lebar dan presisi seperti %04d dan %10b didukung, tetapi argument_index dan flags tidak didukung.

Menggunakan ProtoLog dalam layanan baru

Untuk menggunakan ProtoLog dalam proses baru:

  1. Buat definisi ProtoLogGroup untuk layanan ini.

  2. Lakukan inisialisasi definisi sebelum penggunaan pertamanya (misalnya, saat pembuatan proses):

    Protolog.init(ProtologGroup.values());

  3. Gunakan Protolog dengan cara yang sama seperti android.util.Log:

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

Mengaktifkan pengoptimalan waktu kompilasi

Untuk mengaktifkan ProtoLog waktu kompilasi dalam suatu proses, Anda harus mengubah aturan build-nya dan memanggil biner protologtool.

ProtoLogTool adalah biner transformasi kode yang melakukan penginternan string dan memperbarui pemanggilan ProtoLog. Biner ini mengubah setiap panggilan logging ProtoLog seperti yang ditunjukkan dalam contoh ini:

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

ke:

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

Dalam contoh ini, ProtoLog, ProtoLogImpl, dan ProtoLogGroup adalah class yang diberikan sebagai argumen (dapat diimpor, diimpor statis, atau jalur lengkap, impor wildcard tidak diizinkan) dan x adalah metode logging.

Transformasi dilakukan di tingkat sumber. Hash dibuat dari string format, tingkat log, dan nama grup log, lalu disisipkan setelah argumen ProtoLogGroup. Kode yang dihasilkan sebenarnya disisipkan dan sejumlah karakter baris baru ditambahkan untuk mempertahankan penomoran baris dalam file.

Contoh:

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

Opsi command line

Salah satu keuntungan utama ProtoLog adalah Anda dapat mengaktifkan atau menonaktifkannya saat runtime. Misalnya, Anda dapat memiliki logging yang lebih panjang dalam build, yang dinonaktifkan secara default, dan mengaktifkannya selama pengembangan lokal, untuk men-debug masalah tertentu. Pola ini digunakan, misalnya, di WindowManager dengan grup WM_DEBUG_WINDOW_TRANSITIONS dan WM_DEBUG_WINDOW_TRANSITIONS_MIN yang memungkinkan berbagai jenis logging transisi, dengan yang pertama diaktifkan secara default.

Anda dapat mengonfigurasi ProtoLog menggunakan Perfetto, saat memulai rekaman aktivitas. Anda juga dapat mengonfigurasi ProtoLog secara lokal menggunakan command line adb.

Perintah adb shell cmd protolog_configuration mendukung argumen berikut:

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

Tips penggunaan yang efektif

ProtoLog menggunakan string interning untuk pesan dan argumen string yang diteruskan. Artinya, untuk mendapatkan lebih banyak manfaat dari ProtoLog, pesan harus mengisolasi nilai berulang ke dalam variabel.

Misalnya, perhatikan pernyataan berikut:

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

Jika dioptimalkan pada waktu kompilasi, akan diterjemahkan menjadi:

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

Jika ProtoLog digunakan dalam kode dengan argumen 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");

Hal ini akan menghasilkan pesan berikut dalam memori:

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)

Jika pernyataan ProtoLog ditulis sebagai:

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

Buffer dalam memori akan berakhir sebagai:

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)

Urutan ini menghasilkan footprint memori yang 35% lebih kecil.

Penampil Winscope

Tab ProtoLog viewer Winscope menampilkan rekaman aktivitas ProtoLog yang disusun dalam format tabel. Anda dapat memfilter rekaman aktivitas menurut tingkat log, tag, file sumber (tempat pernyataan ProtoLog berada), dan konten pesan. Semua kolom dapat difilter. Mengklik stempel waktu di kolom pertama akan memindahkan linimasa ke stempel waktu pesan. Selain itu, mengklik Buka Waktu Saat Ini akan men-scroll tabel ProtoLog kembali ke stempel waktu yang dipilih di linimasa:

Penampil ProtoLog

Gambar 1. Penampil ProtoLog