diff --git a/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt new file mode 100644 index 0000000..f3a05c8 --- /dev/null +++ b/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt @@ -0,0 +1,149 @@ +package com.featurevisor.testRunner + +import com.featurevisor.sdk.FeaturevisorInstance +import com.featurevisor.sdk.getVariable +import com.featurevisor.sdk.getVariation +import com.featurevisor.sdk.isEnabled +import com.featurevisor.types.* + +data class BenchmarkOutput( + val value: Any? = null, + val duration: Double +) + +data class BenchMarkOptions( + val environment: String = "", + val feature: String = "", + val n: Int = 0, + val projectRootPath: String = "", + val context: Context = emptyMap(), + val variation: Boolean? = null, + val variable: String? = null, +) + +fun benchmarkFeature(option: BenchMarkOptions) { + println("Running benchmark for feature ${option.feature}...") + + println("Building datafile containing all features for ${option.environment}...") + + val datafileBuildStart = System.nanoTime().toDouble() + + val datafileContent = buildDataFileForStaging(option.projectRootPath) + + val datafileBuildEnd = System.nanoTime().toDouble() + + val datafileBuildDuration = datafileBuildEnd - datafileBuildStart + + println("Datafile build duration: ${convertNanoSecondToMilliSecond(datafileBuildDuration)}") + + val sdk = initializeSdkWithDataFileContent(datafileContent) + + println("...SDK initialized") + + println("Against context: ${option.context}") + + val output: BenchmarkOutput + + if (option.variable != null) { + println("Evaluating variable ${option.variable} ${option.n} times...") + + output = benchmarkFeatureVariable( + sdk, + feature = option.feature, + variableKey = option.variable, + context = option.context, + n = option.n + ) + + } else if (option.variation != null) { + println("Evaluating variation ${option.variation} ${option.n} times...") + + output = benchmarkFeatureVariation( + sdk, + feature = option.feature, + context = option.context, + n = option.n + ) + } else { + println("Evaluating flag ${option.n} times...") + + output = benchmarkFeatureFlag( + sdk, + feature = option.feature, + context = option.context, + n = option.n + ) + } + + println("Evaluated value : ${output.value}") + println("Total duration : ${convertNanoSecondToMilliSecond(output.duration)}") + if (option.n != 0) { + println("Average duration: ${convertNanoSecondToMilliSecond(output.duration / option.n)}") + } +} + + +fun benchmarkFeatureFlag( + f: FeaturevisorInstance, + feature: FeatureKey, + context: Context, + n: Int +): BenchmarkOutput { + val start = System.nanoTime().toDouble() + var value: Any = false + + for (i in 0..n) { + value = f.isEnabled(featureKey = feature, context = context) + } + + val end = System.nanoTime().toDouble() + + return BenchmarkOutput( + value = value, + duration = end - start + ) +} + + +fun benchmarkFeatureVariation( + f: FeaturevisorInstance, + feature: FeatureKey, + context: Context, + n: Int +): BenchmarkOutput { + val start = System.nanoTime().toDouble() + var value: VariationValue? = null + + for (i in 0..n) { + value = f.getVariation(featureKey = feature, context = context) + } + + val end = System.nanoTime().toDouble() + + return BenchmarkOutput( + value = value, + duration = end - start + ) +} + +fun benchmarkFeatureVariable( + f: FeaturevisorInstance, + feature: FeatureKey, + variableKey: VariableKey, + context: Context, + n: Int +): BenchmarkOutput { + val start = System.nanoTime().toDouble() + var value: VariableValue? = null + + for (i in 0..n) { + value = f.getVariable(featureKey = feature, variableKey = variableKey, context = context) + } + + val end = System.nanoTime().toDouble() + + return BenchmarkOutput( + value = value, + duration = end - start + ) +} diff --git a/src/main/kotlin/com/featurevisor/testRunner/Utils.kt b/src/main/kotlin/com/featurevisor/testRunner/Utils.kt index b68ea12..e140ca3 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/Utils.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/Utils.kt @@ -55,6 +55,13 @@ internal fun getSdkInstance(datafileContent: DatafileContent?, assertion: Featur ) ) +internal fun initializeSdkWithDataFileContent(datafileContent: DatafileContent?) = + FeaturevisorInstance.createInstance( + InstanceOptions( + datafile = datafileContent, + ) + ) + internal fun getFileForSpecificPath(path: String) = File(path) internal inline fun String.convertToDataClass() = json.decodeFromString(this) @@ -230,30 +237,29 @@ fun checkJsonIsEquals(a: String, b: String): Boolean { return map1 == map2 } -fun buildDataFileForBothEnvironments(projectRootPath: String): DataFile { - val dataFileForStaging = try { - getJsonForDataFile(environment = "staging", projectRootPath = projectRootPath)?.run { - convertToDataClass() - } - } catch (e: Exception) { - printMessageInRedColor("Unable to parse staging data file") - null - } +fun buildDataFileForBothEnvironments(projectRootPath: String): DataFile = + DataFile( + stagingDataFiles = buildDataFileForStaging(projectRootPath), + productionDataFiles = buildDataFileForProduction(projectRootPath) + ) - val dataFileForProduction = try { - getJsonForDataFile(environment = "production", projectRootPath = projectRootPath)?.run { - convertToDataClass() - } +fun buildDataFileForStaging(projectRootPath: String) = try { + getJsonForDataFile(environment = "staging", projectRootPath = projectRootPath)?.run { + convertToDataClass() + } +} catch (e: Exception) { + printMessageInRedColor("Unable to parse staging data file") + null +} - } catch (e: Exception) { - printMessageInRedColor("Unable to parse production data file") - null +fun buildDataFileForProduction(projectRootPath: String) = try { + getJsonForDataFile(environment = "production", projectRootPath = projectRootPath)?.run { + convertToDataClass() } - return DataFile( - stagingDataFiles = dataFileForStaging, - productionDataFiles = dataFileForProduction - ) +} catch (e: Exception) { + printMessageInRedColor("Unable to parse production data file") + null } fun getDataFileContent(featureName: String, environment: String, projectRootPath: String) = @@ -270,4 +276,12 @@ fun getDataFileContent(featureName: String, environment: String, projectRootPath null } +fun convertNanoSecondToMilliSecond(timeInNanoSecond:Double):String { + val timeInMilliSecond = timeInNanoSecond/1000000 + return if (timeInMilliSecond > 1000){ + "${timeInMilliSecond / 1000} s" + }else{ + "$timeInMilliSecond ms" + } +}