kotlin 扩展类的功能
Using Firebase Performance Monitoring to track the performance of a plain-ol’ function (Java or Kotlin) is drop-dead simple using the @AddTrace
annotation:
使用@AddTrace
批注,使用Firebase Performance Monitoring跟踪简单功能的性能(Java或Kotlin)非常简单:
@AddTrace("some-trace-name")
fun myBoringFunction() {
println("nothing to see here!")
}
Wait a few moments, and you’ll see some-trace-name
in the Firebase Console!
稍等片刻,您将在Firebase控制台中看到some-trace-name
!
But what if you want to track the performance of a suspend
function? Unfortunately, this is not as straightforward.
但是,如果您想跟踪suspend
功能的性能怎么办? 不幸的是,这并不是那么简单。
尝试#1:悬疑 (Attempt #1: Suspending Disbelief)
Let’s say you have some uncomplicated suspending function, like so:
假设您有一些简单的暂停功能,如下所示:
suspend fun myAsyncFunction() : Int {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return result
}
A naive attempt to track this function’s performance would, as you might expect, to use @AddTrace
:
如您所料,天真的尝试跟踪此函数的性能将使用@AddTrace
:
@AddTrace("some-other-trace-name")
suspend fun myAsyncFunction() : Int { ... }
Just eyeballing it, it should work, right? Even if you compile, the firebase-perf
Gradle plugin won’t complain, and the Firebase Performance Monitoring library won’t crash your app. So maybe everything will Just Work™️? 🤞🏽
只是盯着它,它应该起作用,对吗? 即使您进行编译, firebase-perf
Gradle插件也不会抱怨,并且Firebase Performance Monitoring库不会使您的应用程序崩溃。 所以也许一切都会正常工作吗? 🤞🏽
Plot Twist: it won’t work.
剧情扭曲:它将不起作用。
To understand why it doesn’t work, you’ll need to understand two things:
要了解为什么它不起作用,您需要了解两件事:
- How the Kotlin compiler produces a suspending state machine Kotlin编译器如何产生挂起状态机
How the
firebase-perf
Gradle plugin instruments the@AddTrace
annotation into an actual Trace invocation@AddTrace
firebase-perf
Gradle插件如何将@AddTrace
批注检测到实际的Trace调用中
For #1, Manuel Vivo has a great article going into the nuts and bolts of what the suspend
keyword actually means, and if you’re not familiar with the process, I recommend checking it out. For our purposes, we’ll simplify, and say that the Kotlin compiler will rewrite your suspend
method signature, appending a Continuation<T>
parameter, which allows it to build the state machine.
对于#1, Manuel Vivo撰写了一篇很棒的文章 ,深入探讨了suspend
关键字的实际含义 ,如果您不熟悉此过程,建议您进行检查。 就我们的目的而言,我们将进行简化,并说Kotlin编译器将重写您的suspend
挂方法签名,并附加一个Continuation<T>
参数,该参数允许它构建状态机。
For #2, I don’t work at Google, so I’m not entirely sure how the @AddTrace
instrumentation works under the hood. But I’ve got some guesses!
对于#2,我不在Google工作,所以我不确定@AddTrace
工作原理。 但是我有一些猜测!
Let’s take our previous example and “compile” it. We’ll make 3 changes:
让我们以前面的示例进行“编译”。 我们将进行3个更改:
- Simplify the “guts” of the coroutine state machine 简化协程状态机的“胆量”
- Add some “fake” Perf traces 添加一些“假” Perf痕迹
- “Decompile” everything into Kotlin source code, because nobody wants to read Kotlin JVM bytecode! 将所有内容“反编译”为Kotlin源代码,因为没人愿意读取Kotlin JVM字节码!
fun myAsyncFun(cont : Continuation) {
val perf = FirebasePerformance.getInstance()
val trace = perf.newTrace("some-other-trace-name")
trace.start() val result = 1 + 1
//begin: coroutine state machine
when(cont.label){
0 -> {
//will call myAsyncFun with cont.label=1
DelayKt.delay(100, cont)
}
1 -> {
println("something to see here: $result")
//hands control+result back to caller
cont.resume(result, ...)
}
}
//end: coroutine state machine
trace.end()
}
The coroutine’s state machine is muddying the waters — it’s no wonder Firebase’s automated instrumentation can’t figure out where to put the trace.end()
! What’s a performance-minded developer to do?
协程的状态机使水变得浑浊–难怪Firebase的自动化仪器无法弄清楚trace.end()
放置trace.end()
! 注重性能的开发人员该做什么?
尝试2:如果尝试,最终您会成功 (Attempt #2: If you Try, Finally you’ll succeed)
The heart of the issue is that the Firebase performance instrumentation can’t query suspending Kotlin bytecode for the method entry and exit. But, as developers, we know when a suspending function will enter and exit; in fact, there’s even a Java/Kotlin paradigm to codifies this: try/finally
问题的核心是Firebase性能工具无法查询挂起的Kotlin字节码以获取方法的进入和退出。 但是,作为开发人员, 我们知道挂起函数何时进入和退出。 实际上,甚至有一个Java / Kotlin范例可以对此进行编纂: try/finally
try/finally
is exactly what we need: no matter what happens inside a method body, finally
will always be executed last (or, finally!) — this is a guarantee from the JVM, and you can see this reflected in any compiled Java/Kotlin bytecode. Read more here
try/finally
正是我们所需要的:无论方法主体内部发生什么, finally
都将总是finally
执行(或finally!)—这是JVM的保证,您可以在任何已编译的Java /中看到这一点。 Kotlin字节码。 在这里阅读更多
Armed with this knowledge, and a little bit of Kotlin magic✨, we can write a little wrapper that can be used anywhere:
有了这些知识,再加上一点Kotlin的魔力,我们可以编写一个可以在任何地方使用的包装器:
inline fun <E> trace(name : String, block: (Trace) -> E): E {
val trace = startTrace(name) //creates & starts a new Trace
return try {
block(trace)
} finally {
trace.stop()
}
}
Breaking it down:
分解:
the
inline
keyword means we won’t interfere with any coroutine state machine; the entire suspending invocation will occur in thetry {}
blockinline
关键字表示我们不会干扰任何协程状态机; 整个挂起的调用将在try {}
块中进行startTrace
just invokesFirebasePerformance
, names the trace, and starts it.startTrace
只是调用FirebasePerformance
,命名跟踪,然后启动它。we accept a
block: (Trace) -> E
, so that the instrumented code can append information to the Trace, if needed我们接受一个
block: (Trace) -> E
,以便如果需要的话,检测到的代码可以将信息追加到Trace中in the
finally
block, we stop the trace, and return value produced byblock
在
finally
块中,我们停止跟踪,并返回由block
产生的值any exceptions are propagated to the calling code, since there’s no
catch
defined由于没有定义
catch
,任何异常都会传播到调用代码
Sweet; simple; and, most importantly, it works!
甜; 简单; 而且,最重要的是,它有效!
Let’s rewrite our first suspending function using our new toy:
让我们使用新玩具重新编写第一个暂停函数:
suspend fun myAsyncFunction() : Int {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return result
}
becomes:
变成:
suspend fun myAsyncFunction() : Int {
return trace("some-other-trace") {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return@trace result
}
}
This is our solution! If you want to achieve this same result without nesting your method into a lambda, you can push it into a private function:
这是我们的解决方案! 如果要在不将方法嵌套到lambda的情况下实现相同的结果,则可以将其推送到私有函数中:
suspend fun myAsyncFunction() : Int {
return trace("some-other-trace") { _myAsyncFunction() }
}private suspend fun _myAsyncFunction() : Int {
val result = 1 + 1
delay(100L)
println("something to see here: $result")
return result
}
The Firebase Performance Monitoring Console will now show the total time myAsyncFunction
runs, including the time that any actual suspension takes 🎉
Firebase Performance Monitoring Console现在将显示myAsyncFunction
运行的总时间,包括任何实际暂停所需的时间。
Happy coding!
编码愉快!
下一步 (Next Steps)
I’ve filed a feature request with Firebase to augment @AddTrace
to support suspending functions. Let’s see what happens, maybe this blog post will be obsolete in 6 months 👀
我已经向Firebase提出了功能请求,以增强@AddTrace
以支持挂起功能。 让我们看看会发生什么,也许这篇博客文章将在6个月内过时👀
Thanks to Akshay Chordiya and Doug Stevenson for reviewing the early drafts! 🙏🏾
感谢 Akshay Chordiya 和 Doug Stevenson 审阅了初稿! 🙏🏾
Note: this story was originally published on my personal website: https://jvmname.dev/posts/2020/03/tracking-performance-in-kotlin-suspending-functions/
注意:该故事最初发布在我的个人网站上: https : //jvmname.dev/posts/2020/03/tracking-performance-in-kotlin-suspending-functions/
kotlin 扩展类的功能