Skip to content
Snippets Groups Projects
Commit 7aed1e45 authored by Marcel Samir Becker's avatar Marcel Samir Becker
Browse files

Finished capacity metric support for search strategies

parent 2d7b8068
No related branches found
No related tags found
1 merge request!215Redesign Strategy, Load, and Resources data types
......@@ -81,25 +81,25 @@ spec:
description: Defines the overall parameter for the execution.
type: object
required: ["strategy", "duration", "repetitions", "restrictions"]
metric:
default: "demand"
type: string
oneOf:
- "demand"
- "capacity"
strategy:
description: Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'
type: object
name: string
properties:
restrictions:
description: List of restriction strategies used to delimit the search space.
type: array
items:
type: string
guessStrategy: string
searchStrategy: string
properties:
metric:
default: "demand"
type: string
oneOf:
- "demand"
- "capacity"
strategy:
description: Defines the used strategy for the execution, either 'LinearSearch', 'BinarySearch' or 'InitialGuessSearch'
type: object
name: string
properties:
restrictions:
description: List of restriction strategies used to delimit the search space.
type: array
items:
type: string
guessStrategy: string
searchStrategy: string
duration:
description: Defines the duration of each experiment in seconds.
type: integer
......
......@@ -12,7 +12,7 @@ private val logger = KotlinLogging.logger {}
*/
class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) {
override fun findSuitableResource(load: Int, resources: List<Int>): Int? {
val result = binarySearch(load, resources, 0, resources.size - 1, true)
val result = binarySearchDemand(load, resources, 0, resources.size - 1)
if (result == -1) {
return null
}
......@@ -20,24 +20,26 @@ class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchm
}
override fun findSuitableLoad(resource: Int, loads: List<Int>): Int? {
// TODO
return null
val result = binarySearchCapacity(resource, loads, 0, loads.size - 1)
if (result == -1) {
return null
}
return loads[result]
}
/**
* Apply binary search.
* Apply binary search for metric demand.
*
* @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)
*/
private fun binarySearch(load: Int, resources: List<Int>, lower: Int, upper: Int,
demand: Boolean): 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}' and resources '$res'" }
......@@ -48,17 +50,58 @@ class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchm
}
} 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}' and resources '$res'" }
if (this.benchmarkExecutor.runExperiment(load, resources[mid])) {
// case length = 2
if (mid == lower) {
return lower
}
return binarySearch(load, resources, lower, mid - 1, demand)
return binarySearchDemand(load, resources, lower, mid - 1)
} else {
return binarySearchDemand(load, resources, mid + 1, upper)
}
}
}
/**
* Apply binary search for metric capacity.
*
* @param resource the load dimension to perform experiments for
* @param loads the list 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 res = loads[lower]
logger.info { "Running experiment with load '$resource' and resources '$res'" }
if (this.benchmarkExecutor.runExperiment(resource, loads[lower])) 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 res = loads[mid]
logger.info { "Running experiment with load '$resource' and resources '$res'" }
if (this.benchmarkExecutor.runExperiment(resource, loads[mid])) {
// 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, demand)
return binarySearchCapacity(resource, loads, lower, mid - 1)
}
}
}
......
......@@ -35,7 +35,6 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra
return null
}
var lastLowestResource : Int? = null
// Getting the lastLowestResource from results and calling firstGuess() with it
......@@ -50,7 +49,7 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra
val resourcesToCheck: List<Int>
val startIndex: Int = resources.indexOf(lastLowestResource)
logger.info { "Running experiment with load '${load}' and resources '$lastLowestResource'" }
logger.info { "Running experiment with load '$load' and resources '$lastLowestResource'" }
// If the first experiment passes, starting downward linear search
// otherwise starting upward linear search
......@@ -62,7 +61,7 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra
var currentMin: Int = lastLowestResource
for (res in resourcesToCheck) {
logger.info { "Running experiment with load '${load}' and resources '$res'" }
logger.info { "Running experiment with load '$load' and resources '$res'" }
if (this.benchmarkExecutor.runExperiment(load, res)) {
currentMin = res
}
......@@ -78,19 +77,86 @@ class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor, guessStra
for (res in resourcesToCheck) {
logger.info { "Running experiment with load '${load}' and resources '$res'" }
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? {
TODO("Not yet implemented")
if(loads.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
}
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.getMaxRequiredYDimensionValue(maxResource)
if (lastMaxLoad == Int.MIN_VALUE) lastMaxLoad = null
}
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(resource, lastMaxLoad)) {
// 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(resource, load)) {
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(resource, load)) {
currentMax = load
}
}
return currentMax
}
}
else {
logger.info { "lastMaxLoad was null." }
}
return null
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ class PrevResourceMinGuess() : GuessStrategy(){
*
* @return the value of lastLowestResource if given otherwise the first element of the resource list or null
*/
// TODO verallgemeinern für loads
override fun firstGuess(resources: List<Int>, lastLowestResource: Int?): Int? {
if (lastLowestResource != null) return lastLowestResource
......
......@@ -46,7 +46,6 @@ abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor, val gues
*/
abstract fun findSuitableResource(load: Int, resources: List<Int>): Int?
// TODO findSuitableLoad und findSuitableResource zusammenfuehren?
/**
* Find biggest suitable load from the specified load list for the given resource amount.
*
......
......@@ -14,6 +14,8 @@ class Results (val metric: Metric) {
//TODO: enum statt Boolean
private val results: MutableMap<Pair<Int, Int>, Boolean> = mutableMapOf()
//TODO: min instance (or max respectively) also as fields so we do not loop over results, speichert alle results für alle load/resource pairs
/**
* Set the result for an experiment.
*
......@@ -39,12 +41,12 @@ class Results (val metric: Metric) {
/**
* Get the smallest suitable number of instances for a specified LoadDimension.
*
* @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, Int.MIN_VALUE is returned.
* If there is no experiment for the given [xValue] or there is none marked successful yet,
* Int.MAX_VALUE is returned.
*/
fun getMinRequiredYDimensionValue(xValue: Int?): Int {
if (this.results.isEmpty()) { //should add || xValue == null
......@@ -53,7 +55,7 @@ class Results (val metric: Metric) {
var minRequiredYValue = Int.MAX_VALUE
for (experiment in results) {
// Get all successful experiments for requested load
// Get all successful experiments for requested xValue
if (getXDimensionValue(experiment.key) == xValue && experiment.value) {
val experimentYValue = getYDimensionValue(experiment.key)
if (experimentYValue < minRequiredYValue) {
......@@ -65,6 +67,37 @@ class Results (val metric: Metric) {
return minRequiredYValue
}
/**
* Get the largest y-Value for which the given x-Value has a positive experiment outcome.
* x- and y-values depend on the metric in use.
*
* @param xValue the Value of the x-dimension of the current metric
*
* @return the largest suitable number of resources/loads (depending on metric).
* If there wasn't any experiment executed yet, Int.MAX_VALUE is returned.
* If the experiments for the specified [xValue] wasn't executed yet or the experiments were not successful
* Int.MIN_VALUE is returned.
*/
fun getMaxRequiredYDimensionValue(xValue: Int?): Int {
if (this.results.isEmpty()) { //should add || xValue == null
return Int.MAX_VALUE
}
var maxRequiredYValue = Int.MIN_VALUE
for (experiment in results) {
// Get all successful experiments for requested xValue
if (getXDimensionValue(experiment.key) == xValue && experiment.value) {
val experimentYValue = getYDimensionValue(experiment.key)
if (experimentYValue > maxRequiredYValue) {
// Found new largest value
maxRequiredYValue = experimentYValue
}
}
}
return maxRequiredYValue
}
// TODO: SÖREN FRAGEN WARUM WIR DAS BRAUCHEN UND NICHT EINFACH PREV, WEIL NICHT DURCHGELAUFEN?
// TODO Kommentar zu XDimension und YDimension
/**
......
......@@ -20,10 +20,14 @@ spec:
externalSloUrl: "http://localhost:80/evaluate-slope"
warmup: 60 # in seconds
execution:
strategy: "LinearSearch"
metric: "demand"
strategy: "RestrictionSearch"
properties:
restrictions:
- "LowerBound"
searchStrategy: "LinearSearch"
duration: 300 # in seconds
repetitions: 1
loadGenerationDelay: 30 # in seconds
restrictions:
- "LowerBound"
configOverrides: []
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment