مكتبة androidx.tracing:tracing:2.0.0-alpha04 الجديدة هي واجهة برمجة تطبيقات Kotlin منخفضة النفقات
تسمح بتسجيل أحداث التتبُّع داخل العملية. يمكن لهذه الأحداث تسجيل شرائح الوقت والسياق الخاص بها. تتيح المكتبة أيضًا نشر السياق لـ Kotlin Coroutines.
تستخدم المكتبة تنسيق حزمة تتبُّع Perfetto نفسه الذي يعرفه مطوّرو Android. بالإضافة إلى ذلك، تتيح مكتبة Tracing 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")
// ...
}
}
}
}
عليك الإعلان عن اعتمادية على androidx.tracing:tracing-wire:2.0.0-alpha04 إذا كنت تستهدف مكتبة Android أو تطبيق Android أو JVM.
الاستخدام الأساسي
يحدّد TraceSink كيفية تسلسل حِزم التتبُّع. يأتي الإصدار 2.0.0 من Tracing مع عملية تنفيذ لمصدر يستخدم تنسيق حزمة تتبُّع Perfetto. يوفر TraceDriver مقبضًا لـ Tracer ويمكن استخدامه لإنهاء عملية تتبُّع.
يمكنك أيضًا استخدام TraceDriver لإيقاف جميع نقاط التتبُّع في التطبيق، إذا اخترت عدم تتبُّع الأداء على الإطلاق في بعض أشكال التطبيق.
ستسمح واجهات برمجة التطبيقات المستقبلية في TraceDriver للمطوّرين أيضًا بالتحكّم في فئات التتبُّع التي يهمّهم تسجيلها (أو إيقافها عندما تكون الفئة صاخبة).
للبدء، أنشئ مثيلاً من 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)
}
}
}
يؤدي ذلك إلى إنشاء التتبُّع التالي.
الشكل 1: لقطة شاشة لتتبُّع Perfetto أساسي
يمكنك ملاحظة أنّه تم ملء مسارات العملية والمسارات الصحيحة،
وتم إنشاء قسم تتبُّع واحد 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") {
// ...
}
}
يؤدي ذلك إلى إنشاء التتبُّع التالي.
الشكل 2: لقطة شاشة لتتبُّع Perfetto أساسي مع أقسام متداخلة
يمكنك ملاحظة أنّ هناك أحداثًا متداخلة في مسار سلسلة التعليمات الرئيسية. من الواضح جدًا أنّ 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.
الشكل 3: لقطة شاشة لتتبُّع Perfetto أساسي مع بيانات وصفية إضافية
نشر السياق
عند استخدام Kotlin Coroutines (أو الأُطر المشابهة الأخرى التي تساعد في أحمال العمل المتزامنة)، يتيح الإصدار 2.0 من Tracing مفهوم نشر السياق. يمكن شرح ذلك بشكل أفضل من خلال مثال.
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")
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 4: لقطة شاشة لتتبُّع Perfetto أساسي مع نشر السياق
يُسهّل نشر السياق كثيرًا تصوُّر تدفق التنفيذ. يمكنك الاطّلاع بالضبط على المهام ذات الصلة (المرتبطة بمهام أخرى)،
والوقت الذي تم فيه Threads وتعليقها واستئنافها.
على سبيل المثال، يمكنك ملاحظة أنّ الشريحة main أنشأت taskOne وtaskTwo.
بعد ذلك، كانت كلتا سلسلتي التعليمات غير نشطتين (بما أنّه تم تعليق coroutines بسبب استخدام delay).
النشر اليدوي
في بعض الأحيان، عند دمج أحمال العمل المتزامنة باستخدام Kotlin coroutines مع مثيلات Java 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()
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 5: لقطة شاشة لتتبُّع Perfetto أساسي مع نشر السياق يدويًا
يمكنك ملاحظة أنّ التنفيذ بدأ في CoroutineContext، ثم تم التبديل إلى Java Executor، ولكن كان لا يزال بإمكاننا استخدام نشر السياق.
الدمج مع عمليات تتبُّع النظام
لا تسجّل مكتبة androidx.tracing الجديدة معلومات مثل جدولة وحدة المعالجة المركزية واستخدام الذاكرة وتفاعل التطبيقات مع نظام التشغيل بشكل عام. يرجع ذلك إلى أنّ المكتبة توفّر طريقة لإجراء تتبُّع منخفض النفقات داخل العملية.
ومع ذلك، من السهل جدًا دمج عمليات تتبُّع النظام مع عمليات التتبُّع داخل العملية وعرضها كتتبُّع واحد إذا لزم الأمر. يرجع ذلك إلى أنّ Perfetto UI يتيح عرض ملفات تتبُّع متعددة من جهاز على مخطط زمني موحّد.
لإجراء ذلك، يمكنك بدء جلسة تتبُّع النظام باستخدام Perfetto UI باتّباع التعليمات هنا.
يمكنك أيضًا تسجيل أحداث التتبُّع داخل العملية باستخدام واجهة برمجة التطبيقات Tracing 2.0 أثناء تفعيل تتبُّع النظام. بعد الحصول على كلتا ملفَي التتبُّع، يمكنك استخدام الخيار
Open Multiple Trace Files في Perfetto.
الشكل 6: فتح ملفات تتبُّع متعددة في Perfetto 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)
}
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 7: لقطة شاشة لتتبُّع Perfetto مع شرائح مرتبطة
إضافة معلومات عن حزمة التنفيذ
يمكن للأدوات من جهة المضيف (المكوّنات الإضافية للمترجم وبرامج معالجة التعليقات التوضيحية وما إلى ذلك) أيضًا اختيار تضمين معلومات عن حزمة التنفيذ في عملية تتبُّع، لتسهيل تحديد موقع الملف أو الفئة أو الطريقة المسؤولة عن إنشاء قسم تتبُّع في عملية تتبُّع.
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)
}
}
}
يؤدي ذلك إلى ظهور النتيجة التالية.
الشكل 8: لقطة شاشة لتتبُّع Perfetto مع معلومات عن حزمة التنفيذ