diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt index c5d691eb8f1437726bcce22500ad995bc1b6e4da..96f32f634f1d0164b1e25b36fe44de2162c86bfe 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/AnalysisExecutor.kt @@ -2,6 +2,7 @@ package theodolite.evaluation import mu.KotlinLogging import theodolite.benchmark.BenchmarkExecution +import theodolite.util.IOHandler import theodolite.util.LoadDimension import theodolite.util.Resource import java.text.Normalizer @@ -44,15 +45,14 @@ class AnalysisExecutor( query = "sum by(group)(kafka_consumergroup_group_lag >= 0)" ) - var resultsFolder: String = System.getenv("RESULTS_FOLDER") - if (resultsFolder.isNotEmpty()){ - resultsFolder += "/" - } + val ioHandler = IOHandler() + val resultsFolder: String = ioHandler.getResultFolderURL() + val fileURL = "${resultsFolder}exp${executionId}_${load.get()}_${res.get()}_${slo.sloType.toSlug()}" + ioHandler.writeToCSVFile( + fileURL = fileURL, + data = prometheusData.getResultAsList(), + columns = listOf("group", "timestamp", "value")) - CsvExporter().toCsv( - name = "${resultsFolder}exp${executionId}_${load.get()}_${res.get()}_${slo.sloType.toSlug()}", - prom = prometheusData - ) val sloChecker = SloCheckerFactory().create( sloType = slo.sloType, externalSlopeURL = slo.externalSloUrl, diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/CsvExporter.kt b/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/CsvExporter.kt deleted file mode 100644 index 970d1487014bfbddf7391e381d27ad5dbb246a7d..0000000000000000000000000000000000000000 --- a/theodolite-quarkus/src/main/kotlin/theodolite/evaluation/CsvExporter.kt +++ /dev/null @@ -1,53 +0,0 @@ -package theodolite.evaluation - -import mu.KotlinLogging -import theodolite.util.PrometheusResponse -import java.io.File -import java.io.PrintWriter -import java.util.* - -private val logger = KotlinLogging.logger {} - -/** - * Used to document the data received from prometheus for additional offline analysis. - */ -class CsvExporter { - - /** - * Uses the [PrintWriter] to transform a [PrometheusResponse] to a CSV file. - * @param name of the file. - * @param prom Response that is documented. - * - */ - fun toCsv(name: String, prom: PrometheusResponse) { - val responseArray = promResponseToList(prom) - val csvOutputFile = File("$name.csv") - - PrintWriter(csvOutputFile).use { pw -> - pw.println(listOf("group", "timestamp", "value").joinToString(separator=",")) - responseArray.forEach { - pw.println(it.joinToString(separator=",")) - } - } - logger.info { "Wrote CSV file: $name to ${csvOutputFile.absolutePath}." } - } - - /** - * Converts a [PrometheusResponse] into a [List] of [List]s of [String]s - */ - private fun promResponseToList(prom: PrometheusResponse): List<List<String>> { - val name = prom.data?.result?.get(0)?.metric?.group.toString() - val values = prom.data?.result?.get(0)?.values - val dataList = mutableListOf<List<String>>() - - if (values != null) { - for (maybeValuePair in values) { - val valuePair = maybeValuePair as List<*> - val timestamp = (valuePair[0] as Double).toLong().toString() - val value = valuePair[1].toString() - dataList.add(listOf(name, timestamp, value)) - } - } - return Collections.unmodifiableList(dataList) - } -} diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt index c297251e30c17e5824c2541365164d9824718073..c437dd3b1b1adefc3657b7c43c671512f5713858 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/TheodoliteExecutor.kt @@ -7,10 +7,7 @@ import theodolite.benchmark.KubernetesBenchmark import theodolite.patcher.PatcherDefinitionFactory import theodolite.strategies.StrategyFactory import theodolite.strategies.searchstrategy.CompositeStrategy -import theodolite.util.Config -import theodolite.util.LoadDimension -import theodolite.util.Resource -import theodolite.util.Results +import theodolite.util.* import java.io.File import java.io.PrintWriter import java.lang.IllegalArgumentException @@ -102,34 +99,17 @@ class TheodoliteExecutor( return this.kubernetesBenchmark } - private fun getResultFolderString(): String { - var resultsFolder: String = System.getenv("RESULTS_FOLDER") ?: "" - val createResultsFolder = System.getenv("CREATE_RESULTS_FOLDER") ?: "false" - - if (resultsFolder != ""){ - logger.info { "RESULT_FOLDER: $resultsFolder" } - val directory = File(resultsFolder) - if (!directory.exists()) { - logger.error { "Folder $resultsFolder does not exist" } - if (createResultsFolder.toBoolean()) { - directory.mkdirs() - } else { - throw IllegalArgumentException("Result folder not found") - } - } - resultsFolder += "/" - } - return resultsFolder - } /** * Run all experiments which are specified in the corresponding * execution and benchmark objects. */ fun run() { - val resultsFolder = getResultFolderString() - storeAsFile(this.config, "$resultsFolder${this.config.executionId}-execution-configuration") - storeAsFile(kubernetesBenchmark, "$resultsFolder${this.config.executionId}-benchmark-configuration") + val ioHandler = IOHandler() + val resultsFolder = ioHandler.getResultFolderURL() + this.config.executionId = getAndIncrementExecutionID(resultsFolder+"expID.txt") + ioHandler.writeToJSONFile(this.config, "$resultsFolder${this.config.executionId}-execution-configuration") + ioHandler.writeToJSONFile(kubernetesBenchmark, "$resultsFolder${this.config.executionId}-benchmark-configuration") val config = buildConfig() // execute benchmarks for each load @@ -138,14 +118,17 @@ class TheodoliteExecutor( config.compositeStrategy.findSuitableResource(load, config.resources) } } - storeAsFile(config.compositeStrategy.benchmarkExecutor.results, "$resultsFolder${this.config.executionId}-result") + ioHandler.writeToJSONFile(config.compositeStrategy.benchmarkExecutor.results, "$resultsFolder${this.config.executionId}-result") } - private fun <T> storeAsFile(saveObject: T, filename: String) { - val gson = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create() - - PrintWriter(filename).use { pw -> - pw.println(gson.toJson(saveObject)) - } + private fun getAndIncrementExecutionID(fileURL: String): Int { + val ioHandler = IOHandler() + var executionID = 0 + if (File(fileURL).exists()) { + executionID = ioHandler.readFileAsString(fileURL).toInt() + 1 + } + ioHandler.writeStringToTextFile(fileURL, (executionID).toString()) + return executionID } + } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt index 0e889e0393e17fd65f4d8f12c5b95a3dbed7f593..5c8a225a7afa4dd8094ee283af50cbd4efea9f50 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/execution/operator/TheodoliteController.kt @@ -37,7 +37,6 @@ class TheodoliteController( val executionsQueue: ConcurrentLinkedDeque<BenchmarkExecution> = ConcurrentLinkedDeque() val benchmarks: ConcurrentHashMap<String, KubernetesBenchmark> = ConcurrentHashMap() var isUpdated = AtomicBoolean(false) - var executionID = AtomicInteger(0) /** * Runs the TheodoliteController forever. @@ -91,7 +90,6 @@ class TheodoliteController( */ @Synchronized fun runExecution(execution: BenchmarkExecution, benchmark: KubernetesBenchmark) { - execution.executionId = executionID.getAndSet(executionID.get() + 1) isUpdated.set(false) benchmark.path = path logger.info { "Start execution ${execution.name} with benchmark ${benchmark.name}." } diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/IOHandler.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/IOHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..e2679f048b98171089016fab13be7805ec7e6693 --- /dev/null +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/IOHandler.kt @@ -0,0 +1,95 @@ +package theodolite.util + +import com.google.gson.GsonBuilder +import mu.KotlinLogging +import java.io.File +import java.io.PrintWriter +import java.lang.IllegalArgumentException + +private val logger = KotlinLogging.logger {} + +/** + * The IOHandler handles most common I/O operations within the Theodolite framework + */ +class IOHandler { + + /** + * The location in which Theodolite store result and configuration file are depends on + * the values of the environment variables `RESULT_FOLDER` and `CREATE_RESULTS_FOLDER` + * + * @return the URL of the result folder + */ + fun getResultFolderURL(): String { + var resultsFolder: String = System.getenv("RESULTS_FOLDER") ?: "" + val createResultsFolder = System.getenv("CREATE_RESULTS_FOLDER") ?: "false" + + if (resultsFolder != ""){ + logger.info { "RESULT_FOLDER: $resultsFolder" } + val directory = File(resultsFolder) + if (!directory.exists()) { + logger.error { "Folder $resultsFolder does not exist" } + if (createResultsFolder.toBoolean()) { + directory.mkdirs() + } else { + throw IllegalArgumentException("Result folder not found") + } + } + resultsFolder += "/" + } + return resultsFolder + } + + /** + * Read a file as String + * + * @param fileURL the URL of the file + * @return The content of the file as String + */ + fun readFileAsString(fileURL: String): String { + return File(fileURL).inputStream().readBytes().toString(Charsets.UTF_8).trim() + } + + /** + * Creates a JSON string of the given object and store them to file + * + * @param T class of the object to save + * @param objectToSave object which should be saved as file + * @param fileURL the URL of the file + */ + fun <T> writeToJSONFile(objectToSave: T, fileURL: String) { + val gson = GsonBuilder().enableComplexMapKeySerialization().setPrettyPrinting().create() + writeStringToTextFile(fileURL, gson.toJson(objectToSave)) + } + + /** + * Write to CSV file + * + * @param fileURL the URL of the file + * @param data the data to write in the file, as list of list, each subList corresponds to a row in the CSV file + * @param columns columns of the CSV file + */ + fun writeToCSVFile(fileURL: String, data: List<List<String>>, columns: List<String>) { + val outputFile = File("$fileURL.csv") + PrintWriter(outputFile).use { pw -> + pw.println(columns.joinToString(separator=",")) + data.forEach { + pw.println(it.joinToString(separator=",")) + } + } + logger.info { "Wrote CSV file: $fileURL to ${outputFile.absolutePath}." } + } + + /** + * Write to text file + * + * @param fileURL the URL of the file + * @param data the data to write in the file as String + */ + fun writeStringToTextFile(fileURL: String, data: String) { + val outputFile = File("$fileURL") + outputFile.printWriter().use { + it.println(data) + } + logger.info { "Wrote txt file: $fileURL to ${outputFile.absolutePath}." } + } +} \ No newline at end of file diff --git a/theodolite-quarkus/src/main/kotlin/theodolite/util/PrometheusResponse.kt b/theodolite-quarkus/src/main/kotlin/theodolite/util/PrometheusResponse.kt index d1d59c482e64fd14c4744d8fcd606f286da24fb4..846577387c425e920da1c2fca1f972c880e1540a 100644 --- a/theodolite-quarkus/src/main/kotlin/theodolite/util/PrometheusResponse.kt +++ b/theodolite-quarkus/src/main/kotlin/theodolite/util/PrometheusResponse.kt @@ -1,6 +1,7 @@ package theodolite.util import io.quarkus.runtime.annotations.RegisterForReflection +import java.util.* /** * This class corresponds to the JSON response format of a Prometheus @@ -17,6 +18,27 @@ data class PrometheusResponse( */ var data: PromData? = null ) +{ + /** + * Return the data of the PrometheusResponse as [List] of [List]s of [String]s + * The format of the returned list is: `[[ group, timestamp, value ], [ group, timestamp, value ], ... ]` + */ + fun getResultAsList(): List<List<String>> { + val group = data?.result?.get(0)?.metric?.group.toString() + val values = data?.result?.get(0)?.values + val result = mutableListOf<List<String>>() + + if (values != null) { + for (value in values) { + val valueList = value as List<*> + val timestamp = (valueList[0] as Double).toLong().toString() + val value = valueList[1].toString() + result.add(listOf(group, timestamp, value)) + } + } + return Collections.unmodifiableList(result) + } +} /** * Description of Prometheus data. @@ -56,4 +78,5 @@ data class PromResult( @RegisterForReflection data class PromMetric( var group: String? = null -) \ No newline at end of file +) +