Skip to content

Commit

Permalink
Add some 'integration test' benchmarks that execute queries
Browse files Browse the repository at this point in the history
  • Loading branch information
BoD committed Feb 9, 2024
1 parent f87e47a commit c8cee0f
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 3 deletions.
4 changes: 2 additions & 2 deletions benchmark/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="INTERNET" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- Storage permissions required to run the macrobenchmark on my Pixel 3, not sure why -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Expand All @@ -17,4 +17,4 @@
</intent-filter>
</activity>
</application>
</manifest>
</manifest>
2 changes: 2 additions & 0 deletions benchmark/microbenchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ dependencies {

androidTestImplementation(libs.benchmark.junit4)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation("com.apollographql.apollo3:apollo-mockserver")
androidTestImplementation("com.apollographql.apollo3:apollo-testing-support")
}

configure<com.android.build.gradle.LibraryExtension> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.apollographql.apollo3.benchmark

import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.platform.app.InstrumentationRegistry
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.json.jsonReader
import com.apollographql.apollo3.api.parseJsonResponse
import com.apollographql.apollo3.benchmark.Utils.dbName
import com.apollographql.apollo3.benchmark.Utils.operationBasedQuery
import com.apollographql.apollo3.benchmark.Utils.resource
import com.apollographql.apollo3.benchmark.test.R
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.incubating.ApolloStore
import com.apollographql.apollo3.cache.normalized.incubating.api.CacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.incubating.api.CacheResolver
import com.apollographql.apollo3.cache.normalized.incubating.api.FieldPolicyCacheResolver
import com.apollographql.apollo3.cache.normalized.incubating.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.incubating.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.incubating.api.TypePolicyCacheKeyGenerator
import com.apollographql.apollo3.cache.normalized.incubating.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo3.mockserver.MockRequestBase
import com.apollographql.apollo3.mockserver.MockResponse
import com.apollographql.apollo3.mockserver.MockServer
import com.apollographql.apollo3.mockserver.MockServerHandler
import com.apollographql.apollo3.testing.MapTestNetworkTransport
import com.apollographql.apollo3.testing.registerTestResponse
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import java.lang.reflect.Method

