مكتبة androidx.tracing:tracing:2.0.2 هي واجهة برمجة تطبيقات Kotlin ذات تكلفة منخفضة تتيح لك تسجيل أحداث التتبُّع أثناء المعالجة. يمكن أن تسجّل هذه الأحداث شرائح زمنية وسياقها. تتيح المكتبة أيضًا نقل السياق إلى إجراءات Kotlin الفرعية.
تستخدِم المكتبة تنسيق حِزم التتبُّع Perfetto نفسه الذي يعرفه مطوّرو تطبيقات Android. بالإضافة إلى ذلك، تتيح ميزة "تتبُّع التسلسل الهرمي" 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.2")
}
}
androidMain {
dependencies {
// Android implementation (includes the Perfetto Sink and automatic initialization)
implementation("androidx.tracing:tracing-wire:2.0.2")
}
}
jvmMain {
dependencies {
// JVM implementation
implementation("androidx.tracing:tracing-wire:2.0.2")
}
}
}
}
المشاريع المتوافقة مع Android فقط
إذا كنت تستهدف نظام التشغيل Android فقط، أضِف ما يلي إلى ملف build.gradle.kts في تطبيقك أو مكتبتك:
dependencies {
// For libraries and applications to emit events
implementation("androidx.tracing:tracing:2.0.2")
// For applications to configure the tracing backend
implementation("androidx.tracing:tracing-wire:2.0.2")
}
التشغيل والاستكشاف
قبل أن تتمكّن من تسجيل أحداث التتبُّع، يجب تهيئة البنية الأساسية للتتبُّع. ويشمل ذلك إنشاء 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 الفرعية أو غيرها من الأُطر المشابهة التي تساعد في أحمال العمل المتزامنة، يتيح الإصدار 2.0 من Tracing مفهوم نقل السياق. يمكن توضيح ذلك بشكل أفضل من خلال مثال.
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.
بعد ذلك، أصبح كلا مؤشري الترابط غير نشطين لأنّه تم تعليق الروتينات الفرعية بسبب استخدام delay.
النشر اليدوي
في بعض الأحيان، عند دمج أحمال العمل المتزامنة باستخدام إجراءات Kotlin الفرعية مع مثيلات Executor في Java، قد يكون من المفيد نقل السياق من أحدهما إلى الآخر. يُرجى الاطّلاع على المثال أدناه:
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، ثم تم التبديل إلى Executor في Java، ولكن ظلّ بإمكاننا استخدام ميزة نقل السياق.
الدمج مع عمليات تتبُّع النظام
لا تسجّل مكتبة androidx.tracing معلومات مثل جدولة وحدة المعالجة المركزية واستخدام الذاكرة وتفاعل التطبيق مع نظام التشغيل بشكل عام. ويرجع ذلك إلى أنّ المكتبة توفّر طريقة لإجراء
تتبُّع داخل العملية بدون تكلفة إضافية.
ومع ذلك، من السهل جدًا دمج عمليات تتبُّع النظام مع عمليات التتبُّع داخل العملية
وعرضها كعملية تتبُّع واحدة عند الحاجة. ويرجع ذلك إلى أنّ Perfetto UI
يتيح عرض ملفات تتبُّع متعددة من جهاز على مخطط زمني موحّد.
لإجراء ذلك، يمكنك بدء جلسة تتبُّع النظام باستخدام Perfetto UI باتّباع التعليمات هنا.
يمكنك أيضًا تسجيل أحداث التتبُّع أثناء المعالجة باستخدام واجهة برمجة التطبيقات Tracing 2.0، وذلك أثناء تفعيل "تتبُّع نشاط النظام". بعد الحصول على ملفَي التتبُّع، يمكنك استخدام الخيار Open Multiple Trace Files في Perfetto.
الشكل 6. فتح ملفات تتبُّع متعددة في واجهة مستخدم 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)
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 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 تتضمّن معلومات عن حزمة التنفيذ