নতুন androidx.tracing:tracing:2.0.0-alpha04 লাইব্রেরিটি একটি স্বল্প খরচের কোটলিন এপিআই, যা চলমান ট্রেস ইভেন্ট ক্যাপচার করতে দেয়। এই ইভেন্টগুলো টাইম স্লাইস এবং সেগুলোর কনটেক্সট ক্যাপচার করতে পারে। লাইব্রেরিটি অতিরিক্তভাবে কোটলিন কোরাউটিনের জন্য কনটেক্সট প্রোপাগেশন সমর্থন করে।
এই লাইব্রেরিটি সেই একই পারফেটটো ট্রেস প্যাকেট ফরম্যাট ব্যবহার করে, যার সাথে অ্যান্ড্রয়েড ডেভেলপাররা পরিচিত। এছাড়াও, ট্রেসিং 2.0 ( 1.0.0-* এপিআইগুলোর মতো নয়) প্লাগেবল ট্রেসিং ব্যাকএন্ড এবং সিঙ্ক- এর ধারণা সমর্থন করে, ফলে অন্যান্য ট্রেসিং লাইব্রেরিগুলো তাদের নিজস্ব ইমপ্লিমেন্টেশনে আউটপুট ট্রেসিং ফরম্যাট এবং কনটেক্সট প্রোপাগেশন কীভাবে কাজ করে, তা কাস্টমাইজ করতে পারে।
নির্ভরশীলতা
ট্রেসিং শুরু করতে, আপনাকে আপনার build.gradle.kts ফাইলে নিম্নলিখিত ডিপেন্ডেন্সিগুলো সংজ্ঞায়িত করতে হবে।
kotlin {
androidLibrary {
namespace = "com.example.library"
// ...
}
sourceSets {
androidMain {
dependencies {
api("androidx.tracing:tracing-wire:2.0.0-alpha04")
// ...
}
}
jvmMain {
dependencies {
api("androidx.tracing:tracing-wire:2.0.0-alpha04")
// ...
}
}
}
}
যদি আপনি কোনো অ্যান্ড্রয়েড লাইব্রেরি, অ্যান্ড্রয়েড অ্যাপ্লিকেশন বা JVM-কে টার্গেট করেন, তাহলে androidx.tracing:tracing-wire:2.0.0-alpha04 এর উপর একটি ডিপেন্ডেন্সি ঘোষণা করুন।
মৌলিক ব্যবহার
একটি TraceSink নির্ধারণ করে যে ট্রেস প্যাকেটগুলো কীভাবে ক্রমিক করা হবে। ট্রেসিং ২.০.০ (Tracing 2.0.0)-এর সাথে একটি সিঙ্ক (Sink)-এর বাস্তবায়ন রয়েছে যা Perfetto ট্রেস প্যাকেট ফরম্যাট ব্যবহার করে। একটি TraceDriver Tracer -এর জন্য একটি হ্যান্ডেল প্রদান করে এবং এটি একটি ট্রেস চূড়ান্ত করতে ব্যবহার করা যেতে পারে।
আপনি যদি অ্যাপ্লিকেশনের কিছু সংস্করণে একেবারেই ট্রেস করতে না চান, তবে TraceDriver ব্যবহার করে অ্যাপ্লিকেশনের সমস্ত ট্রেস পয়েন্ট নিষ্ক্রিয়ও করতে পারেন। TraceDriver-এর ভবিষ্যৎ API-গুলো ডেভেলপারদেরকে এটিও নিয়ন্ত্রণ করার সুযোগ দেবে যে, তারা কোন ট্রেস ক্যাটাগরিগুলো ক্যাপচার করতে আগ্রহী (অথবা কোনো ক্যাটাগরি কোলাহলপূর্ণ হলে তা নিষ্ক্রিয় করতে পারবেন)।
শুরু করার জন্য, একটি TraceSink এবং একটি TraceDriver এর ইনস্ট্যান্স তৈরি করুন।
/**
* A [TraceSink] defines how traces are serialized.
*
* [androidx.tracing.wire.TraceSink] uses the `Perfetto` trace packet format.
*/
fun createSink(): TraceSink {
val outputDirectory = File(/* path = */ "/tmp/perfetto")
if (!outputDirectory.exists()) {
outputDirectory.mkdirs()
}
// We are using the factory function defined in androidx.tracing.wire
return TraceSink(
sequenceId = 1,
directory = outputDirectory
)
}
/**
* Creates a new instance of [androidx.tracing.wire.TraceDriver].
*/
fun createTraceDriver(): TraceDriver {
// We are using a factory function from androidx.tracing.wire here.
// `isEnabled` controls whether tracing is enabled for the application.
val driver = TraceDriver(sink = createSink(), isEnabled = true)
return driver
}
TraceDriver এর একটি ইনস্ট্যান্স পাওয়ার পর, Tracer সংগ্রহ করুন, যা সমস্ত ট্রেসিং এপিআই-এর এন্ট্রি পয়েন্ট নির্ধারণ করে।
// Tracing Categories identify subsystems that are responsible
// in generating trace sections. Future APIs in `TraceDriver` will allow the
// application to specify which categories they are interested in tracing.
// This lets the application disable entire trace categories, without
// needing to disable trace instrumentation at the call sites for those
// categories.
internal const val CATEGORY_MAIN = "main"
fun main() {
val driver = createTraceDriver()
driver.use {
it.tracer.trace(category = CATEGORY_MAIN, name = "basic") {
Thread.sleep(100L)
}
}
}
এর ফলে নিম্নলিখিত ট্রেসটি তৈরি হয়।

