المكتبة androidx.tracing:tracing:2.0.0-beta01 هي واجهة برمجة تطبيقات Kotlin منخفضة النفقات
تتيح لك تسجيل أحداث التتبُّع داخل العملية. يمكن لهذه الأحداث تسجيل الشرائح الزمنية والسياق الخاص بها. تتيح المكتبة أيضًا نشر السياق لـ Kotlin coroutines.
تستخدم المكتبة تنسيق حزمة تتبُّع Perfetto نفسه الذي يعرفه مطوّرو Android. بالإضافة إلى ذلك، تتيح Tracing 2.0 (على عكس واجهات برمجة التطبيقات 1.0.0-*)
مفهوم الواجهات الخلفية للتتبُّع القابلة للتوصيل والمصادر، ما يتيح لمكتبات التتبُّع الأخرى
تخصيص تنسيق تتبُّع الإخراج وكيفية عمل نشر السياق
في عملية التنفيذ.
الطلبات التابعة
لبدء التتبُّع، عليك تحديد الطلبات التابعة في ملف build.gradle.kts.
مشاريع Kotlin المتعددة المنصات
يجب أن تعتمد المكتبات التي تحتاج فقط إلى إرسال أحداث التتبُّع على واجهة برمجة التطبيقات الخفيفة 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يكتب ملفات تتبُّع Perfetto فيContext.noBackupFilesDir/perfetto_traces/.تسجيل
Tracerالناتج على مستوى العالم.
تخصيص مثيل TraceDriver
إذا كنت بحاجة إلى تخصيص الإعداد، مثلاً لتغيير مكان حفظ ملفات التتبُّع أو استخدام 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)
على 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 كيفية تسلسل حِزم التتبُّع. يأتي الإصدار 2.0.0 من Tracing مع عملية تنفيذ لـ Sink تستخدم تنسيق حزمة تتبُّع Perfetto. توفّر TraceDriver مقبضًا لـ Tracer ويمكن استخدامها لإنهاء عملية التتبُّع.
بعد إعداد Tracer، إما تلقائيًا على Android أو يدويًا على
JVM، استخدِم مثيل Tracer.global على مستوى العالم لإرسال أحداث التتبُّع.
يمكنك أيضًا استخدام TraceDriver لإيقاف جميع نقاط التتبُّع في التطبيق، إذا اخترت عدم التتبُّع على الإطلاق في بعض أشكال التطبيق. يمكنك اختياريًا تفعيل نقاط التتبُّع لـ category معيّنة من خلال توفير عملية تنفيذ لـ isCategoryEnabled عند إنشاء مثيل TraceDriver.
val driver = TraceDriver(
sink = sink,
isCategoryEnabled = { category ->
// Only enable trace points in the "com.example" package
category.startsWith("com.example")
}
)
في ما يلي مثال أساسي على إرسال حدث تتبُّع باستخدام Tracer.global على JVM، بما في ذلك الإعداد اليدوي:
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)
}
}
}
يؤدي ذلك إلى إنشاء التتبُّع التالي.
الشكل 1: لقطة شاشة لتتبُّع 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") {
// ...
}
}
يؤدي ذلك إلى إنشاء التتبُّع التالي.
الشكل 2: لقطة شاشة لتتبُّع Perfetto أساسي مع أقسام متداخلة
يمكنك ملاحظة وجود أحداث متداخلة في مسار سلسلة المحادثات الرئيسية. من الواضح جدًا أنّ processImage تستدعي loadImage وsharpen على سلسلة المحادثات نفسها.
إضافة بيانات وصفية إضافية في أقسام التتبُّع
في بعض الأحيان، قد يكون من المفيد إرفاق بيانات وصفية سياقية إضافية بشريحة تتبُّع للحصول على مزيد من التفاصيل. يمكن أن تتضمّن بعض الأمثلة على هذه البيانات الوصفية 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.
الشكل 3: لقطة شاشة لتتبُّع 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")
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 4: لقطة شاشة لتتبُّع Perfetto أساسي مع نشر السياق
يسهّل نشر السياق تصوُّر تدفق التنفيذ بشكل كبير. يمكنك الاطّلاع بالضبط على المهام ذات الصلة (المرتبطة بمهام أخرى)، ومتى تم إيقاف Threads واستئنافها.
على سبيل المثال، يمكنك ملاحظة أنّ الشريحة main أنشأت taskOne وtaskTwo.
بعد ذلك، كانت كلتا سلسلتي المحادثات غير نشطتين لأنّه تم إيقاف coroutines مؤقتًا بسبب استخدام delay.
النشر اليدوي
في بعض الأحيان، عند دمج أحمال العمل المتزامنة باستخدام Kotlin coroutines مع مثيلات 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()
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 5: لقطة شاشة لتتبُّع Perfetto أساسي مع نشر السياق يدويًا
يمكنك ملاحظة أنّ التنفيذ بدأ في CoroutineContext، ثم تم الانتقال إلى Java Executor، ولكن كان لا يزال بإمكاننا استخدام نشر السياق.
الدمج مع عمليات تتبُّع النظام
لا تسجِّل مكتبة androidx.tracing معلومات مثل جدولة وحدة المعالجة المركزية (CPU) واستخدام الذاكرة وتفاعل التطبيق مع نظام التشغيل بشكل عام. يرجع السبب في ذلك إلى أنّ المكتبة توفّر طريقة لإجراء تتبُّع منخفض النفقات داخل العملية.
ومع ذلك، من السهل جدًا دمج عمليات تتبُّع النظام مع عمليات التتبُّع داخل العملية وعرضها كتتبُّع واحد إذا لزم الأمر. يرجع السبب في ذلك إلى أنّ Perfetto UI يتيح عرض ملفات تتبُّع متعددة من جهاز على مخطط زمني موحّد.
لإجراء ذلك، يمكنك بدء جلسة تتبُّع النظام باستخدام Perfetto UI باتّباع التعليمات هنا.
يمكنك أيضًا تسجيل أحداث التتبُّع داخل العملية باستخدام واجهة برمجة التطبيقات Tracing 2.0، أثناء تفعيل تتبُّع النظام. بعد الحصول على كلتا ملفَي التتبُّع، يمكنك استخدام خيار Open Multiple Trace Files في Perfetto.
الشكل 6: فتح ملفات تتبُّع متعددة في Perfetto UI
طرق العمل المتقدّمة
يوضّح هذا القسم طرق العمل المتقدّمة التي يمكنك تنفيذها باستخدام مكتبة التتبُّع داخل العملية.
ربط الشرائح
في بعض الأحيان، يكون من المفيد إحالة الشرائح في التتبُّع إلى إجراء مستخدم أكثر مستوى أو حدث نظام. على سبيل المثال، لإحالة جميع الشرائح التي تتوافق مع بعض الأعمال في الخلفية كجزء من إشعار، يمكنك إجراء ما يلي:
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)
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 7: لقطة شاشة لتتبُّع Perfetto مع شرائح مرتبطة
إضافة معلومات عن مكدس الاستدعاءات
يمكن أيضًا للأدوات من جهة المضيف، مثل مكوّنات المترجم الإضافية ومعالِجات التعليقات التوضيحية، اختيار تضمين معلومات عن مكدس الاستدعاءات في التتبُّع، لتسهيل تحديد موقع الملف أو الفئة أو الطريقة المسؤولة عن إنشاء قسم تتبُّع في التتبُّع.
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)
}
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 8: لقطة شاشة لتتبُّع Perfetto مع معلومات عن مكدس الاستدعاءات