class CacheIncubatingIntegrationTests {
@get:Rule
val benchmarkRule = BenchmarkRule()

@Test
fun concurrentQueriesTestNetworkTransportMemory() {
concurrentQueries(MemoryCacheFactory(), withMockServer = false)
}

@Test
fun concurrentQueriesTestNetworkTransportSql() {
Utils.dbFile.delete()
val cacheFactory = SqlNormalizedCacheFactory(InstrumentationRegistry.getInstrumentation().context, dbName)
concurrentQueries(cacheFactory, withMockServer = false)
}

@Test
fun concurrentQueriesTestNetworkTransportMemoryThenSql() {
Utils.dbFile.delete()
val cacheFactory = MemoryCacheFactory().chain(SqlNormalizedCacheFactory(InstrumentationRegistry.getInstrumentation().context, dbName))
concurrentQueries(cacheFactory, withMockServer = false)
}


private fun concurrentQueries(cacheFactory: NormalizedCacheFactory, withMockServer: Boolean) {
val mockServer = MockServer.Builder()
.handler(
object : MockServerHandler {
private val mockResponse = MockResponse.Builder()
.statusCode(200)
.body(resource(R.raw.calendar_response_simple).readByteString())
.build()

override fun handle(request: MockRequestBase): MockResponse {
return mockResponse
}
}
)
.build()

val client = ApolloClient.Builder()
.let {
if (withMockServer) {
it.serverUrl(runBlocking { mockServer.url() })
} else {
it.networkTransport(MapTestNetworkTransport())
}
}
.store(createApolloStore(cacheFactory))
.build()
if (!withMockServer) {
client.registerTestResponse(operationBasedQuery, operationBasedQuery.parseJsonResponse(resource(R.raw.calendar_response_simple).jsonReader()).data!!)
}

benchmarkRule.measureRepeated {
runBlocking {
(1..CONCURRENCY).map {
launch {
// Let each job execute a few queries
repeat(WORK_LOAD) {
client.query(operationBasedQuery).fetchPolicy(FetchPolicy.NetworkOnly).execute().dataOrThrow()
client.query(operationBasedQuery).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow()
}
}
}
// Wait for all jobs to finish
.joinAll()
}
}
}

private fun createApolloStore(cacheFactory: NormalizedCacheFactory): ApolloStore {
return createApolloStoreMethod.invoke(
null,
cacheFactory,
TypePolicyCacheKeyGenerator,
FieldPolicyCacheResolver,
) as ApolloStore
}


companion object {
private const val CONCURRENCY = 10
private const val WORK_LOAD = 8

/**
* There doesn't seem to be a way to relocate Kotlin metadata and kotlin_module files so we rely on reflection to call top-level
* methods
* See https://discuss.kotlinlang.org/t/what-is-the-proper-way-to-repackage-shade-kotlin-dependencies/10869
*/
private val apolloStoreKtClass = Class.forName("com.apollographql.apollo3.cache.normalized.incubating.ApolloStoreKt")
private val createApolloStoreMethod: Method = apolloStoreKtClass.getMethod(
"ApolloStore",
NormalizedCacheFactory::class.java,
CacheKeyGenerator::class.java,
CacheResolver::class.java,
)

private val NormalizedCacheClass = Class.forName("com.apollographql.apollo3.cache.normalized.incubating.NormalizedCache")
private val storeMethod: Method = NormalizedCacheClass.getMethod(
"store",
ApolloClient.Builder::class.java,
ApolloStore::class.java,
Boolean::class.java,
)

private fun ApolloClient.Builder.store(store: ApolloStore): ApolloClient.Builder {
return storeMethod.invoke(
null,
this,
store,
false,
) as ApolloClient.Builder
}
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.apollographql.apollo3.benchmark

import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.json.jsonReader
import com.apollographql.apollo3.api.parseJsonResponse
import com.apollographql.apollo3.benchmark.Utils.dbName
import com.apollographql.apollo3.benchmark.Utils.operationBasedQuery
import com.apollographql.apollo3.benchmark.Utils.resource
import com.apollographql.apollo3.benchmark.test.R
import com.apollographql.apollo3.cache.normalized.ApolloStore
import com.apollographql.apollo3.cache.normalized.FetchPolicy
import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
import com.apollographql.apollo3.cache.normalized.api.NormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.fetchPolicy
import com.apollographql.apollo3.cache.normalized.sql.SqlNormalizedCacheFactory
import com.apollographql.apollo3.cache.normalized.store
import com.apollographql.apollo3.mockserver.MockRequestBase
import com.apollographql.apollo3.mockserver.MockResponse
import com.apollographql.apollo3.mockserver.MockServer
import com.apollographql.apollo3.mockserver.MockServerHandler
import com.apollographql.apollo3.testing.MapTestNetworkTransport
import com.apollographql.apollo3.testing.registerTestResponse
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test

class CacheIntegrationTests {
@get:Rule
val benchmarkRule = BenchmarkRule()

@Test
fun concurrentQueriesTestNetworkTransportMemory() {
concurrentQueries(MemoryCacheFactory(), withMockServer = false)
}

@Test
fun concurrentQueriesTestNetworkTransportSql() {
Utils.dbFile.delete()
val cacheFactory = SqlNormalizedCacheFactory(dbName)
concurrentQueries(cacheFactory, withMockServer = false)
}

@Test
fun concurrentQueriesTestNetworkTransportMemoryThenSql() {
Utils.dbFile.delete()
val cacheFactory = MemoryCacheFactory().chain(SqlNormalizedCacheFactory(dbName))
concurrentQueries(cacheFactory, withMockServer = false)
}


private fun concurrentQueries(cacheFactory: NormalizedCacheFactory, withMockServer: Boolean) {
val mockServer = MockServer.Builder()
.handler(
object : MockServerHandler {
private val mockResponse = MockResponse.Builder()
.statusCode(200)
.body(resource(R.raw.calendar_response_simple).readByteString())
.build()

override fun handle(request: MockRequestBase): MockResponse {
return mockResponse
}
}
)
.build()

val client = ApolloClient.Builder()
.let {
if (withMockServer) {
it.serverUrl(runBlocking { mockServer.url() })
} else {
it.networkTransport(MapTestNetworkTransport())
}
}
.store(createApolloStore(cacheFactory))
.build()
if (!withMockServer) {
client.registerTestResponse(operationBasedQuery, operationBasedQuery.parseJsonResponse(resource(R.raw.calendar_response_simple).jsonReader()).data!!)
}

benchmarkRule.measureRepeated {
runBlocking {
(1..CONCURRENCY).map {
launch {
// Let each job execute a few queries
repeat(WORK_LOAD) {
client.query(operationBasedQuery).fetchPolicy(FetchPolicy.NetworkOnly).execute().dataOrThrow()
client.query(operationBasedQuery).fetchPolicy(FetchPolicy.CacheOnly).execute().dataOrThrow()
}
}
}
// Wait for all jobs to finish
.joinAll()
}
}
}

private fun createApolloStore(cacheFactory: NormalizedCacheFactory): ApolloStore {
return ApolloStore(cacheFactory)
}


companion object {
private const val CONCURRENCY = 10
private const val WORK_LOAD = 8
}
}


4 changes: 3 additions & 1 deletion benchmark/microbenchmark/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="false"
android:debuggable="false"
tools:ignore="HardcodedDebugMode"
android:requestLegacyExternalStorage="true" />
</manifest>
</manifest>

0 comments on commit c8cee0f

Please sign in to comment.