การติดตามในกระบวนการ

ไลบรารี androidx.tracing:tracing:2.0.0-beta01 เป็น Kotlin API ที่มีค่าใช้จ่ายต่ำ ซึ่งช่วยให้คุณบันทึกเหตุการณ์การติดตามในกระบวนการได้ เหตุการณ์เหล่านี้จะ บันทึกช่วงเวลาและบริบทของช่วงเวลาได้ นอกจากนี้ ไลบรารียังรองรับการส่งต่อบริบทสำหรับโครูทีน Kotlin ด้วย

ไลบรารีใช้รูปแบบแพ็กเก็ตการติดตาม Perfetto เดียวกันกับที่นักพัฒนาแอป Android คุ้นเคย นอกจากนี้ Tracing 2.0 (ต่างจาก 1.0.0-* API) ยังรองรับแนวคิดของแบ็กเอนด์การติดตามที่เสียบได้และซิงก์ ดังนั้นไลบรารีการติดตามอื่นๆ จึงปรับแต่งรูปแบบการติดตามเอาต์พุตได้ รวมถึงวิธีที่การแพร่บริบททํางานในการติดตั้งใช้งาน

ความสัมพันธ์

หากต้องการเริ่มการติดตาม คุณต้องกำหนดการอ้างอิงใน build.gradle.kts

โปรเจ็กต์ Kotlin Multiplatform

ไลบรารีที่ต้องการปล่อยเฉพาะเหตุการณ์การติดตามควรใช้ androidx.tracing:tracing API แบบเบา แอปที่กำหนดค่าแบ็กเอนด์การติดตาม ควรขึ้นอยู่กับ 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 มาพร้อมกับการติดตั้งใช้งาน Sink ที่ใช้Perfettoรูปแบบแพ็กเก็ตการติดตาม A 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)
        }
    }
}

ซึ่งจะสร้างการติดตามต่อไปนี้

ภาพหน้าจอของการติดตาม Perfetto พื้นฐาน

รูปที่ 1 ภาพหน้าจอของ Perfetto Trace พื้นฐาน

คุณจะเห็นว่ามีการป้อนข้อมูลกระบวนการและเส้นทางเทรดที่ถูกต้อง และ สร้างส่วนการติดตามเดียว 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 พื้นฐานที่มีส่วนที่ซ้อนกัน

รูปที่ 2 การจับภาพหน้าจอของร่องรอย Perfetto พื้นฐานที่มีส่วนที่ซ้อนกัน

คุณจะเห็นว่ามีเหตุการณ์ที่ทับซ้อนกันในแทร็กเทรดหลัก processImage เรียกใช้ loadImage และ sharpen ในเธรดเดียวกันอย่างชัดเจน

เพิ่มข้อมูลเมตาเพิ่มเติมในส่วนการติดตาม

บางครั้งการแนบข้อมูลเมตาเชิงบริบทเพิ่มเติมกับ Trace Slice อาจมีประโยชน์ในการดูรายละเอียดเพิ่มเติม ตัวอย่างของข้อมูลเมตาดังกล่าวอาจรวมถึง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 พื้นฐานที่มีข้อมูลเมตาเพิ่มเติม

รูปที่ 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")
    }
}

ซึ่งจะให้ผลลัพธ์ต่อไปนี้

ภาพหน้าจอของการติดตาม Perfetto ที่มีการส่งต่อบริบท

รูปที่ 4 การจับภาพหน้าจอของ Perfetto Trace พื้นฐานที่มีการส่งต่อบริบท

การส่งต่อบริบทช่วยให้เห็นภาพลำดับการดำเนินการได้ง่ายขึ้นมาก คุณจะเห็นว่างานใดที่เกี่ยวข้อง (เชื่อมต่อกับงานอื่นๆ) และเวลาที่Threadsถูกระงับและกลับมาทำงานอีกครั้ง

เช่น คุณจะเห็นว่าสไลซ์ main สร้าง taskOne และ taskTwo หลังจากนั้น ทั้ง 2 เธรดก็ไม่มีการใช้งานเนื่องจากมีการระงับโครูทีน เนื่องจากการใช้ 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 ที่มีการส่งต่อบริบทด้วยตนเอง

รูปที่ 5 ภาพหน้าจอของการติดตาม Perfetto ขั้นพื้นฐานที่มีการส่งต่อบริบทด้วยตนเอง

คุณจะเห็นว่าการดำเนินการเริ่มต้นใน CoroutineContext และต่อมา เปลี่ยนไปใช้ Executor ของ Java แต่เรายังคงใช้การส่งต่อบริบทได้

รวมกับการติดตามระบบ

androidx.tracing ไม่ได้บันทึกข้อมูล เช่น การจัดกำหนดการ CPU การใช้หน่วยความจำ และการโต้ตอบของแอปพลิเคชันกับระบบปฏิบัติการโดยทั่วไป เนื่องจากไลบรารีมีวิธีในการทำการติดตามในกระบวนการที่มีค่าใช้จ่ายต่ำ

อย่างไรก็ตาม การผสานรวมการติดตามของระบบกับการติดตามในกระบวนการ และแสดงภาพเป็นการติดตามเดียวหากจำเป็นนั้นเป็นเรื่องง่ายมาก เนื่องจาก Perfetto UI รองรับการแสดงภาพไฟล์การติดตามหลายไฟล์จากอุปกรณ์ในไทม์ไลน์แบบรวม

โดยคุณเริ่มเซสชันการติดตามระบบได้โดยใช้ Perfetto UI โดยทำตามวิธีการที่นี่

นอกจากนี้ คุณยังบันทึกเหตุการณ์การติดตามในกระบวนการได้โดยใช้ Tracing 2.0 API ขณะที่ เปิดการติดตามระบบ เมื่อมีไฟล์การติดตามทั้ง 2 ไฟล์แล้ว คุณจะใช้ตัวเลือก Open Multiple Trace Files ใน Perfetto ได้

การเปิดไฟล์การติดตามหลายไฟล์ใน UI ของ Perfetto

รูปที่ 6 การเปิดไฟล์การติดตามหลายไฟล์ใน UI ของ Perfetto

เวิร์กโฟลว์ขั้นสูง

ส่วนนี้อธิบายเวิร์กโฟลว์ขั้นสูงที่คุณสามารถใช้กับ ไลบรารีการติดตามในกระบวนการ

เชื่อมโยงชิ้นส่วน

บางครั้งการระบุแหล่งที่มาของสไลซ์ในเทรซไปยังการดำเนินการของผู้ใช้ในระดับที่สูงขึ้นหรือเหตุการณ์ของระบบก็มีประโยชน์ เช่น หากต้องการระบุแหล่งที่มาของ Slice ทั้งหมดที่ สอดคล้องกับงานที่ทำอยู่เบื้องหลังบางอย่างเป็นส่วนหนึ่งของการแจ้งเตือน คุณอาจทำ ดังนี้

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)
    }
}

ซึ่งจะให้ผลลัพธ์ต่อไปนี้

ภาพหน้าจอของ Perfetto Trace ที่มี Slice ที่สัมพันธ์กัน

รูปที่ 7 ภาพหน้าจอของการติดตาม Perfetto ที่มี Slice ที่สัมพันธ์กัน

เพิ่มข้อมูลสแต็กการเรียก

เครื่องมือฝั่งโฮสต์ เช่น ปลั๊กอินคอมไพเลอร์และโปรเซสเซอร์คำอธิบายประกอบ ยังเลือกฝังข้อมูลสแต็กการเรียกใช้ลงในเทรซได้ด้วย เพื่อให้ค้นหาไฟล์ คลาส หรือเมธอดที่รับผิดชอบในการสร้างส่วนเทรซในเทรซได้สะดวก

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 พร้อมข้อมูลสแต็กการเรียก

รูปที่ 8 ภาพหน้าจอของ Perfetto Trace ที่มีข้อมูล Call Stack