Skip to content

Instantly share code, notes, and snippets.

@chenglaihuang
Last active August 3, 2025 14:32
Show Gist options
  • Save chenglaihuang/5880b4437b20ab4d3f03efba0bf8ee69 to your computer and use it in GitHub Desktop.
Save chenglaihuang/5880b4437b20ab4d3f03efba0bf8ee69 to your computer and use it in GitHub Desktop.
duolingo-method-trace
/*
* Copyright 2025 Duolingo, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** This plugin adds a method trace to all methods in classes that match the given pattern. */
internal object MethodTrace {
/** Configures the given [project] and Android [extension] with common testing properties. */
fun configure(project: Project, extension: AndroidComponentsExtension<*, *, *>) {
val enableMethodTrace =
project.property("com.duolingo.tools.method_trace").toString().toBoolean()
val methodTraceClassPattern =
project.property("com.duolingo.tools.method_trace.class_pattern").toString()
if (enableMethodTrace && methodTraceClassPattern.isNotEmpty()) {
extension.onVariants { variant ->
variant.instrumentation.transformClassesWith(
MethodTraceTransform::class.java,
InstrumentationScope.PROJECT
) {
it.methodTraceClassPattern.set(methodTraceClassPattern)
}
}
}
}
/** Parameters for the [MethodTraceTransform] class. */
interface MethodTraceTransformParams : InstrumentationParameters {
@get:Input val methodTraceClassPattern: Property<String>
}
/**
* This class adds begin/end method traces to all methods in classes that match the given pattern.
*
* This class is intended to be abstract, see [AsmClassVisitorFactory] for more details.
*/
abstract class MethodTraceTransform : AsmClassVisitorFactory<MethodTraceTransformParams> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
return object : ClassVisitor(Opcodes.ASM9, nextClassVisitor) {
val className = classContext.currentClassData.className
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val superMethodVisitor =
super.visitMethod(access, name, descriptor, signature, exceptions)
val methodVisitor =
object : AdviceAdapter(Opcodes.ASM9, superMethodVisitor, access, name, descriptor) {
override fun onMethodEnter() {
// Add Tracer.beginSection at the beginning of the method
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"com/duolingo/core/log/TracerHolder",
"tracer",
"Lcom/duolingo/core/log/Tracer;"
)
mv.visitLdcInsn("$className.$name".takeLast(MAX_SECTION_NAME_LENGTH))
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"com/duolingo/core/log/Tracer",
"beginSection",
"(Ljava/lang/String;)V",
false
)
}
override fun onMethodExit(opcode: Int) {
// Add Tracer.endSection at the end of the method
mv.visitFieldInsn(
Opcodes.GETSTATIC,
"com/duolingo/core/log/TracerHolder",
"tracer",
"Lcom/duolingo/core/log/Tracer;"
)
mv.visitLdcInsn("$className.$name".takeLast(MAX_SECTION_NAME_LENGTH))
mv.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
"com/duolingo/core/log/Tracer",
"endSection",
"(Ljava/lang/String;)V",
false
)
}
}
return methodVisitor
}
}
}
override fun isInstrumentable(classData: ClassData): Boolean {
val isInstrumentable =
classData.className.matches(parameters.get().methodTraceClassPattern.get().toRegex())
if (isInstrumentable) {
println("Instrumenting method for ${classData.className}")
}
return isInstrumentable
}
companion object {
private const val MAX_SECTION_NAME_LENGTH = 127
}
}
}
com.duolingo.tools.method_trace=false
com.duolingo.tools.method_trace.class_pattern=

Method trace

Property: com.duolingo.tools.method_trace

Setting this property to true will enable method tracing for all methods for certain classes. Note that this may cause a significant performance impact, so it should only be used for debugging purposes.

Property: com.duolingo.tools.method_trace.class_pattern

You may also need to set this property to a regex which matches the classes you want to trace. For example, to trace all methods for Repository classes, you would set the property to ^.*Repository.*$.

Copyright 2025 Duolingo, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@eneim
Copy link

eneim commented Jul 31, 2025

I'm coming here from https://blog.duolingo.com/android-app-performance/, and it is a great read.
Can you specify a license to use this gist so that we can learn from it and apply it in other use cases?

Thanks in advance.

@chenglaihuang
Copy link
Author

@eneim Added!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment