Skip to content

Commit

Permalink
feat(analytics): configurable missing analytics values
Browse files Browse the repository at this point in the history
  • Loading branch information
Malinskiy committed Feb 24, 2024
1 parent 1fc058b commit 3707f6c
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.malinskiy.marathon.config

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import com.malinskiy.marathon.config.analytics.Defaults

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
Expand All @@ -21,7 +22,8 @@ sealed class AnalyticsConfiguration {
val user: String,
val password: String,
val dbName: String,
val retentionPolicyConfiguration: RetentionPolicyConfiguration
val retentionPolicyConfiguration: RetentionPolicyConfiguration,
val defaults: Defaults = Defaults(),
) : AnalyticsConfiguration() {
data class RetentionPolicyConfiguration(
val name: String,
Expand All @@ -48,7 +50,8 @@ sealed class AnalyticsConfiguration {
val token: String,
val organization: String,
val bucket: String,
val retentionPolicyConfiguration: RetentionPolicyConfiguration = RetentionPolicyConfiguration.default
val retentionPolicyConfiguration: RetentionPolicyConfiguration = RetentionPolicyConfiguration.default,
val defaults: Defaults = Defaults(),

Check warning on line 54 in configuration/src/main/kotlin/com/malinskiy/marathon/config/AnalyticsConfiguration.kt

View check run for this annotation

Codecov / codecov/patch

configuration/src/main/kotlin/com/malinskiy/marathon/config/AnalyticsConfiguration.kt#L53-L54

Added lines #L53 - L54 were not covered by tests
) : AnalyticsConfiguration() {
data class RetentionPolicyConfiguration(
val everySeconds: Int,
Expand All @@ -69,6 +72,7 @@ sealed class AnalyticsConfiguration {
data class GraphiteConfiguration(
val host: String,
val port: Int?,
val prefix: String?
val prefix: String?,
val defaults: Defaults = Defaults(),

Check warning on line 76 in configuration/src/main/kotlin/com/malinskiy/marathon/config/AnalyticsConfiguration.kt

View check run for this annotation

Codecov / codecov/patch

configuration/src/main/kotlin/com/malinskiy/marathon/config/AnalyticsConfiguration.kt#L75-L76

Added lines #L75 - L76 were not covered by tests
) : AnalyticsConfiguration()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.malinskiy.marathon.config.analytics

import java.time.Duration

data class Defaults(val successRate: Double = .0, val duration: Duration = Duration.ofMinutes(5))
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.malinskiy.marathon.config.serialization

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
Expand Down Expand Up @@ -43,6 +44,7 @@ class ConfigurationFactory(
)
registerModule(JavaTimeModule())
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
},
private val environmentVariableSubstitutor: StringSubstitutor = StringSubstitutor(StringLookupFactory.INSTANCE.environmentVariableStringLookup()),
private val analyticsTracking: Boolean? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.malinskiy.marathon.config.AnalyticsConfiguration
import com.malinskiy.marathon.config.analytics.Defaults
import com.malinskiy.marathon.config.exceptions.ConfigurationException

class GraphiteConfigurationDeserializer
Expand All @@ -17,13 +18,16 @@ class GraphiteConfigurationDeserializer
val host = node?.get("host")?.asText()
val portString = node?.get("port")?.asText()
val prefix = node?.get("prefix")?.asText()
val defaults = node?.get("defaults")?.let {
ctxt?.readTreeAsValue(it, Defaults::class.java)
} ?: Defaults()

Check warning on line 23 in configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/yaml/GraphiteConfigurationDeserializer.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L23 was not covered by tests

val port = portString?.toIntOrNull()

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

return AnalyticsConfiguration.GraphiteConfiguration(host, port, prefix)
return AnalyticsConfiguration.GraphiteConfiguration(host, port, prefix, defaults)

Check warning on line 31 in configuration/src/main/kotlin/com/malinskiy/marathon/config/serialization/yaml/GraphiteConfigurationDeserializer.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L31 was not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.malinskiy.marathon.config.AnalyticsConfiguration
import com.malinskiy.marathon.config.analytics.Defaults
import com.malinskiy.marathon.config.exceptions.ConfigurationException

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

val defaults = node?.get("defaults")?.let {
ctxt?.readTreeAsValue(it, Defaults::class.java)
} ?: Defaults()

if (url == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: url should be specified")
if (user == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: user should be specified")
if (password == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: password should be specified")
if (dbName == null) throw ConfigurationException("InfluxDbConfigurationDeserializer: dbName should be specified")

return AnalyticsConfiguration.InfluxDbConfiguration(url, user, password, dbName, retentionPolicyConfiguration)
return AnalyticsConfiguration.InfluxDbConfiguration(url, user, password, dbName, retentionPolicyConfiguration, defaults)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.malinskiy.marathon.config.AnalyticsConfiguration
import com.malinskiy.marathon.config.ScreenRecordingPolicy
import com.malinskiy.marathon.config.TestFilterConfiguration
import com.malinskiy.marathon.config.analytics.Defaults
import com.malinskiy.marathon.config.environment.EnvironmentConfiguration
import com.malinskiy.marathon.config.environment.EnvironmentReader
import com.malinskiy.marathon.config.exceptions.ConfigurationException
Expand Down Expand Up @@ -72,14 +73,16 @@ class ConfigurationFactoryTest {
mockMarathonFileDir,
)
)
.registerModule(KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.SingletonSupport, true)
.configure(KotlinFeature.StrictNullChecks, false)
.build())
.registerModule(
KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.SingletonSupport, true)
.configure(KotlinFeature.StrictNullChecks, false)
.build()
)
.registerModule(JavaTimeModule())
parser = ConfigurationFactory(
marathonfileDir = mockMarathonFileDir,
Expand All @@ -99,7 +102,8 @@ class ConfigurationFactoryTest {
user = "root",
password = "root",
dbName = "marathon",
retentionPolicyConfiguration = AnalyticsConfiguration.InfluxDbConfiguration.RetentionPolicyConfiguration.default
retentionPolicyConfiguration = AnalyticsConfiguration.InfluxDbConfiguration.RetentionPolicyConfiguration.default,
defaults = Defaults(),
)
val poolingStrategyConfiguration = configuration.poolingStrategy as PoolingStrategyConfiguration.ComboPoolingStrategyConfiguration
poolingStrategyConfiguration.list[0] shouldBeInstanceOf PoolingStrategyConfiguration.OmniPoolingStrategyConfiguration::class
Expand Down Expand Up @@ -208,7 +212,8 @@ class ConfigurationFactoryTest {
"1h",
5,
false
)
),
defaults = Defaults(),
)
}

Expand Down Expand Up @@ -256,7 +261,6 @@ class ConfigurationFactoryTest {
}



@Test
fun `on configuration without an explicit xctestrun path should throw an exception`() {
val file = File(ConfigurationFactoryTest::class.java.getResource("/fixture/config/sample_5.yaml").file)
Expand All @@ -276,14 +280,16 @@ class ConfigurationFactoryTest {
mockMarathonFileDir,
)
)
.registerModule(KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.SingletonSupport, true)
.configure(KotlinFeature.StrictNullChecks, false)
.build())
.registerModule(
KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.SingletonSupport, true)
.configure(KotlinFeature.StrictNullChecks, false)
.build()
)
.registerModule(JavaTimeModule())
parser = ConfigurationFactory(
marathonfileDir = mockMarathonFileDir,
Expand Down Expand Up @@ -391,15 +397,21 @@ class ConfigurationFactoryTest {
outputs shouldBeEqualTo listOf(
AndroidTestBundleConfiguration(
application = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/debug/first-app-debug.apk"),
testApplication = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/first-app-debug-androidTest.apk"),
testApplication = File(
mockMarathonFileDir,
"kotlin-buildscript/build/outputs/apk/androidTest/debug/first-app-debug-androidTest.apk"
),
extraApplications = null,
splitApks = listOf(
File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/first-app-split-debug.apk")
)
),
AndroidTestBundleConfiguration(
application = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/debug/second-app-debug.apk"),
testApplication = File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/second-app-debug-androidTest.apk"),
testApplication = File(
mockMarathonFileDir,
"kotlin-buildscript/build/outputs/apk/androidTest/debug/second-app-debug-androidTest.apk"
),
extraApplications = null,
splitApks = listOf(
File(mockMarathonFileDir, "kotlin-buildscript/build/outputs/apk/androidTest/debug/second-app-split-debug.apk")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class ConfigurationSerializationTest {
)
registerModule(JavaTimeModule())
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
}
parser = ConfigurationFactory(
marathonfileDir = marathonfileDir,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package com.malinskiy.marathon.analytics.metrics.remote
import com.malinskiy.marathon.analytics.external.MetricsProvider
import com.malinskiy.marathon.analytics.external.MetricsProviderImpl
import com.malinskiy.marathon.generateTest
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeInRange
import org.amshove.kluent.shouldEqualTo
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit

Expand All @@ -19,19 +21,19 @@ abstract class BaseMetricsProviderIntegrationTest {

@BeforeEach
fun setUp() {
metricsProvider = MetricsProviderImpl(createRemoteDataSource())
metricsProvider = MetricsProviderImpl(createRemoteDataSource(), .0, Duration.ofMinutes(5))
}

@Test
fun `on empty db success rate default value is 0`() {
val result = metricsProvider.successRate(test, Instant.now())
result shouldEqualTo 0.0
result shouldBeEqualTo 0.0
}

@Test
fun `execution time default value is 300_000`() {
val result = metricsProvider.executionTime(test, 90.0, Instant.now())
result shouldEqualTo 300_000.0
result shouldBeEqualTo 300_000.0
}

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

@Test
fun `verify execution time 90 percentile for 25 minutes`() {
val result = metricsProvider.executionTime(test, 90.0, Instant.now().minus(35, ChronoUnit.MINUTES))
result shouldEqualTo 4000.0
result shouldBeEqualTo 4000.0
}

@Test
fun `verify test success rate should return 1 for last 50 minutes`() {
val result = metricsProvider.successRate(test, Instant.now().minus(50, ChronoUnit.MINUTES))
result shouldEqualTo 1.0
result shouldBeEqualTo 1.0
}

@Test
Expand All @@ -73,6 +75,6 @@ abstract class BaseMetricsProviderIntegrationTest {
@Test
fun `verify test success rate should return 0_5 for last 2 hours`() {
val result = metricsProvider.successRate(test, Instant.now().minus(2, ChronoUnit.HOURS))
result shouldEqualTo 0.5
result shouldBeEqualTo 0.5
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import com.malinskiy.marathon.analytics.external.influx.InfluxDbTracker
import com.malinskiy.marathon.analytics.metrics.remote.getTestEvents
import com.malinskiy.marathon.config.AnalyticsConfiguration
import com.malinskiy.marathon.generateTest
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldEqualTo
import org.influxdb.InfluxDB
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit

Expand Down Expand Up @@ -57,13 +59,13 @@ class InfluxDbProviderIntegrationTest {
thirdDbInstance = provider.createDb()

val metricsProvider =
MetricsProviderImpl(InfluxDBDataSource(thirdDbInstance!!, database, rpName))
MetricsProviderImpl(InfluxDBDataSource(thirdDbInstance!!, database, rpName), .0, Duration.ofMinutes(5))

val result = metricsProvider.executionTime(
test,
50.0,
Instant.now().minus(2, ChronoUnit.DAYS)
)
result shouldEqualTo 5000.0
result shouldBeEqualTo 5000.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.amshove.kluent.shouldBeEqualTo
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.Duration
import java.time.Instant
import java.time.temporal.ChronoUnit

Expand Down Expand Up @@ -65,7 +66,7 @@ class InfluxDb2ProviderIntegrationTest {
thirdDbInstance = provider.createDb()

val metricsProvider =
MetricsProviderImpl(InfluxDB2DataSource(thirdDbInstance!!, bucket))
MetricsProviderImpl(InfluxDB2DataSource(thirdDbInstance!!, bucket), .0, Duration.ofMinutes(5))

val result1 = metricsProvider.executionTime(
test,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal class MetricsProviderFactory(configuration: Configuration) {
return try {
val db = InfluxDbProvider(configuration, debug).createDb()
val dataSource = InfluxDBDataSource(db, configuration.dbName, configuration.retentionPolicyConfiguration.name)
MetricsProviderImpl(dataSource)
MetricsProviderImpl(dataSource, configuration.defaults.successRate, configuration.defaults.duration)

Check warning on line 31 in core/src/main/kotlin/com/malinskiy/marathon/analytics/external/MetricsProviderFactory.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L31 was not covered by tests
} catch (e: Exception) {
fallback
}
Expand All @@ -38,13 +38,13 @@ internal class MetricsProviderFactory(configuration: Configuration) {
return try {
val db = InfluxDb2Provider(configuration, debug).createDb()
val dataSource = InfluxDB2DataSource(db, configuration.bucket)
MetricsProviderImpl(dataSource)
MetricsProviderImpl(dataSource, configuration.defaults.successRate, configuration.defaults.duration)

Check warning on line 41 in core/src/main/kotlin/com/malinskiy/marathon/analytics/external/MetricsProviderFactory.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L41 was not covered by tests
} catch (e: Exception) {
fallback
}
}

private fun createGraphiteMetricsProvider(configuration: GraphiteConfiguration): MetricsProvider {
return MetricsProviderImpl(GraphiteDataSource(QueryableGraphiteClient(configuration.host), configuration.prefix))
return MetricsProviderImpl(GraphiteDataSource(QueryableGraphiteClient(configuration.host), configuration.prefix), configuration.defaults.successRate, configuration.defaults.duration)

Check warning on line 48 in core/src/main/kotlin/com/malinskiy/marathon/analytics/external/MetricsProviderFactory.kt

View check run for this annotation

Codecov / codecov/patch

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

Added line #L48 was not covered by tests
}
}
Loading

0 comments on commit 3707f6c

Please sign in to comment.