diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index 7ab1619931c81e3f98e10af8fa3764c3451bfe88..c4007e7f156edd505397e75b54b963fec6d0ecc9 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -4,6 +4,7 @@ import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark import theodolite.patcher.PatcherDefinitionFactory +import theodolite.strategies.Metric import theodolite.strategies.StrategyFactory import theodolite.util.* import java.io.File @@ -37,7 +38,7 @@ class TheodoliteExecutor( * The [searchStrategy] is configured and able to find the minimum required resource for the given load. */ private fun buildConfig(): Config { - val results = Results() + val results = Results(Metric.from(config.execution.metric)) val strategyFactory = StrategyFactory() val executionDuration = Duration.ofSeconds(config.execution.duration) @@ -92,7 +93,7 @@ class TheodoliteExecutor( resources = config.resources.resourceValues, resourcePatcherDefinitions = resourcePatcherDefinition, searchStrategy = strategyFactory.createSearchStrategy(executor, config.execution.strategy, results), - metric = config.execution.metric + metric = Metric.from(config.execution.metric) ) } @@ -120,7 +121,7 @@ class TheodoliteExecutor( //execute benchmarks for each load for the demand metric, or for each resource amount for capacity metric try { - config.searchStrategy.findSuitableCapacity(config.loads, config.resources, config.metric) + config.searchStrategy.applySearchStrategyByMetric(config.loads, config.resources, config.metric) } finally { ioHandler.writeToJSONFile( @@ -148,7 +149,7 @@ class TheodoliteExecutor( } private fun calculateDemandMetric(loads: List<Int>, results: Results): List<List<String>> { - return loads.map { listOf(it.toString(), results.getMinRequiredInstances(it).toString()) } + return loads.map { listOf(it.toString(), results.getMinRequiredYDimensionValue(it).toString()) } } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/Metric.kt b/theodolite/src/main/kotlin/theodolite/strategies/Metric.kt new file mode 100644 index 0000000000000000000000000000000000000000..05383ca913a460641a6e632211787a2aec17e9d0 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/Metric.kt @@ -0,0 +1,11 @@ +package theodolite.strategies + +enum class Metric(val value: String) { + DEMAND("demand"), + CAPACITY("capacity"); + + companion object { + fun from(metric: String): Metric = + values().find { it.value == metric } ?: throw IllegalArgumentException("Requested Metric does not exist") + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt index f4ff6c5ddfaaa98dc11b8c1d8c449927ecefe9a3..a791bb2a8d383865a8bbb9412b9a519dc23bf6df 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt @@ -10,13 +10,13 @@ import theodolite.util.Results */ class LowerBoundRestriction(results: Results) : RestrictionStrategy(results) { - override fun apply(load: Int, resources: List<Int>): List<Int> { - val maxLoad: Int? = this.results.getMaxBenchmarkedLoad(load) - var lowerBound: Int = this.results.getMinRequiredInstances(maxLoad) + override fun apply(xValue: Int, yValues: List<Int>): List<Int> { + val maxXValue: Int? = this.results.getMaxBenchmarkedXDimensionValue(xValue) + var lowerBound: Int = this.results.getMinRequiredYDimensionValue(maxXValue) if (lowerBound == Int.MIN_VALUE || lowerBound == Int.MAX_VALUE) { - lowerBound = resources[0] + lowerBound = yValues[0] } - return resources.filter { x -> x >= lowerBound } + return yValues.filter { x -> x >= lowerBound } } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt index b402b917f41863f9d749d50a5a14d2244ce8d1be..986c15f3c73593237bef8e3e5e47115a01bf70e1 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt @@ -19,5 +19,5 @@ abstract class RestrictionStrategy(val results: Results) { * @return Returns a list containing only elements that have not been filtered out by the * restriction (possibly empty). */ - abstract fun apply(load: Int, resources: List<Int>): List<Int> + abstract fun apply(xValue: Int, yValues: List<Int>): List<Int> } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt index dd74b7e19dc291f200c0144fba3de4d603115061..7df3f90131e816a48333bd74dde01f1166c539ab 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt @@ -6,6 +6,7 @@ import theodolite.util.Results private val logger = KotlinLogging.logger {} +// TODO: Is actually just a heuristic approach. Not ensured to have opt solution. Maybe Talk to Sören about it. /** * Search strategy implementation for determining the smallest suitable resource demand. * Starting with a resource amount provided by a guess strategy. @@ -39,8 +40,8 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra // Getting the lastLowestResource from results and calling firstGuess() with it if (!results.isEmpty()) { - val maxLoad: Int? = this.results.getMaxBenchmarkedLoad(load) - lastLowestResource = this.results.getMinRequiredInstances(maxLoad) + val maxLoad: Int? = this.results.getMaxBenchmarkedXDimensionValue(load) + lastLowestResource = this.results.getMinRequiredYDimensionValue(maxLoad) if (lastLowestResource == Int.MAX_VALUE) lastLowestResource = null } lastLowestResource = this.guessStrategy.firstGuess(resources, lastLowestResource) diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt index 10cc4a7a2a11c135101249ef88d197e7e08bd437..f7e4ea66de9e4c0adf3f236548c6012e47b4374c 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt @@ -31,5 +31,6 @@ class RestrictionSearch( for (strategy in this.restrictionStrategies) { restrictedLoads = restrictedLoads.intersect(strategy.apply(resource, loads).toSet()).toList() } + return this.searchStrategy.findSuitableLoad(resource, restrictedLoads) } } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt index ff8827ce603ca299aee6ad6297d79be355777c7f..d870d38caf1a0a597fc77dc91779da2f2e9b885d 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt @@ -2,6 +2,7 @@ package theodolite.strategies.searchstrategy import io.quarkus.runtime.annotations.RegisterForReflection import theodolite.execution.BenchmarkExecutor +import theodolite.strategies.Metric import theodolite.util.Results /** @@ -16,9 +17,9 @@ abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor, val gues val results: Results? = null) { - fun findSuitableCapacity(loads: List<Int>, resources: List<Int>, metric: String) { + fun applySearchStrategyByMetric(loads: List<Int>, resources: List<Int>, metric: Metric) { - if (metric == "demand") { + if (metric.value == "demand") { //demand metric for (load in loads) { if (benchmarkExecutor.run.get()) { diff --git a/theodolite/src/main/kotlin/theodolite/util/Config.kt b/theodolite/src/main/kotlin/theodolite/util/Config.kt index 00ccaf339f83c80623c89cf5408f985ad8390c32..e1b6a4c0763129873d525493769ada13a0ba56a3 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Config.kt +++ b/theodolite/src/main/kotlin/theodolite/util/Config.kt @@ -1,6 +1,7 @@ package theodolite.util import io.quarkus.runtime.annotations.RegisterForReflection +import theodolite.strategies.Metric import theodolite.strategies.searchstrategy.SearchStrategy /** @@ -18,5 +19,5 @@ data class Config( val resources: List<Int>, val resourcePatcherDefinitions : List<PatcherDefinition>, val searchStrategy: SearchStrategy, - val metric: String + val metric: Metric ) diff --git a/theodolite/src/main/kotlin/theodolite/util/Results.kt b/theodolite/src/main/kotlin/theodolite/util/Results.kt index c1d6ce8fda3b8eb4e5ace7d748d715224845586f..eca33eaab7e77c85d1b7302dd71a8bc722ec5198 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Results.kt +++ b/theodolite/src/main/kotlin/theodolite/util/Results.kt @@ -1,6 +1,7 @@ package theodolite.util import io.quarkus.runtime.annotations.RegisterForReflection +import theodolite.strategies.Metric /** * Central class that saves the state of an execution of Theodolite. For an execution, it is used to save the result of @@ -8,7 +9,9 @@ import io.quarkus.runtime.annotations.RegisterForReflection * perform the [theodolite.strategies.restriction.RestrictionStrategy]. */ @RegisterForReflection -class Results { +//TODO: Initializing überall anpassen +class Results (val metric: Metric) { + //TODO: enum statt Boolean private val results: MutableMap<Pair<Int, Int>, Boolean> = mutableMapOf() /** @@ -43,24 +46,27 @@ class Results { * If no experiments have been marked as either successful or unsuccessful * yet, a Resource with the constant value Int.MIN_VALUE is returned. */ - fun getMinRequiredInstances(load: Int?): Int { - if (this.results.isEmpty()) { + fun getMinRequiredYDimensionValue(xValue: Int?): Int { + if (this.results.isEmpty()) { //should add || xValue == null return Int.MIN_VALUE } - var minRequiredInstances = Int.MAX_VALUE + var minRequiredYValue = Int.MAX_VALUE for (experiment in results) { // Get all successful experiments for requested load - if (experiment.key.first == load && experiment.value) { - if (experiment.key.second < minRequiredInstances) { + if (getXDimensionValue(experiment.key) == xValue && experiment.value) { + val experimentYValue = getYDimensionValue(experiment.key) + if (experimentYValue < minRequiredYValue) { // Found new smallest resources - minRequiredInstances = experiment.key.second + minRequiredYValue = experimentYValue } } } - return minRequiredInstances + return minRequiredYValue } + // TODO: SÖREN FRAGEN WARUM WIR DAS BRAUCHEN UND NICHT EINFACH PREV, WEIL NICHT DURCHGELAUFEN? + // TODO Kommentar zu XDimension und YDimension /** * Get the largest LoadDimension that has been reported executed successfully (or unsuccessfully) so far, for a * LoadDimension and is smaller than the given LoadDimension. @@ -69,18 +75,19 @@ class Results { * * @return the largest LoadDimension or null, if there is none for this LoadDimension */ - fun getMaxBenchmarkedLoad(load: Int): Int? { - var maxBenchmarkedLoad: Int? = null + fun getMaxBenchmarkedXDimensionValue(xValue: Int): Int? { + var maxBenchmarkedXValue: Int? = null for (experiment in results) { - if (experiment.key.first <= load) { - if (maxBenchmarkedLoad == null) { - maxBenchmarkedLoad = experiment.key.first - } else if (maxBenchmarkedLoad < experiment.key.first) { - maxBenchmarkedLoad = experiment.key.first + val experimentXValue = getXDimensionValue(experiment.key) + if (experimentXValue <= xValue) { //warum \leq? + if (maxBenchmarkedXValue == null) { + maxBenchmarkedXValue = experimentXValue + } else if (maxBenchmarkedXValue < experimentXValue) { + maxBenchmarkedXValue = experimentXValue } } } - return maxBenchmarkedLoad + return maxBenchmarkedXValue } /** @@ -91,4 +98,18 @@ class Results { fun isEmpty(): Boolean{ return results.isEmpty() } + + fun getYDimensionValue(experimentKey: Pair<Int, Int>): Int{ + if(metric.value == "demand"){ + return experimentKey.second + } + return experimentKey.first + } + + fun getXDimensionValue(experimentKey: Pair<Int, Int>): Int{ + if(metric.value == "demand"){ + return experimentKey.first + } + return experimentKey.second + } } diff --git a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt index fa68adeb2fee6e785194c295afb84c09d3163de5..8dcd31481cc966b1c7dab58d6054d2f77fb93802 100644 --- a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt +++ b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import theodolite.benchmark.BenchmarkExecution import theodolite.strategies.searchstrategy.InitialGuessSearchStrategy +import theodolite.strategies.Metric import theodolite.util.Results import mu.KotlinLogging import theodolite.strategies.searchstrategy.PrevResourceMinGuess @@ -27,7 +28,7 @@ class InitialGuessSearchStrategyTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..6).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val guessStrategy = PrevResourceMinGuess() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() @@ -65,7 +66,7 @@ class InitialGuessSearchStrategyTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..6).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val guessStrategy = PrevResourceMinGuess() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() @@ -103,7 +104,7 @@ class InitialGuessSearchStrategyTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..6).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val guessStrategy = PrevResourceMinGuess() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() diff --git a/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt b/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt index 13d6b8fafba4d8a35406f934752f4482c9c211f0..1e973852d8448d0bff3410d5bb84bc5543be497a 100644 --- a/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt +++ b/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt @@ -4,6 +4,7 @@ import io.quarkus.test.junit.QuarkusTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import theodolite.benchmark.BenchmarkExecution +import theodolite.strategies.Metric import theodolite.strategies.restriction.LowerBoundRestriction import theodolite.strategies.searchstrategy.BinarySearch import theodolite.strategies.searchstrategy.FullSearch @@ -28,7 +29,7 @@ class RestrictionSearchTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..6).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) @@ -61,7 +62,7 @@ class RestrictionSearchTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..6).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) @@ -94,7 +95,7 @@ class RestrictionSearchTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..6).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutorImpl = @@ -127,7 +128,7 @@ class RestrictionSearchTest { ) val mockLoads: List<Int> = (0..6).toList() val mockResources: List<Int> = (0..7).toList() - val results = Results() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 0) diff --git a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt b/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt index 78d69cc8b4301ee7c966300adfcf2a5a8e389b6b..230b0f785ea72f061c12fe43049403efa149856f 100644 --- a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt +++ b/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt @@ -4,13 +4,14 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import theodolite.strategies.Metric import theodolite.util.Results internal class LowerBoundRestrictionTest { @Test fun testNoPreviousResults() { - val results = Results() + val results = Results(Metric.from("demand")) val strategy = LowerBoundRestriction(results) val load = 10000 val resources = listOf(1, 2, 3) @@ -22,7 +23,7 @@ internal class LowerBoundRestrictionTest { @Test fun testWithSuccessfulPreviousResults() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 1, false) results.setResult(20000, 2, true) @@ -39,7 +40,7 @@ internal class LowerBoundRestrictionTest { @Disabled fun testWithNoSuccessfulPreviousResults() { // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 1, false) results.setResult(20000, 2, false) @@ -56,13 +57,13 @@ internal class LowerBoundRestrictionTest { @Test fun testNoPreviousResults2() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 2, true) results.setResult(10000, 1, false) results.setResult(20000, 2, true) - val minRequiredInstances = results.getMinRequiredInstances(20000) + val minRequiredInstances = results.getMinRequiredYDimensionValue(20000) assertNotNull(minRequiredInstances) assertEquals(2, minRequiredInstances!!) @@ -72,13 +73,13 @@ internal class LowerBoundRestrictionTest { @Disabled fun testMinRequiredInstancesWhenNotSuccessful() { // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(20000, 2, true) results.setResult(10000, 1, false) results.setResult(20000, 2, false) - val minRequiredInstances = results.getMinRequiredInstances(20000) + val minRequiredInstances = results.getMinRequiredYDimensionValue(20000) assertNotNull(minRequiredInstances) assertEquals(2, minRequiredInstances!!) diff --git a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt index 9bce37a3c4227d25b2eb7531d3c27b1fe1ea51c3..f8f86daeeb77e3928fa7a686dbf05aa4b714af51 100644 --- a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt @@ -5,19 +5,20 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import theodolite.strategies.Metric @QuarkusTest internal class ResultsTest { @Test fun testMinRequiredInstancesWhenSuccessful() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(10000, 2, true) results.setResult(20000, 1, false) results.setResult(20000, 2, true) - val minRequiredInstances = results.getMinRequiredInstances(20000) + val minRequiredInstances = results.getMinRequiredYDimensionValue(20000) assertNotNull(minRequiredInstances) assertEquals(2, minRequiredInstances!!) @@ -27,13 +28,13 @@ internal class ResultsTest { @Disabled fun testMinRequiredInstancesWhenNotSuccessful() { // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(10000, 2, true) results.setResult(20000, 1, false) results.setResult(20000, 2, false) - val minRequiredInstances = results.getMinRequiredInstances(20000) + val minRequiredInstances = results.getMinRequiredYDimensionValue(20000) assertNotNull(minRequiredInstances) assertEquals(2, minRequiredInstances!!) @@ -46,23 +47,23 @@ internal class ResultsTest { @Test fun testGetMaxBenchmarkedLoadWhenAllSuccessful() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(10000, 2, true) - val test1 = results.getMaxBenchmarkedLoad(100000)!! + val test1 = results.getMaxBenchmarkedXDimensionValue(100000)!! assertEquals(10000, test1) } @Test fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessful() { - val results = Results() + val results = Results(Metric.from("demand")) results.setResult(10000, 1, true) results.setResult(10000, 2, true) results.setResult(20000, 1, false) - val test2 = results.getMaxBenchmarkedLoad(100000)!! + val test2 = results.getMaxBenchmarkedXDimensionValue(100000)!! assertEquals(20000, test2) }