diff --git a/docs/api-reference/crds.md b/docs/api-reference/crds.md index c5cc7596f4c23408597f48b366f6b97e889e7a66..996ea1a3a4486ca27031f3d29ad64e018f66f5fb 100644 --- a/docs/api-reference/crds.md +++ b/docs/api-reference/crds.md @@ -1774,7 +1774,7 @@ Contains the Kafka configuration. </td> <td>true</td> </tr><tr> - <td><b><a href="#executionspecload">load</a></b></td> + <td><b><a href="#executionspecloads">loads</a></b></td> <td>object</td> <td> Specifies the load values that are benchmarked.<br/> @@ -1915,35 +1915,83 @@ Defines the overall parameter for the execution. <td><b>repetitions</b></td> <td>integer</td> <td> - Numper of repititions for each experiments.<br/> + Number of repititions for each experiment.<br/> </td> <td>true</td> </tr><tr> - <td><b>restrictions</b></td> - <td>[]string</td> + <td><b><a href="#executionspecexecutionstrategy">strategy</a></b></td> + <td>object</td> <td> - List of restriction strategys used to delimit the search space.<br/> + Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'.<br/> </td> <td>true</td> </tr><tr> - <td><b>strategy</b></td> + <td><b>loadGenerationDelay</b></td> + <td>integer</td> + <td> + Seconds to wait between the start of the SUT and the load generator.<br/> + </td> + <td>false</td> + </tr><tr> + <td><b>metric</b></td> + <td>string</td> + <td> + <br/> + </td> + <td>false</td> + </tr></tbody> +</table> + + +### execution.spec.execution.strategy +<sup><sup>[↩ Parent](#executionspecexecution)</sup></sup> + + + +Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'. + +<table> + <thead> + <tr> + <th>Name</th> + <th>Type</th> + <th>Description</th> + <th>Required</th> + </tr> + </thead> + <tbody><tr> + <td><b>name</b></td> <td>string</td> <td> - Defines the used strategy for the execution, either 'LinearSearch' or 'BinarySearch'<br/> + <br/> </td> <td>true</td> </tr><tr> - <td><b>loadGenerationDelay</b></td> - <td>integer</td> + <td><b>guessStrategy</b></td> + <td>string</td> <td> - Seconds to wait between the start of the SUT and the load generator.<br/> + <br/> + </td> + <td>false</td> + </tr><tr> + <td><b>restrictions</b></td> + <td>[]string</td> + <td> + List of restriction strategies used to delimit the search space.<br/> + </td> + <td>false</td> + </tr><tr> + <td><b>searchStrategy</b></td> + <td>string</td> + <td> + <br/> </td> <td>false</td> </tr></tbody> </table> -### execution.spec.load +### execution.spec.loads <sup><sup>[↩ Parent](#executionspec)</sup></sup> diff --git a/theodolite/crd/crd-execution.yaml b/theodolite/crd/crd-execution.yaml index 92a8ca18d87009143620097caf2abfe8da202c82..0240fc6ee05f5a573b1f738d931d78c3bb9b5859 100644 --- a/theodolite/crd/crd-execution.yaml +++ b/theodolite/crd/crd-execution.yaml @@ -20,7 +20,7 @@ spec: properties: spec: type: object - required: ["benchmark", "load", "resources", "slos", "execution", "configOverrides"] + required: ["benchmark", "loads", "resources", "slos", "execution", "configOverrides"] properties: name: description: This field exists only for technical reasons and should not be set by the user. The value of the field will be overwritten. @@ -29,7 +29,7 @@ spec: benchmark: description: The name of the benchmark this execution is referring to. type: string - load: # definition of the load dimension + loads: # definition of the load dimension description: Specifies the load values that are benchmarked. type: object required: ["loadType", "loadValues"] @@ -80,25 +80,35 @@ spec: execution: # def execution config description: Defines the overall parameter for the execution. type: object - required: ["strategy", "duration", "repetitions", "restrictions"] + required: ["strategy", "duration", "repetitions"] properties: - strategy: - description: Defines the used strategy for the execution, either 'LinearSearch' or 'BinarySearch' + metric: type: string + strategy: + description: Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'. + type: object + required: ["name"] + properties: + name: + type: string + restrictions: + description: List of restriction strategies used to delimit the search space. + type: array + items: + type: string + guessStrategy: + type: string + searchStrategy: + type: string duration: description: Defines the duration of each experiment in seconds. type: integer repetitions: - description: Numper of repititions for each experiments. + description: Number of repititions for each experiment. type: integer loadGenerationDelay: description: Seconds to wait between the start of the SUT and the load generator. type: integer - restrictions: - description: List of restriction strategys used to delimit the search space. - type: array - items: - type: string configOverrides: description: List of patchers that are used to override existing configurations. type: array diff --git a/theodolite/examples/operator/example-execution.yaml b/theodolite/examples/operator/example-execution.yaml index 576a74b90dfc38483de79502ac14d42f6bedfb49..b94aa9b2656f02f1b3f7fbf87edeceaa923e1ab8 100644 --- a/theodolite/examples/operator/example-execution.yaml +++ b/theodolite/examples/operator/example-execution.yaml @@ -4,7 +4,7 @@ metadata: name: theodolite-example-execution spec: benchmark: "example-benchmark" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: @@ -19,12 +19,14 @@ spec: externalSloUrl: "http://localhost:80/evaluate-slope" warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: [] # - patcher: # type: "NodeSelectorPatcher" diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt index cf2fac7337d79c1c5daf2b0fac070200cf27f9a5..f2b587cba90151af199da4b76a9cb8ad407f80ef 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/Benchmark.kt @@ -2,13 +2,12 @@ package theodolite.benchmark import io.quarkus.runtime.annotations.RegisterForReflection import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.util.PatcherDefinition /** * A Benchmark contains: - * - The [Resource]s that can be scaled for the benchmark. - * - The [LoadDimension]s that can be scaled the benchmark. + * - The Resource that can be scaled for the benchmark. + * - The Load that can be scaled the benchmark. * - additional [ConfigurationOverride]s. */ @RegisterForReflection @@ -22,10 +21,12 @@ interface Benchmark { * @return a BenchmarkDeployment. */ fun buildDeployment( - load: LoadDimension, - res: Resource, - configurationOverrides: List<ConfigurationOverride?>, - loadGenerationDelay: Long, - afterTeardownDelay: Long + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long ): BenchmarkDeployment } diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt index f2dda487d390c5f771e4f47c0f9c7ebf2cf971e7..1010a8a344442b24f818d73a400a799153ed8947 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/BenchmarkExecution.kt @@ -12,7 +12,7 @@ import kotlin.properties.Delegates * A BenchmarkExecution consists of: * - A [name]. * - The [benchmark] that should be executed. - * - The [load] that should be checked in the benchmark. + * - The [loads]s that should be checked in the benchmark. * - The [resources] that should be checked in the benchmark. * - A list of [slos] that are used for the evaluation of the experiments. * - An [execution] that encapsulates: the strategy, the duration, and the restrictions @@ -28,27 +28,42 @@ class BenchmarkExecution : KubernetesResource { var executionId: Int = 0 lateinit var name: String lateinit var benchmark: String - lateinit var load: LoadDefinition + lateinit var loads: LoadDefinition lateinit var resources: ResourceDefinition lateinit var slos: List<Slo> lateinit var execution: Execution lateinit var configOverrides: MutableList<ConfigurationOverride?> /** - * This execution encapsulates the [strategy], the [duration], the [repetitions], and the [restrictions] + * This execution encapsulates the [strategy], the [duration], and the [repetitions], * which are used for the concrete benchmark experiments. */ @JsonDeserialize @RegisterForReflection class Execution : KubernetesResource { - lateinit var strategy: String + var metric = "demand" + lateinit var strategy: Strategy var duration by Delegates.notNull<Long>() var repetitions by Delegates.notNull<Int>() - lateinit var restrictions: List<String> var loadGenerationDelay = 0L var afterTeardownDelay = 5L } + /** + * This Strategy encapsulates the [restrictions], [guessStrategy] and [searchStrategy], + * which are used for restricting the resources, the guess Strategy for the + * [theodolite.strategies.searchstrategy.InitialGuessSearchStrategy] and the name of the actual + * [theodolite.strategies.searchstrategy.SearchStrategy] which is used. + */ + @JsonDeserialize + @RegisterForReflection + class Strategy : KubernetesResource { + lateinit var name: String + var restrictions = emptyList<String>() + var guessStrategy = "" + var searchStrategy = "" + } + /** * Measurable metric. * [sloType] determines the type of the metric. @@ -68,8 +83,8 @@ class BenchmarkExecution : KubernetesResource { } /** - * Represents a Load that should be created and checked. - * It can be set to [loadValues]. + * Represents the Loads that should be created and checked if the demand metric is in use or + * represents a Load that can be scaled to [loadValues] if the capacity metric is in use. */ @JsonDeserialize @RegisterForReflection @@ -79,7 +94,8 @@ class BenchmarkExecution : KubernetesResource { } /** - * Represents a resource that can be scaled to [resourceValues]. + * Represents a resource that can be scaled to [resourceValues] if the demand metric is in use or + * represents the Resources that should be created and checked if the capacity metric is in use. */ @JsonDeserialize @RegisterForReflection diff --git a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt index ddb83c54c7845651754ae82c9abf8780c77fdba1..599f723621db2b86c9163af9351ac896f82b2b86 100644 --- a/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt +++ b/theodolite/src/main/kotlin/theodolite/benchmark/KubernetesBenchmark.kt @@ -82,19 +82,22 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { /** * Builds a deployment. - * First loads all required resources and then patches them to the concrete load and resources for the experiment. - * Afterwards patches additional configurations(cluster depending) into the resources. - * @param load concrete load that will be benchmarked in this experiment. - * @param res concrete resource that will be scaled for this experiment. + * First loads all required resources and then patches them to the concrete load and resources for the experiment for the demand metric + * or loads all loads and then patches them to the concrete load and resources for the experiment. + * Afterwards patches additional configurations(cluster depending) into the resources (or loads). + * @param load concrete load that will be benchmarked in this experiment (demand metric), or scaled (capacity metric). + * @param resource concrete resource that will be scaled for this experiment (demand metric), or benchmarked (capacity metric). * @param configurationOverrides * @return a [BenchmarkDeployment] */ override fun buildDeployment( - load: LoadDimension, - res: Resource, + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, configurationOverrides: List<ConfigurationOverride?>, loadGenerationDelay: Long, - afterTeardownDelay: Long, + afterTeardownDelay: Long ): BenchmarkDeployment { logger.info { "Using $namespace as namespace." } @@ -102,14 +105,14 @@ class KubernetesBenchmark : KubernetesResource, Benchmark { val appResources = loadResources(this.sut.resources).toResourceMap() val loadGenResources = loadResources(this.loadGenerator.resources).toResourceMap() - load.getType().forEach { patcherDefinition -> + // patch the load dimension the resources + loadPatcherDefinitions.forEach { patcherDefinition -> loadGenResources[patcherDefinition.resource] = - PatchHandler.patchResource(loadGenResources, patcherDefinition, load.get().toString()) + PatchHandler.patchResource(loadGenResources, patcherDefinition, load.toString()) } - - res.getType().forEach { patcherDefinition -> + resourcePatcherDefinitions.forEach { patcherDefinition -> appResources[patcherDefinition.resource] = - PatchHandler.patchResource(appResources, patcherDefinition, res.get().toString()) + PatchHandler.patchResource(appResources, patcherDefinition, resource.toString()) } configurationOverrides.forEach { override -> diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index b3cc174d2945bf13bc1cc29d4e60d8c9bfbaf7eb..9e460fd0befafef8a644de870a5b33ccdfcf2029 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -1,10 +1,9 @@ package theodolite.evaluation import theodolite.benchmark.BenchmarkExecution +import theodolite.strategies.Metric import theodolite.util.EvaluationFailedException import theodolite.util.IOHandler -import theodolite.util.LoadDimension -import theodolite.util.Resource import java.text.Normalizer import java.time.Duration import java.time.Instant @@ -29,17 +28,17 @@ class AnalysisExecutor( * Analyses an experiment via prometheus data. * First fetches data from prometheus, then documents them and afterwards evaluate it via a [slo]. * @param load of the experiment. - * @param res of the experiment. + * @param resource of the experiment. * @param executionIntervals list of start and end points of experiments * @return true if the experiment succeeded. */ - fun analyze(load: LoadDimension, res: Resource, executionIntervals: List<Pair<Instant, Instant>>): Boolean { + fun analyze(load: Int, resource: Int, executionIntervals: List<Pair<Instant, Instant>>, metric: Metric): Boolean { var repetitionCounter = 1 try { val ioHandler = IOHandler() val resultsFolder = ioHandler.getResultFolderURL() - val fileURL = "${resultsFolder}exp${executionId}_${load.get()}_${res.get()}_${slo.sloType.toSlug()}" + val fileURL = "${resultsFolder}exp${executionId}_${load}_${resource}_${slo.sloType.toSlug()}" val prometheusData = executionIntervals .map { interval -> @@ -61,13 +60,15 @@ class AnalysisExecutor( val sloChecker = SloCheckerFactory().create( sloType = slo.sloType, properties = slo.properties, - load = load + load = load, + resource = resource, + metric = metric ) return sloChecker.evaluate(prometheusData) } catch (e: Exception) { - throw EvaluationFailedException("Evaluation failed for resource '${res.get()}' and load '${load.get()}", e) + throw EvaluationFailedException("Evaluation failed for resource '$resource' and load '$load ", e) } } diff --git a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt index f57cebfcb13d0e86919ec15a0a479d1258e318a6..7ab6a0255f6c078f0f365289baa1eb0300a3244a 100644 --- a/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/evaluation/SloCheckerFactory.kt @@ -1,6 +1,7 @@ package theodolite.evaluation -import theodolite.util.LoadDimension +import theodolite.strategies.Metric + /** * Factory used to potentially create different [SloChecker]s. @@ -41,7 +42,9 @@ class SloCheckerFactory { fun create( sloType: String, properties: MutableMap<String, String>, - load: LoadDimension + load: Int, + resource: Int, + metric: Metric ): SloChecker { return when (SloTypes.from(sloType)) { SloTypes.GENERIC -> ExternalSloChecker( @@ -76,7 +79,7 @@ class SloCheckerFactory { throw IllegalArgumentException("Threshold ratio needs to be an Double greater or equal 0.0") } // cast to int, as rounding is not really necessary - val threshold = (load.get() * thresholdRatio).toInt() + val threshold = (load * thresholdRatio).toInt() ExternalSloChecker( externalSlopeURL = properties["externalSloUrl"] diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt index 3238f447be06ce6486bb7f6ca1758700f36ba558..55233246fedcd5163f5d3c954d32bd2056b2f31d 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutor.kt @@ -4,8 +4,7 @@ import mu.KotlinLogging import theodolite.benchmark.Benchmark import theodolite.benchmark.BenchmarkExecution import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.util.PatcherDefinition import theodolite.util.Results import java.time.Duration import java.util.concurrent.atomic.AtomicBoolean @@ -30,7 +29,9 @@ abstract class BenchmarkExecutor( val executionId: Int, val loadGenerationDelay: Long, val afterTeardownDelay: Long, - val executionName: String + val executionName: String, + val loadPatcherDefinitions: List<PatcherDefinition>, + val resourcePatcherDefinitions: List<PatcherDefinition> ) { var run: AtomicBoolean = AtomicBoolean(true) @@ -39,12 +40,14 @@ abstract class BenchmarkExecutor( * Run a experiment for the given parametrization, evaluate the * experiment and save the result. * - * @param load load to be tested. - * @param res resources to be tested. + * @param load to be tested. + * @param resource to be tested. * @return True, if the number of resources are suitable for the - * given load, false otherwise. + * given load, false otherwise (demand metric), or + * True, if there is a load suitable for the + * given resource, false otherwise. */ - abstract fun runExperiment(load: LoadDimension, res: Resource): Boolean + abstract fun runExperiment(load: Int, resource: Int): Boolean /** * Wait while the benchmark is running and log the number of minutes executed every 1 minute. diff --git a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt index 2e938be3a6e503a5e7e3f94c18a9454e173db5b0..e2b6c0930764e282b92905066a488f76d036db4c 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/BenchmarkExecutorImpl.kt @@ -23,7 +23,9 @@ class BenchmarkExecutorImpl( executionId: Int, loadGenerationDelay: Long, afterTeardownDelay: Long, - executionName: String + executionName: String, + loadPatcherDefinitions: List<PatcherDefinition>, + resourcePatcherDefinitions: List<PatcherDefinition> ) : BenchmarkExecutor( benchmark, results, @@ -34,24 +36,26 @@ class BenchmarkExecutorImpl( executionId, loadGenerationDelay, afterTeardownDelay, - executionName + executionName, + loadPatcherDefinitions, + resourcePatcherDefinitions ) { private val eventCreator = EventCreator() private val mode = Configuration.EXECUTION_MODE - override fun runExperiment(load: LoadDimension, res: Resource): Boolean { + override fun runExperiment(load: Int, resource: Int): Boolean { var result = false val executionIntervals: MutableList<Pair<Instant, Instant>> = ArrayList() for (i in 1.rangeTo(repetitions)) { if (this.run.get()) { logger.info { "Run repetition $i/$repetitions" } - executionIntervals.add(runSingleExperiment(load, res)) + executionIntervals.add(runSingleExperiment( + load, resource)) } else { break } } - /** * Analyse the experiment, if [run] is true, otherwise the experiment was canceled by the user. */ @@ -60,26 +64,26 @@ class BenchmarkExecutorImpl( AnalysisExecutor(slo = it, executionId = executionId) .analyze( load = load, - res = res, - executionIntervals = executionIntervals + resource = resource, + executionIntervals = executionIntervals, + metric = this.results.metric ) } result = (false !in experimentResults) - this.results.setResult(Pair(load, res), result) - } - - if(!this.run.get()) { + this.results.setResult(Pair(load, resource), result) + } else { throw ExecutionFailedException("The execution was interrupted") } - return result } - private fun runSingleExperiment(load: LoadDimension, res: Resource): Pair<Instant, Instant> { + private fun runSingleExperiment(load: Int, resource: Int): Pair<Instant, Instant> { val benchmarkDeployment = benchmark.buildDeployment( load, - res, + this.loadPatcherDefinitions, + resource, + this.resourcePatcherDefinitions, this.configurationOverrides, this.loadGenerationDelay, this.afterTeardownDelay @@ -94,7 +98,7 @@ class BenchmarkExecutorImpl( executionName = executionName, type = "NORMAL", reason = "Start experiment", - message = "load: ${load.get()}, resources: ${res.get()}") + message = "load: $load, resources: $resource") } } catch (e: Exception) { this.run.set(false) @@ -104,7 +108,7 @@ class BenchmarkExecutorImpl( executionName = executionName, type = "WARNING", reason = "Start experiment failed", - message = "load: ${load.get()}, resources: ${res.get()}") + message = "load: $load, resources: $resource") } throw ExecutionFailedException("Error during setup the experiment", e) } diff --git a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt index 29ac39c122f68636e08c6c5ecd5a6c01751edafb..cdfe68e7a93d64f2385bded31f94f77ab7b3be0e 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/Shutdown.kt @@ -3,8 +3,6 @@ package theodolite.execution import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution import theodolite.benchmark.KubernetesBenchmark -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} @@ -26,8 +24,10 @@ class Shutdown(private val benchmarkExecution: BenchmarkExecution, private val b logger.info { "Received shutdown signal -> Shutting down" } val deployment = benchmark.buildDeployment( - load = LoadDimension(0, emptyList()), - res = Resource(0, emptyList()), + load = 0, + loadPatcherDefinitions = emptyList(), + resource = 0, + resourcePatcherDefinitions = emptyList(), configurationOverrides = benchmarkExecution.configOverrides, loadGenerationDelay = 0L, afterTeardownDelay = 5L diff --git a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index 8596576e0a7984c32b6dabf90c6bbf06961d2bb1..3879e67239c9ba7140114ee605a2e71de3409c98 100644 --- a/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -4,8 +4,8 @@ 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.strategies.searchstrategy.CompositeStrategy import theodolite.util.* import java.io.File import java.time.Duration @@ -33,12 +33,12 @@ class TheodoliteExecutor( /** * Creates all required components to start Theodolite. * - * @return a [Config], that contains a list of [LoadDimension]s, - * a list of [Resource]s , and the [CompositeStrategy]. - * The [CompositeStrategy] is configured and able to find the minimum required resource for the given load. + * @return a [Config], that contains a list of LoadDimension s, + * a list of Resource s , and the [restrictionSearch]. + * 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) @@ -51,7 +51,7 @@ class TheodoliteExecutor( val loadDimensionPatcherDefinition = PatcherDefinitionFactory().createPatcherDefinition( - config.load.loadType, + config.loads.loadType, this.kubernetesBenchmark.loadTypes ) @@ -66,14 +66,16 @@ class TheodoliteExecutor( executionId = config.executionId, loadGenerationDelay = config.execution.loadGenerationDelay, afterTeardownDelay = config.execution.afterTeardownDelay, - executionName = config.name + executionName = config.name, + loadPatcherDefinitions = loadDimensionPatcherDefinition, + resourcePatcherDefinitions = resourcePatcherDefinition ) - if (config.load.loadValues != config.load.loadValues.sorted()) { - config.load.loadValues = config.load.loadValues.sorted() + if (config.loads.loadValues != config.loads.loadValues.sorted()) { + config.loads.loadValues = config.loads.loadValues.sorted() logger.info { "Load values are not sorted correctly, Theodolite sorts them in ascending order." + - "New order is: ${config.load.loadValues}" + "New order is: ${config.loads.loadValues}" } } @@ -86,21 +88,12 @@ class TheodoliteExecutor( } return Config( - loads = config.load.loadValues.map { load -> LoadDimension(load, loadDimensionPatcherDefinition) }, - resources = config.resources.resourceValues.map { resource -> - Resource( - resource, - resourcePatcherDefinition - ) - }, - compositeStrategy = CompositeStrategy( - benchmarkExecutor = executor, - searchStrategy = strategyFactory.createSearchStrategy(executor, config.execution.strategy), - restrictionStrategies = strategyFactory.createRestrictionStrategy( - results, - config.execution.restrictions - ) - ) + loads = config.loads.loadValues, + loadPatcherDefinitions = loadDimensionPatcherDefinition, + resources = config.resources.resourceValues, + resourcePatcherDefinitions = resourcePatcherDefinition, + searchStrategy = strategyFactory.createSearchStrategy(executor, config.execution.strategy, results), + metric = Metric.from(config.execution.metric) ) } @@ -125,24 +118,31 @@ class TheodoliteExecutor( ) val config = buildConfig() - // execute benchmarks for each load + + //execute benchmarks for each load for the demand metric, or for each resource amount for capacity metric try { - for (load in config.loads) { - if (executor.run.get()) { - config.compositeStrategy.findSuitableResource(load, config.resources) - } - } + config.searchStrategy.applySearchStrategyByMetric(config.loads, config.resources, config.metric) + } finally { ioHandler.writeToJSONFile( - config.compositeStrategy.benchmarkExecutor.results, + config.searchStrategy.benchmarkExecutor.results, "${resultsFolder}exp${this.config.executionId}-result" ) - // Create expXYZ_demand.csv file - ioHandler.writeToCSVFile( - "${resultsFolder}exp${this.config.executionId}_demand", - calculateDemandMetric(config.loads, config.compositeStrategy.benchmarkExecutor.results), - listOf("load","resources") - ) + // Create expXYZ_demand.csv file or expXYZ_capacity.csv depending on metric + when(config.metric) { + Metric.DEMAND -> + ioHandler.writeToCSVFile( + "${resultsFolder}exp${this.config.executionId}_demand", + calculateMetric(config.loads, config.searchStrategy.benchmarkExecutor.results), + listOf("load","resources") + ) + Metric.CAPACITY -> + ioHandler.writeToCSVFile( + "${resultsFolder}exp${this.config.executionId}_capacity", + calculateMetric(config.resources, config.searchStrategy.benchmarkExecutor.results), + listOf("resource", "loads") + ) + } } kubernetesBenchmark.teardownInfrastructure() } @@ -157,8 +157,8 @@ class TheodoliteExecutor( return executionID } - private fun calculateDemandMetric(loadDimensions: List<LoadDimension>, results: Results): List<List<String>> { - return loadDimensions.map { listOf(it.get().toString(), results.getMinRequiredInstances(it).get().toString()) } + private fun calculateMetric(xValues: List<Int>, results: Results): List<List<String>> { + return xValues.map { listOf(it.toString(), results.getOptYDimensionValue(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/StrategyFactory.kt b/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt index 829370e8ce1c181c1a4cb9fdd8ccf0ecefd48d3d..505681716966016548416bf40cefc2c9ad15d805 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/StrategyFactory.kt @@ -1,12 +1,10 @@ package theodolite.strategies +import theodolite.benchmark.BenchmarkExecution import theodolite.execution.BenchmarkExecutor import theodolite.strategies.restriction.LowerBoundRestriction import theodolite.strategies.restriction.RestrictionStrategy -import theodolite.strategies.searchstrategy.BinarySearch -import theodolite.strategies.searchstrategy.FullSearch -import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.strategies.searchstrategy.SearchStrategy +import theodolite.strategies.searchstrategy.* import theodolite.util.Results /** @@ -18,18 +16,37 @@ class StrategyFactory { * Create a [SearchStrategy]. * * @param executor The [theodolite.execution.BenchmarkExecutor] that executes individual experiments. - * @param searchStrategyString Specifies the [SearchStrategy]. Must either be the string 'LinearSearch', - * or 'BinarySearch'. + * @param searchStrategyObject Specifies the [SearchStrategy]. Must either be an object with name 'FullSearch', + * 'LinearSearch', 'BinarySearch', 'RestrictionSearch' or 'InitialGuessSearch'. + * @param results The [Results] saves the state of the Theodolite benchmark run. * * @throws IllegalArgumentException if the [SearchStrategy] was not one of the allowed options. */ - fun createSearchStrategy(executor: BenchmarkExecutor, searchStrategyString: String): SearchStrategy { - return when (searchStrategyString) { + fun createSearchStrategy(executor: BenchmarkExecutor, searchStrategyObject: BenchmarkExecution.Strategy, + results: Results): SearchStrategy { + + var strategy : SearchStrategy = when (searchStrategyObject.name) { "FullSearch" -> FullSearch(executor) "LinearSearch" -> LinearSearch(executor) "BinarySearch" -> BinarySearch(executor) - else -> throw IllegalArgumentException("Search Strategy $searchStrategyString not found") + "RestrictionSearch" -> when (searchStrategyObject.searchStrategy){ + "FullSearch" -> composeSearchRestrictionStrategy(executor, FullSearch(executor), results, + searchStrategyObject.restrictions) + "LinearSearch" -> composeSearchRestrictionStrategy(executor, LinearSearch(executor), results, + searchStrategyObject.restrictions) + "BinarySearch" -> composeSearchRestrictionStrategy(executor, BinarySearch(executor), results, + searchStrategyObject.restrictions) + else -> throw IllegalArgumentException( + "Search Strategy ${searchStrategyObject.searchStrategy} for RestrictionSearch not found") + } + "InitialGuessSearch" -> when (searchStrategyObject.guessStrategy){ + "PrevResourceMinGuess" -> InitialGuessSearchStrategy(executor,PrevInstanceOptGuess(), results) + else -> throw IllegalArgumentException("Guess Strategy ${searchStrategyObject.guessStrategy} not found") + } + else -> throw IllegalArgumentException("Search Strategy $searchStrategyObject not found") } + + return strategy } /** @@ -37,12 +54,12 @@ class StrategyFactory { * * @param results The [Results] saves the state of the Theodolite benchmark run. * @param restrictionStrings Specifies the list of [RestrictionStrategy] that are used to restrict the amount - * of [theodolite.util.Resource] for a fixed LoadDimension. Must equal the string - * 'LowerBound'. + * of Resource for a fixed load or resource (depending on the metric). + * Must equal the string 'LowerBound'. * * @throws IllegalArgumentException if param searchStrategyString was not one of the allowed options. */ - fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { + private fun createRestrictionStrategy(results: Results, restrictionStrings: List<String>): Set<RestrictionStrategy> { return restrictionStrings .map { restriction -> when (restriction) { @@ -51,4 +68,21 @@ class StrategyFactory { } }.toSet() } + + /** + * Create a RestrictionSearch, if the provided restriction list is not empty. Otherwise just return the given + * searchStrategy. + * + * @param executor The [theodolite.execution.BenchmarkExecutor] that executes individual experiments. + * @param searchStrategy The [SearchStrategy] to use. + * @param results The [Results] saves the state of the Theodolite benchmark run. + * @param restrictions The [RestrictionStrategy]'s to use. + */ + private fun composeSearchRestrictionStrategy(executor: BenchmarkExecutor, searchStrategy: SearchStrategy, + results: Results, restrictions: List<String>): SearchStrategy { + if(restrictions.isNotEmpty()){ + return RestrictionSearch(executor,searchStrategy,createRestrictionStrategy(results, restrictions)) + } + return searchStrategy + } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt index 13bfedfe055f2bd428137f89b2986f3967ec797c..1203d7293e3790aac2af653c9ceccd0c2ef544bd 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/restriction/LowerBoundRestriction.kt @@ -1,24 +1,24 @@ package theodolite.strategies.restriction -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results /** - * The [LowerBoundRestriction] sets the lower bound of the resources to be examined to the value - * needed to successfully execute the next smaller load. + * The [LowerBoundRestriction] sets the lower bound of the resources to be examined in the experiment to the value + * needed to successfully execute the previous smaller load (demand metric), or sets the lower bound of the loads + * to be examined in the experiment to the largest value, which still successfully executed the previous smaller + * resource (capacity metric). * * @param results [Result] object used as a basis to restrict the resources. */ class LowerBoundRestriction(results: Results) : RestrictionStrategy(results) { - override fun apply(load: LoadDimension, resources: List<Resource>): List<Resource> { - val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load) - var lowerBound: Resource? = 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.getOptYDimensionValue(maxXValue) if (lowerBound == null) { - lowerBound = resources[0] + lowerBound = yValues[0] } - return resources.filter { x -> x.get() >= lowerBound.get() } + 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 1ab7302d7898daad729b1c94c32d97138b5cdcf4..1ac9d201c734c84db4ec8837c8a242f2be8531b9 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/restriction/RestrictionStrategy.kt @@ -1,12 +1,10 @@ package theodolite.strategies.restriction import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results /** - * A 'Restriction Strategy' restricts a list of resources based on the current + * A 'Restriction Strategy' restricts a list of resources or loads depending on the metric based on the current * results of all previously performed benchmarks. * * @param results the [Results] object @@ -14,12 +12,13 @@ import theodolite.util.Results @RegisterForReflection abstract class RestrictionStrategy(val results: Results) { /** - * Apply the restriction of the given resource list for the given load based on the results object. + * Apply the restriction of the given resource list for the given load based on the results object (demand metric), + * or apply the restriction of the given load list for the given resource based on the results object (capacity metric). * - * @param load [LoadDimension] for which a subset of resources are required. - * @param resources List of [Resource]s to be restricted. + * @param xValue The value to be examined in the experiment, can be load (demand metric) or resource (capacity metric). + * @param yValues List of values to be restricted, can be resources (demand metric) or loads (capacity metric). * @return Returns a list containing only elements that have not been filtered out by the * restriction (possibly empty). */ - abstract fun apply(load: LoadDimension, resources: List<Resource>): List<Resource> + abstract fun apply(xValue: Int, yValues: List<Int>): List<Int> } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt index 28e8194c699cd074026c8cb7e6f3ce4ec347023b..6e56c3b36c6e2889e4714c890a424e4f6765386e 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt @@ -2,8 +2,6 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} @@ -13,48 +11,97 @@ private val logger = KotlinLogging.logger {} * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - val result = binarySearch(load, resources, 0, resources.size - 1) + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + val result = binarySearchDemand(load, resources, 0, resources.size - 1) if (result == -1) { return null } return resources[result] } + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + val result = binarySearchCapacity(resource, loads, 0, loads.size - 1) + if (result == -1) { + return null + } + return loads[result] + } + /** - * Apply binary search. + * Apply binary search for the demand metric. * - * @param load the load dimension to perform experiments for - * @param resources the list in which binary search is performed - * @param lower lower bound for binary search (inclusive) - * @param upper upper bound for binary search (inclusive) + * @param load the load to perform experiments for. + * @param resources the list of resources in which binary search is performed. + * @param lower lower bound for binary search (inclusive). + * @param upper upper bound for binary search (inclusive). */ - private fun binarySearch(load: LoadDimension, resources: List<Resource>, lower: Int, upper: Int): Int { + private fun binarySearchDemand(load: Int, resources: List<Int>, lower: Int, upper: Int): Int { if (lower > upper) { throw IllegalArgumentException() } - // special case: length == 1 or 2 + // special case: length == 1, so lower and upper bounds are the same if (lower == upper) { val res = resources[lower] - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, resources[lower])) return lower + logger.info { "Running experiment with load '$load' and resource '$res'" } + if (this.benchmarkExecutor.runExperiment(load, res)) return lower else { if (lower + 1 == resources.size) return -1 return lower + 1 } } else { // apply binary search for a list with - // length > 2 and adjust upper and lower depending on the result for `resources[mid]` + // length >= 2 and adjust upper and lower depending on the result for `resources[mid]` val mid = (upper + lower) / 2 val res = resources[mid] - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } - if (this.benchmarkExecutor.runExperiment(load, resources[mid])) { + logger.info { "Running experiment with load '$load' and resource '$res'" } + if (this.benchmarkExecutor.runExperiment(load, res)) { + // case length = 2 if (mid == lower) { return lower } - return binarySearch(load, resources, lower, mid - 1) + return binarySearchDemand(load, resources, lower, mid - 1) + } else { + return binarySearchDemand(load, resources, mid + 1, upper) + } + } + } + + + /** + * Apply binary search for the capacity metric. + * + * @param resource the resource to perform experiments for. + * @param loads the list of loads in which binary search is performed. + * @param lower lower bound for binary search (inclusive). + * @param upper upper bound for binary search (inclusive). + */ + private fun binarySearchCapacity(resource: Int, loads: List<Int>, lower: Int, upper: Int): Int { + if (lower > upper) { + throw IllegalArgumentException() + } + // length = 1, so lower and upper bounds are the same + if (lower == upper) { + val load = loads[lower] + logger.info { "Running experiment with load '$load' and resource '$resource'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) return lower + else { + if (lower + 1 == loads.size) return -1 + return lower - 1 + } + } else { + // apply binary search for a list with + // length > 2 and adjust upper and lower depending on the result for `resources[mid]` + val mid = (upper + lower + 1) / 2 //round to next int + val load = loads[mid] + logger.info { "Running experiment with load '$load' and resource '$resource'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + // length = 2, so since we round down mid is equal to lower + if (mid == upper) { + return upper + } + return binarySearchCapacity(resource, loads, mid + 1, upper) } else { - return binarySearch(load, resources, mid + 1, upper) + return binarySearchCapacity(resource, loads, lower, mid - 1) } } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt deleted file mode 100644 index d6ace6f564239e73a0d59f8eb7900f50018482c5..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt +++ /dev/null @@ -1,30 +0,0 @@ -package theodolite.strategies.searchstrategy - -import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.execution.BenchmarkExecutor -import theodolite.strategies.restriction.RestrictionStrategy -import theodolite.util.LoadDimension -import theodolite.util.Resource - -/** - * Composite strategy that combines a SearchStrategy and a set of RestrictionStrategy. - * - * @param searchStrategy the [SearchStrategy] that is executed as part of this [CompositeStrategy]. - * @param restrictionStrategies the set of [RestrictionStrategy] that are connected conjunctive to restrict the [Resource] - * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. - */ -@RegisterForReflection -class CompositeStrategy( - benchmarkExecutor: BenchmarkExecutor, - private val searchStrategy: SearchStrategy, - val restrictionStrategies: Set<RestrictionStrategy> -) : SearchStrategy(benchmarkExecutor) { - - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - var restrictedResources = resources.toList() - for (strategy in this.restrictionStrategies) { - restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList() - } - return this.searchStrategy.findSuitableResource(load, restrictedResources) - } -} diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt index 83c4abbdf44f1a1c2f3a27714d796580feedee49..6af6aa1602d18a089b9450f6e1eb84bca8edafa3 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt @@ -2,25 +2,24 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} /** - * [SearchStrategy] that executes experiment for provides resources in a linear-search-like fashion, but **without - * stopping** once a suitable resource amount is found. + * [SearchStrategy] that executes an experiment for a load and a resource list (demand metric) or for a resource and a + * load list (capacity metric) in a linear-search-like fashion, but **without stopping** once the desired + * resource (demand) or load (capacity) is found. * - * @see LinearSearch for a SearchStrategy that stops once a suitable resource amount is found. + * @see LinearSearch for a SearchStrategy that stops once the desired resource (demand) or load (capacity) is found. * * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - var minimalSuitableResources: Resource? = null + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + var minimalSuitableResources: Int? = null for (res in resources) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } val result = this.benchmarkExecutor.runExperiment(load, res) if (result && minimalSuitableResources == null) { minimalSuitableResources = res @@ -28,4 +27,13 @@ class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmar } return minimalSuitableResources } + + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + var maxSuitableLoad: Int? = null + for (load in loads) { + logger.info { "Running experiment with resources '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) maxSuitableLoad = load + } + return maxSuitableLoad + } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt index 786a3baf159e94841c1f76c696f030718e8f768f..f243b8e4076d0cf0d2ef23bdd6b02b5da2d34152 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/GuessStrategy.kt @@ -1,22 +1,22 @@ package theodolite.strategies.searchstrategy import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.util.Resource /** * Base class for the implementation of Guess strategies. Guess strategies are strategies to determine the resource - * demand we start with in our initial guess search strategy. + * demand (demand metric) or load (capacity metric) we start with in our initial guess search strategy. */ @RegisterForReflection abstract class GuessStrategy { /** - * Computing the resource demand for the initial guess search strategy to start with. + * Computing the resource demand (demand metric) or load (capacity metric) for the initial guess search strategy + * to start with. * - * @param resources List of all possible [Resource]s. - * @param lastLowestResource Previous resource demand needed for the given load. + * @param valuesToCheck List of all possible resources/loads. + * @param lastOptValue Previous minimal/maximal resource/load value for the given load/resource. * - * @return Returns the resource demand to start the initial guess search strategy with or null + * @return the resource/load to start the initial guess search strategy with or null */ - abstract fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource? + abstract fun firstGuess(valuesToCheck: List<Int>, lastOptValue: Int?): Int? } \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt index d97fb62cc9d37dd50122199e5d089c491784e511..447d810942be74f0cdbee865e3f0bb5d58e61603 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt @@ -2,8 +2,6 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results private val logger = KotlinLogging.logger {} @@ -16,42 +14,28 @@ private val logger = KotlinLogging.logger {} * @param guessStrategy Strategy that provides us with a guess for the first resource amount. * @param results current results of all previously performed benchmarks. */ -class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStrategy: GuessStrategy, results: Results) : - SearchStrategy(benchmarkExecutor, guessStrategy, results) { +class InitialGuessSearchStrategy( + benchmarkExecutor: BenchmarkExecutor, + private val guessStrategy: GuessStrategy, + private var results: Results +) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { - - if(resources.isEmpty()) { - logger.info { "You need to specify resources to be checked for the InitialGuessSearchStrategy to work." } - return null - } - - if(guessStrategy == null){ - logger.info { "Your InitialGuessSearchStrategy doesn't have a GuessStrategy. This is not supported." } - return null - } - - if(results == null){ - logger.info { "The results need to be initialized." } - return null - } + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { - - var lastLowestResource : Resource? = null + var lastLowestResource : Int? = null // Getting the lastLowestResource from results and calling firstGuess() with it if (!results.isEmpty()) { - val maxLoad: LoadDimension? = this.results.getMaxBenchmarkedLoad(load) - lastLowestResource = this.results.getMinRequiredInstances(maxLoad) - if (lastLowestResource.get() == Int.MAX_VALUE) lastLowestResource = null + val maxLoad: Int? = this.results.getMaxBenchmarkedXDimensionValue(load) + lastLowestResource = this.results.getOptYDimensionValue(maxLoad) } lastLowestResource = this.guessStrategy.firstGuess(resources, lastLowestResource) if (lastLowestResource != null) { - val resourcesToCheck: List<Resource> + val resourcesToCheck: List<Int> val startIndex: Int = resources.indexOf(lastLowestResource) - logger.info { "Running experiment with load '${load.get()}' and resources '${lastLowestResource.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$lastLowestResource'" } // If the first experiment passes, starting downward linear search // otherwise starting upward linear search @@ -60,10 +44,10 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra resourcesToCheck = resources.subList(0, startIndex).reversed() if (resourcesToCheck.isEmpty()) return lastLowestResource - var currentMin: Resource = lastLowestResource + var currentMin: Int = lastLowestResource for (res in resourcesToCheck) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } if (this.benchmarkExecutor.runExperiment(load, res)) { currentMin = res } @@ -79,14 +63,69 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra for (res in resourcesToCheck) { - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } if (this.benchmarkExecutor.runExperiment(load, res)) return res } } } else { - logger.info { "InitialGuessSearchStrategy called without lastLowestResource value, which is needed as a " + - "starting point!" } + logger.info { "lastLowestResource was null." } + } + return null + } + + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int?{ + + var lastMaxLoad : Int? = null + + // Getting the lastLowestLoad from results and calling firstGuess() with it + if (!results.isEmpty()) { + val maxResource: Int? = this.results.getMaxBenchmarkedXDimensionValue(resource) + lastMaxLoad = this.results.getOptYDimensionValue(maxResource) + } + lastMaxLoad = this.guessStrategy.firstGuess(loads, lastMaxLoad) + + if (lastMaxLoad != null) { + val loadsToCheck: List<Int> + val startIndex: Int = loads.indexOf(lastMaxLoad) + + logger.info { "Running experiment with resource '$resource' and load '$lastMaxLoad'" } + + // If the first experiment passes, starting upwards linear search + // otherwise starting downward linear search + if (!this.benchmarkExecutor.runExperiment(lastMaxLoad, resource)) { + // downward search + + loadsToCheck = loads.subList(0, startIndex).reversed() + if (loadsToCheck.isNotEmpty()) { + for (load in loadsToCheck) { + + logger.info { "Running experiment with resource '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + return load + } + } + } + } + else { + // upward search + if (loads.size <= startIndex + 1) { + return lastMaxLoad + } + loadsToCheck = loads.subList(startIndex + 1, loads.size) + + var currentMax: Int = lastMaxLoad + for (load in loadsToCheck) { + logger.info { "Running experiment with resource '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + currentMax = load + } + } + return currentMax + } + } + else { + logger.info { "lastMaxLoad was null." } } return null } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt index 85deaf6fa75437199bfc560404eb5b40bb4a986a..1991431a970e44bf70eccf834a2ecb0221502a71 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt @@ -2,24 +2,33 @@ package theodolite.strategies.searchstrategy import mu.KotlinLogging import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource private val logger = KotlinLogging.logger {} /** - * Linear-search-like implementation for determining the smallest suitable number of instances. + * Linear-search-like implementation for determining the smallest/biggest suitable number of resources/loads, + * depending on the metric. * * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. */ class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) { - override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? { + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { for (res in resources) { - - logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" } + logger.info { "Running experiment with load '$load' and resources '$res'" } if (this.benchmarkExecutor.runExperiment(load, res)) return res } return null } + + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + var maxSuitableLoad: Int? = null + for (load in loads) { + logger.info { "Running experiment with resources '$resource' and load '$load'" } + if (this.benchmarkExecutor.runExperiment(load, resource)) { + maxSuitableLoad = load + } else break + } + return maxSuitableLoad + } } diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevInstanceOptGuess.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevInstanceOptGuess.kt new file mode 100644 index 0000000000000000000000000000000000000000..c919b475995a6a5366371336041d9633f0aa1993 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevInstanceOptGuess.kt @@ -0,0 +1,28 @@ +package theodolite.strategies.searchstrategy + + +/** + * [PrevInstanceOptGuess] is a [GuessStrategy] that implements the function [firstGuess], + * where it returns the optimal result of the previous run. + */ + +class PrevInstanceOptGuess() : GuessStrategy(){ + + /** + * If the metric is + * + * - "demand", [valuesToCheck] is a List of resources and [lastOptValue] a resource value. + * - "capacity", [valuesToCheck] is a List of loads and [lastOptValue] a load value. + * + * @param valuesToCheck List of all possible resources/loads. + * @param lastOptValue Previous minimal/maximal resource/load value for the given load/resource. + * + * @return the value of [lastOptValue] if given otherwise the first element of the [valuesToCheck] list or null + */ + override fun firstGuess(valuesToCheck: List<Int>, lastOptValue: Int?): Int? { + + if (lastOptValue != null) return lastOptValue + else if(valuesToCheck.isNotEmpty()) return valuesToCheck[0] + else return null + } +} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt deleted file mode 100644 index 413eecea27279cd79bad155fbb7d5d18b674a12e..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/PrevResourceMinGuess.kt +++ /dev/null @@ -1,24 +0,0 @@ -package theodolite.strategies.searchstrategy - -import theodolite.util.Resource - -/** - * This Guess strategy takes the minimal resource demand of the previous load, which is given as an argument for the - * firstGuess function. - */ - -class PrevResourceMinGuess() : GuessStrategy(){ - - /** - * @param resources List of all possible [Resource]s. - * @param lastLowestResource Previous resource demand needed for the given load. - * - * @return the value of lastLowestResource if given otherwise the first element of the resource list or null - */ - override fun firstGuess(resources: List<Resource>, lastLowestResource: Resource?): Resource? { - - if (lastLowestResource != null) return lastLowestResource - else if(resources.isNotEmpty()) return resources[0] - else return null - } -} \ No newline at end of file diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a136d6ff43e6f62fc5ebe34c392816da3592bf1 --- /dev/null +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/RestrictionSearch.kt @@ -0,0 +1,43 @@ +package theodolite.strategies.searchstrategy + +import io.quarkus.runtime.annotations.RegisterForReflection +import theodolite.execution.BenchmarkExecutor +import theodolite.strategies.restriction.RestrictionStrategy + +/** + * Strategy that combines a SearchStrategy and a set of RestrictionStrategy. + * + * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. + * @param searchStrategy the [SearchStrategy] that is executed as part of this [RestrictionSearch]. + * @param restrictionStrategies the set of [RestrictionStrategy] that are connected conjunctive to restrict the Resource. + * + */ +@RegisterForReflection +class RestrictionSearch( + benchmarkExecutor: BenchmarkExecutor, + private val searchStrategy: SearchStrategy, + private val restrictionStrategies: Set<RestrictionStrategy> +) : SearchStrategy(benchmarkExecutor) { + + /** + * Restricting the possible resources and calling findSuitableResource of the given [SearchStrategy]. + */ + override fun findSuitableResource(load: Int, resources: List<Int>): Int? { + var restrictedResources = resources + for (strategy in this.restrictionStrategies) { + restrictedResources = restrictedResources.intersect(strategy.apply(load, resources).toSet()).toList() + } + return this.searchStrategy.findSuitableResource(load, restrictedResources) + } + + /** + * Restricting the possible loads and calling findSuitableLoad of the given [SearchStrategy]. + */ + override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? { + var restrictedLoads = loads + 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 97c723f2cfe459081cbb327f6860e48319c8f4f1..962504c7d85fa5e28d73fe0cb9d844ed2be66a99 100644 --- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt +++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt @@ -2,27 +2,63 @@ package theodolite.strategies.searchstrategy import io.quarkus.runtime.annotations.RegisterForReflection import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.strategies.Metric import theodolite.util.Results /** - * Base class for the implementation for SearchStrategies. SearchStrategies determine the smallest suitable number of instances. + * Base class for the implementation for SearchStrategies. SearchStrategies determine the smallest suitable number + * of resources/loads for a load/resource (depending on the metric). * * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks. * @param guessStrategy Guess strategy for the initial resource amount in case the InitialGuessStrategy is selected. * @param results the [Results] object. */ @RegisterForReflection -abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor, val guessStrategy: GuessStrategy? = null, - val results: Results? = null) { +abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) { + + + /** + * Calling findSuitableResource or findSuitableLoad for each load/resource depending on the chosen metric. + * + * @param loads List of possible loads for the experiments. + * @param resources List of possible resources for the experiments. + * @param metric The [Metric] for the experiments, either "demand" or "capacity". + */ + fun applySearchStrategyByMetric(loads: List<Int>, resources: List<Int>, metric: Metric) { + + when(metric) { + Metric.DEMAND -> + for (load in loads) { + if (benchmarkExecutor.run.get()) { + this.findSuitableResource(load, resources) + } + } + Metric.CAPACITY -> + for (resource in resources) { + if (benchmarkExecutor.run.get()) { + this.findSuitableLoad(resource, loads) + } + } + } + } + /** - * Find smallest suitable resource from the specified resource list for the given load. + * Find the smallest suitable resource from the specified resource list for the given load. * - * @param load the [LoadDimension] to be tested. - * @param resources List of all possible [Resource]s. + * @param load the load to be tested. + * @param resources List of all possible resources. * * @return suitable resource for the specified load, or null if no suitable resource exists. */ - abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? + abstract fun findSuitableResource(load: Int, resources: List<Int>): Int? + + /** + * Find the biggest suitable load from the specified load list for the given resource amount. + * + * @param resource the resource to be tested. + * @param loads List of all possible loads. + * + * @return suitable load for the specified resource amount, or null if no suitable load exists. + */ + abstract fun findSuitableLoad(resource: Int, loads: List<Int>) : Int? } diff --git a/theodolite/src/main/kotlin/theodolite/util/Config.kt b/theodolite/src/main/kotlin/theodolite/util/Config.kt index afbf784e9d6d72939615e367b54891ecd95a3608..675caea5fbd3b8e8c699019e10f43c36b9fbaebe 100644 --- a/theodolite/src/main/kotlin/theodolite/util/Config.kt +++ b/theodolite/src/main/kotlin/theodolite/util/Config.kt @@ -1,18 +1,23 @@ package theodolite.util import io.quarkus.runtime.annotations.RegisterForReflection -import theodolite.strategies.searchstrategy.CompositeStrategy +import theodolite.strategies.Metric +import theodolite.strategies.searchstrategy.SearchStrategy /** * Config class that represents a configuration of a theodolite run. * - * @param loads the [LoadDimension] of the execution - * @param resources the [Resource] of the execution - * @param compositeStrategy the [CompositeStrategy] of the execution + * @param loads the possible loads of the execution + * @param resources the possible resources of the execution + * @param searchStrategy the [SearchStrategy] of the execution + * @param metric the Metric of the execution */ @RegisterForReflection data class Config( - val loads: List<LoadDimension>, - val resources: List<Resource>, - val compositeStrategy: CompositeStrategy + val loads: List<Int>, + val loadPatcherDefinitions : List<PatcherDefinition>, + val resources: List<Int>, + val resourcePatcherDefinitions : List<PatcherDefinition>, + val searchStrategy: SearchStrategy, + val metric: Metric ) diff --git a/theodolite/src/main/kotlin/theodolite/util/LoadDimension.kt b/theodolite/src/main/kotlin/theodolite/util/LoadDimension.kt deleted file mode 100644 index cf26da979b05f0a2bd82289ce371715ea0d67c93..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/LoadDimension.kt +++ /dev/null @@ -1,26 +0,0 @@ -package theodolite.util - -import io.quarkus.runtime.annotations.RegisterForReflection - -/** - * Representation of the load dimensions for a execution of theodolite. - * - * @param number the value of this [LoadDimension] - * @param type [PatcherDefinition] of this [LoadDimension] - */ -@RegisterForReflection -data class LoadDimension(private val number: Int, private val type: List<PatcherDefinition>) { - /** - * @return the value of this load dimension. - */ - fun get(): Int { - return this.number - } - - /** - * @return the list of [PatcherDefinition] - */ - fun getType(): List<PatcherDefinition> { - return this.type - } -} diff --git a/theodolite/src/main/kotlin/theodolite/util/Resource.kt b/theodolite/src/main/kotlin/theodolite/util/Resource.kt deleted file mode 100644 index 1d6410aa4288e19817e3ba48bfd1bc0d85d006a2..0000000000000000000000000000000000000000 --- a/theodolite/src/main/kotlin/theodolite/util/Resource.kt +++ /dev/null @@ -1,24 +0,0 @@ -package theodolite.util - -import io.quarkus.runtime.annotations.RegisterForReflection - -/** - * Representation of the resources for an execution of Theodolite. - */ -@RegisterForReflection -data class Resource(private val number: Int, private val type: List<PatcherDefinition>) { - - /** - * @return the value of this resource. - */ - fun get(): Int { - return this.number - } - - /** - * @return the list of [PatcherDefinition] - */ - fun getType(): List<PatcherDefinition> { - return this.type - } -} diff --git a/theodolite/src/main/kotlin/theodolite/util/Results.kt b/theodolite/src/main/kotlin/theodolite/util/Results.kt index 2221c2e64f6dbc1776122f20793aa8d04d621d9d..418ac50f285996affd7db1c15fc44a22f1677d29 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,80 +9,111 @@ import io.quarkus.runtime.annotations.RegisterForReflection * perform the [theodolite.strategies.restriction.RestrictionStrategy]. */ @RegisterForReflection -class Results { - private val results: MutableMap<Pair<LoadDimension, Resource>, Boolean> = mutableMapOf() +class Results (val metric: Metric) { + // (load, resource) -> Boolean map + private val results: MutableMap<Pair<Int, Int>, Boolean> = mutableMapOf() + + // if metric is "demand" : load -> resource + // if metric is "capacity": resource -> load + private var optInstances: MutableMap<Int, Int> = mutableMapOf() + /** - * Set the result for an experiment. + * Set the result for an experiment and update the [optInstances] list accordingly. * - * @param experiment A pair that identifies the experiment by the [LoadDimension] and [Resource]. + * @param experiment A pair that identifies the experiment by the Load and Resource. * @param successful the result of the experiment. Successful == true and Unsuccessful == false. */ - fun setResult(experiment: Pair<LoadDimension, Resource>, successful: Boolean) { + fun setResult(experiment: Pair<Int, Int>, successful: Boolean) { this.results[experiment] = successful + + when (metric) { + Metric.DEMAND -> + if (successful) { + if (optInstances.containsKey(experiment.first)) { + if (optInstances[experiment.first]!! > experiment.second) { + optInstances[experiment.first] = experiment.second + } + } else { + optInstances[experiment.first] = experiment.second + } + } else if (!optInstances.containsKey(experiment.first)) { + optInstances[experiment.first] = Int.MAX_VALUE + } + Metric.CAPACITY -> + if (successful) { + if (optInstances.containsKey(experiment.second)) { + if (optInstances[experiment.second]!! < experiment.first) { + optInstances[experiment.second] = experiment.first + } + } else { + optInstances[experiment.second] = experiment.first + } + } else if (!optInstances.containsKey(experiment.second)) { + optInstances[experiment.second] = Int.MIN_VALUE + } + } } /** * Get the result for an experiment. * - * @param experiment A pair that identifies the experiment by the [LoadDimension] and [Resource]. + * @param experiment A pair that identifies the experiment by the Load and Resource. * @return true if the experiment was successful and false otherwise. If the result has not been reported so far, * null is returned. * - * @see Resource */ - fun getResult(experiment: Pair<LoadDimension, Resource>): Boolean? { + fun getResult(experiment: Pair<Int, Int>): Boolean? { return this.results[experiment] } /** - * Get the smallest suitable number of instances for a specified [LoadDimension]. + * Get the smallest suitable number of instances for a specified x-Value. + * The x-Value corresponds to the... + * - load, if the metric is "demand". + * - resource, if the metric is "capacity". * - * @param load the [LoadDimension] + * @param xValue the Value of the x-dimension of the current metric * - * @return the smallest suitable number of resources. If the experiment was not executed yet, - * a @see Resource with the constant Int.MAX_VALUE as value is returned. - * If no experiments have been marked as either successful or unsuccessful - * yet, a Resource with the constant value Int.MIN_VALUE is returned. + * @return the smallest suitable number of resources/loads (depending on metric). + * If there is no experiment that has been executed yet, there is no experiment + * for the given [xValue] or there is none marked successful yet, null is returned. */ - fun getMinRequiredInstances(load: LoadDimension?): Resource { - if (this.results.isEmpty()) { - return Resource(Int.MIN_VALUE, emptyList()) - } - - var minRequiredInstances = Resource(Int.MAX_VALUE, emptyList()) - for (experiment in results) { - // Get all successful experiments for requested load - if (experiment.key.first == load && experiment.value) { - if (experiment.key.second.get() < minRequiredInstances.get()) { - // Found new smallest resources - minRequiredInstances = experiment.key.second - } + fun getOptYDimensionValue(xValue: Int?): Int? { + if (xValue != null) { + val res = optInstances[xValue] + if (res != Int.MAX_VALUE && res != Int.MIN_VALUE) { + return res } } - return minRequiredInstances + return null } /** - * Get the largest [LoadDimension] that has been reported executed successfully (or unsuccessfully) so far, for a - * [LoadDimension] and is smaller than the given [LoadDimension]. + * Get the largest x-Value that has been tested and reported so far (successful or unsuccessful), + * which is smaller than the specified x-Value. + * + * The x-Value corresponds to the... + * - load, if the metric is "demand". + * - resource, if the metric is "capacity". * - * @param load the [LoadDimension] + * @param xValue the Value of the x-dimension of the current metric * - * @return the largest [LoadDimension] or null, if there is none for this [LoadDimension] + * @return the largest tested x-Value or null, if there wasn't any tested which is smaller than the [xValue]. */ - fun getMaxBenchmarkedLoad(load: LoadDimension): LoadDimension? { - var maxBenchmarkedLoad: LoadDimension? = null - for (experiment in results) { - if (experiment.key.first.get() <= load.get()) { - if (maxBenchmarkedLoad == null) { - maxBenchmarkedLoad = experiment.key.first - } else if (maxBenchmarkedLoad.get() < experiment.key.first.get()) { - maxBenchmarkedLoad = experiment.key.first + fun getMaxBenchmarkedXDimensionValue(xValue: Int): Int? { + var maxBenchmarkedXValue: Int? = null + for (instance in optInstances) { + val instanceXValue= instance.key + if (instanceXValue <= xValue) { + if (maxBenchmarkedXValue == null) { + maxBenchmarkedXValue = instanceXValue + } else if (maxBenchmarkedXValue < instanceXValue) { + maxBenchmarkedXValue = instanceXValue } } } - return maxBenchmarkedLoad + return maxBenchmarkedXValue } /** diff --git a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt index 1af6f548b219697009c688ace712a9f7f5620bd0..de7a1f8f170bb4c25aeb328d3f68e3a027712a62 100644 --- a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt +++ b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt @@ -5,11 +5,10 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import theodolite.benchmark.BenchmarkExecution import theodolite.strategies.searchstrategy.InitialGuessSearchStrategy -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.strategies.Metric import theodolite.util.Results import mu.KotlinLogging -import theodolite.strategies.searchstrategy.PrevResourceMinGuess +import theodolite.strategies.searchstrategy.PrevInstanceOptGuess private val logger = KotlinLogging.logger {} @@ -27,23 +26,23 @@ class InitialGuessSearchStrategyTest { arrayOf(false, false, false, false, false, false, true), arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() + val guessStrategy = PrevInstanceOptGuess() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val strategy = InitialGuessSearchStrategy(benchmarkExecutor,guessStrategy, results) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) expected.add(null) for (load in mockLoads) { - val returnVal : Resource? = strategy.findSuitableResource(load, mockResources) + val returnVal : Int? = strategy.findSuitableResource(load, mockResources) if(returnVal != null) { - logger.info { "returnVal '${returnVal.get()}'" } + logger.info { "returnVal '${returnVal}'" } } else { logger.info { "returnVal is null." } @@ -65,23 +64,23 @@ class InitialGuessSearchStrategyTest { arrayOf(false, false, false, false, false, false, true), arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() + val guessStrategy = PrevInstanceOptGuess() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val strategy = InitialGuessSearchStrategy(benchmarkExecutor,guessStrategy, results) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 1, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 1, 4, 6)) expected.add(null) for (load in mockLoads) { - val returnVal : Resource? = strategy.findSuitableResource(load, mockResources) + val returnVal : Int? = strategy.findSuitableResource(load, mockResources) if(returnVal != null) { - logger.info { "returnVal '${returnVal.get()}'" } + logger.info { "returnVal '${returnVal}'" } } else { logger.info { "returnVal is null." } @@ -103,24 +102,24 @@ class InitialGuessSearchStrategyTest { arrayOf(false, false, false, false, false, false, true), arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() - val guessStrategy = PrevResourceMinGuess() + val guessStrategy = PrevInstanceOptGuess() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 5) val strategy = InitialGuessSearchStrategy(benchmarkExecutor, guessStrategy, results) - val actual: ArrayList<Resource?> = ArrayList() - var expected: ArrayList<Resource?> = ArrayList(listOf(2, 3, 0, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + var expected: ArrayList<Int?> = ArrayList(listOf(2, 3, 0, 4, 6)) expected.add(null) expected = ArrayList(listOf(null) + expected) for (load in mockLoads) { - val returnVal : Resource? = strategy.findSuitableResource(load, mockResources) + val returnVal : Int? = strategy.findSuitableResource(load, mockResources) if(returnVal != null) { - logger.info { "returnVal '${returnVal.get()}'" } + logger.info { "returnVal '${returnVal}'" } } else { logger.info { "returnVal is null." } diff --git a/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt similarity index 56% rename from theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt rename to theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt index 580d9e747bde687a91ffb1bce2e7c9dfb6f166a2..1e973852d8448d0bff3410d5bb84bc5543be497a 100644 --- a/theodolite/src/test/kotlin/theodolite/CompositeStrategyTest.kt +++ b/theodolite/src/test/kotlin/theodolite/RestrictionSearchTest.kt @@ -4,19 +4,20 @@ 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.CompositeStrategy +import theodolite.strategies.searchstrategy.FullSearch +import theodolite.strategies.searchstrategy.RestrictionSearch import theodolite.strategies.searchstrategy.LinearSearch -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results @QuarkusTest -class CompositeStrategyTest { +class RestrictionSearchTest { + @Test - fun testEnd2EndLinearSearch() { + fun restrictionSearchTestLinearSearch() { val mockResults = arrayOf( arrayOf(true, true, true, true, true, true, true), arrayOf(false, false, true, true, true, true, true), @@ -26,19 +27,52 @@ class CompositeStrategyTest { arrayOf(false, false, false, false, false, false, true), arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + 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) val linearSearch = LinearSearch(benchmarkExecutor) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = - CompositeStrategy(benchmarkExecutor, linearSearch, setOf(lowerBoundRestriction)) + RestrictionSearch(benchmarkExecutor, linearSearch, setOf(lowerBoundRestriction)) + + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) + expected.add(null) + + for (load in mockLoads) { + actual.add(strategy.findSuitableResource(load, mockResources)) + } + + assertEquals(actual, expected) + } + + @Test + fun restrictionSearchTestFullSearch() { + val mockResults = arrayOf( + arrayOf(true, true, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, true, true, true, true, true), + arrayOf(false, false, false, true, true, true, true), + arrayOf(false, false, false, false, true, true, true), + arrayOf(false, false, false, false, false, false, true), + arrayOf(false, false, false, false, false, false, false) + ) + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + 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) + val fullSearch = FullSearch(benchmarkExecutor) + val lowerBoundRestriction = LowerBoundRestriction(results) + val strategy = + RestrictionSearch(benchmarkExecutor, fullSearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) expected.add(null) for (load in mockLoads) { @@ -49,7 +83,7 @@ class CompositeStrategyTest { } @Test - fun testEnd2EndBinarySearch() { + fun restrictionSearchTestBinarySearch() { val mockResults = arrayOf( arrayOf(true, true, true, true, true, true, true), arrayOf(false, false, true, true, true, true, true), @@ -59,20 +93,19 @@ class CompositeStrategyTest { arrayOf(false, false, false, false, false, false, true), arrayOf(false, false, false, false, false, false, false) ) - val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } - val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..6).toList() + val results = Results(Metric.from("demand")) val benchmark = TestBenchmark() val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo() val benchmarkExecutorImpl = TestBenchmarkExecutorImpl(mockResults, benchmark, results, listOf(sloChecker), 0, 0, 0) val binarySearch = BinarySearch(benchmarkExecutorImpl) val lowerBoundRestriction = LowerBoundRestriction(results) - val strategy = - CompositeStrategy(benchmarkExecutorImpl, binarySearch, setOf(lowerBoundRestriction)) + val strategy = RestrictionSearch(benchmarkExecutorImpl, binarySearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = ArrayList(listOf(0, 2, 2, 3, 4, 6)) expected.add(null) for (load in mockLoads) { @@ -83,7 +116,7 @@ class CompositeStrategyTest { } @Test - fun testEnd2EndBinarySearch2() { + fun restrictionSearchTestBinarySearch2() { val mockResults = arrayOf( arrayOf(true, true, true, true, true, true, true, true), arrayOf(false, false, true, true, true, true, true, true), @@ -93,20 +126,20 @@ class CompositeStrategyTest { arrayOf(false, false, false, false, false, false, true, true), arrayOf(false, false, false, false, false, false, false, true) ) - val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) } - val mockResources: List<Resource> = (0..7).map { number -> Resource(number, emptyList()) } - val results = Results() + val mockLoads: List<Int> = (0..6).toList() + val mockResources: List<Int> = (0..7).toList() + 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) val binarySearch = BinarySearch(benchmarkExecutor) val lowerBoundRestriction = LowerBoundRestriction(results) val strategy = - CompositeStrategy(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) + RestrictionSearch(benchmarkExecutor, binarySearch, setOf(lowerBoundRestriction)) - val actual: ArrayList<Resource?> = ArrayList() - val expected: ArrayList<Resource?> = - ArrayList(listOf(0, 2, 2, 3, 4, 6, 7).map { x -> Resource(x, emptyList()) }) + val actual: ArrayList<Int?> = ArrayList() + val expected: ArrayList<Int?> = + ArrayList(listOf(0, 2, 2, 3, 4, 6, 7)) for (load in mockLoads) { actual.add(strategy.findSuitableResource(load, mockResources)) diff --git a/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt b/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt index b08c1a18a3013e1573e4892f01698b5e509f9609..335b0f8c59ac7642c30a12e494890e8f2c52ccda 100644 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt +++ b/theodolite/src/test/kotlin/theodolite/TestBenchmark.kt @@ -3,8 +3,7 @@ package theodolite import theodolite.benchmark.Benchmark import theodolite.benchmark.BenchmarkDeployment import theodolite.util.ConfigurationOverride -import theodolite.util.LoadDimension -import theodolite.util.Resource +import theodolite.util.PatcherDefinition class TestBenchmark : Benchmark { @@ -15,11 +14,13 @@ class TestBenchmark : Benchmark { } override fun buildDeployment( - load: LoadDimension, - res: Resource, - configurationOverrides: List<ConfigurationOverride?>, - loadGenerationDelay: Long, - afterTeardownDelay: Long + load: Int, + loadPatcherDefinitions: List<PatcherDefinition>, + resource: Int, + resourcePatcherDefinitions: List<PatcherDefinition>, + configurationOverrides: List<ConfigurationOverride?>, + loadGenerationDelay: Long, + afterTeardownDelay: Long ): BenchmarkDeployment { return TestBenchmarkDeployment() } diff --git a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt b/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt index 2efddc48cb93a0870d1716c58a7018145c16e2ff..a5346896f6d20b6d9c0a828d2d3798c70c0861c4 100644 --- a/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt +++ b/theodolite/src/test/kotlin/theodolite/TestBenchmarkExecutorImpl.kt @@ -3,8 +3,6 @@ package theodolite import theodolite.benchmark.Benchmark import theodolite.benchmark.BenchmarkExecution import theodolite.execution.BenchmarkExecutor -import theodolite.util.LoadDimension -import theodolite.util.Resource import theodolite.util.Results import java.time.Duration @@ -27,12 +25,14 @@ class TestBenchmarkExecutorImpl( executionId = executionId, loadGenerationDelay = loadGenerationDelay, afterTeardownDelay = afterTeardownDelay, - executionName = "test-execution" + executionName = "test-execution", + loadPatcherDefinitions = emptyList(), + resourcePatcherDefinitions = emptyList() ) { - override fun runExperiment(load: LoadDimension, res: Resource): Boolean { - val result = this.mockResults[load.get()][res.get()] - this.results.setResult(Pair(load, res), result) + override fun runExperiment(load: Int, resource: Int): Boolean { + val result = this.mockResults[load][resource] + this.results.setResult(Pair(load, resource), result) return result } } diff --git a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt index 9274e283b48a6fd9b30d5ce0aff3cb8b995e0ce5..961c813f432d13fd2b373c74adaa00df4049a334 100644 --- a/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt +++ b/theodolite/src/test/kotlin/theodolite/execution/operator/ExecutionCRDummy.kt @@ -36,16 +36,24 @@ class ExecutionCRDummy(name: String, benchmark: String) { resourceDef.resourceType = "" resourceDef.resourceValues = emptyList() + val strat = BenchmarkExecution.Strategy() + strat.name = "" + strat.restrictions = emptyList() + strat.guessStrategy = "" + strat.searchStrategy = "" + + val exec = BenchmarkExecution.Execution() exec.afterTeardownDelay = 0 exec.duration = 0 exec.loadGenerationDelay = 0 exec.repetitions = 1 - exec.restrictions = emptyList() - exec.strategy = "" + exec.metric = "" + exec.strategy = strat + execution.benchmark = benchmark - execution.load = loadType + execution.loads = loadType execution.resources = resourceDef execution.slos = emptyList() execution.execution = exec diff --git a/theodolite/src/test/kotlin/theodolite/model/crd/CRDExecutionTest.kt b/theodolite/src/test/kotlin/theodolite/model/crd/CRDExecutionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..1150141ecc6a7147c930375af5bc965b81647e5a --- /dev/null +++ b/theodolite/src/test/kotlin/theodolite/model/crd/CRDExecutionTest.kt @@ -0,0 +1,90 @@ +package theodolite.model.crd + +import io.fabric8.kubernetes.api.model.KubernetesResourceList +import io.fabric8.kubernetes.client.dsl.MixedOperation +import io.fabric8.kubernetes.client.dsl.Resource +import io.fabric8.kubernetes.client.server.mock.KubernetesServer +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.kubernetes.client.KubernetesTestServer +import io.quarkus.test.kubernetes.client.WithKubernetesTestServer +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.internal.matchers.apachecommons.ReflectionEquals +import org.apache.commons.lang3.builder.EqualsBuilder +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.instanceOf +import org.mockito.kotlin.isA +import org.mockito.kotlin.mock +import theodolite.benchmark.BenchmarkExecution +import theodolite.execution.operator.* +import theodolite.util.ConfigurationOverride +import java.io.FileInputStream + + +// TODO move somewhere else +typealias ExecutionClient = MixedOperation<ExecutionCRD, KubernetesResourceList<ExecutionCRD>, Resource<ExecutionCRD>> + +@WithKubernetesTestServer +@QuarkusTest +internal class CRDExecutionTest { + + @KubernetesTestServer + private lateinit var server: KubernetesServer + + lateinit var executionClient: ExecutionClient + + lateinit var controller: TheodoliteController + + lateinit var stateHandler: ExecutionStateHandler + + lateinit var eventHandler: ExecutionEventHandler + + @BeforeEach + fun setUp() { + server.before() + + this.server.client + .apiextensions().v1() + .customResourceDefinitions() + .load(FileInputStream("crd/crd-execution.yaml")) + .create() + + this.executionClient = this.server.client.resources(ExecutionCRD::class.java) + + this.controller = mock() + this.stateHandler = ExecutionStateHandler(server.client) + this.eventHandler = ExecutionEventHandler(this.controller, this.stateHandler) + } + + @AfterEach + fun tearDown() { + server.after() + } + + @Test + fun checkParsingCRDTest(){ + // BenchmarkExecution from yaml + val execution = executionClient.load(ClassLoader.getSystemResourceAsStream("k8s-resource-files/test-execution.yaml")).create().spec + + assertEquals(0, execution.executionId) + assertEquals("test", execution.name) + assertEquals("uc1-kstreams", execution.benchmark) + assertEquals(mutableListOf<ConfigurationOverride?>(), execution.configOverrides) + + assertEquals("NumSensors", execution.loads.loadType) + assertEquals(listOf(25000, 50000, 75000, 100000, 125000, 150000),execution.loads.loadValues) + + assertEquals("Instances", execution.resources.resourceType) + assertEquals(listOf(1, 2, 3, 4, 5), execution.resources.resourceValues) + + assertEquals("demand", execution.execution.metric) + assertEquals(300, execution.execution.duration) + assertEquals(1, execution.execution.repetitions) + + assertEquals("RestrictionSearch", execution.execution.strategy.name) + assertEquals("LinearSearch", execution.execution.strategy.searchStrategy) + assertEquals(listOf("LowerBound"), execution.execution.strategy.restrictions) + } +} \ No newline at end of file diff --git a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt b/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt index b368647e314a4d803b444268c8218aefbee00ad4..e659d5f542910611af96d7eb6a68140ebd43065b 100644 --- a/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt +++ b/theodolite/src/test/kotlin/theodolite/strategies/restriction/LowerBoundRestrictionTest.kt @@ -4,22 +4,17 @@ 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.util.LoadDimension -import theodolite.util.Resource +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 = buildLoadDimension(10000) - val resources = listOf( - buildResourcesDimension(1), - buildResourcesDimension(2), - buildResourcesDimension(3) - ) + val load = 10000 + val resources = listOf(1, 2, 3) val restriction = strategy.apply(load, resources) assertEquals(3, restriction.size) @@ -28,17 +23,13 @@ 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) val strategy = LowerBoundRestriction(results) - val load = buildLoadDimension(30000) - val resources = listOf( - buildResourcesDimension(1), - buildResourcesDimension(2), - buildResourcesDimension(3) - ) + val load = 30000 + val resources = listOf(1, 2, 3) val restriction = strategy.apply(load, resources) assertEquals(2, restriction.size) @@ -49,70 +40,54 @@ 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) results.setResult(20000, 3, false) val strategy = LowerBoundRestriction(results) - val load = buildLoadDimension(30000) - val resources = listOf( - buildResourcesDimension(1), - buildResourcesDimension(2), - buildResourcesDimension(3) - ) + val load = 30000 + val resources = listOf(1, 2, 3) val restriction = strategy.apply(load, resources) assertEquals(0, restriction.size) - assertEquals(emptyList<Resource>(), restriction) + assertEquals(emptyList<Int>(), restriction) } @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(LoadDimension(20000, emptyList())) + val minRequiredInstances = results.getOptYDimensionValue(20000) assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertEquals(2, minRequiredInstances!!) } @Test @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(LoadDimension(20000, emptyList())) + val minRequiredInstances = results.getOptYDimensionValue(20000) assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertEquals(2, minRequiredInstances!!) } - private fun buildLoadDimension(load: Int): LoadDimension { - return LoadDimension(load, emptyList()) - } - private fun buildResourcesDimension(resources: Int): Resource { - return Resource(resources, emptyList()) - } - private fun Results.setResult(load: Int, resources: Int, successful: Boolean) { - this.setResult( - Pair( - buildLoadDimension(load), - buildResourcesDimension(resources) - ), - successful - ) + private fun Results.setResult(load: Int, resource: Int, successful: Boolean) { + this.setResult(Pair(load, resource),successful) } } diff --git a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt index 3b31f389bdeb35e6016a56a98abb1e13bf83ae18..cec18832ad99a01dc7977b64a19111f57f49d7f4 100644 --- a/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/IOHandlerTest.kt @@ -69,14 +69,14 @@ internal class IOHandlerTest { fun testWriteToJSONFile() { temporaryFolder.create() val folder = temporaryFolder.newFolder(FOLDER_URL) - val testContent = Resource(0, emptyList()) + val testContentResource = 0 IOHandler().writeToJSONFile( fileURL = "${folder.absolutePath}/test-file.json", - objectToSave = testContent + objectToSave = testContentResource ) - val expected = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create().toJson(testContent) + val expected = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create().toJson(testContentResource) assertEquals( expected, diff --git a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt index 9cfc2ae78e7a8846e3f0fa136699509145e5de22..d453587a9aeeee1ad48cf750a614009ef6be9aff 100644 --- a/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt +++ b/theodolite/src/test/kotlin/theodolite/util/ResultsTest.kt @@ -3,73 +3,89 @@ package theodolite.util import io.quarkus.test.junit.QuarkusTest 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() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) - results.setResult(20000, 2, true) + fun testMinRequiredInstancesWhenSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) + results.setResult(Pair(20000, 1), false) + results.setResult(Pair(20000, 2), true) - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) + val minRequiredInstances = results.getOptYDimensionValue(20000) assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertEquals(2, minRequiredInstances) } @Test - @Disabled - fun testMinRequiredInstancesWhenNotSuccessful() { - // This test is currently not implemented this way, but might later be the desired behavior. - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) - results.setResult(20000, 2, false) + fun testGetMaxBenchmarkedLoadWhenAllSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) - val minRequiredInstances = results.getMinRequiredInstances(LoadDimension(20000, emptyList())) + val test1 = results.getMaxBenchmarkedXDimensionValue(100000) - assertNotNull(minRequiredInstances) - assertEquals(2, minRequiredInstances!!.get()) + assertNotNull(test1) + assertEquals(10000, test1) + } + + @Test + fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessfulDemand() { + val results = Results(Metric.from("demand")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) + results.setResult(Pair(20000, 1), false) + + val test2 = results.getMaxBenchmarkedXDimensionValue(100000) + + assertNotNull(test2) + assertEquals(20000, test2) } - private fun Results.setResult(load: Int, resources: Int, successful: Boolean) { - this.setResult( - Pair( - LoadDimension(load, emptyList()), - Resource(resources, emptyList()) - ), - successful - ) + @Test + fun testMaxRequiredInstancesWhenSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(20000, 1), false) + results.setResult(Pair(10000, 2), true) + results.setResult(Pair(20000, 2), true) + + val maxRequiredInstances = results.getOptYDimensionValue(2) + + assertNotNull(maxRequiredInstances) + assertEquals(20000, maxRequiredInstances) } @Test - fun testGetMaxBenchmarkedLoadWhenAllSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) + fun testGetMaxBenchmarkedLoadWhenAllSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(10000, 2), true) - val test1 = results.getMaxBenchmarkedLoad(LoadDimension(100000, emptyList()))!!.get() + val test1 = results.getMaxBenchmarkedXDimensionValue(5) - assertEquals(10000, test1) + assertNotNull(test1) + assertEquals(2, test1) } @Test - fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessful() { - val results = Results() - results.setResult(10000, 1, true) - results.setResult(10000, 2, true) - results.setResult(20000, 1, false) + fun testGetMaxBenchmarkedLoadWhenLargestNotSuccessfulCapacity() { + val results = Results(Metric.from("capacity")) + results.setResult(Pair(10000, 1), true) + results.setResult(Pair(20000, 1), true) + results.setResult(Pair(10000, 2), false) - val test2 = results.getMaxBenchmarkedLoad(LoadDimension(100000, emptyList()))!!.get() - assertEquals(20000, test2) + val test2 = results.getMaxBenchmarkedXDimensionValue(5) + + assertNotNull(test2) + assertEquals(2, test2) } } diff --git a/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml b/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml index 1407a9952b7454053d204454841d51cfb4d7dbf4..e250d8a9610be6c645ec2da4d6de5b9b59aaa929 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution-1.yaml @@ -5,7 +5,7 @@ metadata: spec: name: test benchmark: "uc1-kstreams" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: @@ -19,10 +19,12 @@ spec: offset: 0 warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: [] diff --git a/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml b/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml index c075702da218397352f1dc1e5b283534fbb4d718..80476ba5c01a7c49b5058448117bc25e960eacee 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution-update.yaml @@ -5,7 +5,7 @@ metadata: spec: name: test benchmark: "uc1-kstreams-update" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: @@ -20,10 +20,12 @@ spec: externalSloUrl: "http://localhost:80/evaluate-slope" warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: [] diff --git a/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml b/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml index e12c851da5d8a79f57b1fa59b86239c219370c0f..2df4760cd6deab72cae0303bb669852b3140baa6 100644 --- a/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml +++ b/theodolite/src/test/resources/k8s-resource-files/test-execution.yaml @@ -5,7 +5,7 @@ metadata: spec: name: test benchmark: "uc1-kstreams" - load: + loads: loadType: "NumSensors" loadValues: [25000, 50000, 75000, 100000, 125000, 150000] resources: @@ -20,10 +20,12 @@ spec: externalSloUrl: "http://localhost:80/evaluate-slope" warmup: 60 # in seconds execution: - strategy: "LinearSearch" + strategy: + name: "RestrictionSearch" + restrictions: + - "LowerBound" + searchStrategy: "LinearSearch" duration: 300 # in seconds repetitions: 1 loadGenerationDelay: 30 # in seconds - restrictions: - - "LowerBound" configOverrides: []