Skip to content

Commit 4bd2c5e

Browse files
authored
Merge pull request #792 from MarathonLabs/feature/grafana-cloud
feat(analytics): migrate to grafana cloud
2 parents 1365090 + c104911 commit 4bd2c5e

File tree

29 files changed

+233
-124
lines changed

29 files changed

+233
-124
lines changed

Diff for: analytics/usage/build.gradle.kts

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@ plugins {
22
`java-library`
33
id("org.jetbrains.kotlin.jvm")
44
id("org.jetbrains.dokka")
5+
jacoco
56
}
67

78
setupDeployment()
89
setupKotlinCompiler()
10+
setupTestTask()
911

1012
dependencies {
11-
implementation(Analytics.googleAnalyticsWrapper)
13+
implementation(Libraries.okhttp)
1214
implementation(Libraries.kotlinStdLib)
15+
testRuntimeOnly(TestLibraries.jupiterEngine)
16+
testImplementation(TestLibraries.junit5)
1317
testImplementation(TestLibraries.kluent)
1418
testImplementation(TestLibraries.mockitoKotlin)
1519
}

Diff for: analytics/usage/src/main/kotlin/com/malinskiy/marathon/usageanalytics/Constants.kt

-6
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.malinskiy.marathon.usageanalytics
2+
3+
sealed class Event {
4+
data class TestsTotal(
5+
val total: Int
6+
) : Event()
7+
8+
data class TestsRun(
9+
val value: Int
10+
) : Event()
11+
12+
data class Devices(
13+
val total: Int
14+
) : Event()
15+
16+
data class Executed(
17+
val seconds: Long
18+
) : Event()
19+
}

Diff for: analytics/usage/src/main/kotlin/com/malinskiy/marathon/usageanalytics/TrackActionType.kt

-12
This file was deleted.

Diff for: analytics/usage/src/main/kotlin/com/malinskiy/marathon/usageanalytics/UsageAnalytics.kt

-13
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.malinskiy.marathon.usageanalytics.tracker
22

3-
internal class EmptyTracker : UsageTracker {
4-
override fun trackEvent(event: Event) {
3+
import com.malinskiy.marathon.usageanalytics.Event
54

6-
}
7-
}
5+
class EmptyTracker : UsageTracker {
6+
override fun trackEvent(event: Event) = Unit
7+
override fun meta(version: String, vendor: String, releaseMode: String) = Unit
8+
override fun close() = Unit
9+
}

Diff for: analytics/usage/src/main/kotlin/com/malinskiy/marathon/usageanalytics/tracker/Event.kt

-8
This file was deleted.

Diff for: analytics/usage/src/main/kotlin/com/malinskiy/marathon/usageanalytics/tracker/GoogleAnalyticsTracker.kt

