Skip to content

Commit baf9f89

Browse files
authored
Add a Garbage Collect benchmark (#6312)
1 parent 9af60c9 commit baf9f89

File tree

7 files changed

+881
-8
lines changed

7 files changed

+881
-8
lines changed

benchmark/microbenchmark/build.gradle.kts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import com.apollographql.apollo.annotations.ApolloExperimental
2+
13
plugins {
24
id("com.android.library")
35
id("org.jetbrains.kotlin.android")
@@ -30,17 +32,17 @@ dependencies {
3032
implementation(libs.moshi)
3133
ksp(libs.moshix.ksp)
3234

35+
// Stable cache
36+
implementation(libs.apollo.normalizedcache)
37+
implementation(libs.apollo.normalizedcache.sqlite)
38+
39+
// Incubating cache
40+
implementation(libs.apollo.normalizedcache.sqlite.incubating.snapshot)
41+
3342
androidTestImplementation(libs.benchmark.junit4)
3443
androidTestImplementation(libs.androidx.test.core)
3544
androidTestImplementation(libs.apollo.mockserver)
3645
androidTestImplementation(libs.apollo.testingsupport)
37-
38-
// Stable cache
39-
androidTestImplementation(libs.apollo.normalizedcache)
40-
androidTestImplementation(libs.apollo.normalizedcache.sqlite)
41-
42-
// Incubating cache
43-
androidTestImplementation(libs.apollo.normalizedcache.sqlite.incubating.snapshot)
4446
}
4547

4648
java {
@@ -64,4 +66,12 @@ configure<com.apollographql.apollo.gradle.api.ApolloExtension> {
6466
codegenModels.set("operationBased")
6567
packageName.set("com.apollographql.apollo.calendar.operation")
6668
}
69+
service("conferences") {
70+
srcDir("src/main/graphql/conferences")
71+
packageName.set("com.apollographql.apollo.conferences")
72+
@OptIn(ApolloExperimental::class)
73+
plugin("com.apollographql.cache:normalized-cache-apollo-compiler-plugin:0.0.5-SNAPSHOT") {
74+
argument("packageName", packageName.get())
75+
}
76+
}
6777
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package com.apollographql.apollo.benchmark
2+
3+
import com.apollographql.apollo.api.Optional
4+
import com.apollographql.apollo.conferences.GetConferenceDataQuery
5+
import com.apollographql.apollo.conferences.fragment.RoomDetails
6+
import com.apollographql.apollo.conferences.fragment.SessionDetails
7+
import com.apollographql.apollo.conferences.fragment.SpeakerDetails
8+
import com.apollographql.cache.normalized.ApolloStore
9+
import com.apollographql.cache.normalized.api.ApolloCacheHeaders
10+
import com.apollographql.cache.normalized.api.CacheHeaders
11+
12+
const val SESSION_COUNT = 100
13+
14+
fun primeCache(apolloStore: ApolloStore) {
15+
val query1 = GetConferenceDataQuery(Optional.present(SESSION_COUNT))
16+
var data: GetConferenceDataQuery.Data = GetConferenceDataQuery.Data(
17+
sessions = createSessions(0),
18+
speakers = createSpeakers(0),
19+
rooms = createRooms(),
20+
config = createConfig(),
21+
venues = createVenues(),
22+
)
23+
apolloStore.writeOperation(query1, data)
24+
25+
// Sessions in the first half of the list become unreachable
26+
data = GetConferenceDataQuery.Data(
27+
sessions = createSessions(SESSION_COUNT / 2),
28+
speakers = createSpeakers(SESSION_COUNT / 2),
29+
rooms = createRooms(),
30+
config = createConfig(),
31+
venues = createVenues(),
32+
)
33+
apolloStore.writeOperation(query1, data)
34+
35+
// Some stale sessions
36+
val query2 = GetConferenceDataQuery(Optional.present(SESSION_COUNT), Optional.present((SESSION_COUNT * 2).toString()))
37+
data = GetConferenceDataQuery.Data(
38+
sessions = createSessions(SESSION_COUNT * 2),
39+
speakers = createSpeakers(SESSION_COUNT * 2),
40+
rooms = createRooms(),
41+
config = createConfig(),
42+
venues = createVenues(),
43+
)
44+
apolloStore.writeOperation(
45+
query2,
46+
data,
47+
cacheHeaders = CacheHeaders.Builder()
48+
.addHeader(ApolloCacheHeaders.RECEIVED_DATE, (System.currentTimeMillis() / 1000 - 90).toString())
49+
.build()
50+
)
51+
}
52+
53+
private fun createSessions(startingAt: Int): GetConferenceDataQuery.Sessions {
54+
return GetConferenceDataQuery.Sessions(
55+
nodes = (0 + startingAt..<SESSION_COUNT + startingAt)
56+
.map { i ->
57+
GetConferenceDataQuery.Node(
58+
__typename = "Session",
59+
id = i.toString(),
60+
sessionDetails = SessionDetails(
61+
id = i.toString(),
62+
title = "Session $i title",
63+
type = "talk",
64+
startsAt = "2021-01-01T00:00:00Z",
65+
endsAt = "2021-01-01T00:00:00Z",
66+
sessionDescription = "Session $i description\n" + lorem(),
67+
language = "en-US",
68+
speakers = listOf(
69+
SessionDetails.Speaker(
70+
__typename = "Speaker",
71+
id = (i * 2).toString(),
72+
speakerDetails = speakerDetails(i * 2),
73+
),
74+
SessionDetails.Speaker(
75+
__typename = "Speaker",
76+
id = (i * 2 + 1).toString(),
77+
speakerDetails = speakerDetails(i * 2 + 1),
78+
),
79+
),
80+
room = SessionDetails.Room(
81+
name = "Room ${i % 8}",
82+
),
83+
tags = listOf("tag1", "tag2", "tag3"),
84+
__typename = "Session",
85+
),
86+
)
87+
},
88+
pageInfo = GetConferenceDataQuery.PageInfo(
89+
endCursor = "endCursor",
90+
),
91+
)
92+
}
93+
94+
private fun createSpeakers(startingAt: Int): GetConferenceDataQuery.Speakers {
95+
return GetConferenceDataQuery.Speakers(
96+
nodes = (0 + startingAt * 2..<(SESSION_COUNT + startingAt) * 2)
97+
.map { i ->
98+
GetConferenceDataQuery.Node1(
99+
__typename = "Speaker",
100+
id = i.toString(),
101+
speakerDetails = speakerDetails(i)
102+
)
103+
}
104+
)
105+
}
106+
107+
private fun speakerDetails(i: Int): SpeakerDetails = SpeakerDetails(
108+
id = i.toString(),
109+
name = "Speaker $i",
110+
photoUrl = "http://example.com/photo-$i",
111+
photoUrlThumbnail = "http://example.com/photo-thumb-$i",
112+
tagline = "Tagline for speaker $i\n" + lorem(),
113+
company = "Company $i",
114+
companyLogoUrl = "http://example.com/company-logo-$i",
115+
city = "City $i",
116+
bio = "Bio for speaker $i\n" + lorem(),
117+
sessions = listOf(
118+
SpeakerDetails.Session(
119+
id = "${i / 2}",
120+
title = "Session ${i / 2} title",
121+
startsAt = "2021-01-01T00:00:00Z",
122+
__typename = "Session",
123+
)
124+
),
125+
socials = listOf(
126+
SpeakerDetails.Social(
127+
name = "Twitter",
128+
url = "http://twitter.com/speaker-$i",
129+
icon = "twitter",
130+
),
131+
SpeakerDetails.Social(
132+
name = "LinkedIn",
133+
url = "http://linkedin.com/speaker-$i",
134+
icon = "linkedin",
135+
),
136+
),
137+
__typename = "Speaker",
138+
)
139+
140+
private fun lorem(): String {
141+
return "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
142+
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " +
143+
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. " +
144+
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " +
145+
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
146+
}
147+
148+
private fun createRooms(): List<GetConferenceDataQuery.Room> {
149+
return (0..7).map { i ->
150+
GetConferenceDataQuery.Room(
151+
__typename = "Room",
152+
roomDetails = RoomDetails(
153+
id = i.toString(),
154+
name = "Room $i",
155+
capacity = 100,
156+
),
157+
)
158+
}
159+
}
160+
161+
private fun createConfig(): GetConferenceDataQuery.Config {
162+
return GetConferenceDataQuery.Config(
163+
id = "Conference-0",
164+
name = "The Conference",
165+
timezone = "UTC",
166+
days = listOf("2021-01-01", "2021-01-02"),
167+
themeColor = "#FF0000",
168+
)
169+
}
170+
171+
private fun createVenues(): List<GetConferenceDataQuery.Venue> {
172+
return listOf(
173+
GetConferenceDataQuery.Venue(
174+
id = "Venue-0",
175+
name = "The Venue",
176+
address = "123 Main St",
177+
description = "The Venue is a great place",
178+
latitude = 37.7749,
179+
longitude = -122.4194,
180+
imageUrl = "http://example.com/venue-image",
181+
floorPlanUrl = "http://example.com/floor-plan",
182+
)
183+
)
184+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.apollographql.apollo.benchmark
2+
3+
import androidx.benchmark.junit4.BenchmarkRule
4+
import androidx.benchmark.junit4.measureRepeated
5+
import com.apollographql.apollo.benchmark.Utils.dbFile
6+
import com.apollographql.apollo.benchmark.Utils.dbName
7+
import com.apollographql.apollo.conferences.cache.Cache
8+
import com.apollographql.cache.normalized.ApolloStore
9+
import com.apollographql.cache.normalized.api.SchemaCoordinatesMaxAgeProvider
10+
import com.apollographql.cache.normalized.garbageCollect
11+
import com.apollographql.cache.normalized.memory.MemoryCacheFactory
12+
import com.apollographql.cache.normalized.sql.SqlNormalizedCacheFactory
13+
import org.junit.Rule
14+
import org.junit.Test
15+
import kotlin.time.Duration.Companion.days
16+
17+
class GarbageCollectTests {
18+
@get:Rule
19+
val benchmarkRule = BenchmarkRule()
20+
21+
@Test
22+
fun garbageCollectMemory() {
23+
lateinit var store: ApolloStore
24+
benchmarkRule.measureRepeated {
25+
runWithTimingDisabled {
26+
store = ApolloStore(MemoryCacheFactory())
27+
primeCache(store)
28+
}
29+
store.garbageCollect(maxAgeProvider)
30+
}
31+
}
32+
33+
@Test
34+
fun garbageCollectSql() {
35+
lateinit var store: ApolloStore
36+
benchmarkRule.measureRepeated {
37+
runWithTimingDisabled {
38+
dbFile.delete()
39+
store = ApolloStore(SqlNormalizedCacheFactory(dbName))
40+
primeCache(store)
41+
}
42+
store.garbageCollect(maxAgeProvider)
43+
}
44+
}
45+
46+
@Test
47+
fun garbageCollectMemoryThenSql() {
48+
lateinit var store: ApolloStore
49+
benchmarkRule.measureRepeated {
50+
runWithTimingDisabled {
51+
dbFile.delete()
52+
store = ApolloStore(MemoryCacheFactory().chain(SqlNormalizedCacheFactory(dbName)))
53+
primeCache(store)
54+
}
55+
store.garbageCollect(maxAgeProvider)
56+
}
57+
}
58+
}
59+
60+
private val maxAgeProvider = SchemaCoordinatesMaxAgeProvider(
61+
Cache.maxAges,
62+
defaultMaxAge = 1.days,
63+
)

0 commit comments

Comments
 (0)