प्रोटोलॉग

Android लॉगिंग सिस्टम का मकसद, सभी के लिए ऐक्सेस और इस्तेमाल में आसानी बनाना है. इसके लिए, यह माना जाता है कि सभी लॉग डेटा को वर्णों के क्रम के तौर पर दिखाया जा सकता है. यह अनुमान, इस्तेमाल के ज़्यादातर उदाहरणों के साथ मेल खाता है. खास तौर पर, जब खास टूल के बिना लॉग को पढ़ना ज़रूरी हो. हालांकि, ऐसे एनवायरमेंट में जहां लॉगिंग की परफ़ॉर्मेंस बेहतर होनी चाहिए और लॉग का साइज़ सीमित होना चाहिए, हो सकता है कि टेक्स्ट पर आधारित लॉगिंग का इस्तेमाल करना सही न हो. WindowManager एक ऐसा उदाहरण है जिसके लिए, सिस्टम पर कम से कम असर डालते हुए, रीयल-टाइम में विंडो ट्रांज़िशन लॉग को मैनेज करने के लिए, लॉगिंग का एक बेहतर सिस्टम ज़रूरी है.

ProtoLog, WindowManager और मिलती-जुलती सेवाओं की लॉगिंग की ज़रूरतों को पूरा करने का विकल्प है. logcat के मुकाबले, ProtoLog के मुख्य फ़ायदे ये हैं:

  • लॉगिंग के लिए इस्तेमाल होने वाले संसाधनों की संख्या कम होती है.
  • डेवलपर के लिहाज़ से, यह डिफ़ॉल्ट Android लॉगिंग फ़्रेमवर्क का इस्तेमाल करने जैसा ही है.
  • रनटाइम के दौरान, लॉग स्टेटमेंट को चालू या बंद किया जा सकता है.
  • ज़रूरत पड़ने पर, अब भी logcat में लॉग किया जा सकता है.

मेमोरी के इस्तेमाल को ऑप्टिमाइज़ करने के लिए, ProtoLog एक स्ट्रिंग इंटरिंग मेकेनिज्म का इस्तेमाल करता है. इसमें मैसेज के कंपाइल किए गए हैश का हिसाब लगाना और उसे सेव करना शामिल है. बेहतर परफ़ॉर्मेंस के लिए, ProtoLog सिस्टम सेवाओं के लिए कंपाइलेशन के दौरान स्ट्रिंग इंटरिंग करता है. साथ ही, रनटाइम के दौरान सिर्फ़ मैसेज आइडेंटिफ़ायर और आर्ग्युमेंट रिकॉर्ड करता है. इसके अलावा, ProtoLog ट्रैस जनरेट करते समय या गड़बड़ी की रिपोर्ट पाने पर, वह संकलन के समय बनाई गई मैसेज डिक्शनरी को अपने-आप शामिल कर लेता है. इससे किसी भी बिल्ड से मैसेज को डिकोड किया जा सकता है.

ProtoLog का इस्तेमाल करके, मैसेज को Perfetto ट्रेस में बाइनरी फ़ॉर्मैट (proto) में सेव किया जाता है. मैसेज को डिकोड करने की प्रोसेस, Perfetto के trace_processor में होती है. इस प्रोसेस में, बाइनरी प्रोटो मैसेज को डिकोड करना, एम्बेड की गई मैसेज डिक्शनरी का इस्तेमाल करके मैसेज आइडेंटिफ़ायर को स्ट्रिंग में बदलना, और डाइनैमिक आर्ग्युमेंट का इस्तेमाल करके स्ट्रिंग को फ़ॉर्मैट करना शामिल है.

ProtoLog, android.utils.Log के लॉग लेवल के साथ काम करता है. जैसे: d, v, i, w, e, wtf.

क्लाइंट-साइड ProtoLog

शुरुआत में, ProtoLog का मकसद सिर्फ़ WindowManager के सर्वर साइड के लिए था, जो एक ही प्रोसेस और कॉम्पोनेंट में काम करता था. इसके बाद, इसे सिस्टम यूज़र इंटरफ़ेस (यूआई) प्रोसेस में WindowManager शेल कोड को शामिल करने के लिए बड़ा किया गया था. हालांकि, ProtoLog का इस्तेमाल करने के लिए, एक जटिल बॉयलरप्लेट सेटअप कोड की ज़रूरत थी. इसके अलावा, प्रोटो लॉगिंग की सुविधा सिर्फ़ सिस्टम सर्वर और सिस्टम यूज़र इंटरफ़ेस (यूआई) प्रोसेस तक सीमित थी. इस वजह से, इसे अन्य प्रोसेस में शामिल करना मुश्किल था. साथ ही, हर प्रोसेस के लिए अलग मेमोरी बफ़र सेटअप करना ज़रूरी था. हालांकि, 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 - इंटिग्रल टाइप (short, integer या long)
  • %f - फ़्लोटिंग पॉइंट टाइप (फ़्लोट या डबल)
  • %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, आर्ग्युमेंट के तौर पर दी गई क्लास हैं. इन्हें इंपोर्ट किया जा सकता है, स्टैटिक इंपोर्ट किया जा सकता है या इनका पूरा पाथ दिया जा सकता है. वाइल्डकार्ड इंपोर्ट की अनुमति नहीं है. 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 ग्रुप के साथ किया जाता है. इससे अलग-अलग तरह के ट्रांज़िशन लॉगिंग चालू होते हैं. हालांकि, पहला ग्रुप डिफ़ॉल्ट रूप से चालू होता है.

ट्रेस शुरू करते समय, Perfetto का इस्तेमाल करके ProtoLog को कॉन्फ़िगर किया जा सकता है. adb कमांड लाइन का इस्तेमाल करके, ProtoLog को स्थानीय तौर पर भी कॉन्फ़िगर किया जा सकता है.

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 व्यूअर

Winscope के ProtoLog व्यूअर टैब में, टेबल के फ़ॉर्मैट में व्यवस्थित किए गए ProtoLog ट्रेस दिखते हैं. ट्रेस को लॉग लेवल, टैग, सोर्स फ़ाइल (जहां ProtoLog स्टेटमेंट मौजूद है), और मैसेज कॉन्टेंट के हिसाब से फ़िल्टर किया जा सकता है. सभी कॉलम को फ़िल्टर किया जा सकता है. पहले कॉलम में मौजूद टाइमस्टैंप पर क्लिक करने से, टाइमलाइन मैसेज के टाइमस्टैंप पर पहुंच जाती है. इसके अलावा, मौजूदा समय पर जाएं पर क्लिक करने से, ProtoLog टेबल को टाइमलाइन में चुने गए टाइमस्टैंप पर वापस ले जाया जाता है:

ProtoLog व्यूअर

पहली इमेज. ProtoLog व्यूअर