ไลบรารี androidx.tracing:tracing:2.0.2 เป็น 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.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ทรัพยากร Dependency การเริ่มต้นจะเกิดขึ้นโดยอัตโนมัติเมื่อแอปพลิเคชันเริ่มต้นโดยใช้ไลบรารี 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รูปแบบแพ็กเก็ตการติดตาม TraceDriver
Tracer มีแฮนเดิลสำหรับ 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ในเธรดเดียวกัน
อย่างชัดเจน
เพิ่มข้อมูลเมตาเพิ่มเติมในส่วนการติดตาม
บางครั้งการแนบข้อมูลเมตาเชิงบริบทเพิ่มเติมกับ 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
รูปที่ 3 ภาพหน้าจอของการติดตาม Perfetto ขั้นพื้นฐานพร้อมข้อมูลเมตาเพิ่มเติม
การส่งต่อบริบท
เมื่อใช้โครูทีน Kotlin หรือเฟรมเวิร์กอื่นๆ ที่คล้ายกันซึ่งช่วยจัดการภาระงานพร้อมกัน 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
หลังจากนั้น ทั้ง 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()
}
}
ซึ่งจะให้ผลลัพธ์ต่อไปนี้
รูปที่ 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 ทั้งหมดที่ สอดคล้องกับงานที่ทำอยู่เบื้องหลังบางอย่างเป็นส่วนหนึ่งของการแจ้งเตือน คุณอาจทำ ดังนี้
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 ที่มี 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)
}
}
}
ซึ่งจะให้ผลลัพธ์ต่อไปนี้
รูปที่ 8 การจับภาพหน้าจอของ Perfetto Trace พร้อมข้อมูลสแต็กการเรียกใช้