article banner

Exercise: KSP execution measurement wrapper

Your task is to implement a custom Kotlin Symbol Processor, that will generate a wrapper for measuring the execution time of public methods annotated with Measured annotation.

This processor should generate a wrapper class for each class that contains at least one method annotated with Measured annotation. This wrapper class should be under the same package, and be named just like the wrapped class but with Measured prefix. It should have two constructors: one that takes an instance of the original class as a parameter, and one that creates an instance of the original class based on the default constructor. It should have the same methods as the original class, but each method with Measured annotation should measure the time of its execution and print it in the format "{method name} from {wrapped class name} took {execution time} ms".

For example, for the following class:

package academy.kt class TokenService { @Measured fun getToken(): String { Thread.sleep(1000) return "ABCD" } }

The following wrapper class should be generated:

package academy.kt class MeasuredTokenService( val wrapper: TokenService, ) { constructor() : this(TokenService()) fun getToken(): String { val before = System.currentTimeMillis() val value = wrapper.getToken() val after = System.currentTimeMillis() println("getToken from TokenService took ${after - before} ms") return value } }

For the following class:

package academy.kt class UserService( private val tokenService: TokenService ) { @Measured fun findUser(id: Int): User { tokenService.getToken() Thread.sleep(1000) return User("$id") } @Measured fun findUsers(): User { Thread.sleep(1000) return User("") } }

The following wrapper class should be generated:

package academy.kt class MeasuredUserService( val wrapper: UserService, ) { constructor(tokenService: TokenService) : this( UserService(tokenService) ) fun findUser(id: Int): User { val before = System.currentTimeMillis() val value = wrapper.findUser(id) val after = System.currentTimeMillis() println("findUser from UserService took ${after - before} ms") return value } fun findUsers(): User { val before = System.currentTimeMillis() val value = wrapper.findUsers() val after = System.currentTimeMillis() println("findUsers from UserService took ${after - before} ms") return value } }

Example usage of generated classes:

fun main() { val tokenService = TokenService() val userService = UserService(tokenService) val measuredService = MeasuredUserService(tokenService) val user = measuredService.findUser(12) // findUser from UserService took 200Xms val user2 = measuredService.findUsers() // findUser from UserService took 100Xms val measuredTokenService = MeasuredTokenService(tokenService) val token = measuredTokenService.getToken() // getToken from TokenService took 100Xms }

For generating Kotlin code use Kotlin Poet. Assume that wrapped class has no type parameters.

Here is a project, that provides configuration, tests and sample use, and only requires processor implementation:

Here is a clean template for a KSP project, that you can use as a more demanding starting point:

For inspiration of how a KSP project can be implemented, see the following projects:

Once you are done with the exercise, you can check your solution here.