ไลบรารี androidx.tracing:tracing:2.0.0-alpha01 ใหม่เป็น Kotlin API ที่มีค่าใช้จ่ายต่ำ
ซึ่งช่วยให้บันทึกเหตุการณ์การติดตามในกระบวนการได้ เหตุการณ์เหล่านี้จะ
บันทึกช่วงเวลาและบริบทของช่วงเวลาดังกล่าวได้ นอกจากนี้ ไลบรารียังรองรับการส่งต่อบริบท
สำหรับโครูทีนของ Kotlin ด้วย
ไลบรารีใช้รูปแบบแพ็กเก็ตการติดตาม Perfetto เดียวกันกับที่นักพัฒนาแอป Android
คุ้นเคย นอกจากนี้ การติดตาม 2.0 (ต่างจาก API ของ 1.0.0-*)
ยังรองรับแนวคิดของแบ็กเอนด์การติดตามที่เสียบได้และซิงก์ ดังนั้นไลบรารีการติดตามอื่นๆ
จึงปรับแต่งรูปแบบการติดตามเอาต์พุต และวิธีที่การแพร่บริบท
ทํางานในการติดตั้งใช้งานได้
การขึ้นต่อกัน
หากต้องการเริ่มการติดตาม คุณต้องกำหนดการอ้างอิงต่อไปนี้ใน
build.gradle.kts
kotlin {
androidLibrary {
namespace = "com.example.library"
// ...
}
sourceSets {
androidMain {
dependencies {
api("androidx.tracing:tracing-wire-android:2.0.0-alpha01")
// ...
}
}
jvmMain {
dependencies {
api("androidx.tracing:tracing-wire-desktop:2.0.0-alpha01")
// ...
}
}
}
}
ประกาศการขึ้นต่อกันใน androidx.tracing:tracing-wire-android:2.0.0-alpha01 หากคุณกําหนดเป้าหมายเป็นไลบรารีหรือแอปพลิเคชัน Android คุณใช้
androidx.tracing:tracing-wire-desktop:2.0.0-alpha01 dependency ได้หาก
กำหนดเป้าหมายเป็น JVM
การใช้งานพื้นฐาน
TraceSink จะกำหนดวิธีจัดรูปแบบแพ็กเก็ตการติดตาม การติดตาม 2.0.0 มาพร้อมกับการใช้งาน Sink ที่ใช้Perfettoรูปแบบแพ็กเก็ตการติดตาม A
TraceDriver มีแฮนเดิลสำหรับ Tracer และใช้เพื่อสรุปการติดตามได้
นอกจากนี้ คุณยังใช้ TraceDriver เพื่อปิดใช้จุดติดตามทั้งหมดใน
แอปพลิเคชันได้ด้วย หากเลือกที่จะไม่ติดตามเลยในแอปพลิเคชันบางเวอร์ชัน
นอกจากนี้ API ในอนาคตใน 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.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 ซึ่งกำหนด
จุดแรกเข้าสำหรับ API การติดตามทั้งหมด
// 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 {
driver.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 {
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ในเธรดเดียวกัน
นั้นชัดเจนมาก
เพิ่มข้อมูลเมตาเพิ่มเติมในส่วนการติดตาม
บางครั้งการแนบข้อมูลเมตาเชิงบริบทเพิ่มเติมกับ Trace
Slice อาจมีประโยชน์เพื่อให้ได้รายละเอียดเพิ่มเติม ตัวอย่างของข้อมูลเมตาดังกล่าว ได้แก่ nav destination ที่ผู้ใช้อยู่ หรือ input arguments ที่อาจเป็นตัวกำหนดระยะเวลาที่ฟังก์ชันใช้
fun main() {
val driver = createTraceDriver()
driver.use {
driver.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 (หรือเฟรมเวิร์กอื่นๆ ที่คล้ายกันซึ่งช่วยจัดการภาระงานพร้อมกัน) Tracing 2.0 จะรองรับแนวคิดของการส่งต่อบริบท ตัวอย่างจะช่วยอธิบายเรื่องนี้ได้ดีที่สุด
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 {
tracer.traceCoroutine(category = CATEGORY_MAIN, name = "main") {
coroutineScope {
launch { taskOne(tracer) }
launch { taskTwo(tracer) }
}
}
println("All done")
}
}
ซึ่งจะให้ผลลัพธ์ต่อไปนี้
รูปที่ 4 ภาพหน้าจอของร่องรอย Perfetto พื้นฐานที่มีการส่งต่อบริบท
การส่งต่อบริบทช่วยให้เห็นภาพโฟลว์ของการ
ดำเนินการได้ง่ายขึ้นมาก คุณจะเห็นว่างานใดที่เกี่ยวข้อง (เชื่อมต่อกับงานอื่นๆ)
และเวลาที่Threadsระงับและกลับมาทำงานต่อ
เช่น คุณจะเห็นว่าสไลซ์ main สร้าง taskOne และ taskTwo
หลังจากนั้นทั้ง 2 เธรดก็ไม่มีการใช้งาน (เนื่องจากมีการระงับโครูทีนเพราะใช้ delay)
การเผยแพร่ด้วยตนเอง
บางครั้งเมื่อคุณผสมภาระงานพร้อมกันโดยใช้โคโรทีน Kotlin กับอินสแตนซ์ของ 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 {
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 และต่อมาได้เปลี่ยนไปใช้ Executor ของ Java แต่เราก็ยังใช้การส่งต่อบริบทได้
รวมกับการติดตามระบบ
androidx.tracing ใหม่จะไม่บันทึกข้อมูล เช่น การจัดกำหนดการ CPU
การใช้หน่วยความจำ และการโต้ตอบของแอปพลิเคชันกับระบบปฏิบัติการโดยทั่วไป
เนื่องจากไลบรารีมีวิธีลด
ค่าใช้จ่ายในการติดตามในกระบวนการ
อย่างไรก็ตาม การผสานรวมการติดตามของระบบกับการติดตามในกระบวนการ
และแสดงภาพเป็นการติดตามเดียวหากจำเป็นนั้นเป็นเรื่องง่ายมาก เนื่องจาก Perfetto UI
รองรับการแสดงภาพไฟล์การติดตามหลายไฟล์จากอุปกรณ์ในไทม์ไลน์แบบรวม
โดยคุณเริ่มเซสชันการติดตามระบบได้โดยใช้ Perfetto UI โดยทำตามวิธีการที่นี่
นอกจากนี้ คุณยังบันทึกเหตุการณ์การติดตามในกระบวนการได้โดยใช้ Tracing 2.0 API ขณะที่เปิดการติดตามระบบ เมื่อมีไฟล์การติดตามทั้ง 2 ไฟล์แล้ว คุณจะใช้ตัวเลือก
Open Multiple Trace Files ใน Perfetto ได้
รูปที่ 6 การเปิดไฟล์การติดตามหลายไฟล์ใน UI ของ Perfetto
เวิร์กโฟลว์ขั้นสูง
เชื่อมโยงชิ้นส่วน
บางครั้งการระบุแหล่งที่มาของ Slice ในการติดตามไปยังการดำเนินการของผู้ใช้ในระดับที่สูงขึ้นหรือเหตุการณ์ของระบบก็มีประโยชน์ ตัวอย่างเช่น หากต้องการระบุแหล่งที่มาของ Slice ทั้งหมดที่ สอดคล้องกับงานที่ทำอยู่เบื้องหลังบางอย่างเป็นส่วนหนึ่งของการแจ้งเตือน คุณอาจทำ ดังนี้
fun main() {
val driver = createTraceDriver()
onEvent(driver, eventId = EVENT_ID)
}
fun onEvent(driver: TraceDriver, eventId: Long) {
driver.use {
driver.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 ที่มี Slice ที่สัมพันธ์กัน
เพิ่มข้อมูลสแต็กการเรียกใช้
เครื่องมือฝั่งโฮสต์ (ปลั๊กอินคอมไพเลอร์ โปรแกรมประมวลผลคำอธิบายประกอบ ฯลฯ) สามารถเลือกฝังข้อมูลสแต็กการเรียกใช้ลงในร่องรอยเพิ่มเติมได้ เพื่อให้สะดวกในการค้นหาไฟล์ คลาส หรือเมธอดที่รับผิดชอบในการสร้างส่วนร่องรอยในร่องรอย
fun main() {
val driver = createTraceDriver()
driver.use {
driver.tracer.trace(
category = CATEGORY_MAIN,
name = "callStackEntry",
metadataBlock = {
addCallStackEntry(
name = "main",
lineNumber = 14,
sourceFile = "Basic.kt"
)
}
) {
Thread.sleep(100L)
}
}
}
ซึ่งจะให้ผลลัพธ์ต่อไปนี้
รูปที่ 8 ภาพหน้าจอของ Perfetto Trace พร้อมข้อมูล Call Stack