চিত্র ১. একটি সাধারণ পারফেটটো ট্রেসের স্ক্রিনশট।
আপনি দেখতে পাচ্ছেন যে সঠিক প্রসেস এবং থ্রেড ট্র্যাকগুলি পূরণ করা হয়েছে, এবং একটি একক ট্রেস basic তৈরি হয়েছে, যা 100ms ধরে চলেছে।
ওভারল্যাপিং ইভেন্টগুলো উপস্থাপন করার জন্য একই ট্র্যাকে ট্রেস সেকশন (বা স্লাইস) নেস্ট করা যেতে পারে। এখানে একটি উদাহরণ দেওয়া হলো।
fun main() {
// Initialize the tracing infrastructure to monitor app performance
val driver = createTraceDriver()
val tracer = driver.tracer
driver.use {
it.tracer.trace(
category = CATEGORY_MAIN,
name = "processImage",
) {
// Load the data first, then apply the sharpen filter
sharpen(tracer = tracer, output = loadImage(tracer))
}
}
}
internal fun loadImage(tracer: Tracer): ByteArray {
return tracer.trace(CATEGORY_MAIN, "loadImage") {
// Loads an image
// ...
// A placeholder
ByteArray(0)
}
}
internal fun sharpen(tracer: Tracer, output: ByteArray) {
// ...
tracer.trace(CATEGORY_MAIN, "sharpen") {
// ...
}
}
এর ফলে নিম্নলিখিত ট্রেসটি তৈরি হয়।

