Skip to content

Instantly share code, notes, and snippets.

@bitristan
Forked from chenglaihuang/DuolingoMethodTrace.kt
Created September 11, 2025 02:28
Show Gist options
  • Select an option

  • Save bitristan/656c04a092a9eb0c4a4f0847447de73d to your computer and use it in GitHub Desktop.

Select an option

Save bitristan/656c04a092a9eb0c4a4f0847447de73d 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment