Skip to content

Commit

Permalink
Merge pull request #32 from Automattic/build_report_file
Browse files Browse the repository at this point in the history
Save build and execution reports locally
  • Loading branch information
wzieba authored Feb 23, 2024
2 parents ef8bab9 + f416c9b commit 1b8026b
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 25 deletions.
2 changes: 1 addition & 1 deletion measure-builds/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies {
implementation("io.ktor:ktor-client-cio:1.6.4")
implementation("io.ktor:ktor-client-logging:1.6.4")
implementation("io.ktor:ktor-client-serialization:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")

testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,13 @@ class BuildTimePlugin @Inject constructor(
extension: MeasureBuildsExtension,
encodedUser: String,
) {
InMemoryReport.buildDataStore =
InMemoryReport.setBuildData(
BuildDataProvider.provide(
project,
extension.automatticProject.get(),
encodedUser,
)
)
}

private fun prepareBuildScanListener(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ package com.automattic.android.measure
import com.automattic.android.measure.models.BuildData
import com.automattic.android.measure.models.ExecutionData

object InMemoryReport : Report {
var buildDataStore: BuildData? = null
var executionDataStore: ExecutionData? = null
object InMemoryReport {
private var buildDataStore: BuildData? = null
private var executionDataStore: ExecutionData? = null

override val buildData: BuildData
get() = buildDataStore ?: throw NullPointerException("Must not be null")
fun setBuildData(buildData: BuildData) {
buildDataStore = buildData
}

override val executionData: ExecutionData
get() = executionDataStore ?: throw NullPointerException("Must not be null")
}
fun setExecutionData(executionData: ExecutionData) {
executionDataStore = executionData
}

interface Report {
val buildData: BuildData
get() = buildDataStore ?: throw NullPointerException("Must not be null")

val executionData: ExecutionData
get() = executionDataStore ?: throw NullPointerException("Must not be null")
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ class BuildFinishedFlowAction : FlowAction<BuildFinishedFlowAction.Parameters> {
val executionData = ExecutionData(
buildTime = buildTime,
failed = result.failure.isPresent,
failure = result.failure.getOrNull(),
failure = result.failure.getOrNull()?.message,
tasks = parameters.buildTaskService.get().tasks,
buildFinishedTimestamp = finish,
configurationPhaseDuration = configurationTime
)

InMemoryReport.executionDataStore = executionData
InMemoryReport.setExecutionData(executionData)

if (parameters.attachGradleScanId.get() == false) {
runBlocking {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.automattic.android.measure.models

import com.automattic.android.measure.MeasureBuildsExtension
import kotlinx.serialization.Serializable

@Serializable
data class BuildData(
val forProject: MeasureBuildsExtension.AutomatticProject,
val user: String,
Expand All @@ -19,10 +21,11 @@ data class BuildData(
val architecture: String,
)

@Serializable
data class ExecutionData(
val buildTime: Long,
val failed: Boolean,
val failure: Throwable?,
val failure: String?,
val tasks: List<MeasuredTask>,
val buildFinishedTimestamp: Long,
val configurationPhaseDuration: Long,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.automattic.android.measure.models

import kotlinx.serialization.Serializable
import kotlin.time.Duration

@Serializable
data class MeasuredTask(
val name: String,
val duration: Duration,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.automattic.android.measure.networking

import com.automattic.android.measure.Report
import com.automattic.android.measure.InMemoryReport
import com.automattic.android.measure.logging.Emojis.FAILURE_ICON
import com.automattic.android.measure.logging.Emojis.SUCCESS_ICON
import com.automattic.android.measure.logging.Emojis.TURTLE_ICON
Expand All @@ -22,21 +22,32 @@ import io.ktor.http.HttpHeaders
import io.ktor.http.HttpHeaders.Authorization
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.gradle.api.logging.Logger
import org.gradle.api.provider.Provider
import java.util.Locale
import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.MINUTES
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.exists
import kotlin.io.path.writeText
import kotlin.time.Duration.Companion.seconds

class MetricsReporter(
private val logger: Logger,
private val authToken: Provider<String>,
) {
suspend fun report(
report: Report,
report: InMemoryReport,
gradleScanId: String?
) {
reportLocally(report)

val payload = report.toAppsInfraPayload(gradleScanId)
@Suppress("TooGenericExceptionCaught")
try {
logSlowTasks(report)
Expand All @@ -54,7 +65,7 @@ class MetricsReporter(
append(Authorization, "Bearer ${authToken.get()}")
}
contentType(ContentType.Application.Json)
body = report.toAppsInfraPayload(gradleScanId)
body = payload
}.execute { response: HttpResponse ->
logger.debug(response.toString())

Expand Down Expand Up @@ -92,6 +103,29 @@ class MetricsReporter(
}
}

private fun reportLocally(report: InMemoryReport) {
Path("build/reports/measure_builds")
.apply {
if (!exists()) {
createDirectories()
}
resolve("build_data.json").apply {
logger.info("Writing build data to ${absolutePathString()}")
if (!exists()) {
createFile()
}
writeText(Json.encodeToString(report.buildData))
}
resolve("execution_data.json").apply {
logger.info("Writing execution data to ${absolutePathString()}")
if (!exists()) {
createFile()
}
writeText(Json.encodeToString(report.executionData))
}
}
}

private fun httpClient(): HttpClient {
val client = HttpClient(CIO) {
install(Logging) {
Expand All @@ -112,7 +146,7 @@ class MetricsReporter(
return client
}

private fun logSlowTasks(report: Report) {
private fun logSlowTasks(report: InMemoryReport) {
val slowTasks =
report.executionData.tasks.sortedByDescending { it.duration }.chunked(atMostLoggedTasks)
.first()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.automattic.android.measure.networking

import com.automattic.android.measure.Report
import com.automattic.android.measure.InMemoryReport
import com.automattic.android.measure.models.MeasuredTask.State.EXECUTED
import com.automattic.android.measure.models.MeasuredTask.State.IS_FROM_CACHE
import com.automattic.android.measure.models.MeasuredTask.State.UP_TO_DATE

fun Report.toAppsInfraPayload(gradleScanId: String?): GroupedAppsMetrics {
fun InMemoryReport.toAppsInfraPayload(gradleScanId: String?): GroupedAppsMetrics {
val projectKey = buildData.forProject.name.lowercase()

val meta = mapOf(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.automattic.android.measure

import com.automattic.android.measure.models.ExecutionData
import com.automattic.android.measure.models.MeasuredTask
import kotlinx.serialization.json.Json
import org.assertj.core.api.Assertions.assertThat
import org.gradle.testkit.runner.GradleRunner
import org.junit.jupiter.api.BeforeEach
Expand All @@ -14,7 +17,8 @@ class BuildTimePluginTest {
// given
val runner = functionalTestRunner(
enable = true,
attachGradleScanId = true
attachGradleScanId = true,
projectWithSendingScans = true
)

// when
Expand Down Expand Up @@ -116,6 +120,30 @@ class BuildTimePluginTest {
.contains("BUILD SUCCESSFUL")
}

@Test
fun `given a help task to execute, when finishing the build, the help task is present in report file`() {
// given
val runner = functionalTestRunner(
enable = true,
attachGradleScanId = false,
applyAppsMetricsToken = false,
)

// when
val run = runner.withArguments("help").build()

// then
File("build/reports/measure_builds/execution_data.json").let {
val executionData = Json.decodeFromString<ExecutionData>(it.readText())

assertThat(executionData.tasks).hasSize(1)
.first().satisfies({ task ->
assertThat(task.name).isEqualTo(":help")
assertThat(task.state).isEqualTo(MeasuredTask.State.EXECUTED)
})
}
}

@BeforeEach
fun clearCache() {
val projectDir = File("build/functionalTest")
Expand All @@ -124,14 +152,16 @@ class BuildTimePluginTest {

private fun functionalTestRunner(
enable: Boolean?,
projectWithSendingScans: Boolean = false,
attachGradleScanId: Boolean,
applyAppsMetricsToken: Boolean = true,
vararg arguments: String,
): GradleRunner {
val projectDir = File("build/functionalTest")
projectDir.mkdirs()
projectDir.resolve("settings.gradle.kts").writeText(
"""
if (projectWithSendingScans) {
projectDir.resolve("settings.gradle.kts").writeText(
"""
plugins {
id("com.gradle.enterprise") version "3.15.1"
}
Expand All @@ -143,8 +173,9 @@ class BuildTimePluginTest {
isUploadInBackground = false
}
}
""".trimIndent()
)
""".trimIndent()
)
}
projectDir.resolve("build.gradle.kts").writeText(
"""
plugins {
Expand Down

0 comments on commit 1b8026b

Please sign in to comment.