চিত্র ২. নেস্টেড সেকশনসহ একটি বেসিক পারফেটটো ট্রেসের স্ক্রিনশট।
আপনি দেখতে পাচ্ছেন যে প্রধান থ্রেড ট্র্যাকে ওভারল্যাপিং ইভেন্ট রয়েছে। এটা খুব স্পষ্ট যে processImage একই থ্রেডে loadImage এবং sharpen কল করে।
ট্রেস বিভাগে অতিরিক্ত মেটাডেটা যোগ করুন
কখনও কখনও, আরও বিশদ বিবরণ পাওয়ার জন্য একটি ট্রেস স্লাইসের সাথে অতিরিক্ত প্রাসঙ্গিক মেটাডেটা সংযুক্ত করা সহায়ক হতে পারে। এই ধরনের মেটাডেটার কিছু উদাহরণ হতে পারে ব্যবহারকারী যে nav destination আছেন, অথবা input arguments যা শেষ পর্যন্ত একটি ফাংশনের সময়কাল নির্ধারণ করতে পারে।
fun main() {
val driver = createTraceDriver()
driver.use {
it.tracer.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 তৈরি করার সময় যোগ করা কী-ভ্যালু পেয়ারগুলো রয়েছে।

চিত্র ৩. অতিরিক্ত মেটাডেটা সহ একটি সাধারণ পারফেটটো ট্রেসের স্ক্রিনশট।
প্রসঙ্গ প্রচার
কোটলিন কোরাউটিন (বা কনকারেন্ট ওয়ার্কলোডে সাহায্যকারী অন্যান্য অনুরূপ ফ্রেমওয়ার্ক) ব্যবহার করার সময়, ট্রেসিং ২.০ কনটেক্সট প্রোপাগেশন ধারণাটিকে সমর্থন করে। একটি উদাহরণের মাধ্যমে এটি সবচেয়ে ভালোভাবে ব্যাখ্যা করা যায়।
suspend fun taskOne(tracer: Tracer) {
tracer.traceCoroutine(category = CATEGORY_MAIN, "taskOne") {
delay(timeMillis = 100L)
}
}
suspend fun taskTwo(tracer: Tracer) {
tracer.traceCoroutine(category = CATEGORY_MAIN, "taskTwo") {
delay(timeMillis = 50L)
}
}
fun main() = runBlocking(context = Dispatchers.Default) {
val driver = createTraceDriver()
val tracer = driver.tracer
driver.use {
it.tracer.traceCoroutine(category = CATEGORY_MAIN, name = "main") {
coroutineScope {
launch { taskOne(tracer) }
launch { taskTwo(tracer) }
}
}
println("All done")
}
}
এর ফলে নিম্নলিখিত ফলাফলটি পাওয়া যায়।

চিত্র ৪। কনটেক্সট প্রোপাগেশন সহ একটি বেসিক পারফেটটো ট্রেসের স্ক্রিনশট।
কন্টেক্সট প্রোপাগেশন এক্সিকিউশনের প্রবাহকে কল্পনা করা অনেক সহজ করে তোলে। আপনি স্পষ্টভাবে দেখতে পারেন কোন টাস্কগুলো একে অপরের সাথে সম্পর্কিত ছিল, এবং ঠিক কখন Threads সাসপেন্ড ও রিজুম করা হয়েছিল।
উদাহরণস্বরূপ, আপনি দেখতে পারেন যে স্লাইস main taskOne এবং taskTwo তৈরি করেছে। এরপর উভয় থ্রেডই নিষ্ক্রিয় ছিল (যেহেতু delay ব্যবহারের কারণে কো-রুটিনগুলো সাসপেন্ড করা ছিল)।
ম্যানুয়াল প্রচার
কখনও কখনও যখন আপনি কোটলিন কো-রুটিন এবং জাভা Executor ইনস্ট্যান্স ব্যবহার করে সমান্তরাল ওয়ার্কলোড একসাথে চালান, তখন একটি থেকে অন্যটিতে কনটেক্সট স্থানান্তর করা দরকারি হতে পারে। এখানে একটি উদাহরণ দেওয়া হলো:
fun executorTask(
tracer: Tracer,
token: PropagationToken,
executor: Executor,
callback: () -> Unit
) {
executor.execute {
tracer.trace(
category = CATEGORY_MAIN,
name = "executeTask",
token = token,
) {
// Do something
Thread.sleep(100)
callback()
}
}
}
@OptIn(DelicateTracingApi::class)
fun main() = runBlocking(context = Dispatchers.Default) {
val driver = createTraceDriver()
val executor = Executors.newSingleThreadExecutor()
val tracer = driver.tracer
driver.use {
it.tracer.traceCoroutine(category = CATEGORY_MAIN, name = "main") {
coroutineScope {
val deferred = CompletableDeferred<Unit>()
executorTask(
tracer = tracer,
// Obtain the propagation token from the CoroutineContext
token = tracer.tokenFromCoroutineContext(),
executor = executor,
callback = {
deferred.complete(Unit)
}
)
deferred.await()
}
}
executor.shutdownNow()
}
}
এর ফলে নিম্নলিখিত ফলাফলটি পাওয়া যায়।

চিত্র ৫. ম্যানুয়াল কনটেক্সট প্রোপাগেশন সহ একটি বেসিক পারফেটটো ট্রেসের স্ক্রিনশট।
আপনি দেখতে পাচ্ছেন যে এক্সিকিউশন একটি CoroutineContext এ শুরু হয়েছিল এবং পরবর্তীতে একটি Java Executor এ স্থানান্তরিত হয়েছিল, কিন্তু তারপরেও আমরা কনটেক্সট প্রোপাগেশন ব্যবহার করতে সক্ষম হয়েছিলাম।
সিস্টেম ট্রেসের সাথে একত্রিত করুন
নতুন androidx.tracing সিপিইউ শিডিউলিং, মেমরি ব্যবহার এবং সাধারণভাবে অপারেটিং সিস্টেমের সাথে অ্যাপ্লিকেশনটির মিথস্ক্রিয়ার মতো তথ্য ক্যাপচার করে না। এর কারণ হলো, লাইব্রেরিটি খুব কম ওভারহেডে ইন-প্রসেস ট্রেসিং করার একটি উপায় প্রদান করে।
তবে, প্রয়োজনে সিস্টেম ট্রেস এবং ইন-প্রসেস ট্রেস একত্রিত করে একটি একক ট্রেস হিসাবে দেখা অত্যন্ত সহজ। এর কারণ হলো, Perfetto UI একটি ডিভাইস থেকে একাধিক ট্রেস ফাইলকে একটি সমন্বিত টাইমলাইনে দেখানোর সুবিধা দেয়।
এটি করার জন্য, আপনি এখানে দেওয়া নির্দেশাবলী অনুসরণ করে Perfetto UI ব্যবহার করে একটি সিস্টেম ট্রেসিং সেশন শুরু করতে পারেন।
সিস্টেম ট্রেসিং চালু থাকা অবস্থায়, আপনি Tracing 2.0 এপিআই ব্যবহার করে চলমান ট্রেস ইভেন্টগুলোও রেকর্ড করতে পারেন। দুটি ট্রেস ফাইলই পেয়ে গেলে আপনি পারফেটোতে থাকা ‘ Open Multiple Trace Files অপশনটি ব্যবহার করতে পারবেন।

চিত্র ৬. পারফেটটো UI-তে একাধিক ট্রেস ফাইল খোলা।
উন্নত কর্মপ্রবাহ
স্লাইসগুলির মধ্যে সম্পর্ক স্থাপন করুন
কখনও কখনও, একটি ট্রেসের স্লাইসগুলিকে আরও উচ্চ-স্তরের ব্যবহারকারীর কার্যকলাপ বা সিস্টেম ইভেন্টের সাথে যুক্ত করা দরকারি হতে পারে। উদাহরণস্বরূপ, কোনো ব্যাকগ্রাউন্ড কাজের সাথে সম্পর্কিত সমস্ত স্লাইসকে একটি নোটিফিকেশনের অংশ হিসাবে যুক্ত করতে, আপনি এইরকম কিছু করতে পারেন:
fun main() {
val driver = createTraceDriver()
onEvent(driver, eventId = EVENT_ID)
}
fun onEvent(driver: TraceDriver, eventId: Long) {
driver.use {
it.tracer.trace(
category = CATEGORY_MAIN,
name = "step-1",
metadataBlock = {
addCorrelationId(eventId)
}
) {
Thread.sleep(100L)
}
Thread.sleep(20)
driver.tracer.trace(
category = CATEGORY_MAIN,
name = "step-2",
metadataBlock = {
addCorrelationId(eventId)
}
) {
Thread.sleep(180)
}
}
}
এর ফলে নিম্নলিখিত ফলাফলটি পাওয়া যায়।

চিত্র ৭। পারস্পরিক সম্পর্কযুক্ত স্লাইসসহ একটি পারফেটটো ট্রেসের স্ক্রিনশট।
কল স্ট্যাক তথ্য যোগ করুন
হোস্ট সাইডের টুলগুলো (কম্পাইলার প্লাগইন, অ্যানোটেশন প্রসেসর ইত্যাদি) চাইলে একটি ট্রেসের মধ্যে কল স্ট্যাকের তথ্যও যুক্ত করে দিতে পারে, যাতে ট্রেসের কোনো একটি অংশ তৈরির জন্য দায়ী ফাইল, ক্লাস বা মেথডটি সহজেই খুঁজে বের করা যায়।
fun main() {
val driver = createTraceDriver()
driver.use {
it.tracer.trace(
category = CATEGORY_MAIN,
name = "callStackEntry",
metadataBlock = {
addCallStackEntry(
name = "main",
lineNumber = 14,
sourceFile = "Basic.kt"
)
}
) {
Thread.sleep(100L)
}
}
}
এর ফলে নিম্নলিখিত ফলাফলটি পাওয়া যায়।

চিত্র ৮. কল স্ট্যাকের তথ্যসহ পারফেটটো ট্রেসের স্ক্রিনশট।