Skip to content

Commit 9ed0b88

Browse files
Add resolver recommendation summaries
1 parent 2fa4b30 commit 9ed0b88

3 files changed

Lines changed: 89 additions & 1 deletion

File tree

app/src/main/java/shop/whitedns/client/scan/ResolverScanResult.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ data class ResolverScanResult(
2424
get() = resolverScanScore(this)
2525
}
2626

27+
data class ResolverScanRecommendation(
28+
val resolver: String,
29+
val score: Int,
30+
val observationCount: Int,
31+
val successfulServerCount: Int,
32+
val bestLatencyMillis: Int?,
33+
val lastObservedAtMillis: Long,
34+
)
35+
2736
fun resolverScanScore(result: ResolverScanResult): Int {
2837
if (!result.isValid) {
2938
return 0
@@ -61,6 +70,39 @@ fun rankResolverScanResults(results: Iterable<ResolverScanResult>): List<Resolve
6170
)
6271
}
6372

73+
fun summarizeResolverScanRecommendations(
74+
results: Iterable<ResolverScanResult>,
75+
): List<ResolverScanRecommendation> {
76+
return results
77+
.filter { it.isValid && it.resolver.isNotBlank() }
78+
.groupBy { it.resolver }
79+
.map { (resolver, resolverResults) ->
80+
val successfulServerCount = resolverResults
81+
.mapNotNull { it.serverDomain.takeIf(String::isNotBlank) }
82+
.distinct()
83+
.size
84+
val bestScore = resolverResults.maxOf { it.score }
85+
val stabilityBonus = ((resolverResults.size - 1).coerceAtMost(4) * 2) +
86+
(successfulServerCount.coerceAtMost(4) * 3)
87+
ResolverScanRecommendation(
88+
resolver = resolver,
89+
score = (bestScore + stabilityBonus).coerceIn(1, 100),
90+
observationCount = resolverResults.size,
91+
successfulServerCount = successfulServerCount,
92+
bestLatencyMillis = resolverResults.mapNotNull { it.latencyMillis }.minOrNull(),
93+
lastObservedAtMillis = resolverResults.maxOf { it.observedAtMillis },
94+
)
95+
}
96+
.sortedWith(
97+
compareByDescending<ResolverScanRecommendation> { it.score }
98+
.thenBy { it.bestLatencyMillis ?: Int.MAX_VALUE }
99+
.thenByDescending { it.successfulServerCount }
100+
.thenByDescending { it.observationCount }
101+
.thenByDescending { it.lastObservedAtMillis }
102+
.thenBy { it.resolver },
103+
)
104+
}
105+
64106
fun ResolverScanResult.toJsonObject(): JSONObject {
65107
return JSONObject()
66108
.put("resolver", resolver)

app/src/main/java/shop/whitedns/client/scan/WhiteDnsScannerResultStore.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,19 @@ object WhiteDnsScannerResultStore {
2727
if (structuredResults.isEmpty()) {
2828
return readValidResolvers(context).take(limit)
2929
}
30-
return rankResolverScanResults(structuredResults)
30+
return summarizeResolverScanRecommendations(structuredResults)
3131
.map { it.resolver }
3232
.take(limit.coerceAtLeast(1))
3333
}
3434

35+
fun readResolverRecommendations(
36+
context: Context,
37+
limit: Int = 64,
38+
): List<ResolverScanRecommendation> {
39+
return summarizeResolverScanRecommendations(readStructuredResults(context))
40+
.take(limit.coerceAtLeast(1))
41+
}
42+
3543
fun readStructuredResults(context: Context): List<ResolverScanResult> {
3644
return runCatching {
3745
val file = structuredResultFile(context)

app/src/test/java/shop/whitedns/client/scan/ResolverScanResultTest.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,42 @@ class ResolverScanResultTest {
7575

7676
assertEquals(result, resolverScanResultFromJson(result.toJsonObject()))
7777
}
78+
79+
@Test
80+
fun summarizeResolverScanRecommendationsRewardsRepeatedServerSuccess() {
81+
val recommendations = summarizeResolverScanRecommendations(
82+
listOf(
83+
ResolverScanResult(
84+
resolver = "8.8.8.8",
85+
status = ResolverScanResultStatus.Valid,
86+
serverDomain = "server-a.example.com",
87+
latencyMillis = 95,
88+
attempts = 1,
89+
observedAtMillis = 10L,
90+
),
91+
ResolverScanResult(
92+
resolver = "8.8.8.8",
93+
status = ResolverScanResultStatus.Valid,
94+
serverDomain = "server-b.example.com",
95+
latencyMillis = 110,
96+
attempts = 1,
97+
observedAtMillis = 20L,
98+
),
99+
ResolverScanResult(
100+
resolver = "1.1.1.1",
101+
status = ResolverScanResultStatus.Valid,
102+
serverDomain = "server-a.example.com",
103+
latencyMillis = 95,
104+
attempts = 1,
105+
observedAtMillis = 30L,
106+
),
107+
),
108+
)
109+
110+
assertEquals("8.8.8.8", recommendations.first().resolver)
111+
assertEquals(2, recommendations.first().observationCount)
112+
assertEquals(2, recommendations.first().successfulServerCount)
113+
assertEquals(95, recommendations.first().bestLatencyMillis)
114+
assertTrue(recommendations.first().score > recommendations.last().score)
115+
}
78116
}

0 commit comments

Comments
 (0)