Skip to content

Commit 1c2139d

Browse files
authored
Benchmark feature implementation (#34)
1 parent d802652 commit 1c2139d

File tree

2 files changed

+183
-20
lines changed

2 files changed

+183
-20
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.featurevisor.testRunner
2+
3+
import com.featurevisor.sdk.FeaturevisorInstance
4+
import com.featurevisor.sdk.getVariable
5+
import com.featurevisor.sdk.getVariation
6+
import com.featurevisor.sdk.isEnabled
7+
import com.featurevisor.types.*
8+
9+
data class BenchmarkOutput(
10+
val value: Any? = null,
11+
val duration: Double
12+
)
13+
14+
data class BenchMarkOptions(
15+
val environment: String = "",
16+
val feature: String = "",
17+
val n: Int = 0,
18+
val projectRootPath: String = "",
19+
val context: Context = emptyMap(),
20+
val variation: Boolean? = null,
21+
val variable: String? = null,
22+
)
23+
24+
fun benchmarkFeature(option: BenchMarkOptions) {
25+
println("Running benchmark for feature ${option.feature}...")
26+
27+
println("Building datafile containing all features for ${option.environment}...")
28+
29+
val datafileBuildStart = System.nanoTime().toDouble()
30+
31+
val datafileContent = buildDataFileForStaging(option.projectRootPath)
32+
33+
val datafileBuildEnd = System.nanoTime().toDouble()
34+
35+
val datafileBuildDuration = datafileBuildEnd - datafileBuildStart
36+
37+
println("Datafile build duration: ${convertNanoSecondToMilliSecond(datafileBuildDuration)}")
38+
39+
val sdk = initializeSdkWithDataFileContent(datafileContent)
40+
41+
println("...SDK initialized")
42+
43+
println("Against context: ${option.context}")
44+
45+
val output: BenchmarkOutput
46+
47+
if (option.variable != null) {
48+
println("Evaluating variable ${option.variable} ${option.n} times...")
49+
50+
output = benchmarkFeatureVariable(
51+
sdk,
52+
feature = option.feature,
53+
variableKey = option.variable,
54+
context = option.context,
55+
n = option.n
56+
)
57+
58+
} else if (option.variation != null) {
59+
println("Evaluating variation ${option.variation} ${option.n} times...")
60+
61+
output = benchmarkFeatureVariation(
62+
sdk,
63+
feature = option.feature,
64+
context = option.context,
65+
n = option.n
66+
)
67+
} else {
68+
println("Evaluating flag ${option.n} times...")
69+
70+
output = benchmarkFeatureFlag(
71+
sdk,
72+
feature = option.feature,
73+
context = option.context,
74+
n = option.n
75+
)
76+
}
77+
78+
println("Evaluated value : ${output.value}")
79+
println("Total duration : ${convertNanoSecondToMilliSecond(output.duration)}")
80+
if (option.n != 0) {
81+
println("Average duration: ${convertNanoSecondToMilliSecond(output.duration / option.n)}")
82+
}
83+
}
84+
85+
86+
fun benchmarkFeatureFlag(
87+
f: FeaturevisorInstance,
88+
feature: FeatureKey,
89+
context: Context,
90+
n: Int
91+
): BenchmarkOutput {
92+
val start = System.nanoTime().toDouble()
93+
var value: Any = false
94+
95+
for (i in 0..n) {
96+
value = f.isEnabled(featureKey = feature, context = context)
97+
}
98+
99+
val end = System.nanoTime().toDouble()
100+
101+
return BenchmarkOutput(
102+
value = value,
103+
duration = end - start
104+
)
105+
}
106+
107+
108+
fun benchmarkFeatureVariation(
109+
f: FeaturevisorInstance,
110+
feature: FeatureKey,
111+
context: Context,
112+
n: Int
113+
): BenchmarkOutput {
114+
val start = System.nanoTime().toDouble()
115+
var value: VariationValue? = null
116+
117+
for (i in 0..n) {
118+
value = f.getVariation(featureKey = feature, context = context)
119+
}
120+
121+
val end = System.nanoTime().toDouble()
122+
123+
return BenchmarkOutput(
124+
value = value,
125+
duration = end - start
126+
)
127+
}
128+
129+
fun benchmarkFeatureVariable(
130+
f: FeaturevisorInstance,
131+
feature: FeatureKey,
132+
variableKey: VariableKey,
133+
context: Context,
134+
n: Int
135+
): BenchmarkOutput {
136+
val start = System.nanoTime().toDouble()
137+
var value: VariableValue? = null
138+
139+
for (i in 0..n) {
140+
value = f.getVariable(featureKey = feature, variableKey = variableKey, context = context)
141+
}
142+
143+
val end = System.nanoTime().toDouble()
144+
145+
return BenchmarkOutput(
146+
value = value,
147+
duration = end - start
148+
)
149+
}

src/main/kotlin/com/featurevisor/testRunner/Utils.kt

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ internal fun getSdkInstance(datafileContent: DatafileContent?, assertion: Featur
5555
)
5656
)
5757

