Skip to content

Commit 3707f6c

Browse files
committed
feat(analytics): configurable missing analytics values
1 parent 1fc058b commit 3707f6c

File tree

17 files changed

+138
-69
lines changed

17 files changed

+138
-69
lines changed

configuration/src/main/kotlin/com/malinskiy/marathon/config/AnalyticsConfiguration.kt

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.malinskiy.marathon.config
22

33
import com.fasterxml.jackson.annotation.JsonSubTypes
44
import com.fasterxml.jackson.annotation.JsonTypeInfo
5+
import com.malinskiy.marathon.config.analytics.Defaults
56

67
@JsonTypeInfo(
78
use = JsonTypeInfo.Id.NAME,
@@ -21,7 +22,8 @@ sealed class AnalyticsConfiguration {
2122
val user: String,
2223
val password: String,
2324
val dbName: String,
24-
val retentionPolicyConfiguration: RetentionPolicyConfiguration
25+
val retentionPolicyConfiguration: RetentionPolicyConfiguration,
26+
val defaults: Defaults = Defaults(),
2527
) : AnalyticsConfiguration() {
2628
data class RetentionPolicyConfiguration(
2729
val name: String,
@@ -48,7 +50,8 @@ sealed class AnalyticsConfiguration {
4850
val token: String,
4951
val organization: String,
5052
val bucket: String,
51-
val retentionPolicyConfiguration: RetentionPolicyConfiguration = RetentionPolicyConfiguration.default
53+
val retentionPolicyConfiguration: RetentionPolicyConfiguration = RetentionPolicyConfiguration.default,
54+
val defaults: Defaults = Defaults(),
5255
) : AnalyticsConfiguration() {
5356
data class RetentionPolicyConfiguration(
5457
val everySeconds: Int,
@@ -69,6 +72,7 @@ sealed class AnalyticsConfiguration {
6972
data class GraphiteConfiguration(
7073
val host: String,
7174
val port: Int?,
72-
val prefix: String?
75+
val prefix: String?,
76+
val defaults: Defaults = Defaults(),
7377
) : AnalyticsConfiguration()
7478
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.malinskiy.marathon.config.analytics
2+
3+
import java.time.Duration
4+
5+
data class Defaults(val successRate: Double = .0, val duration: Duration = Duration.ofMinutes(5))

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

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.malinskiy.marathon.config.serialization
22

33
import com.fasterxml.jackson.annotation.JsonInclude
44
import com.fasterxml.jackson.core.JsonProcessingException
5+
import com.fasterxml.jackson.databind.DeserializationFeature
56
import com.fasterxml.jackson.databind.ObjectMapper
67
import com.fasterxml.jackson.databind.SerializationFeature
78
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
@@ -43,6 +44,7 @@ class ConfigurationFactory(
4344
)
4445
registerModule(JavaTimeModule())
4546
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
47+
configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
4648
},
4749
private val environmentVariableSubstitutor: StringSubstitutor = StringSubstitutor(StringLookupFactory.INSTANCE.environmentVariableStringLookup()),
4850
private val analyticsTracking: Boolean? = null,

configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/yaml/GraphiteConfigurationDeserializer.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationContext
55
import com.fasterxml.jackson.databind.JsonNode
66
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
77
import com.malinskiy.marathon.config.AnalyticsConfiguration
8+
import com.malinskiy.marathon.config.analytics.Defaults
89
import com.malinskiy.marathon.config.exceptions.ConfigurationException
910

1011
class GraphiteConfigurationDeserializer
@@ -17,13 +18,16 @@ class GraphiteConfigurationDeserializer
1718
val host = node?.get("host")?.asText()
1819
val portString = node?.get("port")?.asText()
1920
val prefix = node?.get("prefix")?.asText()
21+
val defaults = node?.get("defaults")?.let {
22+
ctxt?.readTreeAsValue(it, Defaults::class.java)
23+
} ?: Defaults()
2024

2125
val port = portString?.toIntOrNull()
2226

2327
if (host == null) throw ConfigurationException("GraphiteConfigurationDeserializer: host should be specified")
2428
if (portString != null && port == null) throw ConfigurationException("GraphiteConfigurationDeserializer: port should be a number, e.g. 2003")
2529
if (prefix?.isEmpty() == true) throw ConfigurationException("GraphiteConfigurationDeserializer: prefix cannot be empty")
2630

27-
return AnalyticsConfiguration.GraphiteConfiguration(host, port, prefix)
31+
return AnalyticsConfiguration.GraphiteConfiguration(host, port, prefix, defaults)
2832
}
2933
}

configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/yaml/InfluxDbConfigurationDeserializer.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationContext
55
import com.fasterxml.jackson.databind.JsonNode
66
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
77
import com.malinskiy.marathon.config.AnalyticsConfiguration
8+
import com.malinskiy.marathon.config.analytics.Defaults
89
import com.malinskiy.marathon.config.exceptions.ConfigurationException
910

1011
class InfluxDbConfigurationDeserializer
@@ -23,11 +24,15 @@ class InfluxDbConfigurationDeserializer
2324
retentionPolicyNode?.let { ctxt?.readValue(retentionPolicyNode, policyClazz) }
2425
?: AnalyticsConfiguration.InfluxDbConfiguration.RetentionPolicyConfiguration.default
2526

27+
val defaults = node?.get("defaults")?.let {
28+
ctxt?.readTreeAsValue(it, Defaults::class.java)
29+
} ?: Defaults()
30+
2631
if (url == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: url should be specified")
2732
if (user == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: user should be specified")
2833
if (password == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: password should be specified")
2934
if (dbName == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: dbName should be specified")
3035

31-
return AnalyticsConfiguration.InfluxDbConfiguration(url, user, password, dbName, retentionPolicyConfiguration)
36+
return AnalyticsConfiguration.InfluxDbConfiguration(url, user, password, dbName, retentionPolicyConfiguration, defaults)
3237
}
3338
}

configuration/src/test/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationFactoryTest.kt

+33-21
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
99
import com.malinskiy.marathon.config.AnalyticsConfiguration
1010
import com.malinskiy.marathon.config.ScreenRecordingPolicy
1111
import com.malinskiy.marathon.config.TestFilterConfiguration
12+
import com.malinskiy.marathon.config.analytics.Defaults
1213
import com.malinskiy.marathon.config.environment.EnvironmentConfiguration
1314
import com.malinskiy.marathon.config.environment.EnvironmentReader
1415
import com.malinskiy.marathon.config.exceptions.ConfigurationException
@@ -72,14 +73,16 @@ class ConfigurationFactoryTest {
7273
mockMarathonFileDir,
7374
)
7475
)
75-
.registerModule(KotlinModule.Builder()
76-
.withReflectionCacheSize(512)
77-
.configure(KotlinFeature.NullToEmptyCollection, false)
78-
.configure(KotlinFeature.NullToEmptyMap, false)
79-
.configure(KotlinFeature.NullIsSameAsDefault, false)
80-
.configure(KotlinFeature.SingletonSupport, true)
81-
.configure(KotlinFeature.StrictNullChecks, false)
82-
.build())
76+
.registerModule(
77+
KotlinModule.Builder()
78+
.withReflectionCacheSize(512)
79+
.configure(KotlinFeature.NullToEmptyCollection, false)
80+
.configure(KotlinFeature.NullToEmptyMap, false)
81+
.configure(KotlinFeature.NullIsSameAsDefault, false)
82+
.configure(KotlinFeature.SingletonSupport, true)
83+
.configure(KotlinFeature.StrictNullChecks, false)
84+
.build()
85+
)
8386
.registerModule(JavaTimeModule())
8487
parser = ConfigurationFactory(
8588
marathonfileDir = mockMarathonFileDir,
@@ -99,7 +102,8 @@ class ConfigurationFactoryTest {
99102
user = "root",
100103
password = "root",
101104
dbName = "marathon",
102-
retentionPolicyConfiguration = AnalyticsConfiguration.InfluxDbConfiguration.RetentionPolicyConfiguration.default
105+
retentionPolicyConfiguration = AnalyticsConfiguration.InfluxDbConfiguration.RetentionPolicyConfiguration.default,
106+
defaults = Defaults(),
103107
)
104108
val poolingStrategyConfiguration = configuration.poolingStrategy as PoolingStrategyConfiguration.ComboPoolingStrategyConfiguration
105109
poolingStrategyConfiguration.list[0] shouldBeInstanceOf PoolingStrategyConfiguration.OmniPoolingStrategyConfiguration::class
@@ -208,7 +212,8 @@ class ConfigurationFactoryTest {
208212
"1h",
209213
5,
210214
false
211-
)
215+
),
216+
defaults = Defaults(),
212217
)
213218
}
214219

@@ -256,7 +261,6 @@ class ConfigurationFactoryTest {
256261
}
257262

258263

259-
260264
@Test
261265
fun `on configuration without an explicit xctestrun path should throw an exception`() {
262266
val file = File(ConfigurationFactoryTest::class.java.getResource("/fixture/config/sample_5.yaml").file)
@@ -276,14 +280,16 @@ class ConfigurationFactoryTest {
276280
mockMarathonFileDir,
277281
)
278282
)
279-
.registerModule(KotlinModule.Builder()
280-
.withReflectionCacheSize(512)
281-
.configure(KotlinFeature.NullToEmptyCollection, false)
282-
.configure(KotlinFeature.NullToEmptyMap, false)
283-
.configure(KotlinFeature.NullIsSameAsDefault, false)
284-
.configure(KotlinFeature.SingletonSupport, true)
285-
.configure(KotlinFeature.StrictNullChecks, false)
286-
.build())
283+
.registerModule(
284+
KotlinModule.Builder()
285+
.withReflectionCacheSize(512)
286+
.configure(KotlinFeature.NullToEmptyCollection, false)
287+
.configure(KotlinFeature.NullToEmptyMap, false)
288+
.configure(KotlinFeature.NullIsSameAsDefault, false)
289+
.configure(KotlinFeature.SingletonSupport, true)
290+
.configure(KotlinFeature.StrictNullChecks, false)
291+
.build()
292+
)
287293
.registerModule(JavaTimeModule())
288294
parser = ConfigurationFactory(
289295
marathonfileDir = mockMarathonFileDir,
@@ -391,15 +397,21 @@ class ConfigurationFactoryTest {
391397
outputs shouldBeEqualTo listOf(
392398
AndroidTestBundleConfiguration(
393399
application = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/debug/first-app-debug.apk"),
394-
testApplication = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/first-app-debug-androidTest.apk"),
400+
testApplication = File(
401+
mockMarathonFileDir,
402+
"kotlin-buildscript/build/outputs/apk/androidTest/debug/first-app-debug-androidTest.apk"
403+
),
395404
extraApplications = null,
396405
splitApks = listOf(
397406
File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/first-app-split-debug.apk")
398407
)
399408
),
400409
AndroidTestBundleConfiguration(
401410
application = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/debug/second-app-debug.apk"),
402-
testApplication = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/second-app-debug-androidTest.apk"),
411+
testApplication = File(
412+
mockMarathonFileDir,
413+
"kotlin-buildscript/build/outputs/apk/androidTest/debug/second-app-debug-androidTest.apk"
414+
),
403415
extraApplications = null,
404416
splitApks = listOf(
405417
File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/second-app-split-debug.apk")

configuration/src/test/kotlin/com/malinskiy/marathon/config/serialization/ConfigurationSerializationTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class ConfigurationSerializationTest {
7777
)
7878
registerModule(JavaTimeModule())
7979
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
80+
configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
8081
}
8182
parser = ConfigurationFactory(
8283
marathonfileDir = marathonfileDir,

core/src/integrationTest/kotlin/com/malinskiy/marathon/analytics/metrics/remote/BaseMetricsProviderIntegrationTest.kt

+9-7
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package com.malinskiy.marathon.analytics.metrics.remote
33
import com.malinskiy.marathon.analytics.external.MetricsProvider
44
import com.malinskiy.marathon.analytics.external.MetricsProviderImpl
55
import com.malinskiy.marathon.generateTest
6+
import org.amshove.kluent.shouldBeEqualTo
67
import org.amshove.kluent.shouldBeInRange
78
import org.amshove.kluent.shouldEqualTo
89
import org.junit.jupiter.api.BeforeEach
910
import org.junit.jupiter.api.Test
11+
import java.time.Duration
1012
import java.time.Instant
1113
import java.time.temporal.ChronoUnit
1214

@@ -19,19 +21,19 @@ abstract class BaseMetricsProviderIntegrationTest {
1921

2022
@BeforeEach
2123
fun setUp() {
22-
metricsProvider = MetricsProviderImpl(createRemoteDataSource())
24+
metricsProvider = MetricsProviderImpl(createRemoteDataSource(), .0, Duration.ofMinutes(5))
2325
}
2426

2527
@Test
2628
fun `on empty db success rate default value is 0`() {
2729
val result = metricsProvider.successRate(test, Instant.now())
28-
result shouldEqualTo 0.0
30+
result shouldBeEqualTo 0.0
2931
}
3032

3133
@Test
3234
fun `execution time default value is 300_000`() {
3335
val result = metricsProvider.executionTime(test, 90.0, Instant.now())
34-
result shouldEqualTo 300_000.0
36+
result shouldBeEqualTo 300_000.0
3537
}
3638

3739
@Test
@@ -49,19 +51,19 @@ abstract class BaseMetricsProviderIntegrationTest {
4951
@Test
5052
fun `verify execution time 50 percentile for 25 minutes`() {
5153
val result = metricsProvider.executionTime(test, 50.0, Instant.now().minus(25, ChronoUnit.MINUTES))
52-
result shouldEqualTo 2000.0
54+
result shouldBeEqualTo 2000.0
5355
}
5456

5557
@Test
5658
fun `verify execution time 90 percentile for 25 minutes`() {
5759
val result = metricsProvider.executionTime(test, 90.0, Instant.now().minus(35, ChronoUnit.MINUTES))
58-
result shouldEqualTo 4000.0
60+
result shouldBeEqualTo 4000.0
5961
}
6062

6163
@Test
6264
fun `verify test success rate should return 1 for last 50 minutes`() {
6365
val result = metricsProvider.successRate(test, Instant.now().minus(50, ChronoUnit.MINUTES))
64-
result shouldEqualTo 1.0
66+
result shouldBeEqualTo 1.0
6567
}
6668

6769
@Test
@@ -73,6 +75,6 @@ abstract class BaseMetricsProviderIntegrationTest {
7375
@Test
7476
fun `verify test success rate should return 0_5 for last 2 hours`() {
7577
val result = metricsProvider.successRate(test, Instant.now().minus(2, ChronoUnit.HOURS))
76-
result shouldEqualTo 0.5
78+
result shouldBeEqualTo 0.5
7779
}
7880
}

core/src/integrationTest/kotlin/com/malinskiy/marathon/analytics/metrics/remote/influx/InfluxDbProviderIntegrationTest.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import com.malinskiy.marathon.analytics.external.influx.InfluxDbTracker
66
import com.malinskiy.marathon.analytics.metrics.remote.getTestEvents
77
import com.malinskiy.marathon.config.AnalyticsConfiguration
88
import com.malinskiy.marathon.generateTest
9+
import org.amshove.kluent.shouldBeEqualTo
910
import org.amshove.kluent.shouldEqualTo
1011
import org.influxdb.InfluxDB
1112
import org.junit.jupiter.api.AfterEach
1213
import org.junit.jupiter.api.BeforeEach
1314
import org.junit.jupiter.api.Test
15+
import java.time.Duration
1416
import java.time.Instant
1517
import java.time.temporal.ChronoUnit
1618

@@ -57,13 +59,13 @@ class InfluxDbProviderIntegrationTest {
5759
thirdDbInstance = provider.createDb()
5860

5961
val metricsProvider =
60-
MetricsProviderImpl(InfluxDBDataSource(thirdDbInstance!!, database, rpName))
62+
MetricsProviderImpl(InfluxDBDataSource(thirdDbInstance!!, database, rpName), .0, Duration.ofMinutes(5))
6163

6264
val result = metricsProvider.executionTime(
6365
test,
6466
50.0,
6567
Instant.now().minus(2, ChronoUnit.DAYS)
6668
)
67-
result shouldEqualTo 5000.0
69+
result shouldBeEqualTo 5000.0
6870
}
6971
}

core/src/integrationTest/kotlin/com/malinskiy/marathon/analytics/metrics/remote/influx2/InfluxDb2ProviderIntegrationTest.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.amshove.kluent.shouldBeEqualTo
1111
import org.junit.jupiter.api.AfterEach
1212
import org.junit.jupiter.api.BeforeEach
1313
import org.junit.jupiter.api.Test
14+
import java.time.Duration
1415
import java.time.Instant
1516
import java.time.temporal.ChronoUnit
1617

@@ -65,7 +66,7 @@ class InfluxDb2ProviderIntegrationTest {
6566
thirdDbInstance = provider.createDb()
6667

6768
val metricsProvider =
68-
MetricsProviderImpl(InfluxDB2DataSource(thirdDbInstance!!, bucket))
69+
MetricsProviderImpl(InfluxDB2DataSource(thirdDbInstance!!, bucket), .0, Duration.ofMinutes(5))
6970

7071
val result1 = metricsProvider.executionTime(
7172
test,

core/src/main/kotlin/com/malinskiy/marathon/analytics/external/MetricsProviderFactory.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal class MetricsProviderFactory(configuration: Configuration) {
2828
return try {
2929
val db = InfluxDbProvider(configuration, debug).createDb()
3030
val dataSource = InfluxDBDataSource(db, configuration.dbName, configuration.retentionPolicyConfiguration.name)
31-
MetricsProviderImpl(dataSource)
31+
MetricsProviderImpl(dataSource, configuration.defaults.successRate, configuration.defaults.duration)
3232
} catch (e: Exception) {
3333
fallback
3434
}
@@ -38,13 +38,13 @@ internal class MetricsProviderFactory(configuration: Configuration) {
3838
return try {
3939
val db = InfluxDb2Provider(configuration, debug).createDb()
4040
val dataSource = InfluxDB2DataSource(db, configuration.bucket)
41-
MetricsProviderImpl(dataSource)
41+
MetricsProviderImpl(dataSource, configuration.defaults.successRate, configuration.defaults.duration)
4242
} catch (e: Exception) {
4343
fallback
4444
}
4545
}
4646

4747
private fun createGraphiteMetricsProvider(configuration: GraphiteConfiguration): MetricsProvider {
48-
return MetricsProviderImpl(GraphiteDataSource(QueryableGraphiteClient(configuration.host), configuration.prefix))
48+
return MetricsProviderImpl(GraphiteDataSource(QueryableGraphiteClient(configuration.host), configuration.prefix), configuration.defaults.successRate, configuration.defaults.duration)
4949
}
5050
}

0 commit comments

Comments
 (0)