-20
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.malinskiy.marathon.usageanalytics.tracker
2+
3+
import com.malinskiy.marathon.usageanalytics.Event
4+
import okhttp3.MediaType.Companion.toMediaType
5+
import okhttp3.OkHttpClient
6+
import okhttp3.Request
7+
import okhttp3.RequestBody.Companion.toRequestBody
8+
import java.lang.Exception
9+
import java.util.UUID
10+
11+
class GrafanaCloud : UsageTracker {
12+
private val client = OkHttpClient()
13+
private val key =
14+
"967310:eyJrIjoiMmEzNWM4ZmI2ZTExNjg1OWJiZDUxOWE3YWU1N2NiMTI5MDRhOTk2ZiIsIm4iOiJtYXJhdGhvbi1vc3MtcHVibGlzaGluZyIsImlkIjo4NDcyNDZ9"
15+
private val accumulator = mutableListOf<Event>()
16+
private val tags = mutableMapOf<String, String>()
17+
18+
override fun trackEvent(event: Event) {
19+
accumulator.add(event)
20+
}
21+
22+
override fun meta(version: String, vendor: String, releaseMode: String) {
23+
tags["version"] = version
24+
tags["releaseMode"] = releaseMode
25+
tags["vendor"] = vendor
26+
tags["id"] = UUID.randomUUID().toString()
27+
}
28+
29+
private fun sendEvents(events: Collection<Event>) {
30+
val body = convertToJson(events)
31+
send(body)
32+
}
33+
34+
private fun convertToJson(events: Collection<Event>): String {
35+
return StringBuilder().apply {
36+
append("[")
37+
append(
38+
events.map { event ->
39+
when (event) {
40+
is Event.Devices -> Metric(name = "testing.device", value = event.total, tags = tags)
41+
is Event.Executed -> Metric(name = "testing.duration", value = event.seconds, tags = tags)
42+
is Event.TestsTotal -> Metric(name = "testing.test", value = event.total, tags = tags)
43+
is Event.TestsRun -> Metric(name = "testing.executed", value = event.value, tags = tags)
44+
}
45+
}.joinToString(separator = ",") { it.toJson() }
46+
)
47+
append("]")
48+
}.toString()
49+
}
50+
51+
override fun close() {
52+
sendEvents(accumulator)
53+
}
54+
55+
private fun send(body: String) {
56+
try {
57+
val request = Request.Builder()
58+
.url("https://graphite-prod-13-prod-us-east-0.grafana.net/graphite/metrics")
59+
.header("Authorization", "Bearer $key")
60+
.post(body.toByteArray().toRequestBody("application/json".toMediaType()))
61+
.build()
62+
63+
client.newCall(request).execute().use { response ->
64+
if (!response.isSuccessful) {
65+
//se la vie, we shouldn't handle analytics errors because users don't care about them
66+
}
67+
}
68+
} catch (e: Exception) {
69+
//se la vie, we shouldn't handle analytics errors because users don't care about them
70+
}
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.malinskiy.marathon.usageanalytics.tracker
2+
3+
data class Metric(
4+
val name: String,
5+
val value: Number,
6+
val time: Long = System.currentTimeMillis() / 1000,
7+
val interval: Int = 1,
8+
val tags: Map<String, String> = emptyMap(),
9+
) {
10+
fun toJson() = """{"name":"$name","interval":$interval,"value":$value,"time":$time,"tags":[${
11+
tags.map { "${it.key}=${it.value}" }.joinToString(separator = ",") { "\"$it\""}
12+
}]}"""
13+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.malinskiy.marathon.usageanalytics.tracker
22

3+
import com.malinskiy.marathon.usageanalytics.Event
4+
35
interface UsageTracker {
46
fun trackEvent(event: Event)
5-
}
7+
fun meta(version: String, vendor: String, releaseMode: String)
8+
fun close()
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.malinskiy.marathon.usageanalytics.tracker
2+
3+
import org.amshove.kluent.`should be equal to`
4+
import org.junit.jupiter.api.Test
5+
6+
7+
class MetricTest {
8+
9+
@Test
10+
fun toJson() {
11+
val metric = Metric(
12+
name = "testing.count",
13+
interval = 1,
14+
value = 42,
15+
time = 1000,
16+
tags = mapOf("id" to "123")
17+
)
18+
metric.toJson().`should be equal to`("""{"name":"testing.count","interval":1,"value":42,"time":1000,"tags":["id=123"]}""")
19+
}
20+
}

Diff for: buildSrc/src/main/kotlin/Versions.kt

+2-5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ object Versions {
3030
val apacheCommonsText = "1.10.0"
3131
val apacheCommonsIO = "2.11.0"
3232
val apacheCommonsCodec = "1.15"
33+
val okhttp = "4.10.0"
3334
val influxDbClient = "2.23"
3435
val influxDb2Client = "6.8.0"
3536
val clikt = "3.5.2"
@@ -52,7 +53,6 @@ object Versions {
5253
val allureKotlin = "2.4.0"
5354
val allureEnvironment = "1.0.0"
5455
val mockitoKotlin = "2.2.0"
55-
val googleAnalitycsWrapper = "2.0.0"
5656
val dokka = "1.8.10"
5757
val koin = "3.4.0"
5858
val jsonAssert = "1.5.1"
@@ -104,6 +104,7 @@ object Libraries {
104104
val koin = "io.insert-koin:koin-core:${Versions.koin}"
105105
val bugsnag = "com.bugsnag:bugsnag:${Versions.bugsnag}"
106106
val kotlinProcess = "com.github.pgreze:kotlin-process:${Versions.kotlinProcess}"
107+
val okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp}"
107108
}
108109

109110
object TestLibraries {
@@ -126,7 +127,3 @@ object TestLibraries {
126127
val testContainersInflux = "org.testcontainers:influxdb:${Versions.testContainers}"
127128
val adamServerStubJunit5 = "com.malinskiy.adam:server-stub-junit5:${Versions.adam}"
128129
}
129-
130-
object Analytics {
131-
val googleAnalyticsWrapper = "com.brsanthu:google-analytics-java:${Versions.googleAnalitycsWrapper}"
132-
}

Diff for: cli/src/main/kotlin/com/malinskiy/marathon/cli/ApplicationView.kt

+2-6
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import com.malinskiy.marathon.di.marathonStartKoin
2121
import com.malinskiy.marathon.exceptions.ExceptionsReporterFactory
2222
import com.malinskiy.marathon.ios.AppleVendor
2323
import com.malinskiy.marathon.log.MarathonLogging
24-
import com.malinskiy.marathon.usageanalytics.TrackActionType
25-
import com.malinskiy.marathon.usageanalytics.UsageAnalytics
26-
import com.malinskiy.marathon.usageanalytics.tracker.Event
2724
import org.koin.core.context.stopKoin
2825
import org.koin.dsl.module
2926
import kotlin.system.exitProcess
@@ -59,7 +56,8 @@ private fun execute(cliConfiguration: CliConfiguration) {
5956
}
6057

6158
val configuration = ConfigurationFactory(
62-
marathonfileDir = marathonStartConfiguration.marathonfile.canonicalFile.parentFile
59+
marathonfileDir = marathonStartConfiguration.marathonfile.canonicalFile.parentFile,
60+
analyticsTracking = marathonStartConfiguration.analyticsTracking,
6361
).parse(marathonStartConfiguration.marathonfile)
6462
val vendorConfiguration = configuration.vendorConfiguration
6563
val modules = when (vendorConfiguration) {
@@ -75,8 +73,6 @@ private fun execute(cliConfiguration: CliConfiguration) {
7573
val application = marathonStartKoin(configuration, modules)
7674
val marathon: Marathon = application.koin.get()
7775

78-
UsageAnalytics.enable = marathonStartConfiguration.analyticsTracking
79-
UsageAnalytics.USAGE_TRACKER.trackEvent(Event(TrackActionType.RunType, "cli"))
8076
val success = try {
8177
marathon.run(marathonStartConfiguration.executionCommand)
8278
} catch (e: Exception) {

Diff for: cli/src/main/kotlin/com/malinskiy/marathon/cli/args/CliCommands.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class MarathonRunCommonOptions : OptionGroup() {
1515
.default(File("Marathonfile"))
1616
val analyticsTracking by option("--analyticsTracking", help="Enable anonymous analytics tracking")
1717
.convert { it.toBoolean() }
18-
.default(false)
18+
.default(true)
1919
val bugsnagReporting by option("--bugsnag", help="Enable/Disable anonymous crash reporting. Enabled by default")
2020
.convert { it.toBoolean() }
2121
.default(true)

Diff for: configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationFactory.kt

+13-4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class ConfigurationFactory(
4343
registerModule(JavaTimeModule())
4444
},
4545
private val environmentVariableSubstitutor: StringSubstitutor = StringSubstitutor(StringLookupFactory.INSTANCE.environmentVariableStringLookup()),
46+
private val analyticsTracking: Boolean? = null,
4647
) {
4748
fun parse(marathonfile: File): Configuration {
4849
val configWithEnvironmentVariablesReplaced = environmentVariableSubstitutor.replace(marathonfile.readText())
@@ -69,8 +70,13 @@ class ConfigurationFactory(
6970
val resolvedApplication = it.application?.let { ddd -> marathonfileDir.resolve(ddd) }
7071
val resolvedTestApplication = it.testApplication?.let { ddd -> marathonfileDir.resolve(ddd) }
7172
val resolvedExtraApplications = it.extraApplications?.map { ddd -> marathonfileDir.resolve(ddd) }
72-
73-
AppleTestBundleConfiguration(resolvedApplication, resolvedTestApplication, resolvedExtraApplications, resolvedDerivedDataDir).apply { validate() }
73+
74+
AppleTestBundleConfiguration(
75+
resolvedApplication,
76+
resolvedTestApplication,
77+
resolvedExtraApplications,
78+
resolvedDerivedDataDir
79+
).apply { validate() }
7480
}
7581
val optionalDevices = configuration.vendorConfiguration.devicesFile?.resolveAgainst(marathonfileDir)
7682
?: marathonfileDir.resolve("Marathondevices")
@@ -89,7 +95,7 @@ class ConfigurationFactory(
8995
authentication = optionalSshAuthentication,
9096
knownHostsPath = optionalknownHostsPath,
9197
)
92-
98+
9399
configuration.vendorConfiguration.copy(
94100
bundle = resolvedBundle,
95101
devicesFile = optionalDevices,
@@ -101,7 +107,10 @@ class ConfigurationFactory(
101107
is VendorConfiguration.EmptyVendorConfiguration -> throw ConfigurationException("No vendor configuration specified")
102108
}
103109

104-
return configuration.copy(vendorConfiguration = vendorConfiguration)
110+
return configuration.copy(
111+
vendorConfiguration = vendorConfiguration,
112+
analyticsTracking = analyticsTracking ?: configuration.analyticsTracking
113+
)
105114
} catch (e: JsonProcessingException) {
106115
throw ConfigurationException("Error parsing config file ${marathonfile.absolutePath}", e)
107116
}

0 commit comments

Comments
 (0)