58+
internal fun initializeSdkWithDataFileContent(datafileContent: DatafileContent?) =
59+
FeaturevisorInstance.createInstance(
60+
InstanceOptions(
61+
datafile = datafileContent,
62+
)
63+
)
64+
5865
internal fun getFileForSpecificPath(path: String) = File(path)
5966

6067
internal inline fun <reified R : Any> String.convertToDataClass() = json.decodeFromString<R>(this)
@@ -230,30 +237,29 @@ fun checkJsonIsEquals(a: String, b: String): Boolean {
230237
return map1 == map2
231238
}
232239

233-
fun buildDataFileForBothEnvironments(projectRootPath: String): DataFile {
234-
val dataFileForStaging = try {
235-
getJsonForDataFile(environment = "staging", projectRootPath = projectRootPath)?.run {
236-
convertToDataClass<DatafileContent>()
237-
}
238-
} catch (e: Exception) {
239-
printMessageInRedColor("Unable to parse staging data file")
240-
null
241-
}
240+
fun buildDataFileForBothEnvironments(projectRootPath: String): DataFile =
241+
DataFile(
242+
stagingDataFiles = buildDataFileForStaging(projectRootPath),
243+
productionDataFiles = buildDataFileForProduction(projectRootPath)
244+
)
242245

243-
val dataFileForProduction = try {
244-
getJsonForDataFile(environment = "production", projectRootPath = projectRootPath)?.run {
245-
convertToDataClass<DatafileContent>()
246-
}
246+
fun buildDataFileForStaging(projectRootPath: String) = try {
247+
getJsonForDataFile(environment = "staging", projectRootPath = projectRootPath)?.run {
248+
convertToDataClass<DatafileContent>()
249+
}
250+
} catch (e: Exception) {
251+
printMessageInRedColor("Unable to parse staging data file")
252+
null
253+
}
247254

248-
} catch (e: Exception) {
249-
printMessageInRedColor("Unable to parse production data file")
250-
null
255+
fun buildDataFileForProduction(projectRootPath: String) = try {
256+
getJsonForDataFile(environment = "production", projectRootPath = projectRootPath)?.run {
257+
convertToDataClass<DatafileContent>()
251258
}
252259

253-
return DataFile(
254-
stagingDataFiles = dataFileForStaging,
255-
productionDataFiles = dataFileForProduction
256-
)
260+
} catch (e: Exception) {
261+
printMessageInRedColor("Unable to parse production data file")
262+
null
257263
}
258264

259265
fun getDataFileContent(featureName: String, environment: String, projectRootPath: String) =
@@ -270,4 +276,12 @@ fun getDataFileContent(featureName: String, environment: String, projectRootPath
270276
null
271277
}
272278

279+
fun convertNanoSecondToMilliSecond(timeInNanoSecond:Double):String {
280+
val timeInMilliSecond = timeInNanoSecond/1000000
281+
return if (timeInMilliSecond > 1000){
282+
"${timeInMilliSecond / 1000} s"
283+
}else{
284+
"$timeInMilliSecond ms"
285+
}
286+
}
273287

0 commit comments

Comments
 (0)