androidx.tracing:tracing:2.0.0-beta01 लाइब्रेरी, कम ओवरहेड वाला Kotlin API है. इसकी मदद से, प्रोसेस में मौजूद ट्रेस इवेंट कैप्चर किए जा सकते हैं. इन इवेंट से, समय के स्लाइस और उनके कॉन्टेक्स्ट को कैप्चर किया जा सकता है. यह लाइब्रेरी, Kotlin कोरूटीन के लिए कॉन्टेक्स्ट प्रोपगेशन की सुविधा भी देती है.
यह लाइब्रेरी, Perfetto के उसी ट्रेस पैकेट फ़ॉर्मैट का इस्तेमाल करती है जिसके बारे में Android डेवलपर जानते हैं. इसके अलावा, Tracing 2.0 (1.0.0-* एपीआई के उलट) में प्लगेबल ट्रेसिंग बैकएंड और सिंक की सुविधा होती है. इसलिए, अन्य ट्रेसिंग लाइब्रेरी, आउटपुट ट्रेसिंग फ़ॉर्मैट को पसंद के मुताबिक बना सकती हैं. साथ ही, यह तय कर सकती हैं कि उनके लागू करने के तरीके में कॉन्टेक्स्ट प्रोपगेशन कैसे काम करता है.
डिपेंडेंसी
ट्रेसिंग शुरू करने के लिए, आपको अपने build.gradle.kts में डिपेंडेंसी तय करनी होंगी.
Kotlin Multiplatform प्रोजेक्ट
जिन लाइब्रेरी को सिर्फ़ ट्रेस इवेंट भेजने होते हैं उन्हें हल्के-फुल्के androidx.tracing:tracing एपीआई पर निर्भर रहना चाहिए. ट्रेसिंग बैकएंड को कॉन्फ़िगर करने वाले ऐप्लिकेशन को भी androidx.tracing:tracing-wire पर निर्भर रहना चाहिए.
kotlin {
sourceSets {
commonMain {
dependencies {
// API definition
implementation("androidx.tracing:tracing:2.0.0-beta01")
}
}
androidMain {
dependencies {
// Android implementation (includes the Perfetto Sink and automatic initialization)
implementation("androidx.tracing:tracing-wire:2.0.0-beta01")
}
}
jvmMain {
dependencies {
// JVM implementation
implementation("androidx.tracing:tracing-wire:2.0.0-beta01")
}
}
}
}
सिर्फ़ Android के लिए प्रोजेक्ट
अगर आपको सिर्फ़ Android को टारगेट करना है, तो अपने ऐप्लिकेशन या लाइब्रेरी की build.gradle.kts फ़ाइल में यह डिपेंडेंसी जोड़ें:
dependencies {
// For libraries and applications to emit events
implementation("androidx.tracing:tracing:2.0.0-beta01")
// For applications to configure the tracing backend
implementation("androidx.tracing:tracing-wire:2.0.0-beta01")
}
शुरुआत और खोज
ट्रेस इवेंट रिकॉर्ड करने से पहले, आपको ट्रेसिंग इन्फ़्रास्ट्रक्चर को शुरू करना होगा. इसके लिए, आपको एक AbstractTraceDriver बनाना होगा और इसे दुनिया भर में रजिस्टर करना होगा.Tracer
Android
Android पर, अगर आपने androidx.tracing:tracing-wire डिपेंडेंसी शामिल की है, तो ऐप्लिकेशन शुरू होने पर androidx.startup लाइब्रेरी का इस्तेमाल करके, अपने-आप शुरू हो जाती है.
डिफ़ॉल्ट रूप से, अपने-आप शुरू होने की सुविधा ये काम करती है:
यह
TraceDriverबनाता है, जिसमें एकTraceSinkहोता है. यहContext.noBackupFilesDir/perfetto_traces/में Perfetto ट्रेस फ़ाइलें लिखता है.यह कुकी, नतीजे के तौर पर मिले
Tracerको दुनिया भर में रजिस्टर करती है.
TraceDriver इंस्टेंस को पसंद के मुताबिक बनाना
अगर आपको कॉन्फ़िगरेशन को पसंद के मुताबिक बनाना है, तो अपना TraceSink इंस्टेंस उपलब्ध कराएं. उदाहरण के लिए, अगर आपको यह बदलना है कि ट्रेस फ़ाइलें कहां सेव की जाती हैं या आपको कस्टम TraceSink का इस्तेमाल करना है.AbstractTraceDriver
कॉन्फ़िगरेशन को पसंद के मुताबिक बनाने के लिए, अपनी Application क्लास को AbstractTraceDriver.Factory लागू करें:
import android.app.Application
import androidx.tracing.AbstractTraceDriver
import androidx.tracing.wire.TraceDriver
import androidx.tracing.wire.TraceSink
import java.io.File
class App : Application(), AbstractTraceDriver.Factory {
override fun create(): AbstractTraceDriver {
val sink = TraceSink(
context = this,
fileProvider = { File(noBackupFilesDir, "traces") },
)
// Return the custom TraceDriver
// You can also fully customize the instance of Tracer
return TraceDriver(context = this, sink = sink)
}
}
ऑटोमैटिक इनिशियलाइज़र यह पता लगाता है कि आपकी Application सबक्लास, Factory को लागू करती है और आपके कस्टम ड्राइवर फ़ैक्ट्री का इस्तेमाल करती है.
जेवीएम
JVM पर, अपने-आप बूटस्ट्रैप होने की सुविधा उपलब्ध नहीं है. ऐप्लिकेशन, स्टार्टअप के दौरान TraceDriver को शुरू करने और Tracer को ग्लोबल लेवल पर रजिस्टर करने के लिए ज़िम्मेदार होता है. ऐसा आम तौर पर, आपके main फ़ंक्शन में किया जाता है.
ट्रेसर को रजिस्टर करने के लिए, Tracer.setGlobalTracer() पर कॉल करें.
import androidx.tracing.Tracer
import androidx.tracing.DelicateTracingApi
import androidx.tracing.wire.TraceDriver
import androidx.tracing.wire.TraceSink
import java.io.File
fun main() {
// Create the TraceSink, and the `TraceDriver`
val outputDirectory = File("/tmp/perfetto")
val sink = TraceSink(directory = outputDirectory)
val driver = TraceDriver(sink = sink, isEnabled = true)
// Register the tracer
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
// Call driver.close() as a result of the process shutdown hook.
Runtime.getRuntime().addShutdownHook(Thread {
driver.close()
})
}
बुनियादी इस्तेमाल
TraceSink से यह तय होता है कि ट्रेस पैकेट को कैसे क्रम से लगाया जाता है. Tracing 2.0.0 में, सिंक को लागू करने की सुविधा मिलती है. यह Perfetto ट्रेस पैकेट फ़ॉर्मैट का इस्तेमाल करता है. TraceDriver, Tracer के लिए एक हैंडल उपलब्ध कराता है. इसका इस्तेमाल ट्रेस को पूरा करने के लिए किया जा सकता है.
Tracer के शुरू होने के बाद, Android पर अपने-आप या JVM पर मैन्युअल तरीके से, ट्रेस इवेंट को दिखाने के लिए ग्लोबल Tracer.global इंस्टेंस का इस्तेमाल करें.
अगर आपको ऐप्लिकेशन के कुछ वर्शन में ट्रेसिंग की सुविधा का इस्तेमाल नहीं करना है, तो TraceDriver का इस्तेमाल करके, ऐप्लिकेशन में मौजूद सभी ट्रेस पॉइंट बंद किए जा सकते हैं. TraceDriver का इंस्टेंस बनाते समय, isCategoryEnabled के लिए कोई तरीका लागू करके, किसी दिए गए category के लिए ट्रेस पॉइंट चालू किए जा सकते हैं. हालांकि, ऐसा करना ज़रूरी नहीं है.
val driver = TraceDriver(
sink = sink,
isCategoryEnabled = { category ->
// Only enable trace points in the "com.example" package
category.startsWith("com.example")
}
)
यहां JVM पर Tracer.global का इस्तेमाल करके, ट्रेस इवेंट को चालू करने का एक बुनियादी उदाहरण दिया गया है. इसमें मैन्युअल सेटअप भी शामिल है:
import androidx.tracing.Tracer
import androidx.tracing.DelicateTracingApi
import androidx.tracing.wire.TraceDriver
import androidx.tracing.wire.TraceSink
import java.io.File
// Category names should also follow the same convention used for package names
// on Android and Java. This makes them easier to identify and filter.
internal const val CATEGORY_MAIN = "com.example"
fun createSink(): TraceSink {
val outputDirectory = File("/tmp/perfetto")
if (!outputDirectory.exists()) {
outputDirectory.mkdirs()
}
return TraceSink(directory = outputDirectory)
}
fun createTraceDriver(): TraceDriver {
return TraceDriver(sink = createSink(), isCategoryEnabled = {true})
}
fun main() {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
driver.use {
Tracer.global.trace(category = CATEGORY_MAIN, name = "basic") {
// The block of code that needs to be traced.
Thread.sleep(100L)
}
}
}
इससे यह ट्रेस जनरेट होता है.
पहली इमेज. इस इमेज में, Perfetto के बेसिक ट्रेस का स्क्रीन कैप्चर दिखाया गया है.
यहां देखा जा सकता है कि सही प्रोसेस और थ्रेड ट्रैक पॉप्युलेट किए गए हैं. साथ ही, इनसे एक ही ट्रेस सेक्शन basic जनरेट हुआ है, जो 100 मि॰से॰ तक चला.
ओवरलैप होने वाले इवेंट दिखाने के लिए, एक ही ट्रैक पर ट्रेस सेक्शन (या स्लाइस) को नेस्ट किया जा सकता है. यहां एक उदाहरण दिया गया है.
fun main() {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
driver.use {
Tracer.global.trace(
category = CATEGORY_MAIN,
name = "processImage",
) {
// Load the data first, then apply the sharpen filter
sharpen(output = loadImage())
}
}
}
internal fun loadImage(): ByteArray {
return Tracer.global.trace(CATEGORY_MAIN, "loadImage") {
// Loads an image
// ...
// A placeholder
ByteArray(0)
}
}
internal fun sharpen(output: ByteArray) {
// ...
Tracer.global.trace(CATEGORY_MAIN, "sharpen") {
// ...
}
}
इससे यह ट्रेस जनरेट होता है.
दूसरी इमेज. नेस्ट किए गए सेक्शन के साथ, बुनियादी Perfetto ट्रेस का स्क्रीन कैप्चर.
आपको दिखेगा कि मुख्य थ्रेड ट्रैक में इवेंट ओवरलैप हो रहे हैं. इससे यह साफ़ तौर पर पता चलता है कि processImage और sharpen एक ही थ्रेड पर कॉल करते हैं.loadImage
ट्रेस सेक्शन में अतिरिक्त मेटाडेटा जोड़ना
कभी-कभी, ज़्यादा जानकारी पाने के लिए, किसी ट्रेस स्लाइस में कॉन्टेक्स्ट के हिसाब से अतिरिक्त मेटाडेटा अटैच करना फ़ायदेमंद हो सकता है. इस तरह के मेटाडेटा के कुछ उदाहरणों में, nav destination शामिल हो सकता है कि उपयोगकर्ता किस पर है या input arguments शामिल हो सकता है कि कोई फ़ंक्शन पूरा होने में कितना समय लगेगा.
fun main() {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
driver.use {
Tracer.global.trace(
category = CATEGORY_MAIN,
name = "basicWithContext",
// Add additional metadata
metadataBlock = {
// Add key value pairs.
addMetadataEntry("key", "value")
addMetadataEntry("count", 1L)
}
) {
Thread.sleep(100L)
}
}
}
इससे यह नतीजा मिलता है. ध्यान दें कि Arguments सेक्शन में, slice बनाते समय जोड़े गए की-वैल्यू पेयर होते हैं.
तीसरी इमेज. अतिरिक्त मेटाडेटा के साथ, सामान्य Perfetto ट्रेस की स्क्रीन कैप्चर की गई इमेज.
कॉन्टेक्स्ट प्रोपगेशन
Kotlin coroutines या एक साथ कई टास्क पूरे करने में मदद करने वाले अन्य फ़्रेमवर्क का इस्तेमाल करते समय, Tracing 2.0 में कॉन्टेक्स्ट प्रोपगेशन की सुविधा काम करती है. इसे एक उदाहरण की मदद से बेहतर तरीके से समझा जा सकता है.
suspend fun taskOne() {
Tracer.global.traceCoroutine(category = CATEGORY_MAIN, "taskOne") {
delay(timeMillis = 100L)
}
}
suspend fun taskTwo() {
Tracer.global.traceCoroutine(category = CATEGORY_MAIN, "taskTwo") {
delay(timeMillis = 50L)
}
}
fun main() = runBlocking(context = Dispatchers.Default) {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
driver.use {
Tracer.global.traceCoroutine(category = CATEGORY_MAIN, name = "main") {
taskOne()
taskTwo()
}
}
println("All done")
}
}
इससे यह नतीजा मिलता है.
चौथी इमेज. कॉन्टेक्स्ट के साथ फ़ैलाए गए बुनियादी Perfetto ट्रेस का स्क्रीन कैप्चर.
कॉन्टेक्स्ट प्रोपगेशन की मदद से, एक्ज़ीक्यूशन के फ़्लो को विज़ुअलाइज़ करना बहुत आसान हो जाता है. आपको यह पता चल सकता है कि कौनसे टास्क एक-दूसरे से जुड़े थे. साथ ही, यह भी पता चल सकता है कि Threads को कब निलंबित किया गया और कब फिर से शुरू किया गया.
उदाहरण के लिए, यह देखा जा सकता है कि स्लाइस main से taskOne और taskTwo जनरेट हुए हैं.
इसके बाद, दोनों थ्रेड बंद हो गईं, क्योंकि delay का इस्तेमाल करने की वजह से कोरूटीन निलंबित हो गए थे.
मैन्युअल तरीके से लागू करना
कभी-कभी, Kotlin को-रूटीन का इस्तेमाल करके, एक साथ कई वर्कलोड को मिक्स करने पर, Java Executor के इंस्टेंस के साथ एक से दूसरे में कॉन्टेक्स्ट को ट्रांसफ़र करना फ़ायदेमंद हो सकता है. उदाहरण के लिए:
fun executorTask(
token: PropagationToken,
executor: Executor,
callback: () -> Unit
) {
executor.execute {
Tracer.global.trace(
category = CATEGORY_MAIN,
name = "executeTask",
token = token,
) {
// Do something
Thread.sleep(100)
callback()
}
}
}
fun main() = runBlocking(context = Dispatchers.Default) {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
val executor = Executors.newSingleThreadExecutor()
driver.use {
Tracer.global.traceCoroutine(category = CATEGORY_MAIN, name = "main") {
coroutineScope {
val deferred = CompletableDeferred<Unit>()
executorTask(
// Obtain the propagation token from the CoroutineContext
token = Tracer.global.tokenFromCoroutineContext(),
executor = executor,
callback = {
deferred.complete(Unit)
}
)
deferred.await()
}
}
executor.shutdownNow()
}
}
इससे यह नतीजा मिलता है.
पांचवीं इमेज. मैन्युअल कॉन्टेक्स्ट प्रोपगेशन के साथ, बुनियादी Perfetto ट्रेस का स्क्रीन कैप्चर.
यहां देखा जा सकता है कि CoroutineContext में एक्ज़ीक्यूशन शुरू हुआ और बाद में Java Executor पर स्विच हो गया. हालांकि, हम अब भी कॉन्टेक्स्ट प्रोपगेशन का इस्तेमाल कर सकते हैं.
सिस्टम ट्रेस के साथ मिलाएं
androidx.tracing लाइब्रेरी, सीपीयू शेड्यूलिंग, मेमोरी के इस्तेमाल, और ऑपरेटिंग सिस्टम के साथ ऐप्लिकेशन के इंटरैक्शन जैसी जानकारी कैप्चर नहीं करती. ऐसा इसलिए है, क्योंकि लाइब्रेरी प्रोसेस में कम ओवरहेड वाली ट्रेसिंग करने का तरीका उपलब्ध कराती है.
हालांकि, सिस्टम ट्रेस को प्रोसेस में मौजूद ट्रेस के साथ मर्ज करना और ज़रूरत पड़ने पर उन्हें एक ही ट्रेस के तौर पर देखना बहुत आसान है. ऐसा इसलिए है, क्योंकि Perfetto UI
की मदद से, किसी डिवाइस की एक से ज़्यादा ट्रेस फ़ाइलों को एक ही टाइमलाइन पर विज़ुअलाइज़ किया जा सकता है.
इसके लिए, Perfetto UI का इस्तेमाल करके सिस्टम ट्रेसिंग सेशन शुरू किया जा सकता है. इसके लिए, यहां दिए गए निर्देशों का पालन करें.
सिस्टम ट्रेसिंग चालू होने पर, Tracing 2.0 API का इस्तेमाल करके, प्रोसेस में मौजूद ट्रेस इवेंट भी रिकॉर्ड किए जा सकते हैं. दोनों ट्रेस फ़ाइलें मिलने के बाद, Perfetto में Open Multiple Trace Files विकल्प का इस्तेमाल किया जा सकता है.
छठी इमेज. Perfetto यूज़र इंटरफ़ेस (यूआई) में एक से ज़्यादा ट्रेस फ़ाइलें खोली जा रही हैं.
ऐडवांस वर्कफ़्लो
इस सेक्शन में, इन-प्रोसेस ट्रेसिंग लाइब्रेरी की मदद से लागू किए जा सकने वाले बेहतर वर्कफ़्लो के बारे में बताया गया है.
स्लाइस में सहसंबंध दिखाना
कभी-कभी, किसी ट्रेस में मौजूद स्लाइस को उपयोगकर्ता की किसी बड़ी कार्रवाई या सिस्टम इवेंट के लिए एट्रिब्यूट करना फ़ायदेमंद होता है. उदाहरण के लिए, सूचना के हिस्से के तौर पर बैकग्राउंड में किए गए काम से जुड़े सभी स्लाइस को एट्रिब्यूट करने के लिए, कुछ ऐसा किया जा सकता है:
fun main() {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
driver.use {
onEvent(eventId = EVENT_ID)
}
}
fun onEvent(eventId: Long) {
Tracer.global.trace(
category = CATEGORY_MAIN,
name = "step-1",
metadataBlock = {
addCorrelationId(eventId)
}
) {
Thread.sleep(100L)
}
Thread.sleep(20)
Tracer.global.trace(
category = CATEGORY_MAIN,
name = "step-2",
metadataBlock = {
addCorrelationId(eventId)
}
) {
Thread.sleep(180)
}
}
इससे यह नतीजा मिलता है.
सातवीं इमेज. परफ़ेट्टो ट्रेस का स्क्रीन कैप्चर, जिसमें एक-दूसरे से जुड़े स्लाइस दिख रहे हैं.
कॉल स्टैक की जानकारी जोड़ना
होस्ट-साइड टूल, जैसे कि कंपाइलर प्लगिन और एनोटेशन प्रोसेसर, कॉल स्टैक की जानकारी को ट्रेस में एम्बेड करने का विकल्प भी चुन सकते हैं. इससे ट्रेस में किसी ट्रेस सेक्शन को जनरेट करने के लिए ज़िम्मेदार फ़ाइल, क्लास या तरीके का पता लगाना आसान हो जाता है.
fun main() {
val driver = createTraceDriver()
@OptIn(DelicateTracingApi::class)
Tracer.setGlobalTracer(driver.tracer)
driver.use {
Tracer.global.trace(
category = CATEGORY_MAIN,
name = "callStackEntry",
metadataBlock = {
addCallStackEntry(
name = "main",
lineNumber = 14,
sourceFile = "Basic.kt"
)
}
) {
Thread.sleep(100L)
}
}
}
इससे यह नतीजा मिलता है.
आठवीं इमेज. कॉल स्टैक की जानकारी के साथ Perfetto ट्रेस का स्क्रीन कैप्चर.