androidx.tracing:tracing:2.0.0-beta01 程式庫是低負載的 Kotlin API,可讓您擷取程序內追蹤事件。這些事件可以擷取時間片段及其脈絡。此外,這個程式庫也支援 Kotlin 協同程式的內容傳播。
這個程式庫採用 Android 開發人員熟悉的 Perfetto 追蹤封包格式。此外,與 1.0.0-* API 不同,Tracing 2.0 支援可外掛的追蹤後端和接收器的概念,因此其他追蹤程式庫可以自訂輸出追蹤格式,以及實作中的內容傳播方式。
依附元件
如要開始追蹤,您需要在 build.gradle.kts 中定義依附元件。
Kotlin Multiplatform 專案
只需要發出追蹤事件的程式庫應依附於輕量型 androidx.tracing:tracingAPI。設定追蹤後端的應用程式也應依附於 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 程式庫自動初始化。
根據預設,這項自動初始化作業會執行下列動作:
建立含有
TraceSink的TraceDriver,將 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,並使用您的自訂驅動程式 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 後 (Android 上會自動初始化,JVM 上則須手動初始化),請使用全域 Tracer.global 執行個體發出追蹤事件。
如果您選擇不在某些應用程式變體中追蹤,也可以使用 TraceDriver 停用應用程式中的所有追蹤點。您可以選擇在建立 TraceDriver 例項時提供 isCategoryEnabled 的實作項目,為特定 category 啟用追蹤點。
val driver = TraceDriver(
sink = sink,
isCategoryEnabled = { category ->
// Only enable trace points in the "com.example" package
category.startsWith("com.example")
}
)
以下是在 JVM 上使用 Tracer.global 發出追蹤事件的基本範例,包括手動設定:
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 協同程式或其他類似架構來處理並行工作負載時,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。
之後,由於使用了 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 中啟動,隨後切換至 Java Executor,但我們仍可使用內容傳播。
與系統追蹤記錄合併
androidx.tracing 程式庫不會擷取 CPU 排程、記憶體用量,以及應用程式與作業系統的整體互動等資訊。這是因為程式庫提供低負荷的程序內追蹤方式。
不過,合併系統追蹤記錄與程序內追蹤記錄並以單一追蹤記錄的形式呈現,是極為簡單的作業。這是因為 Perfetto UI 支援在統一時間軸上,從裝置視覺化多個追蹤記錄檔。
如要執行這項操作,請使用 Perfetto UI 啟動系統追蹤工作階段,並按照這裡的指示操作。
您也可以在系統追蹤功能開啟時,使用 Tracing 2.0 API 記錄程序內追蹤記錄事件。取得這兩個追蹤記錄檔案後,即可在 Perfetto 中使用 Open Multiple Trace Files 選項。
圖 6. 在 Perfetto UI 中開啟多個追蹤記錄檔案。
進階工作流程
本節說明可透過程序內追蹤程式庫實作的進階工作流程。
關聯切片
有時,將追蹤記錄中的切片歸因於更高層級的使用者動作或系統事件,會很有幫助。舉例來說,如要將對應某些背景工作的切片歸因於通知的一部分,可以執行類似下列的操作:
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 追蹤記錄的螢幕截圖,顯示呼叫堆疊資訊。