-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MBS-7244 Collect remote cache hit rate metrics (#958)
- Loading branch information
1 parent
d9c139f
commit 635ae0f
Showing
15 changed files
with
547 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 44 additions & 15 deletions
59
...ild-metrics/src/gradleTest/kotlin/com/avito/android/plugin/build_metrics/StatsdMetrics.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,41 +1,70 @@ | ||
package com.avito.android.plugin.build_metrics | ||
|
||
import com.avito.android.stats.CountMetric | ||
import com.avito.android.stats.GaugeDoubleMetric | ||
import com.avito.android.stats.GaugeLongMetric | ||
import com.avito.android.stats.SeriesName | ||
import com.avito.android.stats.StatsMetric | ||
import com.avito.android.stats.TimeMetric | ||
import com.avito.test.gradle.TestResult | ||
import com.google.common.truth.Truth.assertWithMessage | ||
|
||
private const val loggerPrefix = "[StatsDSender@:] " | ||
|
||
internal fun TestResult.expectMetric(type: String, metricName: String) { | ||
internal inline fun <reified T : StatsMetric> TestResult.assertHasMetric(path: String): T { | ||
val type = T::class.java | ||
val metrics = statsdMetrics() | ||
val filtered = filter(metrics, type, metricName) | ||
val filtered = metrics | ||
.filterIsInstance(type) | ||
.filter { | ||
it.name.toString().contains(path) | ||
} | ||
|
||
assertWithMessage("Expected metric $type $metricName in $metrics") | ||
assertWithMessage("Expected metric ${type.simpleName}($path) in $metrics") | ||
.that(filtered).hasSize(1) | ||
|
||
return filtered.first() | ||
} | ||
|
||
private fun filter(metrics: List<MetricRecord>, type: String, metricName: String): List<MetricRecord> { | ||
return metrics | ||
.filter { it.type == type } | ||
.filter { it.name.endsWith(".$metricName") } | ||
internal inline fun <reified T : StatsMetric> TestResult.assertNoMetric(path: String) { | ||
val type = T::class.java | ||
val metrics = statsdMetrics() | ||
val filtered = metrics | ||
.filterIsInstance(type) | ||
.filter { | ||
it.name.toString().contains(path) | ||
} | ||
|
||
assertWithMessage("Expected no metric ${type.simpleName}($path) in $metrics") | ||
.that(filtered).isEmpty() | ||
} | ||
|
||
/** | ||
* Example: | ||
* ... time:apps.mobile.statistic.android.local.user.id.success.init_configuration.total:5821 | ||
*/ | ||
// TODO: Intercept on network layer by simulating statsd server | ||
internal fun TestResult.statsdMetrics(): List<MetricRecord> { | ||
internal fun TestResult.statsdMetrics(): List<StatsMetric> { | ||
return output.lines().asSequence() | ||
.filter { it.contains(loggerPrefix) } | ||
.map { it.substringAfter(loggerPrefix) } | ||
.map { it.substringAfter("event: ") } | ||
.map { it.substringBeforeLast(':') } | ||
.map { line -> | ||
val type = line.substringBefore(':') | ||
val name = line.substringAfter(':') | ||
MetricRecord(type, name) | ||
} | ||
.map { parseStatsdMetric(it) } | ||
.toList() | ||
} | ||
|
||
internal data class MetricRecord(val type: String, val name: String) | ||
private fun parseStatsdMetric(raw: String): StatsMetric { | ||
val type = raw.substringBefore(':') | ||
val name = raw.substringBeforeLast(':').substringAfter(':') | ||
val value = raw.substringAfterLast(':') | ||
return when (type) { | ||
"count" -> CountMetric(SeriesName.create(name, multipart = true), value.toLong()) | ||
"time" -> TimeMetric(SeriesName.create(name, multipart = true), value.toLong()) | ||
"gauge" -> if (value.contains('.')) { | ||
GaugeLongMetric(SeriesName.create(name, multipart = true), value.toLong()) | ||
} else { | ||
GaugeDoubleMetric(SeriesName.create(name, multipart = true), value.toDouble()) | ||
} | ||
else -> throw IllegalStateException("Unknown statsd metric: $raw") | ||
} | ||
} |
167 changes: 167 additions & 0 deletions
167
...c/gradleTest/kotlin/com/avito/android/plugin/build_metrics/cache/BuildCacheMetricsTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package com.avito.android.plugin.build_metrics.cache | ||
|
||
import com.avito.android.plugin.build_metrics.assertHasMetric | ||
import com.avito.android.stats.CountMetric | ||
import com.avito.test.gradle.TestResult | ||
import com.google.common.truth.Truth.assertThat | ||
import org.gradle.testkit.runner.TaskOutcome | ||
import org.junit.jupiter.api.Test | ||
import java.io.File | ||
|
||
internal class BuildCacheMetricsTest : BuildCacheTestFixture() { | ||
|
||
override fun setupProject(projectDir: File) { | ||
File(projectDir, "build.gradle.kts").writeText( | ||
""" | ||
plugins { | ||
id("com.avito.android.build-metrics") | ||
} | ||
@CacheableTask | ||
abstract class CustomTask @Inject constructor(objects: ObjectFactory) : DefaultTask() { | ||
@Input | ||
var input: Long = 0 | ||
@OutputFile | ||
val outputFile = objects.fileProperty() | ||
@TaskAction | ||
fun createFile() { | ||
outputFile.get().asFile.writeText("Output of CacheableTask: " + input) | ||
} | ||
} | ||
tasks.register("cacheMissTask", CustomTask::class.java) { | ||
input = System.currentTimeMillis() | ||
outputFile.set(file("build/cacheMissTask.txt")) | ||
} | ||
tasks.register("cacheHitTask", CustomTask::class.java) { | ||
outputFile.set(file("build/cacheHitTask.txt")) | ||
} | ||
tasks.register("nonCacheableTask", CustomTask::class.java) { | ||
outputs.cacheIf { false } | ||
outputFile.set(file("build/nonCacheableTask.txt")) | ||
} | ||
""".trimIndent() | ||
) | ||
} | ||
|
||
@Test | ||
fun `metrics - local cache - hit`() { | ||
val result = warmupAndBuild( | ||
":cacheHitTask", | ||
warmupRemote = false, | ||
expectedOutcome = TaskOutcome.FROM_CACHE | ||
) | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.hit.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.miss.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
} | ||
|
||
@Test | ||
fun `metrics - local cache - miss`() { | ||
val result = warmupAndBuild( | ||
":cacheMissTask", | ||
warmupRemote = false, | ||
expectedOutcome = TaskOutcome.SUCCESS | ||
) | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.hit.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.miss.env.").also { | ||
assertThat(it.delta).isEqualTo(1) | ||
} | ||
} | ||
|
||
@Test | ||
fun `metrics - local cache - non cacheable`() { | ||
val result = warmupAndBuild( | ||
":nonCacheableTask", | ||
warmupRemote = false, | ||
expectedOutcome = TaskOutcome.SUCCESS | ||
) | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.hit.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.miss.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
} | ||
|
||
@Test | ||
fun `metrics - remote cache - hit`() { | ||
val result = warmupAndBuild( | ||
":cacheHitTask", | ||
warmupLocal = false, | ||
expectedOutcome = TaskOutcome.FROM_CACHE | ||
) | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.hit.env.").also { | ||
assertThat(it.delta).isEqualTo(1) | ||
} | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.miss.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
} | ||
|
||
@Test | ||
fun `metrics - remote cache - miss`() { | ||
val result = warmupAndBuild( | ||
":cacheMissTask", | ||
warmupLocal = false, | ||
expectedOutcome = TaskOutcome.SUCCESS | ||
) | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.hit.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.miss.env.").also { | ||
assertThat(it.delta).isEqualTo(1) | ||
} | ||
} | ||
|
||
@Test | ||
fun `metrics - remote cache - non cacheable`() { | ||
val result = warmupAndBuild( | ||
":nonCacheableTask", | ||
warmupLocal = false, | ||
expectedOutcome = TaskOutcome.SUCCESS | ||
) | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.hit.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
|
||
result.assertHasMetric<CountMetric>(".build.cache.remote.miss.env.").also { | ||
assertThat(it.delta).isEqualTo(0) | ||
} | ||
} | ||
|
||
private fun warmupAndBuild( | ||
task: String, | ||
warmupLocal: Boolean = true, | ||
warmupRemote: Boolean = true, | ||
expectedOutcome: TaskOutcome | ||
): TestResult { | ||
build(task, useLocalCache = warmupLocal, useRemoteCache = warmupRemote) | ||
clean() | ||
|
||
val result = build(task, useLocalCache = true, useRemoteCache = true) | ||
|
||
result.assertThat().taskWithOutcome(task, expectedOutcome) | ||
|
||
return result | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
...c/gradleTest/kotlin/com/avito/android/plugin/build_metrics/cache/BuildCacheTestFixture.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package com.avito.android.plugin.build_metrics.cache | ||
|
||
import com.avito.test.gradle.TestResult | ||
import com.avito.test.gradle.gradlew | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.io.TempDir | ||
import java.io.File | ||
|
||
internal abstract class BuildCacheTestFixture { | ||
|
||
private lateinit var projectDir: File | ||
|
||
@BeforeEach | ||
fun setup(@TempDir tempDir: File) { | ||
this.projectDir = tempDir | ||
|
||
File(projectDir, "settings.gradle.kts").writeText( | ||
buildCacheBlock() | ||
) | ||
setupProject(projectDir) | ||
} | ||
|
||
abstract fun setupProject(projectDir: File) | ||
|
||
private fun buildCacheBlock(): String { | ||
return """ | ||
fun booleanProperty(name: String, defaultValue: Boolean): Boolean { | ||
return if (settings.extra.has(name)) { | ||
settings.extra[name]?.toString()?.toBoolean() ?: defaultValue | ||
} else { | ||
defaultValue | ||
} | ||
} | ||
buildCache { | ||
local { | ||
isEnabled = booleanProperty("localCacheEnabled", false) | ||
directory = file(".gradle/build-cache") | ||
} | ||
remote<DirectoryBuildCache> { | ||
isEnabled = booleanProperty("remoteCacheEnabled", false) | ||
directory = file(".gradle/remote-build-cache") | ||
isPush = true | ||
} | ||
} | ||
""".trimIndent() | ||
} | ||
|
||
protected fun clean() { | ||
File(projectDir, "build").deleteRecursively() | ||
} | ||
|
||
protected fun build( | ||
vararg tasks: String, | ||
useLocalCache: Boolean = true, | ||
useRemoteCache: Boolean = true, | ||
): TestResult { | ||
return gradlew( | ||
projectDir, | ||
*tasks, | ||
"-Pavito.build.metrics.enabled=true", | ||
"-Pavito.stats.enabled=false", | ||
"-PlocalCacheEnabled=$useLocalCache", | ||
"-PremoteCacheEnabled=$useRemoteCache", | ||
"--build-cache", | ||
"--debug", // to read statsd logs from stdout | ||
) | ||
} | ||
} |
Oops, something went wrong.