androidx.tracing:tracing:2.0.0-beta01 kitaplığı, işlem içi izleme etkinliklerini yakalamanıza olanak tanıyan düşük ek yüke sahip bir Kotlin API'sidir. Bu etkinlikler, zaman dilimlerini ve bunların bağlamını yakalayabilir. Kitaplık, Kotlin eşzamanlı rutinleri için bağlam yayılımını da destekler.
Kitaplık, Android geliştiricilerinin aşina olduğu Perfetto izleme paketi biçimini kullanır. Ayrıca, Tracing 2.0 (1.0.0-* API'lerinden farklı olarak) takılabilir izleme arka uçlarını ve havuzları destekler. Bu nedenle, diğer izleme kitaplıkları çıkış izleme biçimini ve bağlam yayılımının kendi uygulamalarında nasıl çalıştığını özelleştirebilir.
Bağımlılıklar
İzlemeyi başlatmak için build.gradle.kts dosyanızdaki bağımlılıkları tanımlamanız gerekir.
Kotlin Multiplatform projeleri
Yalnızca izleme etkinlikleri yayınlaması gereken kitaplıklar, hafif androidx.tracing:tracing API'sini kullanmalıdır. İzleme arka ucunu yapılandıran uygulamalar da androidx.tracing:tracing-wire'ya bağlı olmalıdır.
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")
}
}
}
}
Yalnızca Android projeleri
Yalnızca Android'i hedefliyorsanız uygulamanızın veya kitaplığınızın build.gradle.kts dosyasına aşağıdakileri ekleyin:
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")
}
Başlatma ve keşif
İzleme etkinliklerini kaydetmeden önce izleme altyapısını başlatmanız gerekir. Bu işlem için AbstractTraceDriver oluşturulması ve Tracer'nin dünya genelinde tescil edilmesi gerekir.
Android
Android'de androidx.tracing:tracing-wire bağımlılığını eklerseniz başlatma işlemi, androidx.startup kitaplığı kullanılarak uygulama başlatıldığında otomatik olarak gerçekleşir.
Varsayılan olarak bu otomatik başlatma işlemi şunları yapar:
Perfetto izleme dosyalarını
Context.noBackupFilesDir/perfetto_traces/konumuna yazan birTraceSinkileTraceDriveroluşturur.Elde edilen
Traceröğesini global olarak kaydeder.
TraceDriver örneğini özelleştirme
Yapılandırmayı özelleştirmeniz gerekiyorsa (ör. izleme dosyalarının kaydedildiği yeri değiştirmek veya özel bir TraceSink kullanmak için) kendi AbstractTraceDriver örneğinizi sağlayabilirsiniz.
Yapılandırmayı özelleştirmek için Application sınıfınızın AbstractTraceDriver.Factory uygulamasını sağlayın:
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)
}
}
Otomatik başlatıcı, Application alt sınıfınızın Factory uyguladığını algılar ve özel sürücü fabrikanızı kullanır.
JVM
JVM'de otomatik başlatma mekanizması yoktur. Uygulama, başlatma sırasında TraceDriver öğesini başlatmaktan ve Tracer öğesini global olarak kaydetmekten sorumludur. Bu işlem genellikle main işlevinizde yapılır.
İzleyiciyi kaydetmek için Tracer.setGlobalTracer() numarasını arayın.
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()
})
}
Temel kullanım
TraceSink, izleme paketlerinin nasıl serileştirileceğini tanımlar. Tracing 2.0.0, Perfetto izleme paketi biçimini kullanan bir Sink uygulamasıyla birlikte gelir. TraceDriver, Tracer için bir tutma yeri sağlar ve izlemeyi tamamlamak için kullanılabilir.
Tracer başlatıldıktan sonra (Android'de otomatik olarak veya JVM'de manuel olarak) izleme etkinliklerini yayınlamak için genel Tracer.global örneğini kullanın.
Bazı uygulama varyantlarında hiç izleme yapmamayı tercih ederseniz uygulamadaki tüm izleme noktalarını devre dışı bırakmak için TraceDriver simgesini de kullanabilirsiniz. TraceDriver örneği oluştururken isCategoryEnabled için bir uygulama sağlayarak belirli bir category için izleme noktalarını isteğe bağlı olarak etkinleştirebilirsiniz.
val driver = TraceDriver(
sink = sink,
isCategoryEnabled = { category ->
// Only enable trace points in the "com.example" package
category.startsWith("com.example")
}
)
Aşağıda, manuel kurulum da dahil olmak üzere JVM'de Tracer.global kullanarak izleme etkinliği yayınlamanın temel bir örneği verilmiştir:
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)
}
}
}
Bu işlem aşağıdaki izi oluşturur.
Şekil 1. Temel bir Perfetto izinin ekran görüntüsü.
Doğru işlem ve iş parçacığı izlerinin doldurulduğunu ve 100 ms boyunca çalışan tek bir izleme bölümü basic oluşturduğunu görebilirsiniz.
İzleme bölümleri (veya dilimleri), çakışan etkinlikleri göstermek için aynı izde iç içe yerleştirilebilir. Bir örnek verelim.
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") {
// ...
}
}
Bu işlem aşağıdaki izi oluşturur.
Şekil 2. İç içe geçmiş bölümler içeren temel bir Perfetto izinin ekran görüntüsü.
Ana iş parçacığı izinde çakışan etkinlikler olduğunu görebilirsiniz. Aynı ileti dizisindeki processImage aramaların loadImage ve sharpen çok net bir şekilde gösterildiğini görebilirsiniz.
İzleme bölümlerine ek meta veri ekleme
Bazen daha fazla ayrıntı almak için bir iz dilimine ek bağlamsal meta veriler eklemek yararlı olabilir. Bu tür meta verilere örnek olarak kullanıcının bulunduğu nav destination veya bir işlevin ne kadar süreceğini belirleyebilecek input arguments verileri verilebilir.
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)
}
}
}
Bu işlem aşağıdaki sonucu verir. Arguments bölümünde, slice oluşturulurken eklenen anahtar/değer çiftleri bulunur.
Şekil 3. Ek meta veriler içeren temel bir Perfetto izinin ekran görüntüsü.
Bağlam yayma
Kotlin coroutines veya eşzamanlı iş yükleriyle ilgili yardımcı olan diğer benzer çerçeveler kullanılırken Tracing 2.0, bağlam yayma kavramını destekler. Bu durumu en iyi bir örnekle açıklayabiliriz.
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")
}
}
Bu işlem aşağıdaki sonucu verir.
Şekil 4. Bağlam yayılımı içeren temel bir Perfetto izinin ekran görüntüsü.
Bağlam yayma, yürütme akışını görselleştirmeyi çok daha kolay hale getirir. Hangi görevlerin ilişkili (diğerlerine bağlı) olduğunu ve Threads'nın ne zaman askıya alındığını ve devam ettirildiğini tam olarak görebilirsiniz.
Örneğin, main diliminin taskOne ve taskTwo oluşturduğunu görebilirsiniz.
Ardından, delay kullanıldığı için coroutine'ler askıya alındığından her iki iş parçacığı da etkin değildi.
Manuel yayma
Bazen, Kotlin coroutines kullanarak eşzamanlı iş yüklerini Java Executor örnekleriyle karıştırırken bağlamı birinden diğerine yaymak faydalı olabilir. Örnek:
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()
}
}
Bu işlem aşağıdaki sonucu verir.
Şekil 5. Manuel bağlam yayılımı içeren temel bir Perfetto izinin ekran görüntüsü.
Yürütmenin CoroutineContext içinde başladığını ve daha sonra Java Executor'ya geçtiğini görebilirsiniz ancak bağlam yayılımını kullanmaya devam edebildik.
Sistem izleriyle birleştirme
androidx.tracing kitaplığı, CPU planlama, bellek kullanımı ve uygulamanın işletim sistemiyle genel etkileşimi gibi bilgileri yakalamaz. Bunun nedeni, kitaplığın düşük ek yükle işlem içi izleme yapma olanağı sunmasıdır.
Ancak sistem izlerini işlem içi izlerle birleştirmek ve gerekirse tek bir iz olarak görselleştirmek son derece kolaydır. Bunun nedeni, Perfetto UI
bir cihazdaki birden fazla izleme dosyasının birleşik bir zaman çizelgesinde görselleştirilmesini desteklemesidir.
Bunu yapmak için Perfetto UI kullanarak sistem izleme oturumu başlatabilir ve buradaki talimatları uygulayabilirsiniz.
Sistem izleme açıkken Tracing 2.0 API'sini kullanarak devam eden izleme etkinliklerini de kaydedebilirsiniz. Her iki izleme dosyasına da sahip olduğunuzda Perfetto'da Open Multiple Trace Files seçeneğini kullanabilirsiniz.
Şekil 6. Perfetto kullanıcı arayüzünde birden fazla izleme dosyası açma
Gelişmiş iş akışları
Bu bölümde, süreç içi izleme kitaplığıyla uygulayabileceğiniz gelişmiş iş akışları açıklanmaktadır.
Dilimleri ilişkilendirme
Bazen, bir izdeki dilimleri daha üst düzey bir kullanıcı işlemine veya bir sistem etkinliğine atfetmek yararlı olur. Örneğin, arka planda çalışan bazı görevlere karşılık gelen tüm dilimleri bir bildirimin parçası olarak ilişkilendirmek için şuna benzer bir işlem yapabilirsiniz:
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)
}
}
Bu işlem aşağıdaki sonucu verir.
Şekil 7. Korelasyonlu dilimler içeren bir Perfetto izinin ekran görüntüsü.
Çağrı yığını bilgileri ekleme
Derleyici eklentileri ve açıklama işlemcileri gibi ana makine tarafındaki araçlar, izleme bölümü oluşturmaktan sorumlu dosya, sınıf veya yöntemin izlemede kolayca bulunabilmesi için çağrı yığını bilgilerini izlemeye yerleştirmeyi de seçebilir.
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)
}
}
}
Bu işlem aşağıdaki sonucu verir.
Şekil 8. Arama yığını bilgilerini içeren bir Perfetto izinin ekran görüntüsü.