新版 androidx.tracing:tracing:2.0.0-alpha01 程式庫是低負載的 Kotlin API,可擷取程序內追蹤事件。這些事件可以擷取時間片段及其脈絡。此外,這個程式庫也支援 Kotlin 協同程式的內容傳播。
這個程式庫採用 Android 開發人員熟悉的 Perfetto 追蹤封包格式。此外,與 1.0.0-* API 不同,Tracing 2.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")
// ...
}
}
}
}
如果您是以 Android 程式庫或應用程式為目標,請宣告 androidx.tracing:tracing-wire-android:2.0.0-alpha01 的依附元件。如果您以 JVM 為目標,可以使用 androidx.tracing:tracing-wire-desktop:2.0.0-alpha01 依附元件。
基本用法
TraceSink 會定義追蹤封包的序列化方式。Tracing 2.0.0 隨附 Sink 的實作項目,可使用 Perfetto 追蹤記錄封包格式。TraceDriver 提供 Tracer 的控制代碼,可用於完成追蹤。
如果您選擇不在某些應用程式變體中追蹤,也可以使用 TraceDriver 停用應用程式中的所有追蹤點。TraceDriver 的未來 API 也會讓開發人員控管要擷取 (或在類別雜訊過多時停用) 哪些追蹤記錄類別。
如要開始使用,請建立 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位於同一執行緒。
在追蹤區段中新增其他中繼資料
有時,將額外的脈絡中繼資料附加至追蹤記錄切片,可取得更多詳細資料。這類中繼資料的例子包括使用者所在的 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 協同程式 (或其他類似架構來協助處理並行工作負載) 時,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。之後兩個執行緒都會處於閒置狀態 (因為協同程式已暫停 - 這是使用 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 中啟動,隨後切換至 Java Executor,但我們仍可使用內容傳播。
Tracer.tokenForManualPropagation() API。如果非同步工作流程很複雜 (例如在 Kotlin Flow 上串連運算子),這項功能就特別實用。與系統追蹤記錄合併
新的 androidx.tracing 不會擷取 CPU 排程、記憶體用量,以及應用程式與作業系統的一般互動等資訊。這是因為該程式庫提供一種方式,可執行低負荷的程序內追蹤。
不過,合併系統追蹤記錄和程序內追蹤記錄非常簡單,如有需要,可以將兩者視為單一追蹤記錄。這是因為 Perfetto UI 支援在統一時間軸上,從裝置視覺化多個追蹤記錄檔。
如要執行這項操作,請使用 Perfetto UI 啟動系統追蹤工作階段,並按照這裡的指示操作。
您也可以在系統追蹤功能開啟時,使用 Tracing 2.0 API 記錄程序內追蹤記錄事件。取得這兩個追蹤記錄檔案後,即可在 Perfetto 中使用 Open Multiple Trace Files 選項。
圖 6. 在 Perfetto UI 中開啟多個追蹤記錄檔案。
processes 和 threads) 使用的 ID 相同。此外,用於程序內追蹤和系統追蹤的 clock 也會同步。因此這些事件會自動對齊。進階工作流程
關聯切片
有時,將追蹤記錄中的切片歸因於較高層級的使用者動作或系統事件,會很有幫助。舉例來說,如要將對應某些背景工作的切片歸因於通知的一部分,可以執行類似下列的操作:
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 追蹤記錄,其中包含相關切片。
Arguments 區段,以及 Perfetto 會為共用 correlationId 的切片選擇一致的色彩配置。新增呼叫堆疊資訊
主機端工具 (編譯器外掛程式、註解處理器等) 也可以選擇將呼叫堆疊資訊嵌入追蹤記錄,方便您在追蹤記錄中找出負責產生追蹤記錄區段的檔案、類別或方法。
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 追蹤記錄的螢幕截圖,顯示呼叫堆疊資訊。
Arguments 區段,其中包含呼叫堆疊資訊。