Library androidx.tracing:tracing:2.0.2 adalah API Kotlin dengan overhead rendah
yang memungkinkan Anda merekam peristiwa pelacakan dalam proses. Peristiwa ini dapat merekam irisan waktu dan konteksnya. Library ini juga mendukung propagasi konteks untuk
coroutine Kotlin.
Library ini menggunakan format paket rekaman aktivitas Perfetto yang sama dengan yang sudah dikenal oleh developer Android. Selain itu, Perekaman Aktivitas 2.0 (tidak seperti API 1.0.0-*)
mendukung konsep backend perekaman aktivitas yang dapat di-plug dan sink, sehingga library perekaman aktivitas lainnya dapat menyesuaikan format perekaman aktivitas output, dan cara kerja propagasi konteks dalam implementasinya.
Dependensi
Untuk memulai pelacakan, Anda perlu menentukan dependensi di
build.gradle.kts.
Project Multiplatform Kotlin
Library yang hanya perlu memancarkan peristiwa rekaman aktivitas harus bergantung pada API androidx.tracing:tracing ringan. Aplikasi yang mengonfigurasi backend pelacakan juga harus bergantung pada 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")
}
}
}
}
Project khusus Android
Jika Anda hanya menargetkan Android, tambahkan kode berikut ke file build.gradle.kts aplikasi atau library Anda:
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")
}
Inisialisasi dan penemuan
Sebelum dapat merekam peristiwa rekaman aktivitas, Anda harus menginisialisasi infrastruktur rekaman aktivitas. Hal ini melibatkan pembuatan AbstractTraceDriver dan pendaftaran
Tracer-nya secara global.
Android
Di Android, jika Anda menyertakan dependensi androidx.tracing:tracing-wire,
inisialisasi terjadi secara otomatis saat aplikasi dimulai menggunakan
library androidx.startup.
Secara default, inisialisasi otomatis ini melakukan hal berikut:
Membuat
TraceDriverdenganTraceSinkyang menulis file rekaman aktivitas Perfetto keContext.noBackupFilesDir/perfetto_traces/.Mendaftarkan
Traceryang dihasilkan secara global.
Menyesuaikan instance TraceDriver
Jika perlu menyesuaikan konfigurasi, misalnya untuk mengubah lokasi penyimpanan file rekaman aktivitas atau menggunakan TraceSink kustom, Anda dapat menyediakan instance AbstractTraceDriver Anda sendiri.
Untuk menyesuaikan konfigurasi, buat class Application Anda mengimplementasikan
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)
}
}
Penginisialisasi otomatis mendeteksi bahwa subkelas Application Anda mengimplementasikan
Factory dan menggunakan factory driver kustom Anda.
JVM
Di JVM, tidak ada mekanisme bootstrapping otomatis. Aplikasi bertanggung jawab untuk menginisialisasi TraceDriver dan mendaftarkan Tracer secara global selama startup, paling umum dalam fungsi main Anda.
Untuk mendaftarkan tracer, panggil 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()
})
}
Penggunaan dasar
TraceSink menentukan cara serialisasi paket rekaman aktivitas. Tracing 2.0.0 hadir dengan
implementasi Sink yang menggunakan format paket rekaman aktivitas Perfetto. TraceDriver menyediakan tuas ke Tracer dan dapat digunakan untuk menyelesaikan rekaman aktivitas.
Setelah Tracer diinisialisasi, baik secara otomatis di Android maupun secara manual di
JVM, gunakan instance Tracer.global global untuk memancarkan peristiwa rekaman aktivitas.
Anda juga dapat menggunakan TraceDriver untuk menonaktifkan semua titik rekaman aktivitas dalam aplikasi, jika Anda memilih untuk tidak merekam aktivitas sama sekali dalam beberapa varian aplikasi. Anda
dapat secara opsional mengaktifkan titik rekaman aktivitas untuk category tertentu dengan memberikan
implementasi untuk isCategoryEnabled saat membuat instance
TraceDriver.
val driver = TraceDriver(
sink = sink,
isCategoryEnabled = { category ->
// Only enable trace points in the "com.example" package
category.startsWith("com.example")
}
)
Berikut contoh dasar memancarkan peristiwa rekaman aktivitas menggunakan Tracer.global di
JVM, termasuk penyiapan manual:
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)
}
}
}
Tindakan ini akan menghasilkan rekaman aktivitas berikut.
Gambar 1. Screenshot rekaman aktivitas Perfetto dasar.
Anda dapat melihat bahwa jalur proses dan thread yang benar telah diisi dan menghasilkan satu bagian rekaman aktivitas basic, yang berjalan selama 100 md.
Bagian (atau irisan) rekaman aktivitas dapat disusun bertingkat di jalur yang sama untuk merepresentasikan peristiwa yang tumpang-tindih. Ini contohnya.
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") {
// ...
}
}
Tindakan ini akan menghasilkan rekaman aktivitas berikut.
Gambar 2. Screenshot rekaman aktivitas Perfetto dasar dengan bagian bertingkat.
Anda dapat melihat bahwa ada peristiwa yang tumpang-tindih di jalur thread utama. Sangat jelas bahwa processImage memanggil loadImage dan sharpen di thread yang sama.
Menambahkan metadata tambahan di bagian rekaman aktivitas
Terkadang, akan berguna untuk melampirkan metadata kontekstual tambahan ke slice rekaman aktivitas, untuk mendapatkan detail selengkapnya. Beberapa contoh metadata tersebut dapat mencakup
nav destination yang sedang digunakan pengguna, atau input arguments yang mungkin akan
menentukan durasi fungsi.
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)
}
}
}
Hal ini akan menghasilkan hasil berikut. Perhatikan bahwa bagian Arguments berisi pasangan nilai kunci yang ditambahkan saat membuat slice.
Gambar 3. Screenshot rekaman aktivitas Perfetto dasar dengan metadata tambahan.
Penerapan konteks
Saat menggunakan coroutine Kotlin, atau framework serupa lainnya yang membantu workload serentak, Tracing 2.0 mendukung konsep propagasi konteks. Hal ini paling baik dijelaskan dengan contoh.
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")
}
}
Hal ini akan menghasilkan hasil berikut.
Gambar 4. Screenshot rekaman aktivitas Perfetto dasar dengan propagasi konteks.
Propagasi Konteks mempermudah visualisasi alur
eksekusi. Anda dapat melihat secara persis tugas mana yang terkait (terhubung dengan tugas lain),
dan secara persis kapan Threads ditangguhkan dan dilanjutkan.
Misalnya, Anda dapat melihat bahwa irisan main memunculkan taskOne dan taskTwo.
Setelah itu, kedua thread tidak aktif karena coroutine ditangguhkan karena
penggunaan delay.
Penyebaran manual
Terkadang, saat Anda mencampur workload serentak menggunakan coroutine Kotlin dengan
instance Executor Java, akan berguna untuk menyebarkan konteks dari
satu ke yang lain. Berikut ini contohnya:
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()
}
}
Hal ini akan menghasilkan hasil berikut.
Gambar 5. Screenshot rekaman aktivitas Perfetto dasar dengan propagasi konteks manual.
Anda dapat melihat bahwa eksekusi dimulai di CoroutineContext, dan selanjutnya beralih ke Executor Java, tetapi kami masih dapat menggunakan propagasi konteks.
Gabungkan dengan rekaman aktivitas sistem
Library androidx.tracing tidak mengambil informasi seperti penjadwalan CPU, penggunaan memori, dan interaksi aplikasi dengan sistem operasi secara umum. Hal ini karena library menyediakan cara untuk melakukan
pelacakan dalam proses dengan overhead rendah.
Namun, menggabungkan rekaman aktivitas sistem dengan rekaman aktivitas dalam proses dan memvisualisasikannya sebagai satu rekaman aktivitas sangatlah mudah jika diperlukan. Hal ini karena Perfetto UI
mendukung visualisasi beberapa file rekaman aktivitas dari perangkat pada linimasa terpadu.
Untuk melakukannya, Anda dapat memulai sesi perekaman aktivitas sistem menggunakan Perfetto UI dengan
mengikuti petunjuk di sini.
Anda juga dapat merekam peristiwa rekaman aktivitas dalam proses menggunakan Tracing 2.0 API, saat perekaman aktivitas sistem diaktifkan. Setelah memiliki kedua file rekaman aktivitas, Anda dapat menggunakan opsi
Open Multiple Trace Files di Perfetto.
Gambar 6. Membuka beberapa file rekaman aktivitas di UI Perfetto.
Alur kerja lanjutan
Bagian ini menjelaskan alur kerja lanjutan yang dapat Anda terapkan dengan library pelacakan dalam proses.
Mengorelasikan irisan
Terkadang, ada baiknya mengatribusikan irisan dalam rekaman aktivitas ke tindakan pengguna tingkat yang lebih tinggi atau peristiwa sistem. Misalnya, untuk mengatribusikan semua slice yang sesuai dengan beberapa tugas latar belakang sebagai bagian dari notifikasi, Anda dapat melakukan hal berikut:
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)
}
}
Hal ini akan menghasilkan hasil berikut.
Gambar 7. Screenshot rekaman aktivitas Perfetto dengan irisan yang dikorelasikan.
Menambahkan informasi stack panggilan
Alat sisi host, seperti plugin compiler dan pemroses anotasi, juga dapat memilih untuk menyematkan informasi call stack ke dalam rekaman aktivitas, sehingga memudahkan untuk menemukan file, class, atau metode yang bertanggung jawab untuk menghasilkan bagian rekaman aktivitas dalam rekaman aktivitas.
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)
}
}
}
Hal ini akan menghasilkan hasil berikut.
Gambar 8. Screenshot rekaman aktivitas Perfetto dengan informasi stack panggilan.